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 leakcanary
17 
18 import android.app.Activity
19 import android.app.Dialog
20 import android.view.View
21 import android.view.View.OnAttachStateChangeListener
22 import com.squareup.leakcanary.objectwatcher.core.R
23 import curtains.Curtains
24 import curtains.OnRootViewAddedListener
25 import curtains.WindowType.PHONE_WINDOW
26 import curtains.WindowType.POPUP_WINDOW
27 import curtains.WindowType.TOAST
28 import curtains.WindowType.TOOLTIP
29 import curtains.WindowType.UNKNOWN
30 import curtains.phoneWindow
31 import curtains.windowType
32 import curtains.wrappedCallback
33 import leakcanary.internal.friendly.mainHandler
34 
35 /**
36  * Expects root views to become weakly reachable soon after they are removed from the window
37  * manager.
38  */
39 class RootViewWatcher(
40   private val reachabilityWatcher: ReachabilityWatcher
41 ) : InstallableWatcher {
42 
43   private val listener = OnRootViewAddedListener { rootView ->
44     val trackDetached = when(rootView.windowType) {
45       PHONE_WINDOW -> {
46         when (rootView.phoneWindow?.callback?.wrappedCallback) {
47           // Activities are already tracked by ActivityWatcher
48           is Activity -> false
49           is Dialog -> {
50             // Use app context resources to avoid NotFoundException
51             // https://github.com/square/leakcanary/issues/2137
52             val resources = rootView.context.applicationContext.resources
53             resources.getBoolean(R.bool.leak_canary_watcher_watch_dismissed_dialogs)
54           }
55           // Probably a DreamService
56           else -> true
57         }
58       }
59       // Android widgets keep detached popup window instances around.
60       POPUP_WINDOW -> false
61       TOOLTIP, TOAST, UNKNOWN -> true
62     }
63     if (trackDetached) {
64       rootView.addOnAttachStateChangeListener(object : OnAttachStateChangeListener {
65 
66         val watchDetachedView = Runnable {
67           reachabilityWatcher.expectWeaklyReachable(
68             rootView, "${rootView::class.java.name} received View#onDetachedFromWindow() callback"
69           )
70         }
71 
72         override fun onViewAttachedToWindow(v: View) {
73           mainHandler.removeCallbacks(watchDetachedView)
74         }
75 
76         override fun onViewDetachedFromWindow(v: View) {
77           mainHandler.post(watchDetachedView)
78         }
79       })
80     }
81   }
82 
83   override fun install() {
84     Curtains.onRootViewsChangedListeners += listener
85   }
86 
87   override fun uninstall() {
88     Curtains.onRootViewsChangedListeners -= listener
89   }
90 }
91