• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  *  Copyright (C) 2022 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 
18 package com.android.systemui.lifecycle
19 
20 import android.view.View
21 import android.view.ViewTreeObserver
22 import androidx.annotation.MainThread
23 import androidx.lifecycle.Lifecycle
24 import androidx.lifecycle.LifecycleOwner
25 import androidx.lifecycle.LifecycleRegistry
26 import androidx.lifecycle.lifecycleScope
27 import com.android.systemui.util.Assert
28 import kotlin.coroutines.CoroutineContext
29 import kotlin.coroutines.EmptyCoroutineContext
30 import kotlinx.coroutines.Dispatchers
31 import kotlinx.coroutines.DisposableHandle
32 import kotlinx.coroutines.launch
33 
34 /**
35  * Runs the given [block] every time the [View] becomes attached (or immediately after calling this
36  * function, if the view was already attached), automatically canceling the work when the `View`
37  * becomes detached.
38  *
39  * Only use from the main thread.
40  *
41  * When [block] is run, it is run in the context of a [ViewLifecycleOwner] which the caller can use
42  * to launch jobs, with confidence that the jobs will be properly canceled when the view is
43  * detached.
44  *
45  * The [block] may be run multiple times, running once per every time the view is attached. Each
46  * time the block is run for a new attachment event, the [ViewLifecycleOwner] provided will be a
47  * fresh one.
48  *
49  * @param coroutineContext An optional [CoroutineContext] to replace the dispatcher [block] is
50  *   invoked on.
51  * @param block The block of code that should be run when the view becomes attached. It can end up
52  *   being invoked multiple times if the view is reattached after being detached.
53  * @return A [DisposableHandle] to invoke when the caller of the function destroys its [View] and is
54  *   no longer interested in the [block] being run the next time its attached. Calling this is an
55  *   optional optimization as the logic will be properly cleaned up and destroyed each time the view
56  *   is detached. Using this is not *thread-safe* and should only be used on the main thread.
57  */
58 @MainThread
repeatWhenAttachednull59 fun View.repeatWhenAttached(
60     coroutineContext: CoroutineContext = EmptyCoroutineContext,
61     block: suspend LifecycleOwner.(View) -> Unit,
62 ): DisposableHandle {
63     Assert.isMainThread()
64     val view = this
65     // The suspend block will run on the app's main thread unless the caller supplies a different
66     // dispatcher to use. We don't want it to run on the Dispatchers.Default thread pool as
67     // default behavior. Instead, we want it to run on the view's UI thread since the user will
68     // presumably want to call view methods that require being called from said UI thread.
69     val lifecycleCoroutineContext = Dispatchers.Main + coroutineContext
70     var lifecycleOwner: ViewLifecycleOwner? = null
71     val onAttachListener =
72         object : View.OnAttachStateChangeListener {
73             override fun onViewAttachedToWindow(v: View?) {
74                 Assert.isMainThread()
75                 lifecycleOwner?.onDestroy()
76                 lifecycleOwner =
77                     createLifecycleOwnerAndRun(
78                         view,
79                         lifecycleCoroutineContext,
80                         block,
81                     )
82             }
83 
84             override fun onViewDetachedFromWindow(v: View?) {
85                 lifecycleOwner?.onDestroy()
86                 lifecycleOwner = null
87             }
88         }
89 
90     addOnAttachStateChangeListener(onAttachListener)
91     if (view.isAttachedToWindow) {
92         lifecycleOwner =
93             createLifecycleOwnerAndRun(
94                 view,
95                 lifecycleCoroutineContext,
96                 block,
97             )
98     }
99 
100     return object : DisposableHandle {
101         override fun dispose() {
102             Assert.isMainThread()
103 
104             lifecycleOwner?.onDestroy()
105             lifecycleOwner = null
106             view.removeOnAttachStateChangeListener(onAttachListener)
107         }
108     }
109 }
110 
createLifecycleOwnerAndRunnull111 private fun createLifecycleOwnerAndRun(
112     view: View,
113     coroutineContext: CoroutineContext,
114     block: suspend LifecycleOwner.(View) -> Unit,
115 ): ViewLifecycleOwner {
116     return ViewLifecycleOwner(view).apply {
117         onCreate()
118         lifecycleScope.launch(coroutineContext) { block(view) }
119     }
120 }
121 
122 /**
123  * A [LifecycleOwner] for a [View] for exclusive use by the [repeatWhenAttached] extension function.
124  *
125  * The implementation requires the caller to call [onCreate] and [onDestroy] when the view is
126  * attached to or detached from a view hierarchy. After [onCreate] and before [onDestroy] is called,
127  * the implementation monitors window state in the following way
128  * * If the window is not visible, we are in the [Lifecycle.State.CREATED] state
129  * * If the window is visible but not focused, we are in the [Lifecycle.State.STARTED] state
130  * * If the window is visible and focused, we are in the [Lifecycle.State.RESUMED] state
131  *
132  * Or in table format:
133  * ```
134  * ┌───────────────┬───────────────────┬──────────────┬─────────────────┐
135  * │ View attached │ Window Visibility │ Window Focus │ Lifecycle State │
136  * ├───────────────┼───────────────────┴──────────────┼─────────────────┤
137  * │ Not attached  │                 Any              │       N/A       │
138  * ├───────────────┼───────────────────┬──────────────┼─────────────────┤
139  * │               │    Not visible    │     Any      │     CREATED     │
140  * │               ├───────────────────┼──────────────┼─────────────────┤
141  * │   Attached    │                   │   No focus   │     STARTED     │
142  * │               │      Visible      ├──────────────┼─────────────────┤
143  * │               │                   │  Has focus   │     RESUMED     │
144  * └───────────────┴───────────────────┴──────────────┴─────────────────┘
145  * ```
146  */
147 class ViewLifecycleOwner(
148     private val view: View,
149 ) : LifecycleOwner {
150 
151     private val windowVisibleListener =
<lambda>null152         ViewTreeObserver.OnWindowVisibilityChangeListener { updateState() }
<lambda>null153     private val windowFocusListener = ViewTreeObserver.OnWindowFocusChangeListener { updateState() }
154 
155     private val registry = LifecycleRegistry(this)
156 
onCreatenull157     fun onCreate() {
158         registry.currentState = Lifecycle.State.CREATED
159         view.viewTreeObserver.addOnWindowVisibilityChangeListener(windowVisibleListener)
160         view.viewTreeObserver.addOnWindowFocusChangeListener(windowFocusListener)
161         updateState()
162     }
163 
onDestroynull164     fun onDestroy() {
165         view.viewTreeObserver.removeOnWindowVisibilityChangeListener(windowVisibleListener)
166         view.viewTreeObserver.removeOnWindowFocusChangeListener(windowFocusListener)
167         registry.currentState = Lifecycle.State.DESTROYED
168     }
169 
getLifecyclenull170     override fun getLifecycle(): Lifecycle {
171         return registry
172     }
173 
updateStatenull174     private fun updateState() {
175         registry.currentState =
176             when {
177                 view.windowVisibility != View.VISIBLE -> Lifecycle.State.CREATED
178                 !view.hasWindowFocus() -> Lifecycle.State.STARTED
179                 else -> Lifecycle.State.RESUMED
180             }
181     }
182 }
183