1 package leakcanary.internal
2 
3 import android.app.Activity
4 import android.app.Application
5 import android.content.Context
6 import android.content.ContextWrapper
7 import android.os.Looper
8 import android.os.MessageQueue.IdleHandler
9 import android.view.View
10 import android.view.View.OnAttachStateChangeListener
11 import android.view.ViewTreeObserver.OnGlobalFocusChangeListener
12 import android.view.inputmethod.InputMethodManager
13 import java.lang.reflect.Field
14 import java.lang.reflect.Method
15 import shark.SharkLog
16 
17 internal class ReferenceCleaner(
18   private val inputMethodManager: InputMethodManager,
19   private val mHField: Field,
20   private val mServedViewField: Field,
21   private val finishInputLockedMethod: Method
22 ) : IdleHandler,
23   OnAttachStateChangeListener,
24   OnGlobalFocusChangeListener {
onGlobalFocusChangednull25   override fun onGlobalFocusChanged(
26     oldFocus: View?,
27     newFocus: View?
28   ) {
29     if (newFocus == null) {
30       return
31     }
32     oldFocus?.removeOnAttachStateChangeListener(this)
33     Looper.myQueue()
34       .removeIdleHandler(this)
35     newFocus.addOnAttachStateChangeListener(this)
36   }
37 
onViewAttachedToWindownull38   override fun onViewAttachedToWindow(v: View) {}
onViewDetachedFromWindownull39   override fun onViewDetachedFromWindow(v: View) {
40     v.removeOnAttachStateChangeListener(this)
41     Looper.myQueue()
42       .removeIdleHandler(this)
43     Looper.myQueue()
44       .addIdleHandler(this)
45   }
46 
queueIdlenull47   override fun queueIdle(): Boolean {
48     clearInputMethodManagerLeak()
49     return false
50   }
51 
clearInputMethodManagerLeaknull52   private fun clearInputMethodManagerLeak() {
53     try {
54       val lock = mHField[inputMethodManager]
55       if (lock == null) {
56         SharkLog.d { "InputMethodManager.mH was null, could not fix leak." }
57         return
58       }
59       // This is highly dependent on the InputMethodManager implementation.
60       synchronized(lock) {
61         val servedView =
62           mServedViewField[inputMethodManager] as View?
63         if (servedView != null) {
64           val servedViewAttached =
65             servedView.windowVisibility != View.GONE
66           if (servedViewAttached) {
67             // The view held by the IMM was replaced without a global focus change. Let's make
68             // sure we get notified when that view detaches.
69             // Avoid double registration.
70             servedView.removeOnAttachStateChangeListener(this)
71             servedView.addOnAttachStateChangeListener(this)
72           } else { // servedView is not attached. InputMethodManager is being stupid!
73             val activity = extractActivity(servedView.context)
74             if (activity == null || activity.window == null) {
75               // Unlikely case. Let's finish the input anyways.
76               finishInputLockedMethod.invoke(inputMethodManager)
77             } else {
78               val decorView = activity.window
79                 .peekDecorView()
80               val windowAttached =
81                 decorView.windowVisibility != View.GONE
82               // If the window is attached, we do nothing. The IMM is leaking a detached view
83               // hierarchy, but we haven't found a way to clear the reference without breaking
84               // the IMM behavior.
85               if (!windowAttached) {
86                 finishInputLockedMethod.invoke(inputMethodManager)
87               }
88             }
89           }
90         }
91       }
92     } catch (ignored: Throwable) {
93       SharkLog.d(ignored) { "Could not fix leak" }
94     }
95   }
96 
extractActivitynull97   private fun extractActivity(sourceContext: Context): Activity? {
98     var context = sourceContext
99     while (true) {
100       context = when (context) {
101         is Application -> {
102           return null
103         }
104         is Activity -> {
105           return context
106         }
107         is ContextWrapper -> {
108           val baseContext =
109             context.baseContext
110           // Prevent Stack Overflow.
111           if (baseContext === context) {
112             return null
113           }
114           baseContext
115         }
116         else -> {
117           return null
118         }
119       }
120     }
121   }
122 }
123