1 package leakcanary.internal
2 
3 import android.app.Application
4 import android.content.Context
5 import android.content.ContextWrapper
6 import androidx.work.Configuration
7 import androidx.work.multiprocess.RemoteWorkerService
8 import leakcanary.AppWatcher
9 
10 /**
11  * Using a custom class name instead of RemoteWorkerService so that
12  * hosting apps can use RemoteWorkerService without a naming conflict.
13  */
14 class RemoteLeakCanaryWorkerService : RemoteWorkerService() {
15 
16   /**
17    * RemoteLeakCanaryWorkerService is running in the :leakcanary process, and androidx startup only
18    * initializes WorkManager in the main process. In RemoteWorkerService.onCreate(),
19    * WorkManager has not been init, so it would crash. We can't blindly call init() as developers
20    * might be calling WorkManager.initialize() from Application.onCreate() for all processes, in
21    * which case a 2nd init would fail. So we want to init if nothing has init WorkManager before.
22    * But there's no isInit() API. However, WorkManager will automatically pull in the application
23    * context and if that context implements Configuration.Provider then it'll pull the
24    * configuration from it. So we cheat WorkManager by returning a fake app context that provides
25    * our own custom configuration.
26    */
27   class FakeAppContextConfigurationProvider(base: Context) : ContextWrapper(base),
28     Configuration.Provider {
29 
30     // No real app context for you, sorry!
getApplicationContextnull31     override fun getApplicationContext() = this
32 
33     override fun getWorkManagerConfiguration() = Configuration.Builder()
34       // If the default package name is not set, WorkManager will cancel all runnables
35       // when initialized as it can't tell that it's not running in the main process.
36       // This would lead to an extra round trip where the canceling reaches the main process
37       // which then cancels the remote job and reschedules it and then only the work gets done.
38       .setDefaultProcessName(packageName)
39       .build()
40   }
41 
42   private val fakeAppContext by lazy {
43     // We set the base context to the real app context so that getting resources etc still works.
44     FakeAppContextConfigurationProvider(super.getApplicationContext())
45   }
46 
getApplicationContextnull47   override fun getApplicationContext(): Context {
48     return fakeAppContext
49   }
50 
onCreatenull51   override fun onCreate() {
52     // Ideally we wouldn't need to install AppWatcher at all here, however
53     // the installation triggers InternalsLeakCanary to store the application instance
54     // which is then used by the event listeners that respond to analysis progress.
55     if (!AppWatcher.isInstalled) {
56       val application = super.getApplicationContext() as Application
57       AppWatcher.manualInstall(
58         application,
59         // Nothing to watch in the :leakcanary process.
60         watchersToInstall = emptyList()
61       )
62     }
63     super.onCreate()
64   }
65 }
66