xref: /aosp_15_r20/external/leakcanary2/leakcanary-android-release/src/main/java/leakcanary/ProcessInfo.kt (revision d9e8da70d8c9df9a41d7848ae506fb3115cae6e6)

<lambda>null1 package leakcanary
2 
3 import android.annotation.SuppressLint
4 import android.app.ActivityManager
5 import android.app.ActivityManager.MemoryInfo
6 import android.app.ActivityManager.RunningAppProcessInfo
7 import android.content.Context
8 import android.os.Build.VERSION.SDK_INT
9 import android.os.Process
10 import android.os.SystemClock
11 import android.system.Os
12 import android.system.OsConstants
13 import java.io.File
14 import java.io.FileReader
15 import leakcanary.ProcessInfo.AvailableRam.BelowThreshold
16 import leakcanary.ProcessInfo.AvailableRam.LowRamDevice
17 import leakcanary.ProcessInfo.AvailableRam.Memory
18 
19 interface ProcessInfo {
20 
21   val isImportanceBackground: Boolean
22 
23   val elapsedMillisSinceStart: Long
24 
25   fun availableDiskSpaceBytes(path: File): Long
26 
27   sealed class AvailableRam {
28     object LowRamDevice : AvailableRam()
29     object BelowThreshold : AvailableRam()
30     class Memory(val bytes: Long) : AvailableRam()
31   }
32 
33   fun availableRam(context: Context): AvailableRam
34 
35   @SuppressLint("NewApi")
36   object Real : ProcessInfo {
37     private val memoryOutState = RunningAppProcessInfo()
38     private val memoryInfo = MemoryInfo()
39 
40     private val processStartUptimeMillis by lazy {
41       Process.getStartUptimeMillis()
42     }
43 
44     private val processForkRealtimeMillis by lazy {
45       readProcessForkRealtimeMillis()
46     }
47 
48     override val isImportanceBackground: Boolean
49       get() {
50         ActivityManager.getMyMemoryState(memoryOutState)
51         return memoryOutState.importance >= RunningAppProcessInfo.IMPORTANCE_BACKGROUND
52       }
53 
54     override val elapsedMillisSinceStart: Long
55       get() = if (SDK_INT >= 24) {
56         SystemClock.uptimeMillis() - processStartUptimeMillis
57       } else {
58         SystemClock.elapsedRealtime() - processForkRealtimeMillis
59       }
60 
61     @SuppressLint("UsableSpace")
62     override fun availableDiskSpaceBytes(path: File) = path.usableSpace
63 
64     override fun availableRam(context: Context): AvailableRam {
65       val activityManager = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
66 
67       if (SDK_INT >= 19 && activityManager.isLowRamDevice) {
68         return LowRamDevice
69       } else {
70         activityManager.getMemoryInfo(memoryInfo)
71 
72         return if (memoryInfo.lowMemory || memoryInfo.availMem <= memoryInfo.threshold) {
73           BelowThreshold
74         } else {
75           val systemAvailableMemory = memoryInfo.availMem - memoryInfo.threshold
76 
77           val runtime = Runtime.getRuntime()
78           val appUsedMemory = runtime.totalMemory() - runtime.freeMemory()
79           val appAvailableMemory = runtime.maxMemory() - appUsedMemory
80 
81           val availableMemory = systemAvailableMemory.coerceAtMost(appAvailableMemory)
82           Memory(availableMemory)
83         }
84       }
85     }
86 
87     /**
88      * See https://dev.to/pyricau/android-vitals-when-did-my-app-start-24p4#process-fork-time
89      */
90     private fun readProcessForkRealtimeMillis(): Long {
91       val myPid = Process.myPid()
92       val ticksAtProcessStart = readProcessStartTicks(myPid)
93 
94       val ticksPerSecond = if (SDK_INT >= 21) {
95         Os.sysconf(OsConstants._SC_CLK_TCK)
96       } else {
97         val tckConstant = try {
98           Class.forName("android.system.OsConstants").getField("_SC_CLK_TCK").getInt(null)
99         } catch (e: ClassNotFoundException) {
100           Class.forName("libcore.io.OsConstants").getField("_SC_CLK_TCK").getInt(null)
101         }
102         val os = Class.forName("libcore.io.Libcore").getField("os").get(null)!!
103         os::class.java.getMethod("sysconf", Integer.TYPE).invoke(os, tckConstant) as Long
104       }
105       return ticksAtProcessStart * 1000 / ticksPerSecond
106     }
107 
108     // Benchmarked (with Jetpack Benchmark) on Pixel 3 running
109     // Android 10. Median time: 0.13ms
110     private fun readProcessStartTicks(pid: Int): Long {
111       val path = "/proc/$pid/stat"
112       val stat = FileReader(path).buffered().use { reader ->
113         reader.readLine()
114       }
115       val fields = stat.substringAfter(") ")
116         .split(' ')
117       return fields[19].toLong()
118     }
119   }
120 }
121