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