From e3832a84e2c59f734ce969da380cdba830c2e7aa Mon Sep 17 00:00:00 2001 From: Kenneth Ford Date: Thu, 6 Jul 2023 21:44:41 +0000 Subject: [PATCH 01/12] Update rear display mode API's Removes session-based API for rear display mode to better reflect that the entire device is moving to the rear facing display, as well as how it is sticky even if the app that requests the move is backgrounded or killed. This CL also updates the presentContentOnWindowArea method to take a WindowAreaToken object instead of a Binder token Moves the `WindowAreaInfo#getActiveSession` API to be more correctly on the `WindowAreaController`. This is a breaking CL in the attempt to stabilize and remove the experimental tag on the Window Area API's. Relnote: "Renamed transferActivityToWindowArea to transferToWindowArea to be clearer about the behavior when triggering the feature. This also removes the session based behavior for this method as it's an operation that outlives the process that requests it." Bug: 271644150 Fixes: 273807246 Fixes: 272064992 Fixes: 473870239 Test: WindowAreaControllerImplTest Change-Id: Ie7d6fa7258d323363748e25fade8d658c1f0f510 --- .../area/RearDisplayActivityConfigChanges.kt | 166 ++++------ .../area/RearDisplayPresentationActivity.kt | 85 +++-- .../main/res/layout/activity_rear_display.xml | 11 - window/window-java/api/current.txt | 15 +- window/window-java/api/restricted_current.txt | 15 +- .../WindowAreaControllerCallbackAdapter.kt | 62 ++-- window/window-testing/api/current.txt | 13 + .../window-testing/api/restricted_current.txt | 13 + .../window/testing/area/WindowAreaTesting.kt | 44 +++ window/window/api/current.txt | 84 +++-- window/window/api/restricted_current.txt | 84 +++-- .../area/WindowAreaControllerImplTest.kt | 309 ++++++++---------- .../window/area/WindowAreaControllerTest.kt | 34 +- .../area/EmptyWindowAreaControllerImpl.kt | 33 +- .../window/area/RearDisplaySessionImpl.kt | 29 -- .../java/androidx/window/area/WindowArea.kt | 101 ++++++ .../window/area/WindowAreaCapability.kt | 77 +++-- .../window/area/WindowAreaController.kt | 136 ++++---- .../window/area/WindowAreaControllerImpl.kt | 286 +++++++++------- .../androidx/window/area/WindowAreaInfo.kt | 133 -------- .../androidx/window/area/WindowAreaSession.kt | 31 -- .../window/area/WindowAreaSessionCallback.kt | 43 --- .../window/area/WindowAreaSessionPresenter.kt | 5 +- .../androidx/window/area/WindowAreaToken.kt | 45 +++ .../window/area/adapter/WindowAreaAdapter.kt | 2 - .../area/adapter/WindowAreaAdapterApi3.kt | 2 - .../area/adapter/WindowAreaAdapterApi4.kt | 2 - 27 files changed, 925 insertions(+), 935 deletions(-) create mode 100644 window/window-testing/src/main/java/androidx/window/testing/area/WindowAreaTesting.kt delete mode 100644 window/window/src/main/java/androidx/window/area/RearDisplaySessionImpl.kt create mode 100644 window/window/src/main/java/androidx/window/area/WindowArea.kt delete mode 100644 window/window/src/main/java/androidx/window/area/WindowAreaInfo.kt delete mode 100644 window/window/src/main/java/androidx/window/area/WindowAreaSession.kt delete mode 100644 window/window/src/main/java/androidx/window/area/WindowAreaSessionCallback.kt create mode 100644 window/window/src/main/java/androidx/window/area/WindowAreaToken.kt diff --git a/window/window-demos/demo/src/main/java/androidx/window/demo/area/RearDisplayActivityConfigChanges.kt b/window/window-demos/demo/src/main/java/androidx/window/demo/area/RearDisplayActivityConfigChanges.kt index 8bfc49d7a804e..7b3bf88aa875a 100644 --- a/window/window-demos/demo/src/main/java/androidx/window/demo/area/RearDisplayActivityConfigChanges.kt +++ b/window/window-demos/demo/src/main/java/androidx/window/demo/area/RearDisplayActivityConfigChanges.kt @@ -18,17 +18,10 @@ package androidx.window.demo.area import android.os.Bundle import androidx.core.content.ContextCompat -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.lifecycleScope -import androidx.lifecycle.repeatOnLifecycle -import androidx.window.area.WindowAreaCapability.Operation.Companion.OPERATION_TRANSFER_ACTIVITY_TO_AREA -import androidx.window.area.WindowAreaCapability.Status.Companion.WINDOW_AREA_STATUS_AVAILABLE -import androidx.window.area.WindowAreaCapability.Status.Companion.WINDOW_AREA_STATUS_UNAVAILABLE -import androidx.window.area.WindowAreaCapability.Status.Companion.WINDOW_AREA_STATUS_UNSUPPORTED +import androidx.core.util.Consumer +import androidx.window.area.WindowArea +import androidx.window.area.WindowAreaCapability import androidx.window.area.WindowAreaController -import androidx.window.area.WindowAreaInfo -import androidx.window.area.WindowAreaSession -import androidx.window.area.WindowAreaSessionCallback import androidx.window.core.ExperimentalWindowApi import androidx.window.demo.common.EdgeToEdgeActivity import androidx.window.demo.common.infolog.InfoLogAdapter @@ -37,24 +30,36 @@ import java.text.SimpleDateFormat import java.util.Date import java.util.Locale import java.util.concurrent.Executor -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch /** * Demo Activity that showcases listening for RearDisplay Status as well as enabling/disabling - * RearDisplay mode. This Activity implements [WindowAreaSessionCallback] for simplicity. - * - * This Activity overrides configuration changes for simplicity. + * RearDisplay mode. */ @OptIn(ExperimentalWindowApi::class) -class RearDisplayActivityConfigChanges : EdgeToEdgeActivity(), WindowAreaSessionCallback { +class RearDisplayActivityConfigChanges : EdgeToEdgeActivity() { private lateinit var windowAreaController: WindowAreaController - private var rearDisplaySession: WindowAreaSession? = null + private var rearDisplayWindowArea: WindowArea? = null + private var rearDisplayStatus: WindowAreaCapability.Status = + WindowAreaCapability.Status.WINDOW_AREA_STATUS_UNSUPPORTED private val infoLogAdapter = InfoLogAdapter() private lateinit var binding: ActivityRearDisplayBinding private lateinit var executor: Executor - private var currentWindowAreaInfo: WindowAreaInfo? = null + + private val windowAreaListener = + Consumer> { windowAreas -> + for (windowArea in windowAreas) { + if (windowArea.type == WindowArea.Type.TYPE_REAR_FACING) { + rearDisplayWindowArea = windowArea + break + } + } + val status = getRearDisplayStatus(rearDisplayWindowArea) + infoLogAdapter.append(getCurrentTimeString(), status.toString()) + infoLogAdapter.notifyDataSetChanged() + rearDisplayStatus = status + updateRearDisplayButton() + } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -66,104 +71,49 @@ class RearDisplayActivityConfigChanges : EdgeToEdgeActivity(), WindowAreaSession binding.rearStatusRecyclerView.adapter = infoLogAdapter - lifecycleScope.launch(Dispatchers.Main) { - // The block passed to repeatOnLifecycle is executed when the lifecycle - // is at least STARTED and is cancelled when the lifecycle is STOPPED. - // It automatically restarts the block when the lifecycle is STARTED again. - lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { - // Safely collect from windowInfoRepo when the lifecycle is STARTED - // and stops collection when the lifecycle is STOPPED - windowAreaController.windowAreaInfos.collect { windowAreaInfos -> - infoLogAdapter.appendAndNotify( - getCurrentTimeString(), - "number of areas: " + windowAreaInfos.size, - ) - windowAreaInfos.forEach { windowAreaInfo -> - if (windowAreaInfo.type == WindowAreaInfo.Type.TYPE_REAR_FACING) { - currentWindowAreaInfo = windowAreaInfo - val transferCapability = - windowAreaInfo.getCapability(OPERATION_TRANSFER_ACTIVITY_TO_AREA) - infoLogAdapter.append( - getCurrentTimeString(), - transferCapability.status.toString() + - " : " + - windowAreaInfo.metrics.toString(), - ) - updateRearDisplayButton() - } - } - infoLogAdapter.notifyDataSetChanged() - } - } - } - binding.rearDisplayButton.setOnClickListener { - if (rearDisplaySession != null) { - rearDisplaySession?.close() - } else { - currentWindowAreaInfo?.let { - windowAreaController.transferActivityToWindowArea( - it.token, - this, - executor, - this, + if (rearDisplayStatus == WindowAreaCapability.Status.WINDOW_AREA_STATUS_ACTIVE) { + WindowAreaController.getOrCreate() + .transferToWindowArea( + windowAreaToken = null, + activity = this@RearDisplayActivityConfigChanges, ) - } - } - } - - binding.rearDisplaySessionButton.setOnClickListener { - if (rearDisplaySession == null) { - try { - rearDisplaySession = - currentWindowAreaInfo?.getActiveSession(OPERATION_TRANSFER_ACTIVITY_TO_AREA) - updateRearDisplayButton() - } catch (e: IllegalStateException) { - infoLogAdapter.appendAndNotify(getCurrentTimeString(), e.toString()) + } else { + rearDisplayWindowArea?.let { windowArea -> + WindowAreaController.getOrCreate() + .transferToWindowArea(windowAreaToken = windowArea.token, activity = this) } } } } - override fun onSessionStarted(session: WindowAreaSession) { - rearDisplaySession = session - infoLogAdapter.appendAndNotify( - getCurrentTimeString(), - "RearDisplay Session has been started", - ) - updateRearDisplayButton() + override fun onStart() { + super.onStart() + windowAreaController.addWindowAreasListener(executor, windowAreaListener) } - override fun onSessionEnded(t: Throwable?) { - rearDisplaySession = null - infoLogAdapter.appendAndNotify(getCurrentTimeString(), "RearDisplay Session has ended") - updateRearDisplayButton() + override fun onStop() { + super.onStop() + windowAreaController.removeWindowAreasListener(windowAreaListener) } private fun updateRearDisplayButton() { - if (rearDisplaySession != null) { - binding.rearDisplayButton.isEnabled = true - binding.rearDisplayButton.text = "Disable RearDisplay Mode" - return - } - currentWindowAreaInfo?.let { windowAreaInfo -> - when (windowAreaInfo.getCapability(OPERATION_TRANSFER_ACTIVITY_TO_AREA).status) { - WINDOW_AREA_STATUS_UNSUPPORTED -> { - binding.rearDisplayButton.isEnabled = false - binding.rearDisplayButton.text = "RearDisplay is not supported on this device" - } - WINDOW_AREA_STATUS_UNAVAILABLE -> { - binding.rearDisplayButton.isEnabled = false - binding.rearDisplayButton.text = "RearDisplay is not currently available" - } - WINDOW_AREA_STATUS_AVAILABLE -> { - binding.rearDisplayButton.isEnabled = true - binding.rearDisplayButton.text = "Enable RearDisplay Mode" - } - else -> { - binding.rearDisplayButton.isEnabled = false - binding.rearDisplayButton.text = "RearDisplay is not supported on this device" - } + when (rearDisplayStatus) { + WindowAreaCapability.Status.WINDOW_AREA_STATUS_UNSUPPORTED -> { + binding.rearDisplayButton.isEnabled = false + binding.rearDisplayButton.text = "RearDisplay is not supported on this device" + } + WindowAreaCapability.Status.WINDOW_AREA_STATUS_UNAVAILABLE -> { + binding.rearDisplayButton.isEnabled = false + binding.rearDisplayButton.text = "RearDisplay is not currently available" + } + WindowAreaCapability.Status.WINDOW_AREA_STATUS_AVAILABLE -> { + binding.rearDisplayButton.isEnabled = true + binding.rearDisplayButton.text = "Enable RearDisplay Mode" + } + WindowAreaCapability.Status.WINDOW_AREA_STATUS_ACTIVE -> { + binding.rearDisplayButton.isEnabled = true + binding.rearDisplayButton.text = "Disable RearDisplay Mode" } } } @@ -174,6 +124,14 @@ class RearDisplayActivityConfigChanges : EdgeToEdgeActivity(), WindowAreaSession return currentDate.toString() } + private fun getRearDisplayStatus(windowArea: WindowArea?): WindowAreaCapability.Status { + val status = + windowArea + ?.getCapability(WindowAreaCapability.Operation.OPERATION_TRANSFER_TO_AREA) + ?.status + return status ?: WindowAreaCapability.Status.WINDOW_AREA_STATUS_UNSUPPORTED + } + private companion object { private val TAG = RearDisplayActivityConfigChanges::class.java.simpleName } diff --git a/window/window-demos/demo/src/main/java/androidx/window/demo/area/RearDisplayPresentationActivity.kt b/window/window-demos/demo/src/main/java/androidx/window/demo/area/RearDisplayPresentationActivity.kt index d59f57e09dd4a..f20166c28f558 100644 --- a/window/window-demos/demo/src/main/java/androidx/window/demo/area/RearDisplayPresentationActivity.kt +++ b/window/window-demos/demo/src/main/java/androidx/window/demo/area/RearDisplayPresentationActivity.kt @@ -19,14 +19,12 @@ package androidx.window.demo.area import android.content.Context import android.os.Bundle import android.view.LayoutInflater -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.lifecycleScope -import androidx.lifecycle.repeatOnLifecycle +import androidx.core.util.Consumer +import androidx.window.area.WindowArea +import androidx.window.area.WindowArea.Type.Companion.TYPE_REAR_FACING import androidx.window.area.WindowAreaCapability import androidx.window.area.WindowAreaCapability.Operation.Companion.OPERATION_PRESENT_ON_AREA import androidx.window.area.WindowAreaController -import androidx.window.area.WindowAreaInfo -import androidx.window.area.WindowAreaInfo.Type.Companion.TYPE_REAR_FACING import androidx.window.area.WindowAreaPresentationSessionCallback import androidx.window.area.WindowAreaSessionPresenter import androidx.window.core.ExperimentalWindowApi @@ -37,12 +35,11 @@ import androidx.window.demo.databinding.ActivityRearDisplayPresentationBinding import java.text.SimpleDateFormat import java.util.Date import java.util.Locale -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch +import kotlin.toString /** * Demo Activity that showcases listening for the status of the [OPERATION_PRESENT_ON_AREA] - * operation on a [WindowAreaInfo] of type [TYPE_REAR_FACING] as well as enabling/disabling a + * operation on a [WindowArea] of type [TYPE_REAR_FACING] as well as enabling/disabling a * presentation session on that window area. This Activity implements * [WindowAreaPresentationSessionCallback] for simplicity. * @@ -53,12 +50,35 @@ class RearDisplayPresentationActivity : EdgeToEdgeActivity(), WindowAreaPresentationSessionCallback { private var activePresentation: WindowAreaSessionPresenter? = null - private var currentWindowAreaInfo: WindowAreaInfo? = null + private var currentWindowArea: WindowArea? = null private lateinit var windowAreaController: WindowAreaController private val infoLogAdapter = InfoLogAdapter() private lateinit var binding: ActivityRearDisplayPresentationBinding + private val windowAreaListener = + Consumer> { windowAreas -> + infoLogAdapter.appendAndNotify( + getCurrentTimeString(), + "number of areas: " + windowAreas.size, + ) + + for (windowArea in windowAreas) { + if (windowArea.type == TYPE_REAR_FACING) { + currentWindowArea = windowArea + val presentCapability = windowArea.getCapability(OPERATION_PRESENT_ON_AREA) + infoLogAdapter.append( + getCurrentTimeString(), + presentCapability.status.toString() + + " : " + + windowArea.windowMetrics.toString(), + ) + updateRearDisplayPresentationButton() + } + } + infoLogAdapter.notifyDataSetChanged() + } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -68,44 +88,13 @@ class RearDisplayPresentationActivity : setContentView(binding.root) binding.rearStatusRecyclerView.adapter = infoLogAdapter - lifecycleScope.launch(Dispatchers.Main) { - // The block passed to repeatOnLifecycle is executed when the lifecycle - // is at least STARTED and is cancelled when the lifecycle is STOPPED. - // It automatically restarts the block when the lifecycle is STARTED again. - lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { - // Safely collect from windowInfoRepo when the lifecycle is STARTED - // and stops collection when the lifecycle is STOPPED - windowAreaController.windowAreaInfos.collect { windowAreaInfos -> - infoLogAdapter.appendAndNotify( - getCurrentTimeString(), - "number of areas: " + windowAreaInfos.size, - ) - windowAreaInfos.forEach { windowAreaInfo -> - if (windowAreaInfo.type == TYPE_REAR_FACING) { - currentWindowAreaInfo = windowAreaInfo - val presentCapability = - windowAreaInfo.getCapability(OPERATION_PRESENT_ON_AREA) - infoLogAdapter.append( - getCurrentTimeString(), - presentCapability.status.toString() + - " : " + - windowAreaInfo.metrics.toString(), - ) - updateRearDisplayPresentationButton() - } - } - infoLogAdapter.notifyDataSetChanged() - } - } - } - binding.rearDisplayPresentationButton.setOnClickListener { if (activePresentation != null) { activePresentation?.close() } else { - currentWindowAreaInfo?.let { + currentWindowArea?.let { windowArea -> windowAreaController.presentContentOnWindowArea( - it.token, + windowArea.token, this@RearDisplayPresentationActivity, { obj: Runnable -> obj.run() }, this@RearDisplayPresentationActivity, @@ -115,6 +104,16 @@ class RearDisplayPresentationActivity : } } + override fun onStart() { + super.onStart() + windowAreaController.addWindowAreasListener(Runnable::run, windowAreaListener) + } + + override fun onStop() { + super.onStop() + windowAreaController.removeWindowAreasListener(windowAreaListener) + } + override fun onSessionStarted(session: WindowAreaSessionPresenter) { infoLogAdapter.appendAndNotify( getCurrentTimeString(), @@ -151,7 +150,7 @@ class RearDisplayPresentationActivity : binding.rearDisplayPresentationButton.text = "Disable rear display presentation" return } - when (currentWindowAreaInfo?.getCapability(OPERATION_PRESENT_ON_AREA)?.status) { + when (currentWindowArea?.getCapability(OPERATION_PRESENT_ON_AREA)?.status) { WindowAreaCapability.Status.WINDOW_AREA_STATUS_UNSUPPORTED -> { binding.rearDisplayPresentationButton.isEnabled = false binding.rearDisplayPresentationButton.text = diff --git a/window/window-demos/demo/src/main/res/layout/activity_rear_display.xml b/window/window-demos/demo/src/main/res/layout/activity_rear_display.xml index 9d0c9c34b7140..43bea60147c6a 100644 --- a/window/window-demos/demo/src/main/res/layout/activity_rear_display.xml +++ b/window/window-demos/demo/src/main/res/layout/activity_rear_display.xml @@ -37,17 +37,6 @@ android:layout_marginBottom="32dp" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintBottom_toTopOf="@id/rear_display_session_button" /> - -