1 /*
<lambda>null2  * Copyright 2019 The Android Open Source Project
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 
17 @file:Suppress("DEPRECATION")
18 
19 package androidx.compose.ui.platform
20 
21 import android.annotation.SuppressLint
22 import android.content.Context
23 import android.content.res.Configuration
24 import android.graphics.Point
25 import android.graphics.Rect
26 import android.os.Build.VERSION.SDK_INT
27 import android.os.Build.VERSION_CODES.M
28 import android.os.Build.VERSION_CODES.N
29 import android.os.Build.VERSION_CODES.O
30 import android.os.Build.VERSION_CODES.Q
31 import android.os.Build.VERSION_CODES.S
32 import android.os.Build.VERSION_CODES.VANILLA_ICE_CREAM
33 import android.os.Looper
34 import android.os.StrictMode
35 import android.os.SystemClock
36 import android.util.LongSparseArray
37 import android.util.SparseArray
38 import android.view.InputDevice
39 import android.view.KeyEvent as AndroidKeyEvent
40 import android.view.MotionEvent
41 import android.view.MotionEvent.ACTION_CANCEL
42 import android.view.MotionEvent.ACTION_DOWN
43 import android.view.MotionEvent.ACTION_HOVER_ENTER
44 import android.view.MotionEvent.ACTION_HOVER_EXIT
45 import android.view.MotionEvent.ACTION_HOVER_MOVE
46 import android.view.MotionEvent.ACTION_MOVE
47 import android.view.MotionEvent.ACTION_POINTER_DOWN
48 import android.view.MotionEvent.ACTION_POINTER_UP
49 import android.view.MotionEvent.ACTION_SCROLL
50 import android.view.MotionEvent.ACTION_UP
51 import android.view.MotionEvent.TOOL_TYPE_MOUSE
52 import android.view.ScrollCaptureTarget
53 import android.view.View
54 import android.view.ViewGroup
55 import android.view.ViewStructure
56 import android.view.ViewTreeObserver
57 import android.view.accessibility.AccessibilityNodeInfo
58 import android.view.animation.AnimationUtils
59 import android.view.autofill.AutofillManager as PlatformAndroidManager
60 import android.view.autofill.AutofillValue
61 import android.view.inputmethod.EditorInfo
62 import android.view.inputmethod.InputConnection
63 import android.view.translation.ViewTranslationCallback
64 import android.view.translation.ViewTranslationRequest
65 import android.view.translation.ViewTranslationResponse
66 import androidx.annotation.DoNotInline
67 import androidx.annotation.RequiresApi
68 import androidx.annotation.VisibleForTesting
69 import androidx.collection.MutableIntObjectMap
70 import androidx.collection.mutableIntObjectMapOf
71 import androidx.collection.mutableObjectListOf
72 import androidx.compose.runtime.derivedStateOf
73 import androidx.compose.runtime.getValue
74 import androidx.compose.runtime.mutableStateOf
75 import androidx.compose.runtime.referentialEqualityPolicy
76 import androidx.compose.runtime.setValue
77 import androidx.compose.runtime.snapshots.Snapshot
78 import androidx.compose.ui.ComposeUiFlags
79 import androidx.compose.ui.ComposeUiFlags.isAdaptiveRefreshRateEnabled
80 import androidx.compose.ui.ExperimentalComposeUiApi
81 import androidx.compose.ui.InternalComposeUiApi
82 import androidx.compose.ui.Modifier
83 import androidx.compose.ui.R
84 import androidx.compose.ui.SessionMutex
85 import androidx.compose.ui.autofill.AndroidAutofill
86 import androidx.compose.ui.autofill.AndroidAutofillManager
87 import androidx.compose.ui.autofill.Autofill
88 import androidx.compose.ui.autofill.AutofillCallback
89 import androidx.compose.ui.autofill.AutofillManager
90 import androidx.compose.ui.autofill.AutofillTree
91 import androidx.compose.ui.autofill.PlatformAutofillManagerImpl
92 import androidx.compose.ui.autofill.performAutofill
93 import androidx.compose.ui.autofill.populateViewStructure
94 import androidx.compose.ui.contentcapture.AndroidContentCaptureManager
95 import androidx.compose.ui.draganddrop.AndroidDragAndDropManager
96 import androidx.compose.ui.draganddrop.ComposeDragShadowBuilder
97 import androidx.compose.ui.draganddrop.DragAndDropTransferData
98 import androidx.compose.ui.focus.FocusDirection
99 import androidx.compose.ui.focus.FocusDirection.Companion.Down
100 import androidx.compose.ui.focus.FocusDirection.Companion.Enter
101 import androidx.compose.ui.focus.FocusDirection.Companion.Exit
102 import androidx.compose.ui.focus.FocusDirection.Companion.Left
103 import androidx.compose.ui.focus.FocusDirection.Companion.Next
104 import androidx.compose.ui.focus.FocusDirection.Companion.Previous
105 import androidx.compose.ui.focus.FocusDirection.Companion.Right
106 import androidx.compose.ui.focus.FocusDirection.Companion.Up
107 import androidx.compose.ui.focus.FocusOwner
108 import androidx.compose.ui.focus.FocusOwnerImpl
109 import androidx.compose.ui.focus.FocusTargetNode
110 import androidx.compose.ui.focus.calculateBoundingRectRelativeTo
111 import androidx.compose.ui.focus.focusRect
112 import androidx.compose.ui.focus.is1dFocusSearch
113 import androidx.compose.ui.focus.isBetterCandidate
114 import androidx.compose.ui.focus.requestInteropFocus
115 import androidx.compose.ui.focus.toAndroidFocusDirection
116 import androidx.compose.ui.focus.toFocusDirection
117 import androidx.compose.ui.focus.toLayoutDirection
118 import androidx.compose.ui.geometry.Offset
119 import androidx.compose.ui.geometry.Size
120 import androidx.compose.ui.graphics.Canvas
121 import androidx.compose.ui.graphics.CanvasHolder
122 import androidx.compose.ui.graphics.GraphicsContext
123 import androidx.compose.ui.graphics.Matrix
124 import androidx.compose.ui.graphics.drawscope.DrawScope
125 import androidx.compose.ui.graphics.layer.GraphicsLayer
126 import androidx.compose.ui.graphics.setFrom
127 import androidx.compose.ui.graphics.toAndroidRect
128 import androidx.compose.ui.graphics.toComposeRect
129 import androidx.compose.ui.hapticfeedback.HapticFeedback
130 import androidx.compose.ui.hapticfeedback.PlatformHapticFeedback
131 import androidx.compose.ui.input.InputMode.Companion.Keyboard
132 import androidx.compose.ui.input.InputMode.Companion.Touch
133 import androidx.compose.ui.input.InputModeManager
134 import androidx.compose.ui.input.InputModeManagerImpl
135 import androidx.compose.ui.input.indirect.IndirectTouchEvent
136 import androidx.compose.ui.input.indirect.IndirectTouchEventType
137 import androidx.compose.ui.input.key.Key
138 import androidx.compose.ui.input.key.Key.Companion.Back
139 import androidx.compose.ui.input.key.Key.Companion.DirectionCenter
140 import androidx.compose.ui.input.key.Key.Companion.DirectionDown
141 import androidx.compose.ui.input.key.Key.Companion.DirectionLeft
142 import androidx.compose.ui.input.key.Key.Companion.DirectionRight
143 import androidx.compose.ui.input.key.Key.Companion.DirectionUp
144 import androidx.compose.ui.input.key.Key.Companion.Escape
145 import androidx.compose.ui.input.key.Key.Companion.NavigateNext
146 import androidx.compose.ui.input.key.Key.Companion.NavigatePrevious
147 import androidx.compose.ui.input.key.Key.Companion.NumPadEnter
148 import androidx.compose.ui.input.key.Key.Companion.PageDown
149 import androidx.compose.ui.input.key.Key.Companion.PageUp
150 import androidx.compose.ui.input.key.Key.Companion.Tab
151 import androidx.compose.ui.input.key.KeyEvent
152 import androidx.compose.ui.input.key.KeyEventType.Companion.KeyDown
153 import androidx.compose.ui.input.key.isShiftPressed
154 import androidx.compose.ui.input.key.key
155 import androidx.compose.ui.input.key.onKeyEvent
156 import androidx.compose.ui.input.key.type
157 import androidx.compose.ui.input.pointer.AndroidPointerIcon
158 import androidx.compose.ui.input.pointer.AndroidPointerIconType
159 import androidx.compose.ui.input.pointer.MatrixPositionCalculator
160 import androidx.compose.ui.input.pointer.MotionEventAdapter
161 import androidx.compose.ui.input.pointer.PointerIcon
162 import androidx.compose.ui.input.pointer.PointerIconService
163 import androidx.compose.ui.input.pointer.PointerInputEventProcessor
164 import androidx.compose.ui.input.pointer.PointerKeyboardModifiers
165 import androidx.compose.ui.input.pointer.ProcessResult
166 import androidx.compose.ui.input.pointer.SuspendingPointerInputModifierNode
167 import androidx.compose.ui.input.rotary.RotaryScrollEvent
168 import androidx.compose.ui.input.rotary.onRotaryScrollEvent
169 import androidx.compose.ui.internal.checkPreconditionNotNull
170 import androidx.compose.ui.layout.LayoutCoordinates
171 import androidx.compose.ui.layout.Placeable
172 import androidx.compose.ui.layout.PlacementScope
173 import androidx.compose.ui.layout.RootMeasurePolicy
174 import androidx.compose.ui.layout.positionInRoot
175 import androidx.compose.ui.modifier.ModifierLocalManager
176 import androidx.compose.ui.node.InternalCoreApi
177 import androidx.compose.ui.node.LayoutNode
178 import androidx.compose.ui.node.LayoutNode.UsageByParent
179 import androidx.compose.ui.node.LayoutNodeDrawScope
180 import androidx.compose.ui.node.MeasureAndLayoutDelegate
181 import androidx.compose.ui.node.ModifierNodeElement
182 import androidx.compose.ui.node.Nodes
183 import androidx.compose.ui.node.OutOfFrameExecutor
184 import androidx.compose.ui.node.OwnedLayer
185 import androidx.compose.ui.node.Owner
186 import androidx.compose.ui.node.OwnerSnapshotObserver
187 import androidx.compose.ui.node.RootForTest
188 import androidx.compose.ui.node.visitSubtree
189 import androidx.compose.ui.platform.MotionEventVerifierApi29.isValidMotionEvent
190 import androidx.compose.ui.platform.coreshims.ContentCaptureSessionCompat
191 import androidx.compose.ui.platform.coreshims.ViewCompatShims
192 import androidx.compose.ui.relocation.BringIntoViewModifierNode
193 import androidx.compose.ui.scrollcapture.ScrollCapture
194 import androidx.compose.ui.semantics.EmptySemanticsElement
195 import androidx.compose.ui.semantics.EmptySemanticsModifier
196 import androidx.compose.ui.semantics.SemanticsOwner
197 import androidx.compose.ui.semantics.findClosestParentNode
198 import androidx.compose.ui.spatial.RectManager
199 import androidx.compose.ui.text.font.Font
200 import androidx.compose.ui.text.font.FontFamily
201 import androidx.compose.ui.text.font.createFontFamilyResolver
202 import androidx.compose.ui.text.input.PlatformTextInputService
203 import androidx.compose.ui.text.input.TextInputService
204 import androidx.compose.ui.text.input.TextInputServiceAndroid
205 import androidx.compose.ui.unit.Constraints
206 import androidx.compose.ui.unit.Density
207 import androidx.compose.ui.unit.IntOffset
208 import androidx.compose.ui.unit.LayoutDirection
209 import androidx.compose.ui.unit.round
210 import androidx.compose.ui.util.fastIsFinite
211 import androidx.compose.ui.util.fastLastOrNull
212 import androidx.compose.ui.util.fastRoundToInt
213 import androidx.compose.ui.util.trace
214 import androidx.compose.ui.viewinterop.AndroidViewHolder
215 import androidx.compose.ui.viewinterop.InteropView
216 import androidx.core.view.AccessibilityDelegateCompat
217 import androidx.core.view.InputDeviceCompat.SOURCE_CLASS_POINTER
218 import androidx.core.view.InputDeviceCompat.SOURCE_ROTARY_ENCODER
219 import androidx.core.view.MotionEventCompat.AXIS_SCROLL
220 import androidx.core.view.ViewCompat
221 import androidx.core.view.ViewConfigurationCompat.getScaledHorizontalScrollFactor
222 import androidx.core.view.ViewConfigurationCompat.getScaledVerticalScrollFactor
223 import androidx.core.view.accessibility.AccessibilityNodeInfoCompat
224 import androidx.core.view.accessibility.AccessibilityNodeProviderCompat
225 import androidx.lifecycle.DefaultLifecycleObserver
226 import androidx.lifecycle.Lifecycle
227 import androidx.lifecycle.LifecycleOwner
228 import androidx.lifecycle.findViewTreeLifecycleOwner
229 import androidx.savedstate.SavedStateRegistryOwner
230 import androidx.savedstate.findViewTreeSavedStateRegistryOwner
231 import java.lang.reflect.Method
232 import java.util.function.Consumer
233 import kotlin.coroutines.CoroutineContext
234 
235 /** Allows tests to inject a custom [PlatformTextInputService]. */
236 internal var platformTextInputServiceInterceptor:
237     (PlatformTextInputService) -> PlatformTextInputService =
238     {
239         it
240     }
241 
242 private const val ONE_FRAME_120_HERTZ_IN_MILLISECONDS = 8L
243 
244 @Suppress("ViewConstructor", "VisibleForTests", "ConstPropertyName", "NullAnnotationGroup")
245 @OptIn(InternalComposeUiApi::class)
246 internal class AndroidComposeView(context: Context, coroutineContext: CoroutineContext) :
247     ViewGroup(context),
248     Owner,
249     ViewRootForTest,
250     MatrixPositionCalculator,
251     DefaultLifecycleObserver,
252     OutOfFrameExecutor {
253 
254     /**
255      * Remembers the position of the last pointer input event that was down. This position will be
256      * used to calculate whether this view is considered scrollable via [canScrollHorizontally]/
257      * [canScrollVertically].
258      */
259     private var lastDownPointerPosition: Offset = Offset.Unspecified
260 
261     /**
262      * Signal that AndroidComposeView's superclass constructors have finished running. If this is
263      * false, it's because the runtime's default uninitialized value is currently visible and
264      * AndroidComposeView's constructor hasn't started running yet. In this state other expected
265      * invariants do not hold, e.g. property delegates may not be initialized. View/ViewGroup have a
266      * history of calling non-final methods in their constructors that can lead to this case, e.g.
267      * [onRtlPropertiesChanged].
268      */
269     private var superclassInitComplete = true
270 
271     override val sharedDrawScope = LayoutNodeDrawScope()
272 
273     override val view: View
274         get() = this
275 
276     override var density by mutableStateOf(Density(context), referentialEqualityPolicy())
277         private set
278 
279     private lateinit var frameRateCategoryView: View
280 
281     internal val isArrEnabled =
282         @OptIn(ExperimentalComposeUiApi::class) isAdaptiveRefreshRateEnabled &&
283             SDK_INT >= VANILLA_ICE_CREAM
284 
285     private val rootSemanticsNode = EmptySemanticsModifier()
286     private val semanticsModifier = EmptySemanticsElement(rootSemanticsNode)
287     private val bringIntoViewNode =
288         object : ModifierNodeElement<BringIntoViewOnScreenResponderNode>() {
createnull289             override fun create() = BringIntoViewOnScreenResponderNode(this@AndroidComposeView)
290 
291             override fun update(node: BringIntoViewOnScreenResponderNode) {
292                 node.view = this@AndroidComposeView
293             }
294 
inspectablePropertiesnull295             override fun InspectorInfo.inspectableProperties() {
296                 name = "BringIntoViewOnScreen"
297             }
298 
hashCodenull299             override fun hashCode(): Int = this@AndroidComposeView.hashCode()
300 
301             override fun equals(other: Any?) = other === this
302         }
303 
304     override val focusOwner: FocusOwner =
305         FocusOwnerImpl(
306             onRequestApplyChangesListener = ::registerOnEndApplyChangesListener,
307             onRequestFocusForOwner = ::onRequestFocusForOwner,
308             onMoveFocusInterop = ::onMoveFocusInChildren,
309             onClearFocusForOwner = ::onClearFocusForOwner,
310             onFocusRectInterop = ::onFetchFocusRect,
311             onLayoutDirection = ::layoutDirection
312         )
313 
314     override fun getImportantForAutofill(): Int {
315         return View.IMPORTANT_FOR_AUTOFILL_YES
316     }
317 
318     override var coroutineContext: CoroutineContext = coroutineContext
319         // In some rare cases, the CoroutineContext is cancelled (because the parent
320         // CompositionContext containing the CoroutineContext is no longer associated with this
321         // class). Changing this CoroutineContext to the new CompositionContext's CoroutineContext
322         // needs to cancel all Pointer Input Nodes relying on the old CoroutineContext.
323         // See [Wrapper.android.kt] for more details.
324         set(value) {
325             field = value
326 
327             val headModifierNode = root.nodes.head
328 
329             // Reset head Modifier.Node's pointer input handler (that is, the underlying
330             // coroutine used to run the handler for input pointer events).
331             if (headModifierNode is SuspendingPointerInputModifierNode) {
332                 headModifierNode.resetPointerInputHandler()
333             }
334 
335             // Reset all other Modifier.Node's pointer input handler in the chain.
<lambda>null336             headModifierNode.visitSubtree(Nodes.PointerInput) {
337                 if (it is SuspendingPointerInputModifierNode) {
338                     it.resetPointerInputHandler()
339                 }
340             }
341         }
342 
343     override val dragAndDropManager = AndroidDragAndDropManager(::startDrag)
344 
345     private val _windowInfo: LazyWindowInfo = LazyWindowInfo()
346     override val windowInfo: WindowInfo
347         get() = _windowInfo
348 
349     /**
350      * Because AndroidComposeView always accepts focus, we have to divert focus to another View if
351      * there is nothing focusable within. However, if there are only nonfocusable ComposeViews, then
352      * the redirection can recurse infinitely. This makes sure that if that happens, then it can
353      * bail when it is detected
354      */
355     private var processingRequestFocusForNextNonChildView = false
356 
357     // When move focus is triggered by a key event, and move focus does not cause any focus change,
358     // we return the key event to the view system if focus search finds a suitable view which is not
359     // a compose sub-view. However if move focus is triggered programmatically, we have to manually
360     // implement this behavior because the view system does not have a moveFocus API.
onMoveFocusInChildrennull361     private fun onMoveFocusInChildren(focusDirection: FocusDirection): Boolean {
362         @OptIn(ExperimentalComposeUiApi::class)
363         if (!ComposeUiFlags.isViewFocusFixEnabled) {
364             // The view system does not have an API corresponding to Enter/Exit.
365             if (focusDirection == Enter || focusDirection == Exit) return false
366 
367             val direction =
368                 checkNotNull(focusDirection.toAndroidFocusDirection()) { "Invalid focus direction" }
369             val focusedRect = onFetchFocusRect()?.toAndroidRect()
370 
371             val nextView =
372                 FocusFinderCompat.instance.let {
373                     if (focusedRect == null) {
374                         it.findNextFocus(this, findFocus(), direction)
375                     } else {
376                         it.findNextFocusFromRect(this, focusedRect, direction)
377                     }
378                 }
379             return nextView?.requestInteropFocus(direction, focusedRect) ?: false
380         }
381         // The view system does not have an API corresponding to Enter/Exit.
382         if (focusDirection == Enter || focusDirection == Exit || !hasFocus()) return false
383 
384         val androidViewsHandler = _androidViewsHandler ?: return false
385 
386         val direction =
387             checkNotNull(focusDirection.toAndroidFocusDirection()) { "Invalid focus direction" }
388 
389         val root = rootView as ViewGroup
390 
391         val currentFocus = root.findFocus() ?: error("view hasFocus but root can't find it")
392 
393         val focusFinder = FocusFinderCompat.instance
394         val nextView = focusFinder.findNextFocus(root, currentFocus, direction)
395         val focusedRect: Rect?
396         if (focusDirection.is1dFocusSearch() && androidViewsHandler.hasFocus()) {
397             focusedRect = null
398         } else {
399             focusedRect = onFetchFocusRect()?.toAndroidRect()
400             if (nextView != null && focusedRect != null) {
401                 root.offsetDescendantRectToMyCoords(this, focusedRect)
402                 root.offsetRectIntoDescendantCoords(nextView, focusedRect)
403             }
404         }
405 
406         // is it part of the compose hierarchy?
407         if (nextView == null || nextView === currentFocus) {
408             return false
409         }
410 
411         val focusedChild = androidViewsHandler.focusedChild
412         var nextParent = nextView.parent
413         while (nextParent != null && nextParent !== focusedChild) {
414             nextParent = nextParent.parent
415         }
416         if (nextParent == null) {
417             return false // not a part of the compose hierarchy
418         }
419         return nextView.requestInteropFocus(direction, focusedRect)
420     }
421 
422     // If this root view is focused, we can get the focus rect from focusOwner. But if a sub-view
423     // has focus, the rect returned by focusOwner would be the bounds of the focus target
424     // surrounding the embedded view. For a more accurate focus rect, we use the bounds of the
425     // focused sub-view.
onFetchFocusRectnull426     private fun onFetchFocusRect(): androidx.compose.ui.geometry.Rect? =
427         if (isFocused) {
428             focusOwner.getFocusRect()
429         } else {
430             findFocus()?.calculateBoundingRectRelativeTo(this)
431         }
432 
433     // TODO(b/177931787) : Consider creating a KeyInputManager like we have for FocusManager so
434     //  that this common logic can be used by all owners.
435     private val keyInputModifier =
keyEventnull436         Modifier.onKeyEvent { keyEvent ->
437             val focusDirection = getFocusDirection(keyEvent)
438             if (focusDirection == null || keyEvent.type != KeyDown) return@onKeyEvent false
439 
440             val androidDirection = focusDirection.toAndroidFocusDirection()
441 
442             @OptIn(ExperimentalComposeUiApi::class)
443             if (ComposeUiFlags.isViewFocusFixEnabled) {
444                 if (hasFocus() && androidDirection != null) {
445                     // A child AndroidView is focused. See if the view has a child that should be
446                     // focused next.
447                     if (onMoveFocusInChildren(focusDirection)) return@onKeyEvent true
448                 }
449             }
450             val focusedRect = onFetchFocusRect()
451 
452             // Consume the key event if we moved focus or if focus search or requestFocus is
453             // cancelled.
454             val focusWasMovedOrCancelled =
455                 focusOwner.focusSearch(focusDirection, focusedRect) {
456                     it.requestFocus(focusDirection)
457                 } ?: true
458             if (focusWasMovedOrCancelled) return@onKeyEvent true
459 
460             // For 2D focus search, we don't need to wrap around, so we just return false. If there
461             // are
462             // items after this view that haven't been visited, they will be visited when the
463             // unconsumed key event triggers a focus search.
464             if (!focusDirection.is1dFocusSearch()) return@onKeyEvent false
465 
466             // For 1D focus search, we use FocusFinder to find the next view that is not a child of
467             // this view. We don't return false because we don't want to re-visit sub-views. They
468             // will
469             // instead be visited when the AndroidView around them gets a moveFocus(Enter)).
470 
471             if (androidDirection != null) {
472                 val nextView = findNextNonChildView(androidDirection).takeIf { it != this }
473                 if (nextView != null) {
474                     val androidRect = checkNotNull(focusedRect?.toAndroidRect()) { "Invalid rect" }
475                     val rootView = rootView as ViewGroup
476                     rootView.offsetDescendantRectToMyCoords(this, androidRect)
477                     rootView.offsetRectIntoDescendantCoords(nextView, androidRect)
478                     if (nextView.requestInteropFocus(androidDirection, androidRect)) {
479                         return@onKeyEvent true
480                     }
481                 }
482             }
483 
484             // Focus finder couldn't find another view. We manually wrap around since focus remained
485             // on this view.
486             val clearedFocusSuccessfully =
487                 focusOwner.clearFocus(
488                     force = false,
489                     refreshFocusEvents = true,
490                     clearOwnerFocus = false,
491                     focusDirection = focusDirection
492                 )
493 
494             // Consume the key event if clearFocus was cancelled.
495             if (!clearedFocusSuccessfully) return@onKeyEvent true
496 
497             // Perform wrap-around focus search by running a focus search after clearing focus.
498             return@onKeyEvent focusOwner.focusSearch(focusDirection, null) {
499                 it.requestFocus(focusDirection)
500             } ?: true
501         }
502 
findNextNonChildViewnull503     private fun findNextNonChildView(direction: Int): View? {
504         var currentView: View? = this
505         val focusFinder = FocusFinderCompat.instance
506         while (currentView != null) {
507             currentView = focusFinder.findNextFocus(rootView as ViewGroup, currentView, direction)
508             if (currentView != null && !containsDescendant(currentView)) return currentView
509         }
510         return null
511     }
512 
513     private val rotaryInputModifier =
<lambda>null514         Modifier.onRotaryScrollEvent {
515             // TODO(b/210748692): call focusManager.moveFocus() in response to rotary events.
516             false
517         }
518 
519     private val canvasHolder = CanvasHolder()
520 
521     override val viewConfiguration: ViewConfiguration =
522         AndroidViewConfiguration(android.view.ViewConfiguration.get(context))
523 
524     override val root =
<lambda>null525         LayoutNode().also {
526             it.measurePolicy = RootMeasurePolicy
527             it.density = density
528             it.viewConfiguration = viewConfiguration
529             // Composed modifiers cannot be added here directly
530             it.modifier =
531                 Modifier.then(semanticsModifier)
532                     .then(rotaryInputModifier)
533                     .then(keyInputModifier)
534                     .then(focusOwner.modifier)
535                     .then(dragAndDropManager.modifier)
536                     .then(bringIntoViewNode)
537         }
538 
539     override val layoutNodes: MutableIntObjectMap<LayoutNode> = mutableIntObjectMapOf()
540 
541     override val rectManager = RectManager(layoutNodes)
542 
543     override val rootForTest: RootForTest = this
544     internal var uncaughtExceptionHandler: RootForTest.UncaughtExceptionHandler? = null
545 
546     override val semanticsOwner: SemanticsOwner =
547         SemanticsOwner(root, rootSemanticsNode, layoutNodes)
548     private val composeAccessibilityDelegate = AndroidComposeViewAccessibilityDelegateCompat(this)
549     internal var contentCaptureManager =
550         AndroidContentCaptureManager(
551             view = this,
552             onContentCaptureSession = ::getContentCaptureSessionCompat
553         )
554 
555     /**
556      * Provide accessibility manager to the user. Use the Android version of accessibility manager.
557      */
558     override val accessibilityManager = AndroidAccessibilityManager(context)
559 
560     /**
561      * Provide access to a GraphicsContext instance used to create GraphicsLayers for providing
562      * isolation boundaries for rendering portions of a Composition hierarchy as well as for
563      * achieving certain visual effects like masks and blurs
564      */
565     override val graphicsContext = GraphicsContext(this)
566 
567     // Used by components that want to provide autofill semantic information.
568     // TODO: Replace with SemanticsTree: Temporary hack until we have a semantics tree implemented.
569     // TODO: Replace with SemanticsTree.
570     //  This is a temporary hack until we have a semantics tree implemented.
571     override val autofillTree = AutofillTree()
572 
573     // OwnedLayers that are dirty and should be redrawn.
574     private val dirtyLayers = mutableListOf<OwnedLayer>()
575 
576     // OwnerLayers that invalidated themselves during their last draw. They will be redrawn
577     // during the next AndroidComposeView dispatchDraw pass.
578     private var postponedDirtyLayers: MutableList<OwnedLayer>? = null
579 
580     private var isDrawingContent = false
581     private var isPendingInteropViewLayoutChangeDispatch = false
582 
583     private val motionEventAdapter = MotionEventAdapter()
584     private val pointerInputEventProcessor = PointerInputEventProcessor(root)
585 
586     /**
587      * Used for updating LocalConfiguration when configuration changes - consume LocalConfiguration
588      * instead of changing this observer if you are writing a component that adapts to configuration
589      * changes.
590      */
<lambda>null591     var configurationChangeObserver: (Configuration) -> Unit = {}
592 
593     private val _autofill = if (autofillSupported()) AndroidAutofill(this, autofillTree) else null
594 
595     internal val _autofillManager =
596         if (autofillSupported()) {
597             val platformAutofill = context.getSystemService(PlatformAndroidManager::class.java)
<lambda>null598             checkPreconditionNotNull(platformAutofill) { "Autofill service could not be located." }
599             AndroidAutofillManager(
600                 platformAutofillManager = PlatformAutofillManagerImpl(platformAutofill),
601                 semanticsOwner = semanticsOwner,
602                 view = this,
603                 rectManager = rectManager,
604                 packageName = context.packageName
605             )
606         } else {
607             null
608         }
609 
610     // Used as a CompositionLocal for performing autofill.
611     override val autofill: Autofill?
612         get() = _autofill
613 
614     // Used as a CompositionLocal for performing semantic autofill.
615     override val autofillManager: AutofillManager?
616         get() = _autofillManager
617 
618     private var observationClearRequested = false
619 
620     /** Provide clipboard manager to the user. Use the Android version of clipboard manager. */
621     override val clipboardManager = AndroidClipboardManager(context)
622 
623     override val clipboard = AndroidClipboard(clipboardManager)
624 
commandnull625     override val snapshotObserver = OwnerSnapshotObserver { command ->
626         if (handler?.looper === Looper.myLooper()) {
627             command()
628         } else {
629             handler?.post(command)
630         }
631     }
632 
633     @Suppress("UnnecessaryOptInAnnotation")
634     @OptIn(InternalCoreApi::class)
635     override var showLayoutBounds = false
636         get() {
637             return if (SDK_INT >= 30) Api30Impl.isShowingLayoutBounds(this) else field
638         }
639 
640     private var _androidViewsHandler: AndroidViewsHandler? = null
641     internal val androidViewsHandler: AndroidViewsHandler
642         get() {
643             if (_androidViewsHandler == null) {
644                 _androidViewsHandler = AndroidViewsHandler(context)
645                 addView(_androidViewsHandler)
646                 // Ensure that AndroidViewsHandler is measured and laid out after creation, so that
647                 // it can report correct bounds on screen (for semantics, etc).
648                 // Normally this is done by addView, but here we disabled it for optimization
649                 // purposes.
650                 requestLayout()
651             }
652             return _androidViewsHandler!!
653         }
654 
655     private var viewLayersContainer: DrawChildContainer? = null
656 
657     // The constraints being used by the last onMeasure. It is set to null in onLayout. It allows
658     // us to detect the case when the View was measured twice with different constraints within
659     // the same measure pass.
660     private var onMeasureConstraints: Constraints? = null
661 
662     // Will be set to true when we were measured twice with different constraints during the last
663     // measure pass.
664     private var wasMeasuredWithMultipleConstraints = false
665 
666     private val measureAndLayoutDelegate = MeasureAndLayoutDelegate(root)
667 
668     override val measureIteration: Long
669         get() = measureAndLayoutDelegate.measureIteration
670 
671     override val hasPendingMeasureOrLayout
672         get() = measureAndLayoutDelegate.hasPendingMeasureOrLayout
673 
674     private var globalPosition: IntOffset = IntOffset(Int.MAX_VALUE, Int.MAX_VALUE)
675 
676     private val tmpPositionArray = intArrayOf(0, 0)
677     private val tmpMatrix = Matrix()
678     private val viewToWindowMatrix = Matrix()
679     private val windowToViewMatrix = Matrix()
680 
681     @VisibleForTesting internal var lastMatrixRecalculationAnimationTime = -1L
682     private var forceUseMatrixCache = false
683 
684     /**
685      * On some devices, the `getLocationOnScreen()` returns `(0, 0)` even when the Window is offset
686      * in special circumstances. This contains the screen coordinates of the containing Window the
687      * last time the [viewToWindowMatrix] and [windowToViewMatrix] were recalculated.
688      */
689     private var windowPosition = Offset.Infinite
690 
691     // Used to track whether or not there was an exception while creating an MRenderNode
692     // so that we don't have to continue using try/catch after fails once.
693     private var isRenderNodeCompatible = true
694 
695     private var _viewTreeOwners: ViewTreeOwners? by mutableStateOf(null)
696 
697     // Having an extra derived state here (instead of directly using _viewTreeOwners) is a
698     // workaround for b/271579465 to avoid unnecessary extra recompositions when this is mutated
699     // before setContent is called.
700     /**
701      * Current [ViewTreeOwners]. Use [setOnViewTreeOwnersAvailable] if you want to execute your code
702      * when the object will be created.
703      */
<lambda>null704     val viewTreeOwners: ViewTreeOwners? by derivedStateOf { _viewTreeOwners }
705 
706     private var onViewTreeOwnersAvailable: ((ViewTreeOwners) -> Unit)? = null
707 
708     // executed when the layout pass has been finished. as a result of it our view could be moved
709     // inside the window (we are interested not only in the event when our parent positioned us
710     // on a different position, but also in the position of each of the grandparents as all these
711     // positions add up to final global position)
712     private val globalLayoutListener =
<lambda>null713         ViewTreeObserver.OnGlobalLayoutListener { updatePositionCacheAndDispatch() }
714 
715     // executed when a scrolling container like ScrollView of RecyclerView performed the scroll,
716     // this could affect our global position
717     private val scrollChangedListener =
<lambda>null718         ViewTreeObserver.OnScrollChangedListener { updatePositionCacheAndDispatch() }
719 
720     // executed whenever the touch mode changes.
721     private val touchModeChangeListener =
touchModenull722         ViewTreeObserver.OnTouchModeChangeListener { touchMode ->
723             _inputModeManager.inputMode = if (touchMode) Touch else Keyboard
724         }
725 
726     private val legacyTextInputServiceAndroid = TextInputServiceAndroid(view, this)
727 
728     /**
729      * The legacy text input service. This is only used for new text input sessions if
730      * [textInputSessionMutex] is null.
731      */
732     @Deprecated("Use PlatformTextInputModifierNode instead.")
733     override val textInputService =
734         TextInputService(platformTextInputServiceInterceptor(legacyTextInputServiceAndroid))
735 
736     private val textInputSessionMutex = SessionMutex<AndroidPlatformTextInputSession>()
737 
738     override val softwareKeyboardController: SoftwareKeyboardController =
739         DelegatingSoftwareKeyboardController(textInputService)
740 
741     override val placementScope: Placeable.PlacementScope
742         get() = PlacementScope(this)
743 
textInputSessionnull744     override suspend fun textInputSession(
745         session: suspend PlatformTextInputSessionScope.() -> Nothing
746     ): Nothing =
747         textInputSessionMutex.withSessionCancellingPrevious(
748             sessionInitializer = {
749                 AndroidPlatformTextInputSession(
750                     view = this,
751                     textInputService = textInputService,
752                     coroutineScope = it
753                 )
754             },
755             session = session
756         )
757 
758     @Deprecated(
759         "fontLoader is deprecated, use fontFamilyResolver",
760         replaceWith = ReplaceWith("fontFamilyResolver")
761     )
762     @Suppress("DEPRECATION")
763     override val fontLoader: Font.ResourceLoader = AndroidFontResourceLoader(context)
764 
765     // Backed by mutableStateOf so that the local provider recomposes when it changes
766     // FontFamily.Resolver is not guaranteed to be stable or immutable, hence referential check
767     override var fontFamilyResolver: FontFamily.Resolver by
768         mutableStateOf(createFontFamilyResolver(context), referentialEqualityPolicy())
769         private set
770 
771     // keeps track of changes in font weight adjustment to update fontFamilyResolver
772     private var currentFontWeightAdjustment: Int =
773         context.resources.configuration.fontWeightAdjustmentCompat
774 
775     private val Configuration.fontWeightAdjustmentCompat: Int
776         get() = if (SDK_INT >= S) fontWeightAdjustment else 0
777 
778     // Backed by mutableStateOf so that the ambient provider recomposes when it changes
779     override var layoutDirection by
780         mutableStateOf(
781             // We don't use the attached View's layout direction here since that layout direction
782             // may not
783             // be resolved since composables may be composed without attaching to the RootViewImpl.
784             // In Jetpack Compose, use the locale layout direction (i.e. layoutDirection came from
785             // configuration) as a default layout direction.
786             toLayoutDirection(context.resources.configuration.layoutDirection)
787                 ?: LayoutDirection.Ltr
788         )
789         private set
790 
791     /** Provide haptic feedback to the user. Use the Android version of haptic feedback. */
792     override val hapticFeedBack: HapticFeedback = PlatformHapticFeedback(this)
793 
794     /** Provide an instance of [InputModeManager] which is available as a CompositionLocal. */
795     private val _inputModeManager =
796         InputModeManagerImpl(
797             initialInputMode = if (isInTouchMode) Touch else Keyboard,
<lambda>null798             onRequestInputModeChange = {
799                 when (it) {
800                     // Android doesn't support programmatically switching to touch mode, so we
801                     // don't do anything, but just return true if we are already in touch mode.
802                     Touch -> isInTouchMode
803 
804                     // If we are already in keyboard mode, we return true, otherwise, we call
805                     // requestFocusFromTouch, which puts the system in non-touch mode.
806                     Keyboard -> if (isInTouchMode) requestFocusFromTouch() else true
807                     else -> false
808                 }
809             }
810         )
811     override val inputModeManager: InputModeManager
812         get() = _inputModeManager
813 
814     override val modifierLocalManager: ModifierLocalManager = ModifierLocalManager(this)
815 
816     /**
817      * Provide textToolbar to the user, for text-related operation. Use the Android version of
818      * floating toolbar(post-M) and primary toolbar(pre-M).
819      */
820     override val textToolbar: TextToolbar = AndroidTextToolbar(this)
821 
822     /**
823      * When the first event for a mouse is ACTION_DOWN, an ACTION_HOVER_ENTER is never sent. This
824      * means that we won't receive an `Enter` event for the first mouse. In order to prevent this
825      * problem, we track whether or not the previous event was with the mouse inside and if not, we
826      * can create a simulated mouse enter event to force an enter.
827      */
828     private var previousMotionEvent: MotionEvent? = null
829 
830     /** The time of the last layout. This is used to send a synthetic MotionEvent. */
831     private var relayoutTime = 0L
832 
833     /**
834      * A cache for OwnedLayers. Recreating ViewLayers is expensive, so we avoid it as much as
835      * possible. This also helps a little with RenderNodeLayers as well.
836      */
837     private val layerCache = WeakCache<OwnedLayer>()
838 
839     /** List of lambdas to be called when [onEndApplyChanges] is called. */
840     private val endApplyChangesListeners = mutableObjectListOf<(() -> Unit)?>()
841 
842     private var currentFrameRate = 0f
843     private var currentFrameRateCategory = 0f
844 
845     /**
846      * Runnable used to update the pointer position after layout. If another pointer event comes in
847      * before this runs, this Runnable will be removed and not executed.
848      */
849     private val resendMotionEventRunnable =
850         object : Runnable {
runnull851             override fun run() {
852                 removeCallbacks(this)
853                 val lastMotionEvent = previousMotionEvent
854                 if (lastMotionEvent != null) {
855                     val wasMouseEvent = lastMotionEvent.getToolType(0) == TOOL_TYPE_MOUSE
856                     val action = lastMotionEvent.actionMasked
857                     val resend =
858                         if (wasMouseEvent) {
859                             action != ACTION_HOVER_EXIT && action != ACTION_UP
860                         } else {
861                             action != ACTION_UP
862                         }
863                     if (resend) {
864                         val newAction =
865                             if (action == ACTION_HOVER_MOVE || action == ACTION_HOVER_ENTER) {
866                                 ACTION_HOVER_MOVE
867                             } else {
868                                 ACTION_MOVE
869                             }
870                         sendSimulatedEvent(
871                             lastMotionEvent,
872                             newAction,
873                             relayoutTime,
874                             forceHover = false
875                         )
876                     }
877                 }
878             }
879         }
880 
881     /**
882      * If an [ACTION_HOVER_EXIT] event is received, it could be because an [ACTION_DOWN] is coming
883      * from a mouse or stylus. We can't know for certain until the next event is sent. This message
884      * is posted after receiving the [ACTION_HOVER_EXIT] to send the event if nothing else is
885      * received before that.
886      */
<lambda>null887     private val sendHoverExitEvent = Runnable {
888         hoverExitReceived = false
889         val lastEvent = previousMotionEvent!!
890         check(lastEvent.actionMasked == ACTION_HOVER_EXIT) {
891             "The ACTION_HOVER_EXIT event was not cleared."
892         }
893         sendMotionEvent(lastEvent)
894     }
895 
896     /** Set to `true` when [sendHoverExitEvent] has been posted. */
897     private var hoverExitReceived = false
898 
899     /** Callback for [measureAndLayout] to update the pointer position 150ms after layout. */
<lambda>null900     private val resendMotionEventOnLayout: () -> Unit = {
901         val lastEvent = previousMotionEvent
902         if (lastEvent != null) {
903             when (lastEvent.actionMasked) {
904                 // We currently only care about hover events being updated when layout changes
905                 ACTION_HOVER_ENTER,
906                 ACTION_HOVER_MOVE -> {
907                     relayoutTime = SystemClock.uptimeMillis()
908                     post(resendMotionEventRunnable)
909                 }
910             }
911         }
912     }
913 
914     private val matrixToWindow =
915         if (SDK_INT < Q) CalculateMatrixToWindowApi21(tmpMatrix) else CalculateMatrixToWindowApi29()
916 
917     /**
918      * Keyboard modifiers state might be changed when window is not focused, so window doesn't
919      * receive any key events. This flag is set when window focus changes. Then we can rely on it
920      * when handling the first movementEvent to get the actual keyboard modifiers state from it.
921      * After window gains focus, the first motionEvent.metaState (after focus gained) is used to
922      * update windowInfo.keyboardModifiers. See [onWindowFocusChanged] and [sendMotionEvent]
923      */
924     private var keyboardModifiersRequireUpdate = false
925 
<lambda>null926     init {
927         addOnAttachStateChangeListener(contentCaptureManager)
928         setWillNotDraw(false)
929         isFocusable = true
930         if (SDK_INT >= O) {
931             AndroidComposeViewVerificationHelperMethodsO.focusable(
932                 this,
933                 focusable = View.FOCUSABLE,
934                 defaultFocusHighlightEnabled = false
935             )
936         }
937         isFocusableInTouchMode = true
938         clipChildren = false
939         ViewCompat.setAccessibilityDelegate(this, composeAccessibilityDelegate)
940         ViewRootForTest.onViewCreatedCallback?.invoke(this)
941         setOnDragListener(dragAndDropManager)
942         root.attach(this)
943 
944         // Support for this feature in Compose is tracked here: b/207654434
945         if (SDK_INT >= Q) AndroidComposeViewForceDarkModeQ.disallowForceDark(this)
946 
947         if (isArrEnabled) {
948             frameRateCategoryView =
949                 View(context).apply {
950                     layoutParams = LayoutParams(1, 1)
951                     // hide this View from layout inspector
952                     setTag(R.id.hide_in_inspector_tag, true)
953                 }
954             addView(frameRateCategoryView)
955         }
956     }
957 
958     /**
959      * Since this view has its own concept of internal focus, it needs to report that to the view
960      * system for accurate focus searching and so ViewRootImpl will scroll correctly.
961      */
getFocusedRectnull962     override fun getFocusedRect(rect: Rect) {
963         onFetchFocusRect()?.run {
964             rect.left = left.fastRoundToInt()
965             rect.top = top.fastRoundToInt()
966             rect.right = right.fastRoundToInt()
967             rect.bottom = bottom.fastRoundToInt()
968         } ?: super.getFocusedRect(rect)
969     }
970 
971     /**
972      * Avoid crash by not traversing assist structure. Autofill assistStructure will be dispatched
973      * via `dispatchProvideAutofillStructure` from Android 8 and on. See b/251152083 and b/320768586
974      * more details.
975      */
dispatchProvideStructurenull976     override fun dispatchProvideStructure(structure: ViewStructure) {
977         if (SDK_INT in 23..27) {
978             AndroidComposeViewAssistHelperMethodsO.setClassName(structure, view)
979         } else {
980             super.dispatchProvideStructure(structure)
981         }
982     }
983 
984     private val scrollCapture = if (SDK_INT >= 31) ScrollCapture() else null
985     internal val scrollCaptureInProgress: Boolean
986         get() =
987             if (SDK_INT >= 31) {
988                 scrollCapture?.scrollCaptureInProgress ?: false
989             } else {
990                 false
991             }
992 
onScrollCaptureSearchnull993     override fun onScrollCaptureSearch(
994         localVisibleRect: Rect,
995         windowOffset: Point,
996         targets: Consumer<ScrollCaptureTarget>
997     ) {
998         if (SDK_INT >= 31) {
999             scrollCapture?.onScrollCaptureSearch(
1000                 view = this,
1001                 semanticsOwner = semanticsOwner,
1002                 coroutineContext = coroutineContext,
1003                 targets = targets
1004             )
1005         }
1006     }
1007 
onResumenull1008     override fun onResume(owner: LifecycleOwner) {
1009         // Refresh in onResume in case the value has changed.
1010         if (SDK_INT < 30) {
1011             showLayoutBounds = getIsShowingLayoutBounds()
1012         }
1013     }
1014 
focusSearchnull1015     override fun focusSearch(focused: View?, direction: Int): View? {
1016         // do not propagate search if a measurement is happening
1017         if (focused == null || measureAndLayoutDelegate.duringMeasureLayout) {
1018             return super.focusSearch(focused, direction)
1019         }
1020 
1021         // Find the next subview if any using FocusFinder.
1022         val nextView = FocusFinderCompat.instance.findNextFocus(this, focused, direction)
1023 
1024         // Find the next composable using FocusOwner.
1025         val focusedBounds =
1026             if (focused === this) {
1027                 focusOwner.getFocusRect() ?: focused.calculateBoundingRectRelativeTo(this)
1028             } else {
1029                 focused.calculateBoundingRectRelativeTo(this)
1030             }
1031         val focusDirection = toFocusDirection(direction) ?: Down
1032         var focusTarget: FocusTargetNode? = null
1033         val searchResult =
1034             focusOwner.focusSearch(focusDirection, focusedBounds) {
1035                 focusTarget = it
1036                 true
1037             }
1038 
1039         return when {
1040             searchResult == null -> focused // Focus Search Cancelled.
1041             focusTarget == null -> nextView ?: focused // No compose focus item
1042             nextView == null -> this // No found View, so go to the found Compose focus item
1043             focusDirection.is1dFocusSearch() -> super.focusSearch(focused, direction)
1044             isBetterCandidate(
1045                 focusTarget!!.focusRect(),
1046                 nextView.calculateBoundingRectRelativeTo(this),
1047                 focusedBounds,
1048                 focusDirection
1049             ) -> this // Compose focus is better than View focus
1050             else -> nextView // View focus is better than Compose focus
1051         }
1052     }
1053 
requestFocusnull1054     override fun requestFocus(direction: Int, previouslyFocusedRect: Rect?): Boolean {
1055         @OptIn(ExperimentalComposeUiApi::class)
1056         if (!ComposeUiFlags.isViewFocusFixEnabled) {
1057             // This view is already focused.
1058             if (isFocused) return true
1059 
1060             // If the root has focus, it means a sub-view is focused,
1061             // and is trying to move focus within itself.
1062             if (focusOwner.rootState.hasFocus) {
1063                 return super.requestFocus(direction, previouslyFocusedRect)
1064             }
1065 
1066             val focusDirection = toFocusDirection(direction) ?: Enter
1067             return focusOwner.focusSearch(
1068                 focusDirection = focusDirection,
1069                 focusedRect = previouslyFocusedRect?.toComposeRect()
1070             ) {
1071                 it.requestFocus(focusDirection)
1072             } == true
1073         }
1074         // This view is already focused.
1075         if (isFocused) return true
1076 
1077         // There is nothing focusable and we've looped around all Views back to this one, so
1078         // we just return false to indicate that nothing can be focused.
1079         if (processingRequestFocusForNextNonChildView) return false
1080 
1081         // If there is currently a focusRequest() in operation, a clearFocus() due to AndroidView
1082         // may cause a recursive requestFocus(). This prevents that reentrant call.
1083         if (focusOwner.focusTransactionManager.ongoingTransaction) return false
1084 
1085         val focusDirection = toFocusDirection(direction) ?: Enter
1086 
1087         // If the root has focus, it means a sub-view is focused,
1088         // and is trying to move focus within itself.
1089         if (hasFocus() && onMoveFocusInChildren(focusDirection)) return true
1090 
1091         var foundFocusable = false
1092         val focusSearchResult =
1093             focusOwner.focusSearch(
1094                 focusDirection = focusDirection,
1095                 focusedRect = previouslyFocusedRect?.toComposeRect()
1096             ) {
1097                 foundFocusable = true
1098                 it.requestFocus(focusDirection)
1099             }
1100         if (focusSearchResult == null) {
1101             return false // The focus search was canceled
1102         }
1103         if (focusSearchResult) {
1104             return true // We found something to focus on
1105         }
1106         if (foundFocusable) {
1107             return false // The requestFocus() from within the focusSearch was canceled
1108         }
1109 
1110         if (previouslyFocusedRect != null && !hasFocus()) {
1111             // try searching, ignoring the previously focused rect. We've had a request to focus on
1112             // this specific View
1113             val altFocus =
1114                 focusOwner.focusSearch(focusDirection = focusDirection, focusedRect = null) {
1115                     it.requestFocus(focusDirection)
1116                 }
1117             if (altFocus == true) {
1118                 // found alternative focus
1119                 return true
1120             }
1121         }
1122 
1123         // We advertised ourselves as focusable, but we aren't. Try to just move the focus to the
1124         // next item.
1125         val nextFocusedView = findNextNonChildView(direction)
1126 
1127         // Can crash if we return false when we've advertised ourselves as focusable and we aren't
1128         // b/369256395
1129         if (nextFocusedView == null || nextFocusedView === this) {
1130             // There is no next View, so just return true so we don't cause a crash
1131             return true
1132         }
1133 
1134         // Try to focus on the next focusable View
1135         processingRequestFocusForNextNonChildView = true
1136         val requestFocusResult = nextFocusedView.requestFocus(direction)
1137         processingRequestFocusForNextNonChildView = false
1138         return requestFocusResult
1139     }
1140 
onRequestFocusForOwnernull1141     private fun onRequestFocusForOwner(
1142         focusDirection: FocusDirection?,
1143         previouslyFocusedRect: androidx.compose.ui.geometry.Rect?
1144     ): Boolean {
1145         // We don't request focus if the view is already focused, or if an embedded view is focused,
1146         // because this would cause the embedded view to lose focus.
1147         if (isFocused || hasFocus()) return true
1148 
1149         return super.requestFocus(
1150             focusDirection?.toAndroidFocusDirection() ?: FOCUS_DOWN,
1151             @Suppress("DEPRECATION") previouslyFocusedRect?.toAndroidRect()
1152         )
1153     }
1154 
onClearFocusForOwnernull1155     private fun onClearFocusForOwner() {
1156         @OptIn(ExperimentalComposeUiApi::class)
1157         if (isFocused || (!ComposeUiFlags.isViewFocusFixEnabled && hasFocus())) {
1158             super.clearFocus()
1159         } else if (hasFocus()) {
1160             // Call clearFocus() on the child that has focus
1161             findFocus()?.clearFocus()
1162             super.clearFocus()
1163         }
1164     }
1165 
onFocusChangednull1166     override fun onFocusChanged(gainFocus: Boolean, direction: Int, previouslyFocusedRect: Rect?) {
1167         super.onFocusChanged(gainFocus, direction, previouslyFocusedRect)
1168         if (!gainFocus && !hasFocus()) {
1169             focusOwner.releaseFocus()
1170         }
1171     }
1172 
onWindowFocusChangednull1173     override fun onWindowFocusChanged(hasWindowFocus: Boolean) {
1174         _windowInfo.isWindowFocused = hasWindowFocus
1175         keyboardModifiersRequireUpdate = true
1176         super.onWindowFocusChanged(hasWindowFocus)
1177 
1178         if (hasWindowFocus && SDK_INT < 30) {
1179             // Refresh in onResume in case the value has changed from the quick settings tile, in
1180             // which case the activity won't be paused/resumed (b/225937688).
1181             getIsShowingLayoutBounds().also { newShowLayoutBounds ->
1182                 if (showLayoutBounds != newShowLayoutBounds) {
1183                     showLayoutBounds = newShowLayoutBounds
1184                     // Unlike in onResume, getting window focus doesn't automatically trigger a new
1185                     // draw pass, so we have to do that manually.
1186                     invalidateDescendants()
1187                 }
1188             }
1189         }
1190     }
1191 
1192     /** This function is used by the testing framework to send key events. */
sendKeyEventnull1193     override fun sendKeyEvent(keyEvent: KeyEvent): Boolean =
1194         // First dispatch the key event to mimic the event being intercepted before it is sent to
1195         // the soft keyboard.
1196         focusOwner.dispatchInterceptedSoftKeyboardEvent(keyEvent) ||
1197             // Next, send the key event to the Soft Keyboard.
1198             // TODO(b/272600716): Send the key event to the IME.
1199 
1200             // Finally, dispatch the key event to onPreKeyEvent/onKeyEvent listeners.
1201             focusOwner.dispatchKeyEvent(keyEvent)
1202 
1203     /** This function is used by the testing framework to send indirect touch events. */
1204     @OptIn(ExperimentalComposeUiApi::class)
1205     override fun sendIndirectTouchEvent(indirectTouchEvent: IndirectTouchEvent): Boolean {
1206         return focusOwner.dispatchIndirectTouchEvent(indirectTouchEvent)
1207     }
1208 
dispatchKeyEventnull1209     override fun dispatchKeyEvent(event: AndroidKeyEvent): Boolean =
1210         if (isFocused) {
1211             // Focus lies within the Compose hierarchy, so we dispatch the key event to the
1212             // appropriate place.
1213             _windowInfo.keyboardModifiers = PointerKeyboardModifiers(event.metaState)
1214             // If the event is not consumed, use the default implementation.
1215             focusOwner.dispatchKeyEvent(KeyEvent(event)) || super.dispatchKeyEvent(event)
1216         } else {
1217             // This Owner has a focused child view, which is a view interoperability use case,
1218             // so we use the default ViewGroup behavior which will route tke key event to the
1219             // focused child view.
1220             focusOwner.dispatchKeyEvent(
1221                 keyEvent = KeyEvent(event),
<lambda>null1222                 onFocusedItem = {
1223                     // TODO(b/320510084): Add tests to verify that embedded views receive key
1224                     // events.
1225                     super.dispatchKeyEvent(event)
1226                 }
1227             )
1228         }
1229 
dispatchKeyEventPreImenull1230     override fun dispatchKeyEventPreIme(event: AndroidKeyEvent): Boolean {
1231         return (isFocused && focusOwner.dispatchInterceptedSoftKeyboardEvent(KeyEvent(event))) ||
1232             // If this view is not focused, and it received a key event, it means this is a view
1233             // interoperability use case and we need to route the event to the embedded child view.
1234             // Also, if this event wasn't consumed by the compose hierarchy, we need to send it back
1235             // to the parent view. Both these cases are handles by the default view implementation.
1236             super.dispatchKeyEventPreIme(event)
1237     }
1238 
1239     /**
1240      * This function is used by the delegate file to enable accessibility frameworks for testing.
1241      */
forceAccessibilityForTestingnull1242     override fun forceAccessibilityForTesting(enable: Boolean) {
1243         composeAccessibilityDelegate.accessibilityForceEnabledForTesting = enable
1244     }
1245 
1246     /**
1247      * This function is used by the delegate file to set the time interval between sending
1248      * accessibility events in milliseconds.
1249      */
setAccessibilityEventBatchIntervalMillisnull1250     override fun setAccessibilityEventBatchIntervalMillis(intervalMillis: Long) {
1251         composeAccessibilityDelegate.SendRecurringAccessibilityEventsIntervalMillis = intervalMillis
1252     }
1253 
onPreAttachnull1254     override fun onPreAttach(node: LayoutNode) {
1255         layoutNodes[node.semanticsId] = node
1256     }
1257 
onPostAttachnull1258     override fun onPostAttach(node: LayoutNode) {
1259         @OptIn(ExperimentalComposeUiApi::class)
1260         if (autofillSupported() && ComposeUiFlags.isSemanticAutofillEnabled) {
1261             _autofillManager?.onPostAttach(node)
1262         }
1263     }
1264 
onDetachnull1265     override fun onDetach(node: LayoutNode) {
1266         layoutNodes.remove(node.semanticsId)
1267         measureAndLayoutDelegate.onNodeDetached(node)
1268         requestClearInvalidObservations()
1269         @OptIn(ExperimentalComposeUiApi::class)
1270         if (ComposeUiFlags.isRectTrackingEnabled) {
1271             rectManager.remove(node)
1272         }
1273         @OptIn(ExperimentalComposeUiApi::class)
1274         if (autofillSupported() && ComposeUiFlags.isSemanticAutofillEnabled) {
1275             _autofillManager?.onDetach(node)
1276         }
1277     }
1278 
requestAutofillnull1279     override fun requestAutofill(node: LayoutNode) {
1280         @OptIn(ExperimentalComposeUiApi::class)
1281         if (autofillSupported() && ComposeUiFlags.isSemanticAutofillEnabled) {
1282             _autofillManager?.requestAutofill(node)
1283         }
1284     }
1285 
requestClearInvalidObservationsnull1286     fun requestClearInvalidObservations() {
1287         observationClearRequested = true
1288     }
1289 
onEndApplyChangesnull1290     override fun onEndApplyChanges() {
1291         if (observationClearRequested) {
1292             snapshotObserver.clearInvalidObservations()
1293             observationClearRequested = false
1294         }
1295         val childAndroidViews = _androidViewsHandler
1296         if (childAndroidViews != null) {
1297             clearChildInvalidObservations(childAndroidViews)
1298         }
1299         @OptIn(ExperimentalComposeUiApi::class)
1300         if (autofillSupported() && ComposeUiFlags.isSemanticAutofillEnabled) {
1301             _autofillManager?.onEndApplyChanges()
1302         }
1303         // Listeners can add more items to the list and we want to ensure that they
1304         // are executed after being added, so loop until the list is empty
1305         while (endApplyChangesListeners.isNotEmpty() && endApplyChangesListeners[0] != null) {
1306             val size = endApplyChangesListeners.size
1307             for (i in 0 until size) {
1308                 val listener = endApplyChangesListeners[i]
1309                 // null out the item so that if the listener is re-added then we execute it again.
1310                 endApplyChangesListeners[i] = null
1311                 listener?.invoke()
1312             }
1313             // Remove all the items that were visited. Removing items shifts all items after
1314             // to the front of the list, so removing in a chunk is cheaper than removing one-by-one
1315             endApplyChangesListeners.removeRange(0, size)
1316         }
1317     }
1318 
registerOnEndApplyChangesListenernull1319     override fun registerOnEndApplyChangesListener(listener: () -> Unit) {
1320         if (listener !in endApplyChangesListeners) {
1321             endApplyChangesListeners += listener
1322         }
1323     }
1324 
startDragnull1325     private fun startDrag(
1326         transferData: DragAndDropTransferData,
1327         decorationSize: Size,
1328         drawDragDecoration: DrawScope.() -> Unit,
1329     ): Boolean {
1330         val density =
1331             with(context.resources) {
1332                 Density(density = displayMetrics.density, fontScale = configuration.fontScale)
1333             }
1334         val shadowBuilder =
1335             ComposeDragShadowBuilder(
1336                 density = density,
1337                 decorationSize = decorationSize,
1338                 drawDragDecoration = drawDragDecoration,
1339             )
1340         @Suppress("DEPRECATION")
1341         return if (SDK_INT >= N) {
1342             AndroidComposeViewStartDragAndDropN.startDragAndDrop(
1343                 view = this,
1344                 transferData = transferData,
1345                 dragShadowBuilder = shadowBuilder,
1346             )
1347         } else {
1348             startDrag(
1349                 transferData.clipData,
1350                 shadowBuilder,
1351                 transferData.localState,
1352                 transferData.flags,
1353             )
1354         }
1355     }
1356 
clearChildInvalidObservationsnull1357     private fun clearChildInvalidObservations(viewGroup: ViewGroup) {
1358         for (i in 0 until viewGroup.childCount) {
1359             val child = viewGroup.getChildAt(i)
1360             if (child is AndroidComposeView) {
1361                 child.onEndApplyChanges()
1362             } else if (child is ViewGroup) {
1363                 clearChildInvalidObservations(child)
1364             }
1365         }
1366     }
1367 
addExtraDataToAccessibilityNodeInfoHelpernull1368     private fun addExtraDataToAccessibilityNodeInfoHelper(
1369         virtualViewId: Int,
1370         info: AccessibilityNodeInfo,
1371         extraDataKey: String
1372     ) {
1373         // This extra is just for testing: needed a way to retrieve `traversalBefore` and
1374         // `traversalAfter` from a non-sealed instance of an ANI
1375         when (extraDataKey) {
1376             composeAccessibilityDelegate.ExtraDataTestTraversalBeforeVal -> {
1377                 composeAccessibilityDelegate.idToBeforeMap.getOrDefault(virtualViewId, -1).let {
1378                     if (it != -1) {
1379                         info.extras.putInt(extraDataKey, it)
1380                     }
1381                 }
1382             }
1383             composeAccessibilityDelegate.ExtraDataTestTraversalAfterVal -> {
1384                 composeAccessibilityDelegate.idToAfterMap.getOrDefault(virtualViewId, -1).let {
1385                     if (it != -1) {
1386                         info.extras.putInt(extraDataKey, it)
1387                     }
1388                 }
1389             }
1390             else -> {}
1391         }
1392     }
1393 
addViewnull1394     override fun addView(child: View?) = addView(child, -1)
1395 
1396     override fun addView(child: View?, index: Int) =
1397         addView(child, index, child!!.layoutParams ?: generateDefaultLayoutParams())
1398 
1399     override fun addView(child: View?, width: Int, height: Int) =
1400         addView(
1401             child,
1402             -1,
1403             generateDefaultLayoutParams().also {
1404                 it.width = width
1405                 it.height = height
1406             }
1407         )
1408 
addViewnull1409     override fun addView(child: View?, params: LayoutParams?) = addView(child, -1, params)
1410 
1411     /**
1412      * Directly adding _real_ [View]s to this view is not supported for external consumers, so we
1413      * can use the non-layout-invalidating [addViewInLayout] for when we need to add utility
1414      * container views, such as [viewLayersContainer].
1415      */
1416     override fun addView(child: View?, index: Int, params: LayoutParams?) {
1417         addViewInLayout(child, index, params, /* preventRequestLayout= */ true)
1418     }
1419 
1420     /**
1421      * Called to inform the owner that a new Android [View] was [attached][Owner.onPreAttach] to the
1422      * hierarchy.
1423      */
addAndroidViewnull1424     fun addAndroidView(view: AndroidViewHolder, layoutNode: LayoutNode) {
1425         androidViewsHandler.holderToLayoutNode[view] = layoutNode
1426         androidViewsHandler.addView(view)
1427         androidViewsHandler.layoutNodeToHolder[layoutNode] = view
1428         // Fetching AccessibilityNodeInfo from a View which is not set to
1429         // IMPORTANT_FOR_ACCESSIBILITY_YES will return null.
1430         view.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES)
1431         val thisView = this
1432         ViewCompat.setAccessibilityDelegate(
1433             view,
1434             object : AccessibilityDelegateCompat() {
1435                 override fun onInitializeAccessibilityNodeInfo(
1436                     host: View,
1437                     info: AccessibilityNodeInfoCompat
1438                 ) {
1439                     super.onInitializeAccessibilityNodeInfo(host, info)
1440 
1441                     // Prevent TalkBack from trying to focus the AndroidViewHolder.
1442                     // This also prevents UIAutomator from finding nodes, so don't
1443                     // do it if there are no enabled a11y services (which implies that
1444                     // UIAutomator is the one requesting an AccessibilityNodeInfo).
1445                     if (composeAccessibilityDelegate.isEnabled) {
1446                         info.isVisibleToUser = false
1447                     }
1448 
1449                     var parentId =
1450                         layoutNode
1451                             .findClosestParentNode { it.nodes.has(Nodes.Semantics) }
1452                             ?.semanticsId
1453                     if (
1454                         parentId == null || parentId == semanticsOwner.unmergedRootSemanticsNode.id
1455                     ) {
1456                         parentId = AccessibilityNodeProviderCompat.HOST_VIEW_ID
1457                     }
1458                     info.setParent(thisView, parentId)
1459                     val semanticsId = layoutNode.semanticsId
1460 
1461                     val beforeId =
1462                         composeAccessibilityDelegate.idToBeforeMap.getOrDefault(semanticsId, -1)
1463                     if (beforeId != -1) {
1464                         val beforeView = androidViewsHandler.semanticsIdToView(beforeId)
1465                         if (beforeView != null) {
1466                             // If the node that should come before this one is a view, we want to
1467                             // pass in the "before" view itself, which is retrieved
1468                             // from `androidViewsHandler.idToViewMap`.
1469                             info.setTraversalBefore(beforeView)
1470                         } else {
1471                             // Otherwise, we'll just set the "before" value by passing in
1472                             // the semanticsId.
1473                             info.setTraversalBefore(thisView, beforeId)
1474                         }
1475                         addExtraDataToAccessibilityNodeInfoHelper(
1476                             semanticsId,
1477                             info.unwrap(),
1478                             composeAccessibilityDelegate.ExtraDataTestTraversalBeforeVal
1479                         )
1480                     }
1481 
1482                     val afterId =
1483                         composeAccessibilityDelegate.idToAfterMap.getOrDefault(semanticsId, -1)
1484                     if (afterId != -1) {
1485                         val afterView = androidViewsHandler.semanticsIdToView(afterId)
1486                         if (afterView != null) {
1487                             info.setTraversalAfter(afterView)
1488                         } else {
1489                             info.setTraversalAfter(thisView, afterId)
1490                         }
1491                         addExtraDataToAccessibilityNodeInfoHelper(
1492                             semanticsId,
1493                             info.unwrap(),
1494                             composeAccessibilityDelegate.ExtraDataTestTraversalAfterVal
1495                         )
1496                     }
1497                 }
1498             }
1499         )
1500     }
1501 
1502     /**
1503      * Called to inform the owner that an Android [View] was [detached][Owner.onDetach] from the
1504      * hierarchy.
1505      */
removeAndroidViewnull1506     fun removeAndroidView(view: AndroidViewHolder) {
1507         androidViewsHandler.removeViewInLayout(view)
1508         androidViewsHandler.layoutNodeToHolder.remove(
1509             androidViewsHandler.holderToLayoutNode.remove(view)
1510         )
1511         view.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_AUTO)
1512     }
1513 
1514     /** Called to ask the owner to draw a child Android [View] to [canvas]. */
drawAndroidViewnull1515     fun drawAndroidView(view: AndroidViewHolder, canvas: android.graphics.Canvas) {
1516         androidViewsHandler.drawView(view, canvas)
1517     }
1518 
scheduleMeasureAndLayoutnull1519     private fun scheduleMeasureAndLayout(nodeToRemeasure: LayoutNode? = null) {
1520         if (!isLayoutRequested && isAttachedToWindow) {
1521             if (nodeToRemeasure != null) {
1522                 // if [nodeToRemeasure] can potentially resize the root we should call
1523                 // requestLayout() so our parent View can react on this change on the same frame.
1524                 // if instead we just call invalidate() and remeasure inside dispatchDraw()
1525                 // this will cause inconsistency as the Compose content will already have the
1526                 // new size, but the View hierarchy will react only on the next frame.
1527                 var node = nodeToRemeasure
1528                 while (
1529                     node != null &&
1530                         node.measuredByParent == UsageByParent.InMeasureBlock &&
1531                         node.childSizeCanAffectParentSize()
1532                 ) {
1533                     node = node.parent
1534                 }
1535                 if (node === root) {
1536                     requestLayout()
1537                     return
1538                 }
1539             }
1540             if (width == 0 || height == 0) {
1541                 // if the view has no size calling invalidate() will be skipped
1542                 requestLayout()
1543             } else {
1544                 invalidate()
1545             }
1546         }
1547     }
1548 
LayoutNodenull1549     private fun LayoutNode.childSizeCanAffectParentSize(): Boolean {
1550         // if the view was measured twice with different constraints last time it means the
1551         // constraints we have could be not the final constraints and in fact our parent
1552         // ViewGroup can remeasure us with different constraints if we call requestLayout().
1553         return wasMeasuredWithMultipleConstraints ||
1554             // when parent's [hasFixedInnerContentConstraints] is true the child size change
1555             // can't affect parent size as the size is fixed. for example it happens when parent
1556             // has Modifier.fillMaxSize() set on it.
1557             parent?.hasFixedInnerContentConstraints == false
1558     }
1559 
measureAndLayoutnull1560     override fun measureAndLayout(sendPointerUpdate: Boolean) {
1561         // only run the logic when we have something pending
1562         if (
1563             measureAndLayoutDelegate.hasPendingMeasureOrLayout ||
1564                 measureAndLayoutDelegate.hasPendingOnPositionedCallbacks
1565         ) {
1566             trace("AndroidOwner:measureAndLayout") {
1567                 val resend = if (sendPointerUpdate) resendMotionEventOnLayout else null
1568                 val rootNodeResized = measureAndLayoutDelegate.measureAndLayout(resend)
1569                 if (rootNodeResized) {
1570                     requestLayout()
1571                 }
1572                 measureAndLayoutDelegate.dispatchOnPositionedCallbacks()
1573                 dispatchPendingInteropLayoutCallbacks()
1574             }
1575         }
1576     }
1577 
measureAndLayoutnull1578     override fun measureAndLayout(layoutNode: LayoutNode, constraints: Constraints) {
1579         trace("AndroidOwner:measureAndLayout") {
1580             measureAndLayoutDelegate.measureAndLayout(layoutNode, constraints)
1581             // only dispatch the callbacks if we don't have other nodes to process as otherwise
1582             // we will have one more measureAndLayout() pass anyway in the same frame.
1583             // it allows us to not traverse the hierarchy twice.
1584             if (!measureAndLayoutDelegate.hasPendingMeasureOrLayout) {
1585                 measureAndLayoutDelegate.dispatchOnPositionedCallbacks()
1586                 dispatchPendingInteropLayoutCallbacks()
1587             }
1588             @OptIn(ExperimentalComposeUiApi::class)
1589             if (ComposeUiFlags.isRectTrackingEnabled) {
1590                 rectManager.dispatchCallbacks()
1591             }
1592         }
1593     }
1594 
dispatchPendingInteropLayoutCallbacksnull1595     private fun dispatchPendingInteropLayoutCallbacks() {
1596         if (isPendingInteropViewLayoutChangeDispatch) {
1597             viewTreeObserver.dispatchOnGlobalLayout()
1598             isPendingInteropViewLayoutChangeDispatch = false
1599         }
1600     }
1601 
forceMeasureTheSubtreenull1602     override fun forceMeasureTheSubtree(layoutNode: LayoutNode, affectsLookahead: Boolean) {
1603         measureAndLayoutDelegate.forceMeasureTheSubtree(layoutNode, affectsLookahead)
1604     }
1605 
onRequestMeasurenull1606     override fun onRequestMeasure(
1607         layoutNode: LayoutNode,
1608         affectsLookahead: Boolean,
1609         forceRequest: Boolean,
1610         scheduleMeasureAndLayout: Boolean
1611     ) {
1612         if (affectsLookahead) {
1613             if (
1614                 measureAndLayoutDelegate.requestLookaheadRemeasure(layoutNode, forceRequest) &&
1615                     scheduleMeasureAndLayout
1616             ) {
1617                 scheduleMeasureAndLayout(layoutNode)
1618             }
1619         } else if (
1620             measureAndLayoutDelegate.requestRemeasure(layoutNode, forceRequest) &&
1621                 scheduleMeasureAndLayout
1622         ) {
1623             scheduleMeasureAndLayout(layoutNode)
1624         }
1625     }
1626 
onRequestRelayoutnull1627     override fun onRequestRelayout(
1628         layoutNode: LayoutNode,
1629         affectsLookahead: Boolean,
1630         forceRequest: Boolean
1631     ) {
1632         if (affectsLookahead) {
1633             if (measureAndLayoutDelegate.requestLookaheadRelayout(layoutNode, forceRequest)) {
1634                 scheduleMeasureAndLayout()
1635             }
1636         } else {
1637             if (measureAndLayoutDelegate.requestRelayout(layoutNode, forceRequest)) {
1638                 scheduleMeasureAndLayout()
1639             }
1640         }
1641     }
1642 
requestOnPositionedCallbacknull1643     override fun requestOnPositionedCallback(layoutNode: LayoutNode) {
1644         measureAndLayoutDelegate.requestOnPositionedCallback(layoutNode)
1645         scheduleMeasureAndLayout()
1646     }
1647 
measureAndLayoutForTestnull1648     override fun measureAndLayoutForTest() {
1649         measureAndLayout()
1650     }
1651 
setUncaughtExceptionHandlernull1652     override fun setUncaughtExceptionHandler(handler: RootForTest.UncaughtExceptionHandler?) {
1653         uncaughtExceptionHandler = handler
1654         measureAndLayoutDelegate.uncaughtExceptionHandler = handler
1655     }
1656 
onMeasurenull1657     override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
1658         trace("AndroidOwner:onMeasure") {
1659             if (!isAttachedToWindow) {
1660                 invalidateLayoutNodeMeasurement(root)
1661             }
1662             val (minWidth, maxWidth) = convertMeasureSpec(widthMeasureSpec)
1663             val (minHeight, maxHeight) = convertMeasureSpec(heightMeasureSpec)
1664 
1665             val constraints =
1666                 Constraints.fitPrioritizingHeight(
1667                     minWidth = minWidth,
1668                     maxWidth = maxWidth,
1669                     minHeight = minHeight,
1670                     maxHeight = maxHeight
1671                 )
1672             if (onMeasureConstraints == null) {
1673                 // first onMeasure after last onLayout
1674                 onMeasureConstraints = constraints
1675                 wasMeasuredWithMultipleConstraints = false
1676             } else if (onMeasureConstraints != constraints) {
1677                 // we were remeasured twice with different constraints after last onLayout
1678                 wasMeasuredWithMultipleConstraints = true
1679             }
1680             measureAndLayoutDelegate.updateRootConstraints(constraints)
1681             measureAndLayoutDelegate.measureOnly()
1682             setMeasuredDimension(root.width, root.height)
1683 
1684             if (_androidViewsHandler != null) {
1685                 androidViewsHandler.measure(
1686                     MeasureSpec.makeMeasureSpec(root.width, MeasureSpec.EXACTLY),
1687                     MeasureSpec.makeMeasureSpec(root.height, MeasureSpec.EXACTLY)
1688                 )
1689             }
1690         }
1691     }
1692 
1693     @Suppress("NOTHING_TO_INLINE")
component1null1694     private inline operator fun ULong.component1() = (this shr 32).toInt()
1695 
1696     @Suppress("NOTHING_TO_INLINE")
1697     private inline operator fun ULong.component2() = (this and 0xFFFFFFFFUL).toInt()
1698 
1699     private fun pack(a: Int, b: Int) = (a.toULong() shl 32 or b.toULong())
1700 
1701     private fun convertMeasureSpec(measureSpec: Int): ULong {
1702         val mode = MeasureSpec.getMode(measureSpec)
1703         val size = MeasureSpec.getSize(measureSpec)
1704         return when (mode) {
1705             MeasureSpec.EXACTLY -> pack(size, size)
1706             MeasureSpec.UNSPECIFIED -> pack(0, Constraints.Infinity)
1707             MeasureSpec.AT_MOST -> pack(0, size)
1708             else -> throw IllegalStateException()
1709         }
1710     }
1711 
onLayoutnull1712     override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
1713         lastMatrixRecalculationAnimationTime = 0L // reset it so that we're sure to have a new value
1714         measureAndLayoutDelegate.measureAndLayout(resendMotionEventOnLayout)
1715         onMeasureConstraints = null
1716         // we postpone onPositioned callbacks until onLayout as LayoutCoordinates
1717         // are currently wrong if you try to get the global(activity) coordinates -
1718         // View is not yet laid out.
1719         updatePositionCacheAndDispatch()
1720         if (_androidViewsHandler != null) {
1721             // Even if we laid out during onMeasure, we want to set the bounds of the
1722             // AndroidViewsHandler for accessibility and for Views making assumptions based on
1723             // the size of their ancestors. Usually the Views in the hierarchy will not
1724             // be relaid out, as they have not requested layout in the meantime.
1725             // However, there is also chance for the AndroidViewsHandler and the children to be
1726             // isLayoutRequested at this point, in case the Views hierarchy receives forceLayout().
1727             // In case of a forceLayout(), calling layout here will traverse the entire subtree
1728             // and replace the Views at the same position, which is needed to clean up their
1729             // layout state, which otherwise might cause further requestLayout()s to be blocked.
1730             androidViewsHandler.layout(0, 0, r - l, b - t)
1731         }
1732     }
1733 
updatePositionCacheAndDispatchnull1734     private fun updatePositionCacheAndDispatch() {
1735         var positionChanged = false
1736         getLocationOnScreen(tmpPositionArray)
1737         val (globalX, globalY) = globalPosition
1738         if (
1739             globalX != tmpPositionArray[0] ||
1740                 globalY != tmpPositionArray[1] ||
1741                 // -1 means it has never been set, 0 means it has been "reset". We only want to
1742                 // catch the "never been set" case
1743                 lastMatrixRecalculationAnimationTime < 0L
1744         ) {
1745             globalPosition = IntOffset(tmpPositionArray[0], tmpPositionArray[1])
1746             if (globalX != Int.MAX_VALUE && globalY != Int.MAX_VALUE) {
1747                 positionChanged = true
1748                 root.layoutDelegate.measurePassDelegate.notifyChildrenUsingCoordinatesWhilePlacing()
1749             }
1750         }
1751         recalculateWindowPosition()
1752         rectManager.updateOffsets(globalPosition, windowPosition.round(), viewToWindowMatrix)
1753         measureAndLayoutDelegate.dispatchOnPositionedCallbacks(forceDispatch = positionChanged)
1754         @OptIn(ExperimentalComposeUiApi::class)
1755         if (ComposeUiFlags.isRectTrackingEnabled) {
1756             rectManager.dispatchCallbacks()
1757         }
1758     }
1759 
onDrawnull1760     override fun onDraw(canvas: android.graphics.Canvas) {}
1761 
createLayernull1762     override fun createLayer(
1763         drawBlock: (canvas: Canvas, parentLayer: GraphicsLayer?) -> Unit,
1764         invalidateParentLayer: () -> Unit,
1765         explicitLayer: GraphicsLayer?,
1766         forceUseOldLayers: Boolean
1767     ): OwnedLayer {
1768         if (explicitLayer != null) {
1769             return GraphicsLayerOwnerLayer(
1770                 graphicsLayer = explicitLayer,
1771                 context = null,
1772                 ownerView = this,
1773                 drawBlock = drawBlock,
1774                 invalidateParentLayer = invalidateParentLayer
1775             )
1776         }
1777         if (!forceUseOldLayers) {
1778             // First try the layer cache
1779             val layer = layerCache.pop()
1780             if (layer !== null) {
1781                 layer.reuseLayer(drawBlock, invalidateParentLayer)
1782                 return layer
1783             }
1784 
1785             // Prior to M ViewLayer implementation might be doing extra drawing in order
1786             // to support the software rendering. This extra drawing is breaking some of tests
1787             // and we can't fully migrate to it until we figure out how to solve it.
1788             if (SDK_INT >= M) {
1789                 return GraphicsLayerOwnerLayer(
1790                     graphicsLayer = graphicsContext.createGraphicsLayer(),
1791                     context = graphicsContext,
1792                     ownerView = this,
1793                     drawBlock = drawBlock,
1794                     invalidateParentLayer = invalidateParentLayer
1795                 )
1796             }
1797         }
1798 
1799         // RenderNode is supported on Q+ for certain, but may also be supported on M-O.
1800         // We can't be confident that RenderNode is supported, so we try and fail over to
1801         // the ViewLayer implementation. We'll try even on on P devices, but it will fail
1802         // until ART allows things on the unsupported list on P.
1803         if (isHardwareAccelerated && SDK_INT >= M && isRenderNodeCompatible) {
1804             try {
1805                 return RenderNodeLayer(this, drawBlock, invalidateParentLayer)
1806             } catch (_: Throwable) {
1807                 isRenderNodeCompatible = false
1808             }
1809         }
1810         if (viewLayersContainer == null) {
1811             if (!ViewLayer.hasRetrievedMethod) {
1812                 // Test to see if updateDisplayList() can be called. If this fails then
1813                 // ViewLayer.shouldUseDispatchDraw will be true.
1814                 ViewLayer.updateDisplayList(View(context))
1815             }
1816             viewLayersContainer =
1817                 if (ViewLayer.shouldUseDispatchDraw) {
1818                     DrawChildContainer(context)
1819                 } else {
1820                     ViewLayerContainer(context)
1821                 }
1822             addView(viewLayersContainer)
1823         }
1824         return ViewLayer(this, viewLayersContainer!!, drawBlock, invalidateParentLayer)
1825     }
1826 
1827     /**
1828      * Return [layer] to the layer cache. It can be reused in [createLayer] after this. Returns
1829      * `true` if it was recycled or `false` if it will be discarded.
1830      */
recyclenull1831     internal fun recycle(layer: OwnedLayer): Boolean {
1832         val cacheValue =
1833             viewLayersContainer == null ||
1834                 ViewLayer.shouldUseDispatchDraw ||
1835                 SDK_INT >= M // L throws during RenderThread when reusing the Views.
1836         if (cacheValue) {
1837             layerCache.push(layer)
1838         }
1839         dirtyLayers -= layer
1840         return cacheValue
1841     }
1842 
onSemanticsChangenull1843     override fun onSemanticsChange() {
1844         composeAccessibilityDelegate.onSemanticsChange()
1845         contentCaptureManager.onSemanticsChange()
1846     }
1847 
onLayoutChangenull1848     override fun onLayoutChange(layoutNode: LayoutNode) {
1849         composeAccessibilityDelegate.onLayoutChange(layoutNode)
1850         contentCaptureManager.onLayoutChange()
1851     }
1852 
onLayoutNodeDeactivatednull1853     override fun onLayoutNodeDeactivated(layoutNode: LayoutNode) {
1854         @OptIn(ExperimentalComposeUiApi::class)
1855         if (ComposeUiFlags.isRectTrackingEnabled) {
1856             rectManager.remove(layoutNode)
1857         }
1858         @OptIn(ExperimentalComposeUiApi::class)
1859         if (autofillSupported() && ComposeUiFlags.isSemanticAutofillEnabled) {
1860             _autofillManager?.onLayoutNodeDeactivated(layoutNode)
1861         }
1862     }
1863 
onPreLayoutNodeReusednull1864     override fun onPreLayoutNodeReused(layoutNode: LayoutNode, oldSemanticsId: Int) {
1865         // Keep the mapping up to date when the semanticsId changes
1866         layoutNodes.remove(oldSemanticsId)
1867         layoutNodes[layoutNode.semanticsId] = layoutNode
1868     }
1869 
onPostLayoutNodeReusednull1870     override fun onPostLayoutNodeReused(layoutNode: LayoutNode, oldSemanticsId: Int) {
1871         @OptIn(ExperimentalComposeUiApi::class)
1872         if (autofillSupported() && ComposeUiFlags.isSemanticAutofillEnabled) {
1873             _autofillManager?.onPostLayoutNodeReused(layoutNode, oldSemanticsId)
1874         }
1875     }
1876 
onInteropViewLayoutChangenull1877     override fun onInteropViewLayoutChange(view: InteropView) {
1878         isPendingInteropViewLayoutChangeDispatch = true
1879     }
1880 
registerOnLayoutCompletedListenernull1881     override fun registerOnLayoutCompletedListener(listener: Owner.OnLayoutCompletedListener) {
1882         measureAndLayoutDelegate.registerOnLayoutCompletedListener(listener)
1883         scheduleMeasureAndLayout()
1884     }
1885 
getFocusDirectionnull1886     override fun getFocusDirection(keyEvent: KeyEvent): FocusDirection? {
1887         return when (keyEvent.key) {
1888             NavigatePrevious -> Previous
1889             NavigateNext -> Next
1890             Tab -> if (keyEvent.isShiftPressed) Previous else Next
1891             DirectionRight -> Right
1892             DirectionLeft -> Left
1893             // For the initial key input of a new composable, both up/down and page up/down will
1894             // trigger the composable to get focus (so the composable can handle key events to
1895             // move focus or scroll content). Remember, composables can't receive key events without
1896             // focus.
1897             DirectionUp,
1898             PageUp -> Up
1899             DirectionDown,
1900             PageDown -> Down
1901             DirectionCenter,
1902             Key.Enter,
1903             NumPadEnter -> Enter
1904             Back,
1905             Escape -> Exit
1906             else -> null
1907         }
1908     }
1909 
dispatchDrawnull1910     override fun dispatchDraw(canvas: android.graphics.Canvas) {
1911         if (!isAttachedToWindow) {
1912             invalidateLayers(root)
1913         }
1914         measureAndLayout()
1915         Snapshot.notifyObjectsInitialized()
1916 
1917         isDrawingContent = true
1918         // we don't have to observe here because the root has a layer modifier
1919         // that will observe all children. The AndroidComposeView has only the
1920         // root, so it doesn't have to invalidate itself based on model changes.
1921         try {
1922             canvasHolder.drawInto(canvas) {
1923                 root.draw(
1924                     canvas = this,
1925                     graphicsLayer = null // the root node will provide the root graphics layer
1926                 )
1927             }
1928 
1929             if (dirtyLayers.isNotEmpty()) {
1930                 for (i in 0 until dirtyLayers.size) {
1931                     val layer = dirtyLayers[i]
1932                     layer.updateDisplayList()
1933                 }
1934             }
1935 
1936             if (ViewLayer.shouldUseDispatchDraw) {
1937                 // We must update the display list of all children using dispatchDraw()
1938                 // instead of updateDisplayList(). But since we don't want to actually draw
1939                 // the contents, we will clip out everything from the canvas.
1940                 val saveCount = canvas.save()
1941                 canvas.clipRect(0f, 0f, 0f, 0f)
1942 
1943                 super.dispatchDraw(canvas)
1944                 canvas.restoreToCount(saveCount)
1945             }
1946 
1947             dirtyLayers.clear()
1948             isDrawingContent = false
1949         } catch (t: Throwable) {
1950             uncaughtExceptionHandler?.onUncaughtException(t) ?: throw t
1951         }
1952 
1953         // updateDisplayList operations performed above (during root.draw and during the explicit
1954         // layer.updateDisplayList() calls) can result in the same layers being invalidated. These
1955         // layers have been added to postponedDirtyLayers and will be redrawn during the next
1956         // dispatchDraw.
1957         if (postponedDirtyLayers != null) {
1958             val postponed = postponedDirtyLayers!!
1959             dirtyLayers.addAll(postponed)
1960             postponed.clear()
1961         }
1962 
1963         // Used to handle frame rate information
1964         if (isArrEnabled) {
1965             super.setRequestedFrameRate(currentFrameRate)
1966             frameRateCategoryView.requestedFrameRate = currentFrameRateCategory
1967 
1968             if (!currentFrameRateCategory.isNaN()) {
1969                 frameRateCategoryView.invalidate()
1970                 drawChild(canvas, frameRateCategoryView, drawingTime)
1971             }
1972 
1973             currentFrameRate = Float.NaN
1974             currentFrameRateCategory = Float.NaN
1975         }
1976     }
1977 
notifyLayerIsDirtynull1978     internal fun notifyLayerIsDirty(layer: OwnedLayer, isDirty: Boolean) {
1979         if (!isDirty) {
1980             // It is correct to remove the layer here regardless of this if, but for performance
1981             // we are hackily not doing the removal here in order to just do clear() a bit later.
1982             if (!isDrawingContent) {
1983                 dirtyLayers.remove(layer)
1984                 postponedDirtyLayers?.remove(layer)
1985             }
1986         } else if (!isDrawingContent) {
1987             dirtyLayers += layer
1988         } else {
1989             val postponed =
1990                 postponedDirtyLayers
1991                     ?: mutableListOf<OwnedLayer>().also { postponedDirtyLayers = it }
1992             postponed += layer
1993         }
1994     }
1995 
1996     /**
1997      * The callback to be executed when [viewTreeOwners] is created and not-null anymore. Note that
1998      * this callback will be fired inline when it is already available
1999      */
setOnViewTreeOwnersAvailablenull2000     fun setOnViewTreeOwnersAvailable(callback: (ViewTreeOwners) -> Unit) {
2001         val viewTreeOwners = viewTreeOwners
2002         if (viewTreeOwners != null) {
2003             callback(viewTreeOwners)
2004         }
2005         if (!isAttachedToWindow) {
2006             onViewTreeOwnersAvailable = callback
2007         }
2008     }
2009 
2010     // TODO(mnuzen): combine both event loops into one larger one
boundsUpdatesContentCaptureEventLoopnull2011     suspend fun boundsUpdatesContentCaptureEventLoop() {
2012         contentCaptureManager.boundsUpdatesEventLoop()
2013     }
2014 
boundsUpdatesAccessibilityEventLoopnull2015     suspend fun boundsUpdatesAccessibilityEventLoop() {
2016         composeAccessibilityDelegate.boundsUpdatesEventLoop()
2017     }
2018 
2019     /** Walks the entire LayoutNode sub-hierarchy and marks all nodes as needing measurement. */
invalidateLayoutNodeMeasurementnull2020     private fun invalidateLayoutNodeMeasurement(node: LayoutNode) {
2021         measureAndLayoutDelegate.requestRemeasure(node)
2022         node.forEachChild { invalidateLayoutNodeMeasurement(it) }
2023     }
2024 
2025     /** Walks the entire LayoutNode sub-hierarchy and marks all layers as needing to be redrawn. */
invalidateLayersnull2026     private fun invalidateLayers(node: LayoutNode) {
2027         node.invalidateLayers()
2028         node.forEachChild { invalidateLayers(it) }
2029     }
2030 
invalidateDescendantsnull2031     override fun invalidateDescendants() {
2032         invalidateLayers(root)
2033     }
2034 
onAttachedToWindownull2035     override fun onAttachedToWindow() {
2036         super.onAttachedToWindow()
2037         if (SDK_INT < 30) {
2038             showLayoutBounds = getIsShowingLayoutBounds()
2039         }
2040         addNotificationForSysPropsChange(this)
2041         _windowInfo.isWindowFocused = hasWindowFocus()
2042         _windowInfo.setOnInitializeContainerSize { calculateWindowSize(this) }
2043         updateWindowMetrics()
2044         invalidateLayoutNodeMeasurement(root)
2045         invalidateLayers(root)
2046         snapshotObserver.startObserving()
2047         ifDebug {
2048             if (autofillSupported()) {
2049                 // TODO(b/333102566): Use _semanticAutofill after switching to the newer Autofill
2050                 // system.
2051                 _autofill?.let { AutofillCallback.register(it) }
2052             }
2053         }
2054 
2055         val lifecycleOwner = findViewTreeLifecycleOwner()
2056         val savedStateRegistryOwner = findViewTreeSavedStateRegistryOwner()
2057 
2058         val oldViewTreeOwners = viewTreeOwners
2059         // We need to change the ViewTreeOwner if there isn't one yet (null)
2060         // or if either the lifecycleOwner or savedStateRegistryOwner has changed.
2061         val resetViewTreeOwner =
2062             oldViewTreeOwners == null ||
2063                 ((lifecycleOwner != null && savedStateRegistryOwner != null) &&
2064                     (lifecycleOwner !== oldViewTreeOwners.lifecycleOwner ||
2065                         savedStateRegistryOwner !== oldViewTreeOwners.lifecycleOwner))
2066         if (resetViewTreeOwner) {
2067             if (lifecycleOwner == null) {
2068                 throw IllegalStateException(
2069                     "Composed into the View which doesn't propagate ViewTreeLifecycleOwner!"
2070                 )
2071             }
2072             if (savedStateRegistryOwner == null) {
2073                 throw IllegalStateException(
2074                     "Composed into the View which doesn't propagate" +
2075                         "ViewTreeSavedStateRegistryOwner!"
2076                 )
2077             }
2078             oldViewTreeOwners?.lifecycleOwner?.lifecycle?.removeObserver(this)
2079             lifecycleOwner.lifecycle.addObserver(this)
2080             val viewTreeOwners =
2081                 ViewTreeOwners(
2082                     lifecycleOwner = lifecycleOwner,
2083                     savedStateRegistryOwner = savedStateRegistryOwner
2084                 )
2085             _viewTreeOwners = viewTreeOwners
2086             onViewTreeOwnersAvailable?.invoke(viewTreeOwners)
2087             onViewTreeOwnersAvailable = null
2088         }
2089 
2090         _inputModeManager.inputMode = if (isInTouchMode) Touch else Keyboard
2091 
2092         val lifecycle =
2093             checkPreconditionNotNull(viewTreeOwners?.lifecycleOwner?.lifecycle) {
2094                 "No lifecycle owner exists"
2095             }
2096         lifecycle.addObserver(this)
2097         lifecycle.addObserver(contentCaptureManager)
2098         viewTreeObserver.addOnGlobalLayoutListener(globalLayoutListener)
2099         viewTreeObserver.addOnScrollChangedListener(scrollChangedListener)
2100         viewTreeObserver.addOnTouchModeChangeListener(touchModeChangeListener)
2101 
2102         if (SDK_INT >= S) AndroidComposeViewTranslationCallbackS.setViewTranslationCallback(this)
2103         _autofillManager?.let {
2104             focusOwner.listeners += it
2105             semanticsOwner.listeners += it
2106         }
2107     }
2108 
onDetachedFromWindownull2109     override fun onDetachedFromWindow() {
2110         super.onDetachedFromWindow()
2111         if (isArrEnabled) {
2112             removeView(frameRateCategoryView)
2113         }
2114 
2115         removeNotificationForSysPropsChange(this)
2116         snapshotObserver.stopObserving()
2117         _windowInfo.setOnInitializeContainerSize(null)
2118         val lifecycle =
2119             checkPreconditionNotNull(viewTreeOwners?.lifecycleOwner?.lifecycle) {
2120                 "No lifecycle owner exists"
2121             }
2122         lifecycle.removeObserver(contentCaptureManager)
2123         lifecycle.removeObserver(this)
2124         ifDebug {
2125             if (autofillSupported()) {
2126                 // TODO(b/333102566): Use _semanticAutofill after switching to the newer Autofill
2127                 // system.
2128                 _autofill?.let { AutofillCallback.unregister(it) }
2129             }
2130         }
2131         viewTreeObserver.removeOnGlobalLayoutListener(globalLayoutListener)
2132         viewTreeObserver.removeOnScrollChangedListener(scrollChangedListener)
2133         viewTreeObserver.removeOnTouchModeChangeListener(touchModeChangeListener)
2134 
2135         if (SDK_INT >= S) AndroidComposeViewTranslationCallbackS.clearViewTranslationCallback(this)
2136         _autofillManager?.let {
2137             semanticsOwner.listeners -= it
2138             focusOwner.listeners -= it
2139         }
2140     }
2141 
onProvideAutofillVirtualStructurenull2142     override fun onProvideAutofillVirtualStructure(structure: ViewStructure?, flags: Int) {
2143         if (autofillSupported() && structure != null) {
2144             if (@OptIn(ExperimentalComposeUiApi::class) ComposeUiFlags.isSemanticAutofillEnabled) {
2145                 _autofillManager?.populateViewStructure(structure)
2146             }
2147             _autofill?.populateViewStructure(structure)
2148         }
2149     }
2150 
autofillnull2151     override fun autofill(values: SparseArray<AutofillValue>) {
2152         if (autofillSupported()) {
2153             if (@OptIn(ExperimentalComposeUiApi::class) ComposeUiFlags.isSemanticAutofillEnabled) {
2154                 _autofillManager?.performAutofill(values)
2155             }
2156             _autofill?.performAutofill(values)
2157         }
2158     }
2159 
2160     @RequiresApi(S)
onCreateVirtualViewTranslationRequestsnull2161     override fun onCreateVirtualViewTranslationRequests(
2162         virtualIds: LongArray,
2163         supportedFormats: IntArray,
2164         requestsCollector: Consumer<ViewTranslationRequest?>
2165     ) {
2166         contentCaptureManager.onCreateVirtualViewTranslationRequests(
2167             virtualIds,
2168             supportedFormats,
2169             requestsCollector
2170         )
2171     }
2172 
2173     @RequiresApi(S)
onVirtualViewTranslationResponsesnull2174     override fun onVirtualViewTranslationResponses(
2175         response: LongSparseArray<ViewTranslationResponse?>
2176     ) {
2177         contentCaptureManager.onVirtualViewTranslationResponses(contentCaptureManager, response)
2178     }
2179 
dispatchGenericMotionEventnull2180     override fun dispatchGenericMotionEvent(motionEvent: MotionEvent): Boolean {
2181         if (hoverExitReceived) {
2182             removeCallbacks(sendHoverExitEvent)
2183             // Ignore ACTION_HOVER_EXIT if it is directly followed by an ACTION_SCROLL.
2184             // Note: In some versions of Android Studio with screen mirroring, studio will
2185             // incorrectly add an ACTION_HOVER_EXIT during a scroll event which causes
2186             // issues (b/314269723), so we ignore the exit in that case.
2187             if (motionEvent.actionMasked == ACTION_SCROLL) {
2188                 hoverExitReceived = false
2189             } else {
2190                 sendHoverExitEvent.run()
2191             }
2192         }
2193         if (isBadMotionEvent(motionEvent) || !isAttachedToWindow) {
2194             return super.dispatchGenericMotionEvent(motionEvent)
2195         }
2196 
2197         return when (motionEvent.actionMasked) {
2198             ACTION_SCROLL ->
2199                 if (motionEvent.isFromSource(SOURCE_ROTARY_ENCODER)) {
2200                     handleRotaryEvent(motionEvent)
2201                 } else {
2202                     handleMotionEvent(motionEvent).dispatchedToAPointerInputModifier
2203                 }
2204             else -> {
2205                 @OptIn(ExperimentalComposeUiApi::class)
2206                 if (!motionEvent.isFromSource(SOURCE_CLASS_POINTER)) {
2207                     val indirectTouchEvent =
2208                         IndirectTouchEvent(
2209                             position = Offset(motionEvent.x, motionEvent.y),
2210                             eventTimeMillis = motionEvent.eventTime,
2211                             type = convertActionToIndirectTouchEventType(motionEvent.actionMasked),
2212                         )
2213                     val handled =
2214                         focusOwner.dispatchIndirectTouchEvent(indirectTouchEvent) {
2215                             super.dispatchGenericMotionEvent(motionEvent)
2216                         }
2217 
2218                     if (handled) return true
2219                 }
2220 
2221                 // If focus owner did not handle, rely on ViewGroup to handle.
2222                 super.dispatchGenericMotionEvent(motionEvent)
2223             }
2224         }
2225     }
2226 
2227     @OptIn(ExperimentalComposeUiApi::class)
convertActionToIndirectTouchEventTypenull2228     private fun convertActionToIndirectTouchEventType(actionMasked: Int): IndirectTouchEventType {
2229         return when (actionMasked) {
2230             ACTION_UP -> IndirectTouchEventType.Release
2231             ACTION_DOWN -> IndirectTouchEventType.Press
2232             ACTION_MOVE -> IndirectTouchEventType.Move
2233             else -> IndirectTouchEventType.Unknown
2234         }
2235     }
2236 
2237     // TODO(shepshapard): Test this method.
2238     @OptIn(ExperimentalComposeUiApi::class)
dispatchTouchEventnull2239     override fun dispatchTouchEvent(motionEvent: MotionEvent): Boolean {
2240         if (ComposeUiFlags.isHitPathTrackerLoggingEnabled) {
2241             println("POINTER_INPUT_DEBUG_LOG_TAG AndroidComposeView.dispatchTouchEvent()")
2242             println("POINTER_INPUT_DEBUG_LOG_TAG \t\tmotionEvent: $motionEvent")
2243         }
2244 
2245         if (hoverExitReceived) {
2246             // Go ahead and send ACTION_HOVER_EXIT if this isn't an ACTION_DOWN for the same
2247             // pointer
2248             removeCallbacks(sendHoverExitEvent)
2249             val lastEvent = previousMotionEvent!!
2250             if (
2251                 motionEvent.actionMasked != ACTION_DOWN || hasChangedDevices(motionEvent, lastEvent)
2252             ) {
2253                 sendHoverExitEvent.run()
2254             } else {
2255                 hoverExitReceived = false
2256             }
2257         }
2258         if (isBadMotionEvent(motionEvent) || !isAttachedToWindow) {
2259             return false // Bad MotionEvent. Don't handle it.
2260         }
2261 
2262         if (motionEvent.actionMasked == ACTION_MOVE && !isPositionChanged(motionEvent)) {
2263             // There was no movement from previous MotionEvent, so we don't need to dispatch this.
2264             // This could be a scroll event or some other non-touch event that results in an
2265             // ACTION_MOVE without any movement.
2266             return false
2267         }
2268 
2269         val processResult = handleMotionEvent(motionEvent)
2270 
2271         if (processResult.anyMovementConsumed) {
2272             parent.requestDisallowInterceptTouchEvent(true)
2273         }
2274 
2275         return processResult.dispatchedToAPointerInputModifier
2276     }
2277 
handleRotaryEventnull2278     private fun handleRotaryEvent(event: MotionEvent): Boolean {
2279         val config = android.view.ViewConfiguration.get(context)
2280         val axisValue = -event.getAxisValue(AXIS_SCROLL)
2281         val rotaryEvent =
2282             RotaryScrollEvent(
2283                 verticalScrollPixels = axisValue * getScaledVerticalScrollFactor(config, context),
2284                 horizontalScrollPixels =
2285                     axisValue * getScaledHorizontalScrollFactor(config, context),
2286                 uptimeMillis = event.eventTime,
2287                 inputDeviceId = event.deviceId
2288             )
2289         return focusOwner.dispatchRotaryEvent(rotaryEvent) {
2290             super.dispatchGenericMotionEvent(event)
2291         }
2292     }
2293 
handleMotionEventnull2294     private fun handleMotionEvent(motionEvent: MotionEvent): ProcessResult {
2295         removeCallbacks(resendMotionEventRunnable)
2296         try {
2297             recalculateWindowPosition(motionEvent)
2298             forceUseMatrixCache = true
2299             measureAndLayout(sendPointerUpdate = false)
2300             val result =
2301                 trace("AndroidOwner:onTouch") {
2302                     val action = motionEvent.actionMasked
2303                     val lastEvent = previousMotionEvent
2304 
2305                     val wasMouseEvent = lastEvent?.getToolType(0) == TOOL_TYPE_MOUSE
2306                     if (lastEvent != null && hasChangedDevices(motionEvent, lastEvent)) {
2307                         if (isDevicePressEvent(lastEvent)) {
2308                             // Send a cancel event
2309                             pointerInputEventProcessor.processCancel()
2310                         } else if (lastEvent.actionMasked != ACTION_HOVER_EXIT && wasMouseEvent) {
2311                             // The mouse cursor disappeared without sending an ACTION_HOVER_EXIT, so
2312                             // we have to send that event.
2313                             sendSimulatedEvent(lastEvent, ACTION_HOVER_EXIT, lastEvent.eventTime)
2314                         }
2315                     }
2316 
2317                     val isMouseEvent = motionEvent.getToolType(0) == TOOL_TYPE_MOUSE
2318 
2319                     if (
2320                         !wasMouseEvent &&
2321                             isMouseEvent &&
2322                             action != ACTION_CANCEL &&
2323                             action != ACTION_HOVER_ENTER &&
2324                             isInBounds(motionEvent)
2325                     ) {
2326                         // We didn't previously have an enter event and we're getting our first
2327                         // mouse event. Send a simulated enter event so that we have a consistent
2328                         // enter/exit.
2329                         sendSimulatedEvent(motionEvent, ACTION_HOVER_ENTER, motionEvent.eventTime)
2330                     }
2331                     lastEvent?.recycle()
2332 
2333                     // If the previous MotionEvent was an ACTION_HOVER_EXIT, we need to check if it
2334                     // was a synthetic MotionEvent generated by the platform for an ACTION_DOWN
2335                     // event
2336                     // or not.
2337                     //
2338                     // If it was synthetic, we do nothing, because we want to keep the existing
2339                     // cache
2340                     // of "Hit" Modifier.Node(s) from the previous hover events, so we can reuse
2341                     // them
2342                     // once an ACTION_UP event is triggered and we return to the same hover state
2343                     // (cache improves performance for this frequent event sequence with a mouse).
2344                     //
2345                     // If it was NOT synthetic, we end the event stream in MotionEventAdapter and
2346                     // clear
2347                     // the hit cache used in PointerInputEventProcessor (specifically, the
2348                     // HitPathTracker cache inside PointerInputEventProcessor), so events in this
2349                     // new
2350                     // stream do not trigger Modifier.Node(s) hit by the previous stream.
2351                     if (previousMotionEvent?.action == ACTION_HOVER_EXIT) {
2352                         val previousEventDefaultPointerId =
2353                             previousMotionEvent?.getPointerId(0) ?: -1
2354 
2355                         // New ACTION_HOVER_ENTER, so this should be considered a new stream
2356                         if (
2357                             motionEvent.action == ACTION_HOVER_ENTER && motionEvent.historySize == 0
2358                         ) {
2359                             if (previousEventDefaultPointerId >= 0) {
2360                                 motionEventAdapter.endStream(previousEventDefaultPointerId)
2361                             }
2362                         } else if (
2363                             motionEvent.action == ACTION_DOWN && motionEvent.historySize == 0
2364                         ) {
2365                             val previousX = previousMotionEvent?.x ?: Float.NaN
2366                             val previousY = previousMotionEvent?.y ?: Float.NaN
2367 
2368                             val currentX = motionEvent.x
2369                             val currentY = motionEvent.y
2370 
2371                             val previousAndCurrentCoordinatesDoNotMatch =
2372                                 (previousX != currentX || previousY != currentY)
2373 
2374                             val previousEventTime = previousMotionEvent?.eventTime ?: -1L
2375 
2376                             val previousAndCurrentEventTimesDoNotMatch =
2377                                 previousEventTime != motionEvent.eventTime
2378 
2379                             // A synthetically created Hover Exit event will always have the same x,
2380                             // y, and timestamp as the down event it proceeds.
2381                             val previousHoverEventWasNotSyntheticallyProducedFromADownEvent =
2382                                 previousAndCurrentCoordinatesDoNotMatch ||
2383                                     previousAndCurrentEventTimesDoNotMatch
2384 
2385                             if (previousHoverEventWasNotSyntheticallyProducedFromADownEvent) {
2386                                 // This should be considered a new stream, and we should
2387                                 // reset everything.
2388                                 if (previousEventDefaultPointerId >= 0) {
2389                                     motionEventAdapter.endStream(previousEventDefaultPointerId)
2390                                 }
2391                                 pointerInputEventProcessor.clearPreviouslyHitModifierNodes()
2392                             }
2393                         }
2394                     }
2395 
2396                     previousMotionEvent = MotionEvent.obtainNoHistory(motionEvent)
2397 
2398                     sendMotionEvent(motionEvent)
2399                 }
2400             return result
2401         } finally {
2402             forceUseMatrixCache = false
2403         }
2404     }
2405 
hasChangedDevicesnull2406     private fun hasChangedDevices(event: MotionEvent, lastEvent: MotionEvent): Boolean {
2407         return lastEvent.source != event.source || lastEvent.getToolType(0) != event.getToolType(0)
2408     }
2409 
isDevicePressEventnull2410     private fun isDevicePressEvent(event: MotionEvent): Boolean {
2411         if (event.buttonState != 0) {
2412             return true
2413         }
2414         return when (event.actionMasked) {
2415             ACTION_POINTER_UP, // means that there is at least one remaining pointer
2416             ACTION_DOWN,
2417             ACTION_MOVE -> true
2418             //            ACTION_SCROLL, // We've already checked for buttonState, so it must not be
2419             // down
2420             //            ACTION_HOVER_ENTER,
2421             //            ACTION_HOVER_MOVE,
2422             //            ACTION_HOVER_EXIT,
2423             //            ACTION_UP,
2424             //            ACTION_CANCEL,
2425             else -> false
2426         }
2427     }
2428 
2429     @OptIn(InternalCoreApi::class)
sendMotionEventnull2430     private fun sendMotionEvent(motionEvent: MotionEvent): ProcessResult {
2431         if (keyboardModifiersRequireUpdate) {
2432             keyboardModifiersRequireUpdate = false
2433             _windowInfo.keyboardModifiers = PointerKeyboardModifiers(motionEvent.metaState)
2434         }
2435         val pointerInputEvent = motionEventAdapter.convertToPointerInputEvent(motionEvent, this)
2436         return if (pointerInputEvent != null) {
2437             // Cache the last position of the last pointer to go down so we can check if
2438             // it's in a scrollable region in canScroll{Vertically|Horizontally}. Those
2439             // methods use semantics data, and because semantics coordinates are local to
2440             // this view, the pointer _position_, not _positionOnScreen_, is the offset that
2441             // needs to be cached.
2442             pointerInputEvent.pointers
2443                 .fastLastOrNull { it.down }
2444                 ?.position
2445                 ?.let { lastDownPointerPosition = it }
2446 
2447             val result =
2448                 pointerInputEventProcessor.process(pointerInputEvent, this, isInBounds(motionEvent))
2449             // Clear the MotionEvent reference after dispatching it.
2450             pointerInputEvent.motionEvent = null
2451             val action = motionEvent.actionMasked
2452             if (
2453                 (action == ACTION_DOWN || action == ACTION_POINTER_DOWN) &&
2454                     !result.dispatchedToAPointerInputModifier
2455             ) {
2456                 // We aren't handling the pointer, so the event stream has ended for us.
2457                 // The next time we receive a pointer event, it should be considered a new
2458                 // pointer.
2459                 motionEventAdapter.endStream(motionEvent.getPointerId(motionEvent.actionIndex))
2460             }
2461             result
2462         } else {
2463             pointerInputEventProcessor.processCancel()
2464             ProcessResult(dispatchedToAPointerInputModifier = false, anyMovementConsumed = false)
2465         }
2466     }
2467 
2468     @OptIn(InternalCoreApi::class)
sendSimulatedEventnull2469     private fun sendSimulatedEvent(
2470         motionEvent: MotionEvent,
2471         action: Int,
2472         eventTime: Long,
2473         forceHover: Boolean = true
2474     ) {
2475         // don't send any events for pointers that are "up" unless they support hover
2476         val upIndex =
2477             when (motionEvent.actionMasked) {
2478                 ACTION_UP ->
2479                     if (action == ACTION_HOVER_ENTER || action == ACTION_HOVER_EXIT) -1 else 0
2480                 ACTION_POINTER_UP -> motionEvent.actionIndex
2481                 else -> -1
2482             }
2483         val pointerCount = motionEvent.pointerCount - if (upIndex >= 0) 1 else 0
2484         if (pointerCount == 0) {
2485             return
2486         }
2487         val pointerProperties = Array(pointerCount) { MotionEvent.PointerProperties() }
2488         val pointerCoords = Array(pointerCount) { MotionEvent.PointerCoords() }
2489         for (i in 0 until pointerCount) {
2490             val sourceIndex = i + if (upIndex < 0 || i < upIndex) 0 else 1
2491             motionEvent.getPointerProperties(sourceIndex, pointerProperties[i])
2492             val coords = pointerCoords[i]
2493             motionEvent.getPointerCoords(sourceIndex, coords)
2494             val localPosition = Offset(coords.x, coords.y)
2495             val screenPosition = localToScreen(localPosition)
2496             coords.x = screenPosition.x
2497             coords.y = screenPosition.y
2498         }
2499         val buttonState = if (forceHover) 0 else motionEvent.buttonState
2500 
2501         val downTime =
2502             if (motionEvent.downTime == motionEvent.eventTime) {
2503                 eventTime
2504             } else {
2505                 motionEvent.downTime
2506             }
2507         val event =
2508             MotionEvent.obtain(
2509                 /* downTime */ downTime,
2510                 /* eventTime */ eventTime,
2511                 /* action */ action,
2512                 /* pointerCount */ pointerCount,
2513                 /* pointerProperties */ pointerProperties,
2514                 /* pointerCoords */ pointerCoords,
2515                 /* metaState */ motionEvent.metaState,
2516                 /* buttonState */ buttonState,
2517                 /* xPrecision */ motionEvent.xPrecision,
2518                 /* yPrecision */ motionEvent.yPrecision,
2519                 /* deviceId */ motionEvent.deviceId,
2520                 /* edgeFlags */ motionEvent.edgeFlags,
2521                 /* source */ motionEvent.source,
2522                 /* flags */ motionEvent.flags
2523             )
2524         val pointerInputEvent = motionEventAdapter.convertToPointerInputEvent(event, this)!!
2525 
2526         pointerInputEventProcessor.process(pointerInputEvent, this, true)
2527         event.recycle()
2528     }
2529 
2530     /**
2531      * This method is required to correctly support swipe-to-dismiss layouts on WearOS, which search
2532      * their children for scrollable views to determine whether or not to intercept touch events – a
2533      * sort of simplified nested scrolling mechanism.
2534      *
2535      * Because a composition may contain many scrollable and non-scrollable areas, and this method
2536      * doesn't know which part of the view the caller cares about, it uses the
2537      * [lastDownPointerPosition] as the location to check.
2538      */
canScrollHorizontallynull2539     override fun canScrollHorizontally(direction: Int): Boolean =
2540         composeAccessibilityDelegate.canScroll(vertical = false, direction, lastDownPointerPosition)
2541 
2542     /** See [canScrollHorizontally]. */
2543     override fun canScrollVertically(direction: Int): Boolean =
2544         composeAccessibilityDelegate.canScroll(vertical = true, direction, lastDownPointerPosition)
2545 
2546     private fun isInBounds(motionEvent: MotionEvent): Boolean {
2547         val x = motionEvent.x
2548         val y = motionEvent.y
2549         return (x in 0f..width.toFloat() && y in 0f..height.toFloat())
2550     }
2551 
localToScreennull2552     override fun localToScreen(localPosition: Offset): Offset {
2553         recalculateWindowPosition()
2554         val local = viewToWindowMatrix.map(localPosition)
2555         return Offset(local.x + windowPosition.x, local.y + windowPosition.y)
2556     }
2557 
localToScreennull2558     override fun localToScreen(localTransform: Matrix) {
2559         recalculateWindowPosition()
2560         localTransform.timesAssign(viewToWindowMatrix)
2561         localTransform.preTranslate(windowPosition.x, windowPosition.y, tmpMatrix)
2562     }
2563 
screenToLocalnull2564     override fun screenToLocal(positionOnScreen: Offset): Offset {
2565         recalculateWindowPosition()
2566         val x = positionOnScreen.x - windowPosition.x
2567         val y = positionOnScreen.y - windowPosition.y
2568         return windowToViewMatrix.map(Offset(x, y))
2569     }
2570 
recalculateWindowPositionnull2571     private fun recalculateWindowPosition() {
2572         if (!forceUseMatrixCache) {
2573             val animationTime = AnimationUtils.currentAnimationTimeMillis()
2574             if (animationTime != lastMatrixRecalculationAnimationTime) {
2575                 lastMatrixRecalculationAnimationTime = animationTime
2576                 recalculateWindowViewTransforms()
2577                 var viewParent = parent
2578                 var view: View = this
2579                 while (viewParent is ViewGroup) {
2580                     view = viewParent
2581                     viewParent = view.parent
2582                 }
2583                 view.getLocationOnScreen(tmpPositionArray)
2584                 val screenX = tmpPositionArray[0].toFloat()
2585                 val screenY = tmpPositionArray[1].toFloat()
2586                 view.getLocationInWindow(tmpPositionArray)
2587                 val windowX = tmpPositionArray[0].toFloat()
2588                 val windowY = tmpPositionArray[1].toFloat()
2589                 windowPosition = Offset(screenX - windowX, screenY - windowY)
2590             }
2591         }
2592     }
2593 
2594     /**
2595      * Recalculates the window position based on the [motionEvent]'s coordinates and screen
2596      * coordinates. Some devices give false positions for [getLocationOnScreen] in some unusual
2597      * circumstances, so a different mechanism must be used to determine the actual position.
2598      */
recalculateWindowPositionnull2599     private fun recalculateWindowPosition(motionEvent: MotionEvent) {
2600         lastMatrixRecalculationAnimationTime = AnimationUtils.currentAnimationTimeMillis()
2601         recalculateWindowViewTransforms()
2602         val positionInWindow = viewToWindowMatrix.map(Offset(motionEvent.x, motionEvent.y))
2603 
2604         windowPosition =
2605             Offset(motionEvent.rawX - positionInWindow.x, motionEvent.rawY - positionInWindow.y)
2606     }
2607 
recalculateWindowViewTransformsnull2608     private fun recalculateWindowViewTransforms() {
2609         matrixToWindow.calculateMatrixToWindow(this, viewToWindowMatrix)
2610         viewToWindowMatrix.invertTo(windowToViewMatrix)
2611     }
2612 
updateWindowMetricsnull2613     private fun updateWindowMetrics() {
2614         _windowInfo.updateContainerSizeIfObserved { calculateWindowSize(this) }
2615     }
2616 
onCheckIsTextEditornull2617     override fun onCheckIsTextEditor(): Boolean {
2618         val parentSession =
2619             textInputSessionMutex.currentSession
2620                 ?: return legacyTextInputServiceAndroid.isEditorFocused()
2621         // Don't bring this up before the ?: – establishTextInputSession has been called, but
2622         // startInputMethod has not, we're not a text editor until the session is cancelled or
2623         // startInputMethod is called.
2624         return parentSession.isReadyForConnection
2625     }
2626 
onCreateInputConnectionnull2627     override fun onCreateInputConnection(outAttrs: EditorInfo): InputConnection? {
2628         val parentSession =
2629             textInputSessionMutex.currentSession
2630                 ?: return legacyTextInputServiceAndroid.createInputConnection(outAttrs)
2631         // Don't bring this up before the ?: - if this returns null, we SHOULD NOT fall back to
2632         // the legacy input system.
2633         return parentSession.createInputConnection(outAttrs)
2634     }
2635 
calculateLocalPositionnull2636     override fun calculateLocalPosition(positionInWindow: Offset): Offset {
2637         recalculateWindowPosition()
2638         return windowToViewMatrix.map(positionInWindow)
2639     }
2640 
calculatePositionInWindownull2641     override fun calculatePositionInWindow(localPosition: Offset): Offset {
2642         recalculateWindowPosition()
2643         return viewToWindowMatrix.map(localPosition)
2644     }
2645 
onConfigurationChangednull2646     override fun onConfigurationChanged(newConfig: Configuration) {
2647         super.onConfigurationChanged(newConfig)
2648         density = Density(context)
2649         updateWindowMetrics()
2650         if (newConfig.fontWeightAdjustmentCompat != currentFontWeightAdjustment) {
2651             currentFontWeightAdjustment = newConfig.fontWeightAdjustmentCompat
2652             fontFamilyResolver = createFontFamilyResolver(context)
2653         }
2654         configurationChangeObserver(newConfig)
2655     }
2656 
onRtlPropertiesChangednull2657     override fun onRtlPropertiesChanged(layoutDirection: Int) {
2658         // This method can be called while View's constructor is running
2659         // by way of resolving padding in response to initScrollbars.
2660         // If we get such a call, don't try to write to a property delegate
2661         // that hasn't been initialized yet.
2662         if (superclassInitComplete) {
2663             this.layoutDirection = toLayoutDirection(layoutDirection) ?: LayoutDirection.Ltr
2664         }
2665     }
2666 
autofillSupportednull2667     private fun autofillSupported() = SDK_INT >= O
2668 
2669     public override fun dispatchHoverEvent(event: MotionEvent): Boolean {
2670         if (hoverExitReceived) {
2671             // Go ahead and send it now
2672             removeCallbacks(sendHoverExitEvent)
2673             sendHoverExitEvent.run()
2674         }
2675         if (isBadMotionEvent(event) || !isAttachedToWindow) {
2676             return false // Bad MotionEvent. Don't handle it.
2677         }
2678 
2679         // Always call accessibilityDelegate dispatchHoverEvent (since accessibilityDelegate's
2680         // dispatchHoverEvent only runs if touch exploration is enabled)
2681         composeAccessibilityDelegate.dispatchHoverEvent(event)
2682 
2683         when (event.actionMasked) {
2684             ACTION_HOVER_EXIT -> {
2685                 if (isInBounds(event)) {
2686                     if (event.getToolType(0) == TOOL_TYPE_MOUSE && event.buttonState != 0) {
2687                         // We know that this is caused by a mouse button press, so we can ignore it
2688                         return false
2689                     }
2690 
2691                     // This may be caused by a press (e.g. stylus pressed on the screen), but
2692                     // we can't be sure until the ACTION_DOWN is received. Let's delay this
2693                     // message and see if the ACTION_DOWN comes.
2694                     previousMotionEvent?.recycle()
2695                     previousMotionEvent = MotionEvent.obtainNoHistory(event)
2696                     hoverExitReceived = true
2697                     // There are cases where the hover exit will incorrectly trigger because this
2698                     // post is called right before the end of the frame and the new frame checks for
2699                     // a press/down event (which hasn't occurred yet). Therefore, we delay the post
2700                     // call a small amount to account for that.
2701                     postDelayed(sendHoverExitEvent, ONE_FRAME_120_HERTZ_IN_MILLISECONDS)
2702                     return false
2703                 }
2704             }
2705             ACTION_HOVER_MOVE ->
2706                 // Check if we're receiving this when we've already handled it elsewhere
2707                 if (!isPositionChanged(event)) {
2708                     return false
2709                 }
2710         }
2711         val result = handleMotionEvent(event)
2712         return result.dispatchedToAPointerInputModifier
2713     }
2714 
isBadMotionEventnull2715     private fun isBadMotionEvent(event: MotionEvent): Boolean {
2716         var eventInvalid =
2717             !event.x.fastIsFinite() ||
2718                 !event.y.fastIsFinite() ||
2719                 !event.rawX.fastIsFinite() ||
2720                 !event.rawY.fastIsFinite()
2721 
2722         if (!eventInvalid) {
2723             // First event x,y is checked above if block, so we can skip index 0.
2724             for (index in 1 until event.pointerCount) {
2725                 eventInvalid =
2726                     !event.getX(index).fastIsFinite() ||
2727                         !event.getY(index).fastIsFinite() ||
2728                         (SDK_INT >= Q && !isValidMotionEvent(event, index))
2729 
2730                 if (eventInvalid) break
2731             }
2732         }
2733 
2734         return eventInvalid
2735     }
2736 
isPositionChangednull2737     private fun isPositionChanged(event: MotionEvent): Boolean {
2738         if (event.pointerCount != 1) {
2739             return true
2740         }
2741         val lastEvent = previousMotionEvent
2742         return lastEvent == null ||
2743             lastEvent.pointerCount != event.pointerCount ||
2744             event.rawX != lastEvent.rawX ||
2745             event.rawY != lastEvent.rawY
2746     }
2747 
findViewByAccessibilityIdRootedAtCurrentViewnull2748     private fun findViewByAccessibilityIdRootedAtCurrentView(
2749         accessibilityId: Int,
2750         currentView: View
2751     ): View? {
2752         if (SDK_INT < Q) {
2753             val getAccessibilityViewIdMethod =
2754                 Class.forName("android.view.View").getDeclaredMethod("getAccessibilityViewId")
2755             getAccessibilityViewIdMethod.isAccessible = true
2756             if (getAccessibilityViewIdMethod.invoke(currentView) == accessibilityId) {
2757                 return currentView
2758             }
2759             if (currentView is ViewGroup) {
2760                 for (i in 0 until currentView.childCount) {
2761                     val foundView =
2762                         findViewByAccessibilityIdRootedAtCurrentView(
2763                             accessibilityId,
2764                             currentView.getChildAt(i)
2765                         )
2766                     if (foundView != null) {
2767                         return foundView
2768                     }
2769                 }
2770             }
2771         }
2772         return null
2773     }
2774 
2775     @RequiresApi(N)
onResolvePointerIconnull2776     override fun onResolvePointerIcon(
2777         event: MotionEvent,
2778         pointerIndex: Int
2779     ): android.view.PointerIcon {
2780         val toolType = event.getToolType(pointerIndex)
2781         if (
2782             !event.isFromSource(InputDevice.SOURCE_MOUSE) &&
2783                 event.isFromSource(InputDevice.SOURCE_STYLUS) &&
2784                 (toolType == MotionEvent.TOOL_TYPE_STYLUS ||
2785                     toolType == MotionEvent.TOOL_TYPE_ERASER)
2786         ) {
2787             val icon = pointerIconService.getStylusHoverIcon()
2788             if (icon != null) {
2789                 return AndroidComposeViewVerificationHelperMethodsN.toAndroidPointerIcon(
2790                     context,
2791                     icon
2792                 )
2793             }
2794         }
2795         return super.onResolvePointerIcon(event, pointerIndex)
2796     }
2797 
2798     override val pointerIconService: PointerIconService =
2799         object : PointerIconService {
2800             private var currentMouseCursorIcon: PointerIcon = PointerIcon.Default
2801             private var currentStylusHoverIcon: PointerIcon? = null
2802 
getIconnull2803             override fun getIcon(): PointerIcon {
2804                 return currentMouseCursorIcon
2805             }
2806 
setIconnull2807             override fun setIcon(value: PointerIcon?) {
2808                 currentMouseCursorIcon = value ?: PointerIcon.Default
2809                 if (SDK_INT >= N) {
2810                     AndroidComposeViewVerificationHelperMethodsN.setPointerIcon(
2811                         this@AndroidComposeView,
2812                         currentMouseCursorIcon
2813                     )
2814                 }
2815             }
2816 
getStylusHoverIconnull2817             override fun getStylusHoverIcon(): PointerIcon? {
2818                 return currentStylusHoverIcon
2819             }
2820 
setStylusHoverIconnull2821             override fun setStylusHoverIcon(value: PointerIcon?) {
2822                 currentStylusHoverIcon = value
2823             }
2824         }
2825 
2826     /**
2827      * This overrides an @hide method in ViewGroup. Because of the @hide, the override keyword
2828      * cannot be used, but the override works anyway because the ViewGroup method is not final. In
2829      * Android P and earlier, the call path is
2830      * AccessibilityInteractionController#findViewByAccessibilityId ->
2831      * View#findViewByAccessibilityId -> ViewGroup#findViewByAccessibilityIdTraversal. In Android Q
2832      * and later, AccessibilityInteractionController#findViewByAccessibilityId uses
2833      * AccessibilityNodeIdManager and findViewByAccessibilityIdTraversal is only used by autofill.
2834      */
2835     @Suppress("BanHideTag")
findViewByAccessibilityIdTraversalnull2836     fun findViewByAccessibilityIdTraversal(accessibilityId: Int): View? {
2837         try {
2838             // AccessibilityInteractionController#findViewByAccessibilityId doesn't call this
2839             // method in Android Q and later. Ideally, we should only define this method in
2840             // Android P and earlier, but since we don't have a way to do so, we can simply
2841             // invoke the hidden parent method after Android P. If in new android, the hidden method
2842             // ViewGroup#findViewByAccessibilityIdTraversal signature is changed or removed, we can
2843             // simply return null here because there will be no call to this method.
2844             return if (SDK_INT >= Q) {
2845                 val findViewByAccessibilityIdTraversalMethod =
2846                     Class.forName("android.view.View")
2847                         .getDeclaredMethod("findViewByAccessibilityIdTraversal", Int::class.java)
2848                 findViewByAccessibilityIdTraversalMethod.isAccessible = true
2849                 findViewByAccessibilityIdTraversalMethod.invoke(this, accessibilityId) as? View
2850             } else {
2851                 findViewByAccessibilityIdRootedAtCurrentView(accessibilityId, this)
2852             }
2853         } catch (e: NoSuchMethodException) {
2854             return null
2855         }
2856     }
2857 
2858     override val isLifecycleInResumedState: Boolean
2859         get() = viewTreeOwners?.lifecycleOwner?.lifecycle?.currentState == Lifecycle.State.RESUMED
2860 
shouldDelayChildPressedStatenull2861     override fun shouldDelayChildPressedState(): Boolean = false
2862 
2863     // Track sensitive composable visible in this view
2864     private var sensitiveComponentCount = 0
2865 
2866     override fun incrementSensitiveComponentCount() {
2867         if (SDK_INT >= 35) {
2868             if (sensitiveComponentCount == 0) {
2869                 AndroidComposeViewSensitiveContent35.setContentSensitivity(view, true)
2870             }
2871             sensitiveComponentCount += 1
2872         }
2873     }
2874 
decrementSensitiveComponentCountnull2875     override fun decrementSensitiveComponentCount() {
2876         if (SDK_INT >= 35) {
2877             if (sensitiveComponentCount == 1) {
2878                 AndroidComposeViewSensitiveContent35.setContentSensitivity(view, false)
2879             }
2880             sensitiveComponentCount -= 1
2881         }
2882     }
2883 
2884     override val outOfFrameExecutor
2885         get() = if (isAttachedToWindow) this else null
2886 
schedulenull2887     override fun schedule(block: () -> Unit) {
2888         val handler =
2889             requireNotNull(handler) {
2890                 "schedule is called when outOfFrameExecutor is not available (view is detached)"
2891             }
2892         handler.postAtFrontOfQueue { trace("AndroidOwner:outOfFrameExecutor", block) }
2893     }
2894 
2895     @RequiresApi(VANILLA_ICE_CREAM)
setRequestedFrameRatenull2896     override fun setRequestedFrameRate(frameRate: Float) {
2897         if (isArrEnabled) {
2898             if (frameRate > 0) {
2899                 if (currentFrameRate.isNaN() || frameRate > currentFrameRate) {
2900                     currentFrameRate = frameRate // set frame rate
2901                 }
2902             } else if (frameRate < 0) {
2903                 if (currentFrameRateCategory.isNaN() || frameRate < currentFrameRateCategory) {
2904                     currentFrameRateCategory = frameRate // set frame rate category
2905                 }
2906             }
2907         } else {
2908             super.setRequestedFrameRate(frameRate)
2909         }
2910     }
2911 
2912     @RequiresApi(VANILLA_ICE_CREAM)
voteFrameRatenull2913     override fun voteFrameRate(frameRate: Float) {
2914         if (isArrEnabled) {
2915             requestedFrameRate = frameRate
2916         }
2917     }
2918 
2919     @OptIn(ExperimentalComposeUiApi::class)
dispatchOnScrollChangednull2920     override fun dispatchOnScrollChanged(delta: Offset) {
2921         // TODO(levima) b/402138549: Use viewTreeObserver.dispatchOnScrollChanged()
2922         dispatchOnScrollChanged(viewTreeObserver)
2923     }
2924 
2925     companion object {
2926         private var systemPropertiesClass: Class<*>? = null
2927         private var getBooleanMethod: Method? = null
2928         private var addChangeCallbackMethod: Method? = null
2929         private val composeViews = mutableObjectListOf<AndroidComposeView>()
2930         private var systemPropertiesChangedRunnable: Runnable? = null
2931         private var dispatchOnScrollChangedMethod: Method? = null
2932 
2933         @Suppress("BanUncheckedReflection")
getIsShowingLayoutBoundsnull2934         private fun getIsShowingLayoutBounds(): Boolean =
2935             try {
2936                 if (systemPropertiesClass == null) {
2937                     systemPropertiesClass = Class.forName("android.os.SystemProperties")
2938                 }
2939                 if (getBooleanMethod == null) {
2940                     getBooleanMethod =
2941                         systemPropertiesClass?.getDeclaredMethod(
2942                             "getBoolean",
2943                             String::class.java,
2944                             Boolean::class.java
2945                         )
2946                 }
2947                 getBooleanMethod?.invoke(null, "debug.layout", false) as? Boolean == true
2948             } catch (_: Exception) {
2949                 false
2950             }
2951 
2952         @Suppress("BanUncheckedReflection")
addNotificationForSysPropsChangenull2953         private fun addNotificationForSysPropsChange(composeView: AndroidComposeView) {
2954             if (SDK_INT > 28) {
2955                 // Removing the callback is prohibited on newer versions, so we should only add one
2956                 // callback and use it for all AndroidComposeViews
2957                 if (systemPropertiesChangedRunnable == null) {
2958                     val runnable = Runnable {
2959                         synchronized(composeViews) {
2960                             if (SDK_INT < 30) {
2961                                 composeViews.forEach {
2962                                     val oldValue = it.showLayoutBounds
2963                                     it.showLayoutBounds = getIsShowingLayoutBounds()
2964                                     if (oldValue != it.showLayoutBounds) {
2965                                         it.invalidateDescendants()
2966                                     }
2967                                 }
2968                             } else {
2969                                 composeViews.forEach { it.invalidateDescendants() }
2970                             }
2971                         }
2972                     }
2973                     systemPropertiesChangedRunnable = runnable
2974                     val origPolicy = StrictMode.getVmPolicy()
2975                     try {
2976                         if (systemPropertiesClass == null) {
2977                             systemPropertiesClass = Class.forName("android.os.SystemProperties")
2978                         }
2979                         if (addChangeCallbackMethod == null) {
2980                             StrictMode.setVmPolicy(StrictMode.VmPolicy.LAX)
2981                             addChangeCallbackMethod =
2982                                 systemPropertiesClass?.getDeclaredMethod(
2983                                     "addChangeCallback",
2984                                     Runnable::class.java
2985                                 )
2986                         }
2987                         addChangeCallbackMethod?.invoke(null, runnable)
2988                     } catch (_: Throwable) {} finally {
2989                         StrictMode.setVmPolicy(origPolicy)
2990                     }
2991                 }
2992                 synchronized(composeViews) { composeViews += composeView }
2993             }
2994         }
2995 
removeNotificationForSysPropsChangenull2996         private fun removeNotificationForSysPropsChange(composeView: AndroidComposeView) {
2997             if (SDK_INT > 28) {
2998                 synchronized(composeViews) { composeViews -= composeView }
2999             }
3000         }
3001 
3002         // Back compat implementation
3003         @SuppressLint("BanUncheckedReflection") // suppress for now, the API is available in MIN_SDK
dispatchOnScrollChangednull3004         fun dispatchOnScrollChanged(viewTreeObserver: ViewTreeObserver) {
3005             try {
3006                 if (dispatchOnScrollChangedMethod == null) {
3007                     dispatchOnScrollChangedMethod =
3008                         viewTreeObserver.javaClass
3009                             .getDeclaredMethod("dispatchOnScrollChanged")
3010                             .also { it.isAccessible = true }
3011                 }
3012                 dispatchOnScrollChangedMethod?.invoke(viewTreeObserver)
3013             } catch (_: Exception) {}
3014         }
3015     }
3016 
3017     /** Combines objects populated via ViewTree*Owner */
3018     class ViewTreeOwners(
3019         /** The [LifecycleOwner] associated with this owner. */
3020         val lifecycleOwner: LifecycleOwner,
3021         /** The [SavedStateRegistryOwner] associated with this owner. */
3022         val savedStateRegistryOwner: SavedStateRegistryOwner
3023     )
3024 }
3025 
3026 @RequiresApi(S)
3027 private object AndroidComposeViewTranslationCallback : ViewTranslationCallback {
onShowTranslationnull3028     override fun onShowTranslation(view: View): Boolean {
3029         val androidComposeView = view as AndroidComposeView
3030         androidComposeView.contentCaptureManager.onShowTranslation()
3031         return true
3032     }
3033 
onHideTranslationnull3034     override fun onHideTranslation(view: View): Boolean {
3035         val androidComposeView = view as AndroidComposeView
3036         androidComposeView.contentCaptureManager.onHideTranslation()
3037         return true
3038     }
3039 
onClearTranslationnull3040     override fun onClearTranslation(view: View): Boolean {
3041         val androidComposeView = view as AndroidComposeView
3042         androidComposeView.contentCaptureManager.onClearTranslation()
3043         return true
3044     }
3045 }
3046 
3047 /**
3048  * These classes are here to ensure that the classes that use this API will get verified and can be
3049  * AOT compiled. It is expected that this class will soft-fail verification, but the classes which
3050  * use this method will pass.
3051  */
3052 @RequiresApi(O)
3053 private object AndroidComposeViewVerificationHelperMethodsO {
3054     @RequiresApi(O)
3055     @DoNotInline
focusablenull3056     fun focusable(view: View, focusable: Int, defaultFocusHighlightEnabled: Boolean) {
3057         view.focusable = focusable
3058         // not to add the default focus highlight to the whole compose view
3059         view.defaultFocusHighlightEnabled = defaultFocusHighlightEnabled
3060     }
3061 }
3062 
3063 @RequiresApi(M)
3064 private object AndroidComposeViewAssistHelperMethodsO {
3065     @RequiresApi(M)
3066     @DoNotInline
setClassNamenull3067     fun setClassName(structure: ViewStructure, view: View) {
3068         structure.setClassName(view.accessibilityClassName.toString())
3069     }
3070 }
3071 
3072 @RequiresApi(N)
3073 private object AndroidComposeViewVerificationHelperMethodsN {
3074     @RequiresApi(N)
toAndroidPointerIconnull3075     fun toAndroidPointerIcon(context: Context, icon: PointerIcon?): android.view.PointerIcon =
3076         when (icon) {
3077             is AndroidPointerIcon -> icon.pointerIcon
3078             is AndroidPointerIconType -> android.view.PointerIcon.getSystemIcon(context, icon.type)
3079             else ->
3080                 android.view.PointerIcon.getSystemIcon(
3081                     context,
3082                     android.view.PointerIcon.TYPE_DEFAULT
3083                 )
3084         }
3085 
3086     @DoNotInline
3087     @RequiresApi(N)
setPointerIconnull3088     fun setPointerIcon(view: View, icon: PointerIcon?) {
3089         val iconToSet = toAndroidPointerIcon(view.context, icon)
3090 
3091         if (view.pointerIcon != iconToSet) {
3092             view.pointerIcon = iconToSet
3093         }
3094     }
3095 }
3096 
3097 @RequiresApi(Q)
3098 private object AndroidComposeViewForceDarkModeQ {
3099     @DoNotInline
3100     @RequiresApi(Q)
disallowForceDarknull3101     fun disallowForceDark(view: View) {
3102         view.isForceDarkAllowed = false
3103     }
3104 }
3105 
3106 @RequiresApi(S)
3107 internal object AndroidComposeViewTranslationCallbackS {
3108     @DoNotInline
3109     @RequiresApi(S)
setViewTranslationCallbacknull3110     fun setViewTranslationCallback(view: View) {
3111         view.setViewTranslationCallback(AndroidComposeViewTranslationCallback)
3112     }
3113 
3114     @DoNotInline
3115     @RequiresApi(S)
clearViewTranslationCallbacknull3116     fun clearViewTranslationCallback(view: View) {
3117         view.clearViewTranslationCallback()
3118     }
3119 }
3120 
3121 /** Sets this [Matrix] to be the result of this * [other] */
Matrixnull3122 private fun Matrix.preTransform(other: Matrix) {
3123     val v00 = dot(other, 0, this, 0)
3124     val v01 = dot(other, 0, this, 1)
3125     val v02 = dot(other, 0, this, 2)
3126     val v03 = dot(other, 0, this, 3)
3127     val v10 = dot(other, 1, this, 0)
3128     val v11 = dot(other, 1, this, 1)
3129     val v12 = dot(other, 1, this, 2)
3130     val v13 = dot(other, 1, this, 3)
3131     val v20 = dot(other, 2, this, 0)
3132     val v21 = dot(other, 2, this, 1)
3133     val v22 = dot(other, 2, this, 2)
3134     val v23 = dot(other, 2, this, 3)
3135     val v30 = dot(other, 3, this, 0)
3136     val v31 = dot(other, 3, this, 1)
3137     val v32 = dot(other, 3, this, 2)
3138     val v33 = dot(other, 3, this, 3)
3139     this[0, 0] = v00
3140     this[0, 1] = v01
3141     this[0, 2] = v02
3142     this[0, 3] = v03
3143     this[1, 0] = v10
3144     this[1, 1] = v11
3145     this[1, 2] = v12
3146     this[1, 3] = v13
3147     this[2, 0] = v20
3148     this[2, 1] = v21
3149     this[2, 2] = v22
3150     this[2, 3] = v23
3151     this[3, 0] = v30
3152     this[3, 1] = v31
3153     this[3, 2] = v32
3154     this[3, 3] = v33
3155 }
3156 
3157 /** Like [android.graphics.Matrix.preTranslate], for a Compose [Matrix] */
Matrixnull3158 private fun Matrix.preTranslate(x: Float, y: Float, tmpMatrix: Matrix) {
3159     tmpMatrix.reset()
3160     tmpMatrix.translate(x, y)
3161     preTransform(tmpMatrix)
3162 }
3163 
3164 // Taken from Matrix.kt
dotnull3165 private fun dot(m1: Matrix, row: Int, m2: Matrix, column: Int): Float {
3166     return m1[row, 0] * m2[0, column] +
3167         m1[row, 1] * m2[1, column] +
3168         m1[row, 2] * m2[2, column] +
3169         m1[row, 3] * m2[3, column]
3170 }
3171 
3172 private interface CalculateMatrixToWindow {
3173     /**
3174      * Calculates the matrix from [view] to screen coordinates and returns the value in [matrix].
3175      */
calculateMatrixToWindownull3176     fun calculateMatrixToWindow(view: View, matrix: Matrix)
3177 }
3178 
3179 @RequiresApi(35)
3180 private object AndroidComposeViewSensitiveContent35 {
3181     @DoNotInline
3182     @RequiresApi(35)
3183     fun setContentSensitivity(view: View, isSensitiveContent: Boolean) {
3184         if (isSensitiveContent) {
3185             view.setContentSensitivity(View.CONTENT_SENSITIVITY_SENSITIVE)
3186         } else {
3187             view.setContentSensitivity(View.CONTENT_SENSITIVITY_AUTO)
3188         }
3189     }
3190 }
3191 
3192 @RequiresApi(Q)
3193 private class CalculateMatrixToWindowApi29 : CalculateMatrixToWindow {
3194     private val tmpMatrix = android.graphics.Matrix()
3195     private val tmpPosition = IntArray(2)
3196 
3197     @DoNotInline
calculateMatrixToWindownull3198     override fun calculateMatrixToWindow(view: View, matrix: Matrix) {
3199         tmpMatrix.reset()
3200         view.transformMatrixToGlobal(tmpMatrix)
3201         var parent = view.parent
3202         var root = view
3203         while (parent is View) {
3204             root = parent
3205             parent = root.parent
3206         }
3207         root.getLocationOnScreen(tmpPosition)
3208         val (screenX, screenY) = tmpPosition
3209         root.getLocationInWindow(tmpPosition)
3210         val (windowX, windowY) = tmpPosition
3211         tmpMatrix.postTranslate((windowX - screenX).toFloat(), (windowY - screenY).toFloat())
3212         matrix.setFrom(tmpMatrix)
3213     }
3214 }
3215 
3216 private class CalculateMatrixToWindowApi21(private val tmpMatrix: Matrix) :
3217     CalculateMatrixToWindow {
3218     private val tmpLocation = IntArray(2)
3219 
calculateMatrixToWindownull3220     override fun calculateMatrixToWindow(view: View, matrix: Matrix) {
3221         matrix.reset()
3222         transformMatrixToWindow(view, matrix)
3223     }
3224 
transformMatrixToWindownull3225     private fun transformMatrixToWindow(view: View, matrix: Matrix) {
3226         val parentView = view.parent
3227         if (parentView is View) {
3228             transformMatrixToWindow(parentView, matrix)
3229             matrix.preTranslate(-view.scrollX.toFloat(), -view.scrollY.toFloat())
3230             matrix.preTranslate(view.left.toFloat(), view.top.toFloat())
3231         } else {
3232             val pos = tmpLocation
3233             view.getLocationInWindow(pos)
3234             matrix.preTranslate(-view.scrollX.toFloat(), -view.scrollY.toFloat())
3235             matrix.preTranslate(pos[0].toFloat(), pos[1].toFloat())
3236         }
3237 
3238         val viewMatrix = view.matrix
3239         if (!viewMatrix.isIdentity) {
3240             matrix.preConcat(viewMatrix)
3241         }
3242     }
3243 
3244     /**
3245      * Like [android.graphics.Matrix.preConcat], for a Compose [Matrix] that accepts an [other]
3246      * [android.graphics.Matrix].
3247      */
Matrixnull3248     private fun Matrix.preConcat(other: android.graphics.Matrix) {
3249         tmpMatrix.setFrom(other)
3250         preTransform(tmpMatrix)
3251     }
3252 
3253     /** Like [android.graphics.Matrix.preTranslate], for a Compose [Matrix] */
Matrixnull3254     private fun Matrix.preTranslate(x: Float, y: Float) {
3255         preTranslate(x, y, tmpMatrix)
3256     }
3257 }
3258 
3259 @RequiresApi(29)
3260 private object MotionEventVerifierApi29 {
3261     @DoNotInline
isValidMotionEventnull3262     fun isValidMotionEvent(event: MotionEvent, index: Int): Boolean {
3263         return event.getRawX(index).fastIsFinite() && event.getRawY(index).fastIsFinite()
3264     }
3265 }
3266 
3267 @RequiresApi(N)
3268 private object AndroidComposeViewStartDragAndDropN {
3269     @DoNotInline
3270     @RequiresApi(N)
startDragAndDropnull3271     fun startDragAndDrop(
3272         view: View,
3273         transferData: DragAndDropTransferData,
3274         dragShadowBuilder: ComposeDragShadowBuilder
3275     ): Boolean =
3276         view.startDragAndDrop(
3277             transferData.clipData,
3278             dragShadowBuilder,
3279             transferData.localState,
3280             transferData.flags,
3281         )
3282 }
3283 
3284 private fun View.containsDescendant(other: View): Boolean {
3285     if (other == this) return false
3286     var viewParent = other.parent
3287     while (viewParent != null) {
3288         if (viewParent === this) return true
3289         viewParent = viewParent.parent
3290     }
3291     return false
3292 }
3293 
Viewnull3294 private fun View.getContentCaptureSessionCompat(): ContentCaptureSessionCompat? {
3295     ViewCompatShims.setImportantForContentCapture(
3296         this,
3297         ViewCompatShims.IMPORTANT_FOR_CONTENT_CAPTURE_YES
3298     )
3299     return ViewCompatShims.getContentCaptureSession(this)
3300 }
3301 
3302 private class BringIntoViewOnScreenResponderNode(var view: ViewGroup) :
3303     Modifier.Node(), BringIntoViewModifierNode {
bringIntoViewnull3304     override suspend fun bringIntoView(
3305         childCoordinates: LayoutCoordinates,
3306         boundsProvider: () -> androidx.compose.ui.geometry.Rect?
3307     ) {
3308         val childOffset = childCoordinates.positionInRoot()
3309         val rootRect = boundsProvider()?.translate(childOffset)
3310         if (rootRect != null) {
3311             view.requestRectangleOnScreen(rootRect.toAndroidRect(), false)
3312         }
3313     }
3314 }
3315 
3316 /** Split out to avoid class verification errors. This class will only be loaded when SDK >= 30. */
3317 @RequiresApi(30)
3318 private object Api30Impl {
isShowingLayoutBoundsnull3319     @DoNotInline fun isShowingLayoutBounds(view: View) = view.isShowingLayoutBounds
3320 }
3321