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