1 /* 2 * 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.area 18 19 import android.app.Activity 20 import android.os.Binder 21 import android.os.Build 22 import android.util.Log 23 import androidx.annotation.RestrictTo 24 import androidx.window.WindowSdkExtensions 25 import androidx.window.area.WindowAreaInfo.Type.Companion.TYPE_REAR_FACING 26 import androidx.window.core.BuildConfig 27 import androidx.window.core.ExperimentalWindowApi 28 import androidx.window.core.ExtensionsUtil 29 import androidx.window.core.VerificationMode 30 import java.util.concurrent.Executor 31 import kotlinx.coroutines.flow.Flow 32 33 /** 34 * An interface to provide the information and behavior around moving windows between displays or 35 * display areas on a device. 36 */ 37 @ExperimentalWindowApi 38 abstract class WindowAreaController @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) constructor() { 39 40 /** 41 * [Flow] of the list of current [WindowAreaInfo]s that are currently available to be interacted 42 * with. 43 * 44 * If [WindowSdkExtensions.extensionVersion] is less than 2, the flow will return empty 45 * [WindowAreaInfo] list flow. 46 */ 47 abstract val windowAreaInfos: Flow<List<WindowAreaInfo>> 48 49 /** 50 * Starts a transfer session where the calling [Activity] is moved to the window area identified 51 * by the [token]. Updates on the session are provided through the [WindowAreaSessionCallback]. 52 * Attempting to start a transfer session when the [WindowAreaInfo] does not return 53 * [WindowAreaCapability.Status.WINDOW_AREA_STATUS_AVAILABLE] will result in 54 * [WindowAreaSessionCallback.onSessionEnded] containing an [IllegalStateException] 55 * 56 * Only the top visible application can request to start a transfer session. 57 * 58 * The calling [Activity] will likely go through a configuration change since the window area it 59 * will be transferred to is usually different from the current area the [Activity] is in. The 60 * callback is retained during the lifetime of the session. If an [Activity] is captured in the 61 * callback and it does not handle the configuration change then it will be leaked. Consider 62 * using an [androidx.lifecycle.ViewModel] since that is meant to outlive the [Activity] 63 * lifecycle. If the [Activity] does override configuration changes, it is safe to have the 64 * [Activity] handle the WindowAreaSessionCallback. This guarantees that the calling [Activity] 65 * will continue to receive [WindowAreaSessionCallback.onSessionEnded] and keep a handle to the 66 * [WindowAreaSession] provided through [WindowAreaSessionCallback.onSessionStarted]. 67 * 68 * The [windowAreaSessionCallback] provided will receive a call to 69 * [WindowAreaSessionCallback.onSessionStarted] after the [Activity] has been transferred to the 70 * window area. The transfer session will stay active until the session provided through 71 * [WindowAreaSessionCallback.onSessionStarted] is closed. Depending on the 72 * [WindowAreaInfo.Type] there may be other triggers that end the session, such as if a device 73 * state change makes the window area unavailable. One example of this is if the [Activity] is 74 * currently transferred to the [TYPE_REAR_FACING] window area of a foldable device, the session 75 * will be ended when the device is closed. When this occurs, 76 * [WindowAreaSessionCallback.onSessionEnded] is called. 77 * 78 * @param token [Binder] token identifying the window area to be transferred to. 79 * @param activity Base Activity making the call to [transferActivityToWindowArea]. 80 * @param executor Executor used to provide updates to [windowAreaSessionCallback]. 81 * @param windowAreaSessionCallback to be notified when the rear display session is started and 82 * ended. 83 * @see windowAreaInfos 84 */ transferActivityToWindowAreanull85 abstract fun transferActivityToWindowArea( 86 token: Binder, 87 activity: Activity, 88 executor: Executor, 89 // TODO(272064992) investigate how to make this safer from leaks 90 windowAreaSessionCallback: WindowAreaSessionCallback 91 ) 92 93 /** 94 * Starts a presentation session on the [WindowAreaInfo] identified by the [token] and sends 95 * updates through the [WindowAreaPresentationSessionCallback]. 96 * 97 * If a presentation session is attempted to be started without it being available, 98 * [WindowAreaPresentationSessionCallback.onSessionEnded] will be called immediately with an 99 * [IllegalStateException]. 100 * 101 * Only the top visible application can request to start a presentation session. 102 * 103 * The presentation session will stay active until the presentation provided through 104 * [WindowAreaPresentationSessionCallback.onSessionStarted] is closed. The [WindowAreaInfo.Type] 105 * may provide different triggers to close the session such as if the calling application is no 106 * longer in the foreground, or there is a device state change that makes the window area 107 * unavailable to be presented on. One example scenario is if a [TYPE_REAR_FACING] window area 108 * is being presented to on a foldable device that is open and has 2 screens. If the device is 109 * closed and the internal display is turned off, the session would be ended and 110 * [WindowAreaPresentationSessionCallback.onSessionEnded] is called to notify that the session 111 * has been ended. The session may end prematurely if the device gets to a critical thermal 112 * level, or if power saver mode is enabled. 113 * 114 * @param token [Binder] token to identify which [WindowAreaInfo] is to be presented on 115 * @param activity An [Activity] that will present content on the Rear Display. 116 * @param executor Executor used to provide updates to [windowAreaPresentationSessionCallback]. 117 * @param windowAreaPresentationSessionCallback to be notified of updates to the lifecycle of 118 * the currently enabled rear display presentation. 119 * @see windowAreaInfos 120 */ 121 abstract fun presentContentOnWindowArea( 122 token: Binder, 123 activity: Activity, 124 executor: Executor, 125 windowAreaPresentationSessionCallback: WindowAreaPresentationSessionCallback 126 ) 127 128 companion object { 129 130 private val TAG = WindowAreaController::class.simpleName 131 132 private var decorator: WindowAreaControllerDecorator = EmptyDecorator 133 134 /** Provides an instance of [WindowAreaController]. */ 135 @JvmName("getOrCreate") 136 @JvmStatic 137 fun getOrCreate(): WindowAreaController { 138 val windowAreaComponentExtensions = 139 try { 140 this::class.java.classLoader?.let { 141 SafeWindowAreaComponentProvider(it).windowAreaComponent 142 } 143 } catch (t: Throwable) { 144 if (BuildConfig.verificationMode == VerificationMode.LOG) { 145 Log.d(TAG, "Failed to load WindowExtensions") 146 } 147 null 148 } 149 150 val deviceSupported = 151 Build.VERSION.SDK_INT > Build.VERSION_CODES.Q && 152 windowAreaComponentExtensions != null && 153 ExtensionsUtil.safeVendorApiLevel >= 3 154 155 val controller = 156 if (deviceSupported) { 157 WindowAreaControllerImpl( 158 windowAreaComponent = windowAreaComponentExtensions!!, 159 ) 160 } else { 161 EmptyWindowAreaControllerImpl() 162 } 163 return decorator.decorate(controller) 164 } 165 166 @JvmStatic 167 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) 168 fun overrideDecorator(overridingDecorator: WindowAreaControllerDecorator) { 169 decorator = overridingDecorator 170 } 171 172 @JvmStatic 173 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) 174 fun reset() { 175 decorator = EmptyDecorator 176 } 177 } 178 } 179 180 /** Decorator that allows us to provide different functionality in our window-testing artifact. */ 181 @ExperimentalWindowApi 182 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) 183 interface WindowAreaControllerDecorator { 184 /** Returns an instance of [WindowAreaController] associated to the [Activity] */ 185 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) decoratenull186 public fun decorate(controller: WindowAreaController): WindowAreaController 187 } 188 189 @ExperimentalWindowApi 190 private object EmptyDecorator : WindowAreaControllerDecorator { 191 override fun decorate(controller: WindowAreaController): WindowAreaController { 192 return controller 193 } 194 } 195