1 /*
<lambda>null2  * Copyright (C) 2015 Square, Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.example.leakcanary
17 
18 import android.animation.ObjectAnimator
19 import android.animation.ValueAnimator
20 import android.app.Activity
21 import android.app.AlertDialog
22 import android.content.BroadcastReceiver
23 import android.content.Context
24 import android.content.Intent
25 import android.content.IntentFilter
26 import android.os.Build
27 import android.os.Bundle
28 import android.os.Handler
29 import android.os.Looper
30 import android.os.SystemClock
31 import android.view.View
32 import android.widget.Button
33 import java.util.concurrent.atomic.AtomicReference
34 import kotlin.concurrent.thread
35 import kotlin.random.Random
36 import leakcanary.AppWatcher
37 
38 class MainActivity : Activity() {
39 
40   private var leakyReceiver = false
41 
42   override fun onCreate(savedInstanceState: Bundle?) {
43     super.onCreate(savedInstanceState)
44     setContentView(R.layout.main_activity)
45 
46     val app = application as ExampleApplication
47     findViewById<Button>(R.id.recreate_activity_button).setOnClickListener { recreate() }
48     findViewById<Button>(R.id.leak_activity_button).setOnClickListener {
49       val leakedView = findViewById<View>(R.id.helper_text)
50       when (Random.nextInt(4)) {
51         // Leak from application class
52         0 -> app.leakedViews.add(leakedView)
53         // Leak from Kotlin object singleton
54         1 -> LeakingSingleton.leakedViews.add(leakedView)
55         2 -> {
56           // Leak from local variable on thread
57           val ref = AtomicReference(this)
58           thread(name = "Leaking local variables") {
59             val activity = ref.get()
60             ref.set(null)
61             while (true) {
62               print(activity)
63               SystemClock.sleep(1000)
64             }
65           }
66         }
67         // Leak from thread fields
68         else -> LeakingThread.thread.leakedViews.add(leakedView)
69       }
70     }
71     findViewById<Button>(R.id.show_dialog_button).setOnClickListener {
72       AlertDialog.Builder(this)
73         .setTitle("Leaky dialog")
74         .setPositiveButton("Dismiss and leak dialog") { dialog, _ ->
75           app.leakedDialogs += dialog as AlertDialog
76         }
77         .show()
78     }
79     findViewById<Button>(R.id.start_service_button).setOnClickListener {
80       startService(Intent(this, LeakingService::class.java))
81     }
82     findViewById<Button>(R.id.leak_receiver_button).setOnClickListener {
83       leakyReceiver = true
84       recreate()
85     }
86     findViewById<Button>(R.id.message_leak_button).setOnClickListener {
87       val leaky = Any()
88       AppWatcher.objectWatcher.expectWeaklyReachable(leaky, "Repeated Message")
89       @Suppress("unused")
90       class LeakyReschedulingRunnable(private val leaky: Any) : Runnable {
91         private val handler = Handler(Looper.getMainLooper())
92         override fun run() {
93           handler.postDelayed(this, 1000)
94         }
95       }
96       LeakyReschedulingRunnable(leaky).run()
97     }
98     findViewById<Button>(R.id.infinite_animator).setOnClickListener { view ->
99       ObjectAnimator.ofFloat(view, View.ALPHA, 0.1f, 0.2f).apply {
100         duration = 100
101         repeatMode = ValueAnimator.REVERSE
102         repeatCount = ValueAnimator.INFINITE
103         start()
104       }
105     }
106     findViewById<Button>(R.id.finish_activity).setOnClickListener { view ->
107       finish()
108     }
109   }
110 
111   class NoOpBroadcastReceiver : BroadcastReceiver() {
112     override fun onReceive(
113       context: Context,
114       intent: Intent
115     ) = Unit
116   }
117 
118   override fun onDestroy() {
119     super.onDestroy()
120     if (leakyReceiver) {
121       Handler().postDelayed({
122         if (Build.VERSION.SDK_INT >= 33) {
123           val flags = Context.RECEIVER_EXPORTED
124           application.registerReceiver(NoOpBroadcastReceiver(), IntentFilter(), flags)
125         } else {
126           application.registerReceiver(NoOpBroadcastReceiver(), IntentFilter())
127         }
128       }, 500)
129     }
130   }
131 }
132