1 /*
<lambda>null2  * Copyright 2021 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 package androidx.compose.ui.focus
18 
19 import androidx.compose.runtime.collection.MutableVector
20 import androidx.compose.runtime.collection.mutableVectorOf
21 import androidx.compose.ui.ComposeUiFlags
22 import androidx.compose.ui.ExperimentalComposeUiApi
23 import androidx.compose.ui.InternalComposeUiApi
24 import androidx.compose.ui.focus.CustomDestinationResult.Cancelled
25 import androidx.compose.ui.focus.CustomDestinationResult.None
26 import androidx.compose.ui.focus.CustomDestinationResult.RedirectCancelled
27 import androidx.compose.ui.focus.CustomDestinationResult.Redirected
28 import androidx.compose.ui.focus.FocusRequester.Companion.Cancel
29 import androidx.compose.ui.focus.FocusRequester.Companion.Redirect
30 import androidx.compose.ui.focus.FocusStateImpl.Active
31 import androidx.compose.ui.focus.FocusStateImpl.ActiveParent
32 import androidx.compose.ui.focus.FocusStateImpl.Captured
33 import androidx.compose.ui.focus.FocusStateImpl.Inactive
34 import androidx.compose.ui.node.Nodes
35 import androidx.compose.ui.node.nearestAncestor
36 import androidx.compose.ui.node.observeReads
37 import androidx.compose.ui.node.requireLayoutNode
38 import androidx.compose.ui.node.requireOwner
39 import androidx.compose.ui.node.visitAncestors
40 
41 /**
42  * This function performs the request focus action.
43  *
44  * Note: Do not call this directly, consider using [requestFocus], which will check if any custom
45  * focus [enter][FocusProperties.onEnter] and [exit][FocusProperties.onExit]
46  * [properties][FocusProperties] have been specified.
47  */
48 internal fun FocusTargetNode.performRequestFocus(): Boolean {
49     return if (@OptIn(ExperimentalComposeUiApi::class) ComposeUiFlags.isTrackFocusEnabled) {
50         performRequestFocusOptimized()
51     } else {
52         performRequestFocusLegacy()
53     }
54 }
55 
FocusTargetNodenull56 private fun FocusTargetNode.performRequestFocusOptimized(): Boolean {
57     val focusOwner = requireOwner().focusOwner
58     val previousActiveNode = focusOwner.activeFocusTargetNode
59     val previousFocusState = focusState
60     if (previousActiveNode === this) {
61         // Focus events should be sent again if focus is requested for an already focused node
62         dispatchFocusCallbacks(previousFocusState, previousFocusState)
63         return true
64     }
65 
66     // Request owner focus if it doesn't already have focus
67     if (previousActiveNode == null && !requestFocusForOwner()) {
68         return false // Don't grant focus if requesting owner focus failed
69     }
70 
71     // Find ancestor target and event nodes of the previous active target node
72     var previousAncestorTargetNodes: MutableVector<FocusTargetNode>? = null
73     if (previousActiveNode != null) {
74         previousAncestorTargetNodes = mutableVectorOf()
75         previousActiveNode.visitAncestors(Nodes.FocusTarget) { previousAncestorTargetNodes.add(it) }
76     }
77 
78     // Diff the previous ancestor nodes with the ancestors of the new active target node.
79     // We also check if the previous active node is an ancestor of the new active node, in which
80     // case we don't need to clear focus from it.
81     var shouldClearFocusFromPreviousActiveNode = true
82     val ancestorTargetNodes = mutableVectorOf<FocusTargetNode>()
83     visitAncestors(Nodes.FocusTarget) {
84         val removed = previousAncestorTargetNodes?.remove(it)
85         if (removed == null || !removed) {
86             ancestorTargetNodes.add(it)
87         }
88         if (it === previousActiveNode) shouldClearFocusFromPreviousActiveNode = false
89     }
90 
91     if (shouldClearFocusFromPreviousActiveNode) {
92         if (previousActiveNode?.clearFocus(refreshFocusEvents = true) == false) {
93             return false // Don't grant focus if clearing focus from the previous node was rejected
94         }
95     }
96 
97     grantFocus()
98 
99     // Notify ancestor target nodes of the previous active node that are no longer ActiveParent
100     // The ancestors are traversed in the reversed order to dispatch events top->down
101     previousAncestorTargetNodes?.forEachReversed {
102         // Check if focus was cleared or redirected in a previous focus change callback
103         if (focusOwner.activeFocusTargetNode !== this) {
104             // The focus request was redirected or cancelled in a previous focus change callback
105             return false
106         }
107         it.dispatchFocusCallbacks(ActiveParent, Inactive)
108     }
109 
110     // Notify ancestor target nodes of the new active node that become ActiveParent
111     // The ancestors are traversed in the reversed order to dispatch events top->down
112     ancestorTargetNodes.forEachReversed {
113         // Check if focus was cleared or redirected in a previous focus change callback
114         if (focusOwner.activeFocusTargetNode !== this) {
115             // The focus request was redirected or cancelled in a previous focus change callback
116             return false
117         }
118         it.dispatchFocusCallbacks(
119             previousState = if (it === previousActiveNode) Active else Inactive,
120             newState = ActiveParent
121         )
122     }
123 
124     // Check if focus was cleared or redirected in a previous focus change callback
125     if (focusOwner.activeFocusTargetNode !== this) {
126         // The focus request was redirected or cancelled in a previous focus change callback
127         return false
128     }
129 
130     // Send events to the new active node
131     dispatchFocusCallbacks(previousFocusState, Active)
132 
133     // Check if focus was cleared or redirected in a previous focus change callback
134     if (focusOwner.activeFocusTargetNode !== this) {
135         // The focus request was redirected or cancelled in a previous focus change callback
136         return false
137     }
138 
139     @OptIn(ExperimentalComposeUiApi::class, InternalComposeUiApi::class)
140     if (ComposeUiFlags.isViewFocusFixEnabled && requireLayoutNode().getInteropView() == null) {
141         // This isn't an AndroidView, so we should be focused on this ComposeView
142         requireOwner().focusOwner.requestFocusForOwner(FocusDirection.Next, null)
143     }
144 
145     return true
146 }
147 
FocusTargetNodenull148 private fun FocusTargetNode.performRequestFocusLegacy(): Boolean {
149     val success =
150         when (focusState) {
151             Active,
152             Captured -> true
153             ActiveParent -> clearChildFocus() && grantFocus()
154             Inactive -> {
155                 val parent = nearestAncestor(Nodes.FocusTarget)
156                 if (parent != null) {
157                     val prevState = parent.focusState
158                     val success = parent.requestFocusForChild(this)
159                     if (success && prevState !== parent.focusState) {
160                         parent.dispatchFocusCallbacks()
161                     }
162                     success
163                 } else {
164                     requestFocusForOwner() && grantFocus()
165                 }
166             }
167         }
168     if (success) {
169         @OptIn(ExperimentalComposeUiApi::class, InternalComposeUiApi::class)
170         if (ComposeUiFlags.isViewFocusFixEnabled && requireLayoutNode().getInteropView() == null) {
171             // This isn't an AndroidView, so we should be focused on this ComposeView
172             requireOwner().focusOwner.requestFocusForOwner(FocusDirection.Next, null)
173         }
174         dispatchFocusCallbacks()
175     }
176     return success
177 }
178 
179 /**
180  * Deny requests to clear focus.
181  *
182  * This is used when a component wants to hold onto focus (eg. A phone number field with an invalid
183  * number.
184  *
185  * @return true if the focus was successfully captured. False otherwise.
186  */
captureFocusnull187 internal fun FocusTargetNode.captureFocus() =
188     if (@OptIn(ExperimentalComposeUiApi::class) ComposeUiFlags.isTrackFocusEnabled) {
189         when (focusState) {
190             Active -> {
191                 requireOwner().focusOwner.isFocusCaptured = true
192                 dispatchFocusCallbacks(Active, Captured)
193                 true
194             }
195             Captured -> true
196             ActiveParent,
197             Inactive -> false
198         }
199     } else {
<lambda>null200         requireTransactionManager().withNewTransaction {
201             when (focusState) {
202                 Active -> {
203                     focusState = Captured
204                     dispatchFocusCallbacks()
205                     true
206                 }
207                 Captured -> true
208                 ActiveParent,
209                 Inactive -> false
210             }
211         }
212     }
213 
214 /**
215  * When the node is in the [Captured] state, it rejects all requests to clear focus. Calling
216  * [freeFocus] puts the node in the [Active] state, where it is no longer preventing other nodes
217  * from requesting focus.
218  *
219  * @return true if the captured focus was released. False Otherwise.
220  */
freeFocusnull221 internal fun FocusTargetNode.freeFocus() =
222     if (@OptIn(ExperimentalComposeUiApi::class) ComposeUiFlags.isTrackFocusEnabled) {
223         when (focusState) {
224             Captured -> {
225                 requireOwner().focusOwner.isFocusCaptured = false
226                 dispatchFocusCallbacks(previousState = Captured, newState = Active)
227                 true
228             }
229             Active -> true
230             ActiveParent,
231             Inactive -> false
232         }
233     } else {
<lambda>null234         requireTransactionManager().withNewTransaction {
235             when (focusState) {
236                 Captured -> {
237                     focusState = Active
238                     dispatchFocusCallbacks()
239                     true
240                 }
241                 Active -> true
242                 ActiveParent,
243                 Inactive -> false
244             }
245         }
246     }
247 
248 /**
249  * This function clears focus from this node.
250  *
251  * Note: This function should only be called by a parent [focus node][FocusTargetNode] to clear
252  * focus from one of its child [focus node][FocusTargetNode]s. It does not change the state of the
253  * parent.
254  */
clearFocusnull255 internal fun FocusTargetNode.clearFocus(
256     forced: Boolean = false,
257     refreshFocusEvents: Boolean
258 ): Boolean =
259     when (focusState) {
260         Active -> {
261             if (@OptIn(ExperimentalComposeUiApi::class) ComposeUiFlags.isTrackFocusEnabled) {
262                 requireOwner().focusOwner.activeFocusTargetNode = null
263                 if (refreshFocusEvents)
264                     dispatchFocusCallbacks(previousState = Active, newState = Inactive)
265             } else {
266                 focusState = Inactive
267                 if (refreshFocusEvents) dispatchFocusCallbacks()
268             }
269             true
270         }
271         /**
272          * If the node is [ActiveParent], we need to clear focus from the [Active] descendant first,
273          * before clearing focus from this node.
274          */
275         ActiveParent ->
276             if (clearChildFocus(forced, refreshFocusEvents)) {
277                 if (@OptIn(ExperimentalComposeUiApi::class) ComposeUiFlags.isTrackFocusEnabled) {
278                     if (refreshFocusEvents)
279                         dispatchFocusCallbacks(previousState = ActiveParent, newState = Inactive)
280                 } else {
281                     focusState = Inactive
282                     if (refreshFocusEvents) dispatchFocusCallbacks()
283                 }
284                 true
285             } else {
286                 false
287             }
288 
289         /** If the node is [Captured], deny requests to clear focus, except for a forced clear. */
290         Captured -> {
291             if (forced) {
292                 if (@OptIn(ExperimentalComposeUiApi::class) ComposeUiFlags.isTrackFocusEnabled) {
293                     requireOwner().focusOwner.activeFocusTargetNode = null
294                     if (refreshFocusEvents)
295                         dispatchFocusCallbacks(previousState = Captured, newState = Inactive)
296                 } else {
297                     focusState = Inactive
298                     if (refreshFocusEvents) dispatchFocusCallbacks()
299                 }
300             }
301             forced
302         }
303         /** Nothing to do if the node is not focused. */
304         Inactive -> true
305     }
306 
307 /**
308  * This function grants focus to this node. Note: This is a private function that just changes the
309  * state of this node and does not affect any other nodes in the hierarchy.
310  */
FocusTargetNodenull311 private fun FocusTargetNode.grantFocus(): Boolean {
312     // When we grant focus to this node, we need to observe changes to the canFocus property.
313     // If canFocus is set to false, we need to clear focus.
314     observeReads { fetchFocusProperties() }
315     // No Focused Children, or we don't want to propagate focus to children.
316     when (focusState) {
317         Inactive,
318         ActiveParent -> {
319             if (@OptIn(ExperimentalComposeUiApi::class) ComposeUiFlags.isTrackFocusEnabled)
320                 requireOwner().focusOwner.activeFocusTargetNode = this
321             else focusState = Active
322         }
323         Active,
324         Captured -> {
325             /* Already focused. */
326         }
327     }
328     return true
329 }
330 
331 /** This function clears any focus from the focused child. */
clearChildFocusnull332 private fun FocusTargetNode.clearChildFocus(
333     forced: Boolean = false,
334     refreshFocusEvents: Boolean = true
335 ): Boolean = activeChild?.clearFocus(forced, refreshFocusEvents) ?: true
336 
337 /**
338  * Focusable children of this [focus node][FocusTargetNode] can use this function to request focus.
339  *
340  * @param childNode: The node that is requesting focus.
341  * @return true if focus was granted, false otherwise.
342  */
343 private fun FocusTargetNode.requestFocusForChild(childNode: FocusTargetNode): Boolean {
344 
345     // Only this node's children can ask for focus.
346     if (childNode.nearestAncestor(Nodes.FocusTarget) != this) {
347         error("Non child node cannot request focus.")
348     }
349 
350     return when (focusState) {
351         // If this node is [Active], it can give focus to the requesting child.
352         Active -> childNode.grantFocus().also { success -> if (success) focusState = ActiveParent }
353         // If this node is [ActiveParent] ie, one of the parent's descendants is [Active],
354         // remove focus from the currently focused child and grant it to the requesting child.
355         ActiveParent -> {
356             requireActiveChild()
357             clearChildFocus() && childNode.grantFocus()
358         }
359         // If this node is not [Active], we must gain focus first before granting it
360         // to the requesting child.
361         Inactive -> {
362             val focusParent = nearestAncestor(Nodes.FocusTarget)
363             when {
364                 // If this node is the root, request focus from the compose owner.
365                 focusParent == null && requestFocusForOwner() -> {
366                     childNode.grantFocus().also { success ->
367                         if (success) focusState = ActiveParent
368                     }
369                 }
370                 // For non-root nodes, request focus for this node before the child.
371                 // We request focus even if this is a deactivated node, as we will end up taking
372                 // focus away and granting it to the child.
373                 focusParent != null && focusParent.requestFocusForChild(this) -> {
374                     requestFocusForChild(childNode).also { success ->
375                         // Verify that focus state was granted to the child.
376                         // If this child didn't take focus then we can end up in a situation where
377                         // a deactivated parent is focused.
378                         check(this.focusState == ActiveParent) { "Deactivated node is focused" }
379                         if (success) focusParent.dispatchFocusCallbacks()
380                     }
381                 }
382 
383                 // Could not gain focus, so have no focus to give.
384                 else -> false
385             }
386         }
387         // If this node is [Captured], decline requests from the children.
388         Captured -> false
389     }
390 }
391 
requestFocusForOwnernull392 private fun FocusTargetNode.requestFocusForOwner(): Boolean {
393     return requireOwner().focusOwner.requestFocusForOwner(null, null)
394 }
395 
FocusTargetNodenull396 private fun FocusTargetNode.requireActiveChild(): FocusTargetNode {
397     return requireNotNull(activeChild) { "ActiveParent with no focused child" }
398 }
399 
400 internal enum class CustomDestinationResult {
401     None,
402     Cancelled,
403     Redirected,
404     RedirectCancelled
405 }
406 
performCustomRequestFocusnull407 internal fun FocusTargetNode.performCustomRequestFocus(
408     focusDirection: FocusDirection
409 ): CustomDestinationResult {
410     when (focusState) {
411         Active,
412         Captured -> return None
413         ActiveParent -> return requireActiveChild().performCustomClearFocus(focusDirection)
414         Inactive -> {
415             val focusParent = nearestAncestor(Nodes.FocusTarget) ?: return None
416             return when (focusParent.focusState) {
417                 Captured -> Cancelled
418                 ActiveParent -> focusParent.performCustomRequestFocus(focusDirection)
419                 Active -> focusParent.performCustomEnter(focusDirection)
420                 Inactive ->
421                     focusParent.performCustomRequestFocus(focusDirection).takeUnless { it == None }
422                         ?: focusParent.performCustomEnter(focusDirection)
423             }
424         }
425     }
426 }
427 
performCustomClearFocusnull428 internal fun FocusTargetNode.performCustomClearFocus(
429     focusDirection: FocusDirection
430 ): CustomDestinationResult =
431     when (focusState) {
432         Active,
433         Inactive -> None
434         Captured -> Cancelled
435         ActiveParent ->
436             requireActiveChild().performCustomClearFocus(focusDirection).takeUnless { it == None }
437                 ?: performCustomExit(focusDirection)
438     }
439 
performCustomEnternull440 private fun FocusTargetNode.performCustomEnter(
441     focusDirection: FocusDirection
442 ): CustomDestinationResult {
443     fetchCustomEnter(focusDirection) {
444         if (it === Cancel) return Cancelled else if (it === Redirect) return Redirected
445         return if (it.requestFocus()) Redirected else RedirectCancelled
446     }
447     return None
448 }
449 
performCustomExitnull450 private fun FocusTargetNode.performCustomExit(
451     focusDirection: FocusDirection
452 ): CustomDestinationResult {
453     fetchCustomExit(focusDirection) {
454         if (it === Cancel) return Cancelled else if (it === Redirect) return Redirected
455         return if (it.requestFocus()) Redirected else RedirectCancelled
456     }
457     return None
458 }
459