<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