-
Notifications
You must be signed in to change notification settings - Fork 29
Expand file tree
/
Copy pathWindows.kt
More file actions
238 lines (224 loc) · 8.74 KB
/
Windows.kt
File metadata and controls
238 lines (224 loc) · 8.74 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
package curtains
import android.content.res.Resources
import android.os.Build
import android.view.FrameMetrics
import android.view.View
import android.view.Window
import android.view.WindowManager
import android.view.windowAttachCount
import androidx.annotation.RequiresApi
import curtains.WindowType.PHONE_WINDOW
import curtains.WindowType.POPUP_WINDOW
import curtains.WindowType.TOAST
import curtains.WindowType.TOOLTIP
import curtains.WindowType.UNKNOWN
import curtains.internal.CurrentFrameMetricsListener
import curtains.internal.NextDrawListener.Companion.onNextDraw
import curtains.internal.WindowCallbackWrapper.Companion.listeners
import curtains.internal.WindowCallbackWrapper.Companion.unwrap
import curtains.internal.WindowSpy
import curtains.internal.frameMetricsHandler
/**
* If this view is part of the view hierarchy from a [android.app.Activity], [android.app.Dialog] or
* [android.service.dreams.DreamService], then this returns the [android.view.Window] instance
* associated to it. Otherwise, this returns null.
*
* Note: this property is called [phoneWindow] because the only implementation of [Window] is
* the internal class android.view.PhoneWindow.
*/
val View.phoneWindow: Window?
get() {
return WindowSpy.pullWindow(rootView)
}
val View.windowType: WindowType
get() {
val rootView = rootView
if (rootView::class.java.name == "androidx.compose.ui.window.PopupLayout") {
return POPUP_WINDOW
}
if (WindowSpy.attachedToPhoneWindow(rootView)) {
return PHONE_WINDOW
}
val windowLayoutParams = rootView.layoutParams as? WindowManager.LayoutParams
return if (windowLayoutParams == null) {
UNKNOWN
} else {
val title = windowLayoutParams.title
when {
title == "Toast" -> TOAST
title == tooltipString -> TOOLTIP
// App compat tooltip uses the class simple name.
title == "TooltipPopup" -> TOOLTIP
title.startsWith("PopupWindow:") -> POPUP_WINDOW
else -> UNKNOWN
}
}
}
// see [com.android.internal.view.TooltipPopup]
private val tooltipString by lazy(LazyThreadSafetyMode.NONE) {
// use id rather than 'Tooltip' because of i18n
val tooltipStringId = Resources.getSystem().getIdentifier("tooltip_popup_title", "string", "android")
try {
Resources.getSystem().getString(tooltipStringId)
} catch (e: Resources.NotFoundException) {
"Tooltip"
}
}
/**
* The list of touch event interceptors, inserted in [Window.Callback.dispatchTouchEvent].
*
* If you only care about logging touch events without intercepting, you can implement the SAM
* interface [OnTouchEventListener] which extends [TouchEventInterceptor].
*
* Calling this has a side effect of wrapping the window callback (on first call).
*/
val Window.touchEventInterceptors: MutableList<TouchEventInterceptor>
get() {
return listeners.touchEventInterceptors
}
/**
* The list of key event interceptors, inserted in [Window.Callback.dispatchKeyEvent].
*
* If you only care about logging key events without intercepting, you can implement the SAM
* interface [OnKeyEventListener] which extends [KeyEventInterceptor].
*
* Calling this has a side effect of wrapping the window callback (on first call).
*/
val Window.keyEventInterceptors: MutableList<KeyEventInterceptor>
get() {
return listeners.keyEventInterceptors
}
/**
* The list of content changed listeners, inserted in [Window.Callback.onContentChanged].
*
* Calling this has a side effect of wrapping the window callback (on first call).
*/
val Window.onContentChangedListeners: MutableList<OnContentChangedListener>
get() {
return listeners.onContentChangedListeners
}
/**
* The list of window focus changed listeners, inserted in [Window.Callback.onWindowFocusChanged].
*
* Calling this has a side effect of wrapping the window callback (on first call).
*/
val Window.onWindowFocusChangedListeners: MutableList<OnWindowFocusChangedListener>
get() {
return listeners.onWindowFocusChangedListeners
}
/**
* Calls [onDecorViewReady] with the decor view when the [android.view.Window] has a decor view
* available.
*
* When an activity Window is created, it initially doesn't have a decor view set. Calling
* [android.view.Window.getDecorView] has the side effect of creating that decor view if it hasn't
* been created yet, which prevents further configuration that needs to happen before creation
* of the decor view.
*
* This utility allows getting access to the decor view as soon as its ready (i.e. as soon as
* [android.view.Window.setContentView] is called).
*
* Calling this has a side effect of wrapping the window callback (on first call), unless
* the decor view was already set.
*/
fun Window.onDecorViewReady(onDecorViewReady: (View) -> Unit) {
val decorViewOrNull = peekDecorView()
if (decorViewOrNull != null) {
onDecorViewReady(decorViewOrNull)
} else {
listeners.run {
onContentChangedListeners += object : OnContentChangedListener {
override fun onContentChanged() {
onContentChangedListeners -= this
onDecorViewReady(peekDecorView())
}
}
}
}
}
/**
* Calls [onNextDraw] the next time this window is drawn, i.e. the next time
* [android.view.ViewTreeObserver.OnDrawListener.onDraw] is called.
*
* The most common use for this is to measure the first draw time from
* [android.app.Activity.onCreate] or
* [android.app.Application.ActivityLifecycleCallbacks.onActivityCreated].
*
* This utility exists to work around [android.view.ViewTreeObserver] bugs: if the window
* isn't attached then prior to Android API 26 all [android.view.ViewTreeObserver.OnDrawListener]
* listeners are lost when the window gets attached:
* https://android.googlesource.com/platform/frameworks/base/+/9f8ec54244a5e0343b9748db3329733f259604f3
*
* Also, [android.view.ViewTreeObserver.removeOnDrawListener] cannot be called from within the
* [android.view.ViewTreeObserver.OnDrawListener.onDraw] callback, so this works around that
* by posting the removal.
*
* Calling this has a side effect of wrapping the window callback (on first call), unless
* the decor view was already set.
*
* No-op below Android API 16.
*/
fun Window.onNextDraw(onNextDraw: () -> Unit) {
if (Build.VERSION.SDK_INT < 16) {
return
}
onDecorViewReady { decorView ->
decorView.onNextDraw(onNextDraw)
}
}
/**
* This is a helper extension method to simplify the usage of
* [android.view.Window.addOnFrameMetricsAvailableListener] when trying to get the frame metrics
* only once, for the next frame.
*
* The provided [onNextFrameMetrics] callback will run some time after the next frame is rendered,
* with [FrameMetrics] information. The callback does not run on the main thread but on a
* background handler thread (always the same single thread).
*
* Usage:
*
* ```
* Choreographer.getInstance().postFrameCallback { frameTimeNanos ->
* window.onNextFrameMetrics(frameTimeNanos) { frameMetrics ->
* }
* }
* ```
*
* You should follow the recommendations from
* [android.view.Window.OnFrameMetricsAvailableListener.onFrameMetricsAvailable], particularly:
* It is highly recommended that clients copy the passed in FrameMetrics within this method and
* defer additional computation or storage to another thread to avoid unnecessarily dropping
* reports.
*
* The report producer cannot wait for the consumer to complete, so it's possible that the next
* frame metrics is dropped. To detect this, [frameTimeNanos] is used as a key to identify the
* current frame, and compared against [android.view.FrameMetrics.VSYNC_TIMESTAMP] to ensure
* [onNextFrameMetrics] doesn't run if the next frame metrics were skipped.
*/
@RequiresApi(26)
fun Window.onNextFrameMetrics(frameTimeNanos: Long, onNextFrameMetrics: (FrameMetrics) -> Unit) {
val frameMetricsListener = CurrentFrameMetricsListener(frameTimeNanos, onNextFrameMetrics)
addOnFrameMetricsAvailableListener(frameMetricsListener, frameMetricsHandler)
}
/**
* Returns [View.getWindowAttachCount] which has protected visibility and is normally only
* accessible from within view subclasses.
*/
val View.windowAttachCount: Int
get() {
return windowAttachCount(this)
}
/**
* Returns the original window callback.
*
* The helper functions provided in this file replace the original window callback and delegate to
* it. Jetpack libraries (Android X and previously the support library) also replace the window
* callback for activities. [wrappedCallback] returns the callback that was replaced. This is
* useful to check its type, which should be either [android.app.Activity], [android.app.Dialog] or
* [android.service.dreams.DreamService]
*
* Note that this may be null if the Window doesn't have a callback set, which normally doesn't
* happen.
*/
val Window.Callback?.wrappedCallback: Window.Callback?
get() = unwrap()