<lambda>null1 package leakcanary.internal.activity
2 
3 import android.content.Context
4 import android.content.Intent
5 import android.net.Uri
6 import android.os.AsyncTask
7 import android.os.Bundle
8 import android.view.View
9 import android.widget.Toast
10 import com.squareup.leakcanary.core.R
11 import java.io.FileInputStream
12 import java.io.IOException
13 import java.util.UUID
14 import leakcanary.EventListener.Event.HeapDump
15 import leakcanary.internal.InternalLeakCanary
16 import leakcanary.internal.activity.db.Db
17 import leakcanary.internal.activity.screen.AboutScreen
18 import leakcanary.internal.activity.screen.HeapAnalysisFailureScreen
19 import leakcanary.internal.activity.screen.HeapDumpScreen
20 import leakcanary.internal.activity.screen.HeapDumpsScreen
21 import leakcanary.internal.activity.screen.LeaksScreen
22 import leakcanary.internal.navigation.NavigatingActivity
23 import leakcanary.internal.navigation.Screen
24 import shark.SharkLog
25 
26 internal class LeakActivity : NavigatingActivity() {
27 
28   private val leaksButton by lazy {
29     findViewById<View>(R.id.leak_canary_navigation_button_leaks)
30   }
31 
32   private val leaksButtonIconView by lazy {
33     findViewById<View>(R.id.leak_canary_navigation_button_leaks_icon)
34   }
35 
36   private val heapDumpsButton by lazy {
37     findViewById<View>(R.id.leak_canary_navigation_button_heap_dumps)
38   }
39 
40   private val heapDumpsButtonIconView by lazy {
41     findViewById<View>(R.id.leak_canary_navigation_button_heap_dumps_icon)
42   }
43 
44   private val aboutButton by lazy {
45     findViewById<View>(R.id.leak_canary_navigation_button_about)
46   }
47 
48   private val aboutButtonIconView by lazy {
49     findViewById<View>(R.id.leak_canary_navigation_button_about_icon)
50   }
51 
52   private val bottomNavigationBar by lazy {
53     findViewById<View>(R.id.leak_canary_bottom_navigation_bar)
54   }
55 
56   override fun onCreate(savedInstanceState: Bundle?) {
57     super.onCreate(savedInstanceState)
58     setContentView(R.layout.leak_canary_leak_activity)
59 
60     installNavigation(savedInstanceState, findViewById(R.id.leak_canary_main_container))
61 
62     leaksButton.setOnClickListener { resetTo(LeaksScreen()) }
63     heapDumpsButton.setOnClickListener { resetTo(HeapDumpsScreen()) }
64     aboutButton.setOnClickListener { resetTo(AboutScreen()) }
65 
66     handleViewHprof(intent)
67   }
68 
69   private fun handleViewHprof(intent: Intent?) {
70     if (intent?.action != Intent.ACTION_VIEW) return
71     val uri = intent.data ?: return
72     if (uri.lastPathSegment?.endsWith(".hprof") != true) {
73       Toast.makeText(this, getString(R.string.leak_canary_import_unsupported_file_extension, uri.lastPathSegment), Toast.LENGTH_LONG).show()
74       return
75     }
76     resetTo(HeapDumpsScreen())
77     AsyncTask.THREAD_POOL_EXECUTOR.execute {
78       importHprof(uri)
79     }
80   }
81 
82   override fun onNewScreen(screen: Screen) {
83     when (screen) {
84       is LeaksScreen -> {
85         bottomNavigationBar.visibility = View.VISIBLE
86         leaksButton.isSelected = true
87         leaksButtonIconView.alpha = 1.0f
88         heapDumpsButton.isSelected = false
89         heapDumpsButtonIconView.alpha = 0.4f
90         aboutButton.isSelected = false
91         aboutButtonIconView.alpha = 0.4f
92       }
93       is HeapDumpsScreen -> {
94         bottomNavigationBar.visibility = View.VISIBLE
95         leaksButton.isSelected = false
96         leaksButtonIconView.alpha = 0.4f
97         heapDumpsButton.isSelected = true
98         heapDumpsButtonIconView.alpha = 1.0f
99         aboutButton.isSelected = false
100         aboutButtonIconView.alpha = 0.4f
101       }
102       is AboutScreen -> {
103         bottomNavigationBar.visibility = View.VISIBLE
104         leaksButton.isSelected = false
105         leaksButtonIconView.alpha = 0.4f
106         heapDumpsButton.isSelected = false
107         heapDumpsButtonIconView.alpha = 0.4f
108         aboutButton.isSelected = true
109         aboutButtonIconView.alpha = 1.0f
110       }
111       else -> {
112         bottomNavigationBar.visibility = View.GONE
113       }
114     }
115   }
116 
117   override fun getLauncherScreen(): Screen {
118     return LeaksScreen()
119   }
120 
121   fun requestImportHprof() {
122     val requestFileIntent = Intent(Intent.ACTION_GET_CONTENT).apply {
123       type = "*/*"
124       addCategory(Intent.CATEGORY_OPENABLE)
125     }
126 
127     val chooserIntent = Intent.createChooser(
128       requestFileIntent, resources.getString(R.string.leak_canary_import_from_title)
129     )
130     startActivityForResult(chooserIntent, FILE_REQUEST_CODE)
131   }
132 
133   override fun onActivityResult(
134     requestCode: Int,
135     resultCode: Int,
136     returnIntent: Intent?
137   ) {
138     SharkLog.d {
139       "Got activity result with requestCode=$requestCode resultCode=$resultCode returnIntent=$returnIntent"
140     }
141     if (requestCode == FILE_REQUEST_CODE && resultCode == RESULT_OK && returnIntent != null) {
142       returnIntent.data?.let { fileUri ->
143         AsyncTask.THREAD_POOL_EXECUTOR.execute {
144           importHprof(fileUri)
145         }
146       }
147     }
148   }
149 
150   private fun importHprof(fileUri: Uri) {
151     try {
152       contentResolver.openFileDescriptor(fileUri, "r")
153         ?.fileDescriptor?.let { fileDescriptor ->
154           val inputStream = FileInputStream(fileDescriptor)
155           InternalLeakCanary.createLeakDirectoryProvider(this)
156             .newHeapDumpFile()
157             ?.let { target ->
158               inputStream.use { input ->
159                 target.outputStream()
160                   .use { output ->
161                     input.copyTo(output, DEFAULT_BUFFER_SIZE)
162                   }
163               }
164               InternalLeakCanary.sendEvent(
165                 HeapDump(
166                   uniqueId = UUID.randomUUID().toString(),
167                   file = target,
168                   durationMillis = -1,
169                   reason = "Imported by user"
170                 )
171               )
172             }
173         }
174     } catch (e: IOException) {
175       SharkLog.d(e) { "Could not import Hprof file" }
176     }
177   }
178 
179   override fun onDestroy() {
180     super.onDestroy()
181     if (!isChangingConfigurations) {
182       Db.closeDatabase()
183     }
184   }
185 
186   override fun setTheme(resid: Int) {
187     // We don't want this to be called with an incompatible theme.
188     // This could happen if you implement runtime switching of themes
189     // using ActivityLifecycleCallbacks.
190     if (resid != R.style.leak_canary_LeakCanary_Base) {
191       return
192     }
193     super.setTheme(resid)
194   }
195 
196   override fun parseIntentScreens(intent: Intent): List<Screen> {
197     val heapAnalysisId = intent.getLongExtra("heapAnalysisId", -1L)
198     if (heapAnalysisId == -1L) {
199       return emptyList()
200     }
201     val success = intent.getBooleanExtra("success", false)
202     return if (success) {
203       arrayListOf(HeapDumpsScreen(), HeapDumpScreen(heapAnalysisId))
204     } else {
205       arrayListOf(HeapDumpsScreen(), HeapAnalysisFailureScreen(heapAnalysisId))
206     }
207   }
208 
209   companion object {
210     private const val FILE_REQUEST_CODE = 0
211 
212     fun createHomeIntent(context: Context): Intent {
213       val intent = Intent(context, LeakActivity::class.java)
214       intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
215       return intent
216     }
217 
218     fun createSuccessIntent(context: Context, heapAnalysisId: Long): Intent {
219       val intent = createHomeIntent(context)
220       intent.putExtra("heapAnalysisId", heapAnalysisId)
221       intent.putExtra("success", true)
222       return intent
223     }
224 
225     fun createFailureIntent(context: Context, heapAnalysisId: Long): Intent {
226       val intent = createHomeIntent(context)
227       intent.putExtra("heapAnalysisId", heapAnalysisId)
228       intent.putExtra("success", false)
229       return intent
230     }
231   }
232 }
233