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.window.layout
18 
19 import android.app.Activity
20 import android.content.Context
21 import android.inputmethodservice.InputMethodService
22 import android.util.Log
23 import androidx.annotation.RestrictTo
24 import androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP
25 import androidx.annotation.UiContext
26 import androidx.window.RequiresWindowSdkExtension
27 import androidx.window.WindowSdkExtensions
28 import androidx.window.core.ConsumerAdapter
29 import androidx.window.layout.adapter.WindowBackend
30 import androidx.window.layout.adapter.extensions.ExtensionWindowBackend
31 import androidx.window.layout.adapter.sidecar.SidecarWindowBackend
32 import kotlinx.coroutines.flow.Flow
33 
34 /**
35  * An interface to provide all the relevant info about a [android.view.Window].
36  *
37  * @see WindowInfoTracker.getOrCreate to get an instance.
38  */
39 interface WindowInfoTracker {
40 
41     /**
42      * A [Flow] of [WindowLayoutInfo] that contains all the available features. A [WindowLayoutInfo]
43      * contains a [List] of [DisplayFeature] that intersect the associated [android.view.Window].
44      *
45      * This method exports the same content as the [windowLayoutInfo] method that accepts an
46      * [Activity] as a parameter, but also supports non-Activity windows to receive
47      * [WindowLayoutInfo] updates. A [WindowLayoutInfo] value should be published when
48      * [DisplayFeature] have changed, but the behavior is ultimately decided by the hardware
49      * implementation. It is recommended to test the following scenarios:
50      * * Values are emitted immediately after subscribing to this function.
51      * * There is a long delay between subscribing and receiving the first value.
52      * * Never receiving a value after subscription.
53      *
54      * A derived class may throw NotImplementedError if this method is not overridden. Obtaining a
55      * [WindowInfoTracker] through [WindowInfoTracker.getOrCreate] guarantees having a default
56      * implementation for this method.
57      *
58      * If the passed [context] is not an [Activity] and [WindowSdkExtensions.extensionVersion] is
59      * less than 2, the flow will return empty [WindowLayoutInfo] list flow.
60      *
61      * @param context a [UiContext] such as an [Activity], an [InputMethodService], or an instance
62      *   created via [Context.createWindowContext] that listens to configuration changes.
63      * @throws IllegalArgumentException when [context] is not an [UiContext].
64      * @throws NotImplementedError when this method has no supporting implementation.
65      * @see WindowLayoutInfo
66      * @see DisplayFeature
67      */
68     fun windowLayoutInfo(@UiContext context: Context): Flow<WindowLayoutInfo> {
69         val windowLayoutInfoFlow: Flow<WindowLayoutInfo>? =
70             (context as? Activity)?.let { activity -> windowLayoutInfo(activity) }
71         return windowLayoutInfoFlow
72             ?: throw NotImplementedError(
73                 message = "Must override windowLayoutInfo(context) and provide an implementation."
74             )
75     }
76 
77     /**
78      * A [Flow] of [WindowLayoutInfo] that contains all the available features. A [WindowLayoutInfo]
79      * contains a [List] of [DisplayFeature] that intersect the associated [android.view.Window].
80      *
81      * The first [WindowLayoutInfo] will not be emitted until [Activity.onStart] has been called.
82      * which values you receive and when is device dependent.
83      *
84      * It is recommended to test the following scenarios since behaviors may differ between hardware
85      * implementations:
86      * * Values are emitted immediately after subscribing to this function.
87      * * There is a long delay between subscribing and receiving the first value.
88      * * Never receiving a value after subscription.
89      *
90      * Since the information is associated to the [Activity] you should not retain the [Flow] across
91      * [Activity] recreations. Doing so can result in unpredictable behavior such as a memory leak
92      * or incorrect values for [WindowLayoutInfo].
93      *
94      * @param activity an [Activity] that has not been destroyed.
95      * @see WindowLayoutInfo
96      * @see DisplayFeature
97      */
98     fun windowLayoutInfo(activity: Activity): Flow<WindowLayoutInfo>
99 
100     /**
101      * Returns the [List] of [SupportedPosture] values. This value will not change during runtime.
102      * These values are for determining if the device supports the given [SupportedPosture] but does
103      * not mean the device is in the given [SupportedPosture]. Use [windowLayoutInfo] to determine
104      * the current state of the [DisplayFeature]'s on the device.
105      *
106      * @throws UnsupportedOperationException if [WindowSdkExtensions.extensionVersion] is less
107      *   than 6.
108      * @throws NotImplementedError if a derived test class does not override this method.
109      * @see windowLayoutInfo
110      */
111     @RequiresWindowSdkExtension(version = 6)
112     @get:RequiresWindowSdkExtension(version = 6)
113     val supportedPostures: List<SupportedPosture>
114         get() {
115             throw NotImplementedError("Method was not implemented.")
116         }
117 
118     /**
119      * Returns the current [WindowLayoutInfo] for the given [context].
120      *
121      * This API provides a convenient way to access the current [WindowLayoutInfo]. It can be used
122      * after the [context] associated window has been created, such as [Activity.onCreate]. Calling
123      * it before that will return an empty info.
124      *
125      * For apps that need to update layout UI based on the [WindowLayoutInfo], it should also listen
126      * to [windowLayoutInfo] for any changes later.
127      *
128      * @param context a [UiContext] such as an [Activity], an [InputMethodService], or an instance
129      *   created via [Context.createWindowContext] that listens to configuration changes.
130      * @return the current [WindowLayoutInfo] for the given [context]. If the [context] is not
131      *   associated with a window, returns an empty [WindowLayoutInfo].
132      * @throws UnsupportedOperationException if [WindowSdkExtensions.extensionVersion] is less
133      *   than 9.
134      * @throws IllegalArgumentException when [context] is not an [UiContext].
135      * @throws NotImplementedError when this method has no supporting implementation.
136      */
137     @RequiresWindowSdkExtension(version = 9)
138     fun getCurrentWindowLayoutInfo(@UiContext context: Context): WindowLayoutInfo =
139         throw NotImplementedError("Method was not implemented.")
140 
141     companion object {
142 
143         private val DEBUG = false
144         private val TAG = WindowInfoTracker::class.simpleName
145 
146         @Suppress("MemberVisibilityCanBePrivate") // Avoid synthetic accessor
147         internal val extensionBackend: WindowBackend? by lazy {
148             try {
149                 val loader = WindowInfoTracker::class.java.classLoader
150                 val provider =
151                     loader?.let {
152                         SafeWindowLayoutComponentProvider(loader, ConsumerAdapter(loader))
153                     }
154                 provider?.windowLayoutComponent?.let { component ->
155                     ExtensionWindowBackend.newInstance(component, ConsumerAdapter(loader))
156                 }
157             } catch (t: Throwable) {
158                 if (DEBUG) {
159                     Log.d(TAG, "Failed to load WindowExtensions")
160                 }
161                 null
162             }
163         }
164 
165         private var decorator: WindowInfoTrackerDecorator = EmptyDecorator
166 
167         /**
168          * Provide an instance of [WindowInfoTracker] that is associated to the given [Context]. The
169          * instance created should be safe to retain globally. The actual data is provided by
170          * [WindowInfoTracker.windowLayoutInfo] and requires an [Activity].
171          *
172          * @param context Any valid [Context].
173          * @see WindowInfoTracker.windowLayoutInfo
174          */
175         @JvmName("getOrCreate")
176         @JvmStatic
177         fun getOrCreate(context: Context): WindowInfoTracker {
178             val backend = extensionBackend ?: SidecarWindowBackend.getInstance(context)
179             val repo =
180                 WindowInfoTrackerImpl(
181                     WindowMetricsCalculatorCompat(),
182                     backend,
183                     WindowSdkExtensions.getInstance()
184                 )
185             return decorator.decorate(repo)
186         }
187 
188         @JvmStatic
189         @RestrictTo(LIBRARY_GROUP)
190         fun overrideDecorator(overridingDecorator: WindowInfoTrackerDecorator) {
191             decorator = overridingDecorator
192         }
193 
194         @JvmStatic
195         @RestrictTo(LIBRARY_GROUP)
196         fun reset() {
197             decorator = EmptyDecorator
198         }
199     }
200 }
201 
202 @RestrictTo(LIBRARY_GROUP)
203 interface WindowInfoTrackerDecorator {
204     /** Returns an instance of [WindowInfoTracker] associated to the [Activity] */
decoratenull205     @RestrictTo(LIBRARY_GROUP) fun decorate(tracker: WindowInfoTracker): WindowInfoTracker
206 }
207 
208 private object EmptyDecorator : WindowInfoTrackerDecorator {
209     override fun decorate(tracker: WindowInfoTracker): WindowInfoTracker {
210         return tracker
211     }
212 }
213