1 package leakcanary
2 
3 import android.app.ActivityManager
4 import android.app.Service
5 import android.content.ComponentName
6 import android.content.Context
7 import android.content.pm.PackageInfo
8 import android.content.pm.PackageManager
9 import android.content.pm.ServiceInfo
10 import leakcanary.internal.RemoteLeakCanaryWorkerService
11 import shark.SharkLog
12 
13 /**
14  * Used to determine whether the current process is the LeakCanary analyzer process. By depending
15  * on the `leakcanary-android-process` artifact instead of the `leakcanary-android`, LeakCanary
16  * will automatically run its analysis in a separate process.
17  *
18  * As such, you'll need to be careful to do any custom configuration of LeakCanary in both the main
19  * process and the analyzer process.
20  */
21 object LeakCanaryProcess {
22 
23   @Volatile private var isInAnalyzerProcess: Boolean? = null
24 
25   /**
26    * Whether the current process is the process running the heap analyzer, which is
27    * a different process than the normal app process.
28    */
isInAnalyzerProcessnull29   fun isInAnalyzerProcess(context: Context): Boolean {
30     var isInAnalyzerProcess: Boolean? = isInAnalyzerProcess
31     // This only needs to be computed once per process.
32     if (isInAnalyzerProcess == null) {
33       isInAnalyzerProcess = isInServiceProcess(context, RemoteLeakCanaryWorkerService::class.java)
34       this.isInAnalyzerProcess = isInAnalyzerProcess
35     }
36     return isInAnalyzerProcess
37   }
38 
39   @Suppress("ReturnCount")
isInServiceProcessnull40   private fun isInServiceProcess(
41     context: Context,
42     serviceClass: Class<out Service>
43   ): Boolean {
44     val packageManager = context.packageManager
45     val packageInfo: PackageInfo
46     try {
47       packageInfo = packageManager.getPackageInfo(context.packageName, PackageManager.GET_SERVICES)
48     } catch (e: Exception) {
49       SharkLog.d(e) { "Could not get package info for ${context.packageName}" }
50       return false
51     }
52 
53     val mainProcess = packageInfo.applicationInfo.processName
54 
55     val component = ComponentName(context, serviceClass)
56     val serviceInfo: ServiceInfo
57     try {
58       serviceInfo =
59         packageManager.getServiceInfo(component, PackageManager.GET_DISABLED_COMPONENTS)
60     } catch (ignored: PackageManager.NameNotFoundException) {
61       // Service is disabled.
62       return false
63     }
64 
65     if (serviceInfo.processName == null) {
66       SharkLog.d { "Did not expect service $serviceClass to have a null process name" }
67       return false
68     } else if (serviceInfo.processName == mainProcess) {
69       SharkLog.d { "Did not expect service $serviceClass to run in main process $mainProcess" }
70       // Technically we are in the service process, but we're not in the service dedicated process.
71       return false
72     }
73 
74     val myPid = android.os.Process.myPid()
75     val activityManager = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
76     var myProcess: ActivityManager.RunningAppProcessInfo? = null
77     val runningProcesses: List<ActivityManager.RunningAppProcessInfo>?
78     try {
79       runningProcesses = activityManager.runningAppProcesses
80     } catch (exception: SecurityException) {
81       // https://github.com/square/leakcanary/issues/948
82       SharkLog.d { "Could not get running app processes $exception" }
83       return false
84     }
85 
86     if (runningProcesses != null) {
87       for (process in runningProcesses) {
88         if (process.pid == myPid) {
89           myProcess = process
90           break
91         }
92       }
93     }
94     if (myProcess == null) {
95       SharkLog.d { "Could not find running process for $myPid" }
96       return false
97     }
98 
99     return myProcess.processName == serviceInfo.processName
100   }
101 }
102