Merge "Fix flicker when starting folding" into tm-qpr-dev
diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp
index 3f6046f..53a84bd 100644
--- a/cmds/bootanimation/BootAnimation.cpp
+++ b/cmds/bootanimation/BootAnimation.cpp
@@ -583,6 +583,15 @@
mFlingerSurface = s;
mTargetInset = -1;
+ // Rotate the boot animation according to the value specified in the sysprop
+ // ro.bootanim.set_orientation_<display_id>. Four values are supported: ORIENTATION_0,
+ // ORIENTATION_90, ORIENTATION_180 and ORIENTATION_270.
+ // If the value isn't specified or is ORIENTATION_0, nothing will be changed.
+ // This is needed to support having boot animation in orientations different from the natural
+ // device orientation. For example, on tablets that may want to keep natural orientation
+ // portrait for applications compatibility and to have the boot animation in landscape.
+ rotateAwayFromNaturalOrientationIfNeeded();
+
projectSceneToWindow();
// Register a display event receiver
@@ -596,6 +605,50 @@
return NO_ERROR;
}
+void BootAnimation::rotateAwayFromNaturalOrientationIfNeeded() {
+ const auto orientation = parseOrientationProperty();
+
+ if (orientation == ui::ROTATION_0) {
+ // Do nothing if the sysprop isn't set or is set to ROTATION_0.
+ return;
+ }
+
+ if (orientation == ui::ROTATION_90 || orientation == ui::ROTATION_270) {
+ std::swap(mWidth, mHeight);
+ std::swap(mInitWidth, mInitHeight);
+ mFlingerSurfaceControl->updateDefaultBufferSize(mWidth, mHeight);
+ }
+
+ Rect displayRect(0, 0, mWidth, mHeight);
+ Rect layerStackRect(0, 0, mWidth, mHeight);
+
+ SurfaceComposerClient::Transaction t;
+ t.setDisplayProjection(mDisplayToken, orientation, layerStackRect, displayRect);
+ t.apply();
+}
+
+ui::Rotation BootAnimation::parseOrientationProperty() {
+ const auto displayIds = SurfaceComposerClient::getPhysicalDisplayIds();
+ if (displayIds.size() == 0) {
+ return ui::ROTATION_0;
+ }
+ const auto displayId = displayIds[0];
+ const auto syspropName = [displayId] {
+ std::stringstream ss;
+ ss << "ro.bootanim.set_orientation_" << displayId.value;
+ return ss.str();
+ }();
+ const auto syspropValue = android::base::GetProperty(syspropName, "ORIENTATION_0");
+ if (syspropValue == "ORIENTATION_90") {
+ return ui::ROTATION_90;
+ } else if (syspropValue == "ORIENTATION_180") {
+ return ui::ROTATION_180;
+ } else if (syspropValue == "ORIENTATION_270") {
+ return ui::ROTATION_270;
+ }
+ return ui::ROTATION_0;
+}
+
void BootAnimation::projectSceneToWindow() {
glViewport(0, 0, mWidth, mHeight);
glScissor(0, 0, mWidth, mHeight);
diff --git a/cmds/bootanimation/BootAnimation.h b/cmds/bootanimation/BootAnimation.h
index 8658205..8683b71 100644
--- a/cmds/bootanimation/BootAnimation.h
+++ b/cmds/bootanimation/BootAnimation.h
@@ -30,6 +30,8 @@
#include <utils/Thread.h>
#include <binder/IBinder.h>
+#include <ui/Rotation.h>
+
#include <EGL/egl.h>
#include <GLES2/gl2.h>
@@ -200,6 +202,8 @@
ui::Size limitSurfaceSize(int width, int height) const;
void resizeSurface(int newWidth, int newHeight);
void projectSceneToWindow();
+ void rotateAwayFromNaturalOrientationIfNeeded();
+ ui::Rotation parseOrientationProperty();
bool shouldStopPlayingPart(const Animation::Part& part, int fadedFramesCount,
int lastDisplayedProgress);
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index 162a997..7eacc3c 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -1096,7 +1096,7 @@
}
/**
- * Like {@link #getFastDrawable(int)}, but the returned Drawable has a number
+ * Like {@link #getDrawable(int)}, but the returned Drawable has a number
* of limitations to reduce its overhead as much as possible. It will
* never scale the wallpaper (only centering it if the requested bounds
* do match the bitmap bounds, which should not be typical), doesn't
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index 30b7d25..be99f0f 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -114,7 +114,7 @@
*/
@ChangeId
@Overridable
- @EnabledSince(targetSdkVersion = android.os.Build.VERSION_CODES.TIRAMISU)
+ @EnabledSince(targetSdkVersion = android.os.Build.VERSION_CODES.BASE)
@TestApi
public static final long OVERRIDE_FRONT_CAMERA_APP_COMPAT = 250678880L;
diff --git a/core/java/android/service/wallpaper/OWNERS b/core/java/android/service/wallpaper/OWNERS
index 756eef8..71bd190 100644
--- a/core/java/android/service/wallpaper/OWNERS
+++ b/core/java/android/service/wallpaper/OWNERS
@@ -3,3 +3,6 @@
dupin@google.com
dsandler@android.com
dsandler@google.com
+pomini@google.com
+poultney@google.com
+santie@google.com
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index b0f6ae6..ba274a7 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2331,6 +2331,7 @@
<java-symbol type="drawable" name="scrubber_control_selector_holo" />
<java-symbol type="drawable" name="scrubber_progress_horizontal_holo_dark" />
<java-symbol type="drawable" name="progress_small_material" />
+ <java-symbol type="drawable" name="ic_chevron_end" />
<java-symbol type="string" name="chooseUsbActivity" />
<java-symbol type="string" name="ext_media_badremoval_notification_message" />
<java-symbol type="string" name="ext_media_badremoval_notification_title" />
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index b075b14..3341470 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -17,12 +17,20 @@
package com.android.wm.shell.desktopmode
import android.app.ActivityManager
-import android.app.WindowConfiguration
import android.app.WindowConfiguration.ACTIVITY_TYPE_HOME
+import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
+import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
+import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED
import android.app.WindowConfiguration.WindowingMode
import android.content.Context
-import android.view.WindowManager
+import android.os.IBinder
+import android.view.SurfaceControl
+import android.view.WindowManager.TRANSIT_CHANGE
+import android.view.WindowManager.TRANSIT_OPEN
+import android.view.WindowManager.TRANSIT_TO_FRONT
+import android.window.TransitionInfo
+import android.window.TransitionRequestInfo
import android.window.WindowContainerTransaction
import androidx.annotation.BinderThread
import com.android.internal.protolog.common.ProtoLog
@@ -51,7 +59,7 @@
private val transitions: Transitions,
private val desktopModeTaskRepository: DesktopModeTaskRepository,
@ShellMainThread private val mainExecutor: ShellExecutor
-) : RemoteCallable<DesktopTasksController> {
+) : RemoteCallable<DesktopTasksController>, Transitions.TransitionHandler {
private val desktopMode: DesktopModeImpl
@@ -69,6 +77,7 @@
{ createExternalInterface() },
this
)
+ transitions.addHandler(this)
}
/** Show all tasks, that are part of the desktop, on top of launcher */
@@ -81,7 +90,7 @@
// Execute transaction if there are pending operations
if (!wct.isEmpty) {
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
- transitions.startTransition(WindowManager.TRANSIT_TO_FRONT, wct, null /* handler */)
+ transitions.startTransition(TRANSIT_TO_FRONT, wct, null /* handler */)
} else {
shellTaskOrganizer.applyTransaction(wct)
}
@@ -101,11 +110,11 @@
// Bring other apps to front first
bringDesktopAppsToFront(wct)
- wct.setWindowingMode(task.getToken(), WindowConfiguration.WINDOWING_MODE_FREEFORM)
+ wct.setWindowingMode(task.getToken(), WINDOWING_MODE_FREEFORM)
wct.reorder(task.getToken(), true /* onTop */)
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
- transitions.startTransition(WindowManager.TRANSIT_CHANGE, wct, null /* handler */)
+ transitions.startTransition(TRANSIT_CHANGE, wct, null /* handler */)
} else {
shellTaskOrganizer.applyTransaction(wct)
}
@@ -121,10 +130,10 @@
ProtoLog.v(WM_SHELL_DESKTOP_MODE, "moveToFullscreen: %d", task.taskId)
val wct = WindowContainerTransaction()
- wct.setWindowingMode(task.getToken(), WindowConfiguration.WINDOWING_MODE_FULLSCREEN)
+ wct.setWindowingMode(task.getToken(), WINDOWING_MODE_FULLSCREEN)
wct.setBounds(task.getToken(), null)
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
- transitions.startTransition(WindowManager.TRANSIT_CHANGE, wct, null /* handler */)
+ transitions.startTransition(TRANSIT_CHANGE, wct, null /* handler */)
} else {
shellTaskOrganizer.applyTransaction(wct)
}
@@ -181,6 +190,80 @@
return mainExecutor
}
+ override fun startAnimation(
+ transition: IBinder,
+ info: TransitionInfo,
+ startTransaction: SurfaceControl.Transaction,
+ finishTransaction: SurfaceControl.Transaction,
+ finishCallback: Transitions.TransitionFinishCallback
+ ): Boolean {
+ // This handler should never be the sole handler, so should not animate anything.
+ return false
+ }
+
+ override fun handleRequest(
+ transition: IBinder,
+ request: TransitionRequestInfo
+ ): WindowContainerTransaction? {
+ // Check if we should skip handling this transition
+ val task: ActivityManager.RunningTaskInfo? = request.triggerTask
+ val shouldHandleRequest =
+ when {
+ // Only handle open or to front transitions
+ request.type != TRANSIT_OPEN && request.type != TRANSIT_TO_FRONT -> false
+ // Only handle when it is a task transition
+ task == null -> false
+ // Only handle standard type tasks
+ task.activityType != ACTIVITY_TYPE_STANDARD -> false
+ // Only handle fullscreen or freeform tasks
+ task.windowingMode != WINDOWING_MODE_FULLSCREEN &&
+ task.windowingMode != WINDOWING_MODE_FREEFORM -> false
+ // Otherwise process it
+ else -> true
+ }
+
+ if (!shouldHandleRequest) {
+ return null
+ }
+
+ val activeTasks = desktopModeTaskRepository.getActiveTasks()
+
+ // Check if we should switch a fullscreen task to freeform
+ if (task?.windowingMode == WINDOWING_MODE_FULLSCREEN) {
+ // If there are any visible desktop tasks, switch the task to freeform
+ if (activeTasks.any { desktopModeTaskRepository.isVisibleTask(it) }) {
+ ProtoLog.d(
+ WM_SHELL_DESKTOP_MODE,
+ "DesktopTasksController#handleRequest: switch fullscreen task to freeform," +
+ " taskId=%d",
+ task.taskId
+ )
+ return WindowContainerTransaction().apply {
+ setWindowingMode(task.token, WINDOWING_MODE_FREEFORM)
+ }
+ }
+ }
+
+ // CHeck if we should switch a freeform task to fullscreen
+ if (task?.windowingMode == WINDOWING_MODE_FREEFORM) {
+ // If no visible desktop tasks, switch this task to freeform as the transition came
+ // outside of this controller
+ if (activeTasks.none { desktopModeTaskRepository.isVisibleTask(it) }) {
+ ProtoLog.d(
+ WM_SHELL_DESKTOP_MODE,
+ "DesktopTasksController#handleRequest: switch freeform task to fullscreen," +
+ " taskId=%d",
+ task.taskId
+ )
+ return WindowContainerTransaction().apply {
+ setWindowingMode(task.token, WINDOWING_MODE_FULLSCREEN)
+ setBounds(task.token, null)
+ }
+ }
+ }
+ return null
+ }
+
/** Creates a new instance of the external interface to pass to another process. */
private fun createExternalInterface(): ExternalInterfaceBinder {
return IDesktopModeImpl(this)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index de2473b..9a92879 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -17,10 +17,18 @@
package com.android.wm.shell.desktopmode
import android.app.ActivityManager.RunningTaskInfo
+import android.app.WindowConfiguration.ACTIVITY_TYPE_HOME
+import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
+import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW
import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED
+import android.os.Binder
import android.testing.AndroidTestingRunner
+import android.view.WindowManager
+import android.view.WindowManager.TRANSIT_OPEN
+import android.view.WindowManager.TRANSIT_TO_FRONT
+import android.window.TransitionRequestInfo
import android.window.WindowContainerTransaction
import android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER
import androidx.test.filters.SmallTest
@@ -29,6 +37,7 @@
import com.android.dx.mockito.inline.extended.StaticMockitoSession
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.TestRunningTaskInfoBuilder
import com.android.wm.shell.TestShellExecutor
import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFreeformTask
@@ -37,9 +46,11 @@
import com.android.wm.shell.sysui.ShellController
import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.transition.Transitions
+import com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
import org.junit.After
+import org.junit.Assume.assumeTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -79,6 +90,7 @@
desktopModeTaskRepository = DesktopModeTaskRepository()
whenever(shellTaskOrganizer.getRunningTasks(anyInt())).thenAnswer { runningTasks }
+ whenever(transitions.startTransition(anyInt(), any(), isNull())).thenAnswer { Binder() }
controller = createController()
@@ -221,6 +233,114 @@
assertThat(controller.getTaskWindowingMode(999)).isEqualTo(WINDOWING_MODE_UNDEFINED)
}
+ @Test
+ fun handleRequest_fullscreenTask_freeformVisible_returnSwitchToFreeformWCT() {
+ assumeTrue(ENABLE_SHELL_TRANSITIONS)
+
+ val freeformTask = setUpFreeformTask()
+ markTaskVisible(freeformTask)
+ val fullscreenTask = createFullscreenTask()
+
+ val result = controller.handleRequest(Binder(), createTransition(fullscreenTask))
+ assertThat(result?.changes?.get(fullscreenTask.token.asBinder())?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_FREEFORM)
+ }
+
+ @Test
+ fun handleRequest_fullscreenTask_freeformNotVisible_returnNull() {
+ assumeTrue(ENABLE_SHELL_TRANSITIONS)
+
+ val freeformTask = setUpFreeformTask()
+ markTaskHidden(freeformTask)
+ val fullscreenTask = createFullscreenTask()
+ assertThat(controller.handleRequest(Binder(), createTransition(fullscreenTask))).isNull()
+ }
+
+ @Test
+ fun handleRequest_fullscreenTask_noOtherTasks_returnNull() {
+ assumeTrue(ENABLE_SHELL_TRANSITIONS)
+
+ val fullscreenTask = createFullscreenTask()
+ assertThat(controller.handleRequest(Binder(), createTransition(fullscreenTask))).isNull()
+ }
+
+ @Test
+ fun handleRequest_freeformTask_freeformVisible_returnNull() {
+ assumeTrue(ENABLE_SHELL_TRANSITIONS)
+
+ val freeformTask1 = setUpFreeformTask()
+ markTaskVisible(freeformTask1)
+
+ val freeformTask2 = createFreeformTask()
+ assertThat(controller.handleRequest(Binder(), createTransition(freeformTask2))).isNull()
+ }
+
+ @Test
+ fun handleRequest_freeformTask_freeformNotVisible_returnSwitchToFullscreenWCT() {
+ assumeTrue(ENABLE_SHELL_TRANSITIONS)
+
+ val freeformTask1 = setUpFreeformTask()
+ markTaskHidden(freeformTask1)
+
+ val freeformTask2 = createFreeformTask()
+ val result =
+ controller.handleRequest(
+ Binder(),
+ createTransition(freeformTask2, type = TRANSIT_TO_FRONT)
+ )
+ assertThat(result?.changes?.get(freeformTask2.token.asBinder())?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_FULLSCREEN)
+ }
+
+ @Test
+ fun handleRequest_freeformTask_noOtherTasks_returnSwitchToFullscreenWCT() {
+ assumeTrue(ENABLE_SHELL_TRANSITIONS)
+
+ val task = createFreeformTask()
+ val result = controller.handleRequest(Binder(), createTransition(task))
+ assertThat(result?.changes?.get(task.token.asBinder())?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_FULLSCREEN)
+ }
+
+ @Test
+ fun handleRequest_notOpenOrToFrontTransition_returnNull() {
+ assumeTrue(ENABLE_SHELL_TRANSITIONS)
+
+ val task =
+ TestRunningTaskInfoBuilder()
+ .setActivityType(ACTIVITY_TYPE_STANDARD)
+ .setWindowingMode(WINDOWING_MODE_FULLSCREEN)
+ .build()
+ val transition = createTransition(task = task, type = WindowManager.TRANSIT_CLOSE)
+ val result = controller.handleRequest(Binder(), transition)
+ assertThat(result).isNull()
+ }
+
+ @Test
+ fun handleRequest_noTriggerTask_returnNull() {
+ assumeTrue(ENABLE_SHELL_TRANSITIONS)
+ assertThat(controller.handleRequest(Binder(), createTransition(task = null))).isNull()
+ }
+
+ @Test
+ fun handleRequest_triggerTaskNotStandard_returnNull() {
+ assumeTrue(ENABLE_SHELL_TRANSITIONS)
+ val task = TestRunningTaskInfoBuilder().setActivityType(ACTIVITY_TYPE_HOME).build()
+ assertThat(controller.handleRequest(Binder(), createTransition(task))).isNull()
+ }
+
+ @Test
+ fun handleRequest_triggerTaskNotFullscreenOrFreeform_returnNull() {
+ assumeTrue(ENABLE_SHELL_TRANSITIONS)
+
+ val task =
+ TestRunningTaskInfoBuilder()
+ .setActivityType(ACTIVITY_TYPE_STANDARD)
+ .setWindowingMode(WINDOWING_MODE_MULTI_WINDOW)
+ .build()
+ assertThat(controller.handleRequest(Binder(), createTransition(task))).isNull()
+ }
+
private fun setUpFreeformTask(): RunningTaskInfo {
val task = createFreeformTask()
whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task)
@@ -254,7 +374,7 @@
private fun getLatestWct(): WindowContainerTransaction {
val arg = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
- if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+ if (ENABLE_SHELL_TRANSITIONS) {
verify(transitions).startTransition(anyInt(), arg.capture(), isNull())
} else {
verify(shellTaskOrganizer).applyTransaction(arg.capture())
@@ -263,12 +383,19 @@
}
private fun verifyWCTNotExecuted() {
- if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+ if (ENABLE_SHELL_TRANSITIONS) {
verify(transitions, never()).startTransition(anyInt(), any(), isNull())
} else {
verify(shellTaskOrganizer, never()).applyTransaction(any())
}
}
+
+ private fun createTransition(
+ task: RunningTaskInfo?,
+ @WindowManager.TransitionType type: Int = TRANSIT_OPEN
+ ): TransitionRequestInfo {
+ return TransitionRequestInfo(type, task, null /* remoteTransition */)
+ }
}
private fun WindowContainerTransaction.assertReorderAt(index: Int, task: RunningTaskInfo) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryKey.java b/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryKey.java
index 4da47fd..db224be 100644
--- a/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryKey.java
+++ b/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryKey.java
@@ -66,6 +66,8 @@
"com.android.settings.category.ia.battery_saver_settings";
public static final String CATEGORY_SMART_BATTERY_SETTINGS =
"com.android.settings.category.ia.smart_battery_settings";
+ public static final String CATEGORY_COMMUNAL_SETTINGS =
+ "com.android.settings.category.ia.communal";
public static final Map<String, String> KEY_COMPAT_MAP;
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/CategoryKeyTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/CategoryKeyTest.java
index 340a6c7..c9dc1ba 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/CategoryKeyTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/CategoryKeyTest.java
@@ -60,8 +60,9 @@
allKeys.add(CategoryKey.CATEGORY_GESTURES);
allKeys.add(CategoryKey.CATEGORY_NIGHT_DISPLAY);
allKeys.add(CategoryKey.CATEGORY_SMART_BATTERY_SETTINGS);
+ allKeys.add(CategoryKey.CATEGORY_COMMUNAL_SETTINGS);
// DO NOT REMOVE ANYTHING ABOVE
- assertThat(allKeys.size()).isEqualTo(19);
+ assertThat(allKeys.size()).isEqualTo(20);
}
}
diff --git a/packages/SystemUI/compose/core/src/com/android/systemui/compose/animation/Expandable.kt b/packages/SystemUI/compose/core/src/com/android/systemui/compose/animation/Expandable.kt
index 18534f4..d31ca51 100644
--- a/packages/SystemUI/compose/core/src/com/android/systemui/compose/animation/Expandable.kt
+++ b/packages/SystemUI/compose/core/src/com/android/systemui/compose/animation/Expandable.kt
@@ -24,6 +24,7 @@
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
+import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.defaultMinSize
@@ -62,6 +63,7 @@
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.graphics.drawscope.scale
import androidx.compose.ui.layout.boundsInRoot
+import androidx.compose.ui.layout.findRootCoordinates
import androidx.compose.ui.layout.layout
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.ComposeView
@@ -118,12 +120,14 @@
contentColor: Color = contentColorFor(color),
borderStroke: BorderStroke? = null,
onClick: ((Expandable) -> Unit)? = null,
+ interactionSource: MutableInteractionSource? = null,
content: @Composable (Expandable) -> Unit,
) {
Expandable(
rememberExpandableController(color, shape, contentColor, borderStroke),
modifier,
onClick,
+ interactionSource,
content,
)
}
@@ -158,6 +162,7 @@
controller: ExpandableController,
modifier: Modifier = Modifier,
onClick: ((Expandable) -> Unit)? = null,
+ interactionSource: MutableInteractionSource? = null,
content: @Composable (Expandable) -> Unit,
) {
val controller = controller as ExpandableControllerImpl
@@ -190,6 +195,18 @@
var thisExpandableSize by remember { mutableStateOf(Size.Zero) }
+ /** Set the current element size as this Expandable size. */
+ fun Modifier.updateExpandableSize(): Modifier {
+ return this.onGloballyPositioned { coords ->
+ thisExpandableSize =
+ coords
+ .findRootCoordinates()
+ // Make sure that we report the actual size, and not the visual/clipped one.
+ .localBoundingBoxOf(coords, clipBounds = false)
+ .size
+ }
+ }
+
// Make sure we don't read animatorState directly here to avoid recomposition every time the
// state changes (i.e. every frame of the animation).
val isAnimating by remember {
@@ -247,7 +264,7 @@
controller.isDialogShowing.value -> {
Box(
modifier
- .onGloballyPositioned { thisExpandableSize = it.boundsInRoot().size }
+ .updateExpandableSize()
.then(minInteractiveSizeModifier)
.drawWithContent { /* Don't draw anything when the dialog is shown. */}
.onGloballyPositioned {
@@ -258,18 +275,25 @@
else -> {
val clickModifier =
if (onClick != null) {
- Modifier.clickable { onClick(controller.expandable) }
+ if (interactionSource != null) {
+ // If the caller provided an interaction source, then that means that they
+ // will draw the click indication themselves.
+ Modifier.clickable(interactionSource, indication = null) {
+ onClick(controller.expandable)
+ }
+ } else {
+ // If no interaction source is provided, we draw the default indication (a
+ // ripple) and make sure it's clipped by the expandable shape.
+ Modifier.clip(shape).clickable { onClick(controller.expandable) }
+ }
} else {
Modifier
}
Box(
modifier
- .onGloballyPositioned { thisExpandableSize = it.boundsInRoot().size }
+ .updateExpandableSize()
.then(minInteractiveSizeModifier)
- // Note that clip() *must* be above the clickModifier to properly clip the
- // ripple.
- .clip(shape)
.then(clickModifier)
.background(color, shape)
.border(controller)
diff --git a/packages/SystemUI/compose/core/src/com/android/systemui/compose/theme/AndroidColorScheme.kt b/packages/SystemUI/compose/core/src/com/android/systemui/compose/theme/AndroidColorScheme.kt
index b8639e6..caa7e5f 100644
--- a/packages/SystemUI/compose/core/src/com/android/systemui/compose/theme/AndroidColorScheme.kt
+++ b/packages/SystemUI/compose/core/src/com/android/systemui/compose/theme/AndroidColorScheme.kt
@@ -65,10 +65,12 @@
val colorForeground = getColor(context, R.attr.colorForeground)
val colorForegroundInverse = getColor(context, R.attr.colorForegroundInverse)
- private fun getColor(context: Context, attr: Int): Color {
- val ta = context.obtainStyledAttributes(intArrayOf(attr))
- @ColorInt val color = ta.getColor(0, 0)
- ta.recycle()
- return Color(color)
+ companion object {
+ fun getColor(context: Context, attr: Int): Color {
+ val ta = context.obtainStyledAttributes(intArrayOf(attr))
+ @ColorInt val color = ta.getColor(0, 0)
+ ta.recycle()
+ return Color(color)
+ }
}
}
diff --git a/packages/SystemUI/compose/core/src/com/android/systemui/compose/theme/Color.kt b/packages/SystemUI/compose/core/src/com/android/systemui/compose/theme/Color.kt
new file mode 100644
index 0000000..de47cce
--- /dev/null
+++ b/packages/SystemUI/compose/core/src/com/android/systemui/compose/theme/Color.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.compose.theme
+
+import android.annotation.AttrRes
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.ReadOnlyComposable
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.LocalContext
+
+/** Read the [Color] from the given [attribute]. */
+@Composable
+@ReadOnlyComposable
+fun colorAttr(@AttrRes attribute: Int): Color {
+ return AndroidColorScheme.getColor(LocalContext.current, attribute)
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/ContentDescription.kt b/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/ContentDescription.kt
new file mode 100644
index 0000000..4a5ad65
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/ContentDescription.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.common.ui.compose
+
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.res.stringResource
+import com.android.systemui.common.shared.model.ContentDescription
+
+/** Returns the loaded [String] or `null` if there isn't one. */
+@Composable
+fun ContentDescription.load(): String? {
+ return when (this) {
+ is ContentDescription.Loaded -> description
+ is ContentDescription.Resource -> stringResource(res)
+ }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/Icon.kt b/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/Icon.kt
new file mode 100644
index 0000000..6e83124
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/Icon.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.common.ui.compose
+
+import androidx.compose.material3.Icon
+import androidx.compose.material3.LocalContentColor
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.asImageBitmap
+import androidx.compose.ui.res.painterResource
+import androidx.core.graphics.drawable.toBitmap
+import com.android.systemui.common.shared.model.Icon
+
+/**
+ * Icon composable that draws [icon] using [tint].
+ *
+ * Note: You can use [Color.Unspecified] to disable the tint and keep the original icon colors.
+ */
+@Composable
+fun Icon(
+ icon: Icon,
+ modifier: Modifier = Modifier,
+ tint: Color = LocalContentColor.current,
+) {
+ val contentDescription = icon.contentDescription?.load()
+ when (icon) {
+ is Icon.Loaded -> {
+ Icon(icon.drawable.toBitmap().asImageBitmap(), contentDescription, modifier, tint)
+ }
+ is Icon.Resource -> Icon(painterResource(icon.res), contentDescription, modifier, tint)
+ }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreen.kt b/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreen.kt
index 2bf1937..2aac46e 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreen.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreen.kt
@@ -53,7 +53,6 @@
import com.android.systemui.compose.theme.LocalAndroidColorScheme
import com.android.systemui.people.ui.viewmodel.PeopleTileViewModel
import com.android.systemui.people.ui.viewmodel.PeopleViewModel
-import kotlinx.coroutines.flow.collect
/**
* Compose the screen associated to a [PeopleViewModel].
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt
new file mode 100644
index 0000000..654b723
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt
@@ -0,0 +1,361 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.footer.ui.compose
+
+import androidx.compose.foundation.BorderStroke
+import androidx.compose.foundation.Canvas
+import androidx.compose.foundation.LocalIndication
+import androidx.compose.foundation.indication
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.RowScope
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.Icon
+import androidx.compose.material3.LocalContentColor
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.draw.drawWithContent
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.layout.layout
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.dimensionResource
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.semantics.contentDescription
+import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.unit.constrainHeight
+import androidx.compose.ui.unit.constrainWidth
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.em
+import androidx.compose.ui.unit.sp
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.R
+import com.android.systemui.animation.Expandable
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.common.ui.compose.Icon
+import com.android.systemui.compose.animation.Expandable
+import com.android.systemui.compose.modifiers.background
+import com.android.systemui.compose.theme.LocalAndroidColorScheme
+import com.android.systemui.compose.theme.colorAttr
+import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsButtonViewModel
+import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsForegroundServicesButtonViewModel
+import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsSecurityButtonViewModel
+import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
+import kotlinx.coroutines.launch
+
+/** The Quick Settings footer actions row. */
+@Composable
+fun FooterActions(
+ viewModel: FooterActionsViewModel,
+ qsVisibilityLifecycleOwner: LifecycleOwner,
+ modifier: Modifier = Modifier,
+) {
+ val context = LocalContext.current
+
+ // Collect visibility and alphas as soon as we are composed, even when not visible.
+ val isVisible by viewModel.isVisible.collectAsState()
+ val alpha by viewModel.alpha.collectAsState()
+ val backgroundAlpha = viewModel.backgroundAlpha.collectAsState()
+
+ var security by remember { mutableStateOf<FooterActionsSecurityButtonViewModel?>(null) }
+ var foregroundServices by remember {
+ mutableStateOf<FooterActionsForegroundServicesButtonViewModel?>(null)
+ }
+ var userSwitcher by remember { mutableStateOf<FooterActionsButtonViewModel?>(null) }
+
+ LaunchedEffect(
+ context,
+ qsVisibilityLifecycleOwner,
+ viewModel,
+ viewModel.security,
+ viewModel.foregroundServices,
+ viewModel.userSwitcher,
+ ) {
+ launch {
+ // Listen for dialog requests as soon as we are composed, even when not visible.
+ viewModel.observeDeviceMonitoringDialogRequests(context)
+ }
+
+ // Listen for model changes only when QS are visible.
+ qsVisibilityLifecycleOwner.repeatOnLifecycle(Lifecycle.State.RESUMED) {
+ launch { viewModel.security.collect { security = it } }
+ launch { viewModel.foregroundServices.collect { foregroundServices = it } }
+ launch { viewModel.userSwitcher.collect { userSwitcher = it } }
+ }
+ }
+
+ val backgroundColor = colorAttr(R.attr.underSurfaceColor)
+ val contentColor = LocalAndroidColorScheme.current.textColorPrimary
+ val backgroundTopRadius = dimensionResource(R.dimen.qs_corner_radius)
+ val backgroundModifier =
+ remember(
+ backgroundColor,
+ backgroundAlpha,
+ backgroundTopRadius,
+ ) {
+ Modifier.background(
+ backgroundColor,
+ backgroundAlpha::value,
+ RoundedCornerShape(topStart = backgroundTopRadius, topEnd = backgroundTopRadius),
+ )
+ }
+
+ Row(
+ modifier
+ .fillMaxWidth()
+ .graphicsLayer { this.alpha = alpha }
+ .drawWithContent {
+ if (isVisible) {
+ drawContent()
+ }
+ }
+ .then(backgroundModifier)
+ .padding(
+ top = dimensionResource(R.dimen.qs_footer_actions_top_padding),
+ bottom = dimensionResource(R.dimen.qs_footer_actions_bottom_padding),
+ )
+ .layout { measurable, constraints ->
+ // All buttons have a 4dp padding to increase their touch size. To be consistent
+ // with the View implementation, we want to left-most and right-most buttons to be
+ // visually aligned with the left and right sides of this row. So we let this
+ // component be 2*4dp wider and then offset it by -4dp to the start.
+ val inset = 4.dp.roundToPx()
+ val additionalWidth = inset * 2
+ val newConstraints =
+ if (constraints.hasBoundedWidth) {
+ constraints.copy(maxWidth = constraints.maxWidth + additionalWidth)
+ } else {
+ constraints
+ }
+ val placeable = measurable.measure(newConstraints)
+
+ val width = constraints.constrainWidth(placeable.width - additionalWidth)
+ val height = constraints.constrainHeight(placeable.height)
+ layout(width, height) { placeable.place(-inset, 0) }
+ },
+ verticalAlignment = Alignment.CenterVertically,
+ ) {
+ CompositionLocalProvider(
+ LocalContentColor provides contentColor,
+ ) {
+ if (security == null && foregroundServices == null) {
+ Spacer(Modifier.weight(1f))
+ }
+
+ security?.let { SecurityButton(it, Modifier.weight(1f)) }
+ foregroundServices?.let { ForegroundServicesButton(it) }
+ userSwitcher?.let { IconButton(it) }
+ IconButton(viewModel.settings)
+ viewModel.power?.let { IconButton(it) }
+ }
+ }
+}
+
+/** The security button. */
+@Composable
+private fun SecurityButton(
+ model: FooterActionsSecurityButtonViewModel,
+ modifier: Modifier = Modifier,
+) {
+ val onClick: ((Expandable) -> Unit)? =
+ model.onClick?.let { onClick ->
+ val context = LocalContext.current
+ { expandable -> onClick(context, expandable) }
+ }
+
+ TextButton(
+ model.icon,
+ model.text,
+ showNewDot = false,
+ onClick = onClick,
+ modifier,
+ )
+}
+
+/** The foreground services button. */
+@Composable
+private fun RowScope.ForegroundServicesButton(
+ model: FooterActionsForegroundServicesButtonViewModel,
+) {
+ if (model.displayText) {
+ TextButton(
+ Icon.Resource(R.drawable.ic_info_outline, contentDescription = null),
+ model.text,
+ showNewDot = model.hasNewChanges,
+ onClick = model.onClick,
+ Modifier.weight(1f),
+ )
+ } else {
+ NumberButton(
+ model.foregroundServicesCount,
+ showNewDot = model.hasNewChanges,
+ onClick = model.onClick,
+ )
+ }
+}
+
+/** A button with an icon. */
+@Composable
+private fun IconButton(
+ model: FooterActionsButtonViewModel,
+ modifier: Modifier = Modifier,
+) {
+ Expandable(
+ color = colorAttr(model.backgroundColor),
+ shape = CircleShape,
+ onClick = model.onClick,
+ modifier = modifier,
+ ) {
+ val tint = model.iconTint?.let { Color(it) } ?: Color.Unspecified
+ Icon(
+ model.icon,
+ tint = tint,
+ modifier = Modifier.size(20.dp),
+ )
+ }
+}
+
+/** A button with a number an an optional dot (to indicate new changes). */
+@Composable
+private fun NumberButton(
+ number: Int,
+ showNewDot: Boolean,
+ onClick: (Expandable) -> Unit,
+ modifier: Modifier = Modifier,
+) {
+ // By default Expandable will show a ripple above its content when clicked, and clip the content
+ // with the shape of the expandable. In this case we also want to show a "new changes dot"
+ // outside of the shape, so we can't clip. To work around that we can pass our own interaction
+ // source and draw the ripple indication ourselves above the text but below the "new changes
+ // dot".
+ val interactionSource = remember { MutableInteractionSource() }
+
+ Expandable(
+ color = colorAttr(R.attr.offStateColor),
+ shape = CircleShape,
+ onClick = onClick,
+ interactionSource = interactionSource,
+ modifier = modifier,
+ ) {
+ Box(Modifier.size(40.dp)) {
+ Box(
+ Modifier.fillMaxSize()
+ .clip(CircleShape)
+ .indication(
+ interactionSource,
+ LocalIndication.current,
+ )
+ ) {
+ Text(
+ number.toString(),
+ modifier = Modifier.align(Alignment.Center),
+ style = MaterialTheme.typography.bodyLarge,
+ color = LocalAndroidColorScheme.current.textColorPrimary,
+ // TODO(b/242040009): This should only use a standard text style instead and
+ // should not override the text size.
+ fontSize = 18.sp,
+ )
+ }
+
+ if (showNewDot) {
+ NewChangesDot(Modifier.align(Alignment.BottomEnd))
+ }
+ }
+ }
+}
+
+/** A dot that indicates new changes. */
+@Composable
+private fun NewChangesDot(modifier: Modifier = Modifier) {
+ val contentDescription = stringResource(R.string.fgs_dot_content_description)
+ val color = LocalAndroidColorScheme.current.colorAccentTertiary
+
+ Canvas(modifier.size(12.dp).semantics { this.contentDescription = contentDescription }) {
+ drawCircle(color)
+ }
+}
+
+/** A larger button with an icon, some text and an optional dot (to indicate new changes). */
+@Composable
+private fun TextButton(
+ icon: Icon,
+ text: String,
+ showNewDot: Boolean,
+ onClick: ((Expandable) -> Unit)?,
+ modifier: Modifier = Modifier,
+) {
+ Expandable(
+ shape = CircleShape,
+ color = colorAttr(R.attr.underSurfaceColor),
+ contentColor = LocalAndroidColorScheme.current.textColorSecondary,
+ borderStroke = BorderStroke(1.dp, LocalAndroidColorScheme.current.colorBackground),
+ modifier = modifier.padding(horizontal = 4.dp),
+ onClick = onClick,
+ ) {
+ Row(
+ Modifier.padding(horizontal = dimensionResource(R.dimen.qs_footer_padding)),
+ verticalAlignment = Alignment.CenterVertically,
+ ) {
+ Icon(icon, Modifier.padding(end = 12.dp).size(20.dp))
+
+ Text(
+ text,
+ Modifier.weight(1f),
+ style = MaterialTheme.typography.bodyMedium,
+ // TODO(b/242040009): Remove this letter spacing. We should only use the M3 text
+ // styles without modifying them.
+ letterSpacing = 0.01.em,
+ maxLines = 1,
+ overflow = TextOverflow.Ellipsis,
+ )
+
+ if (showNewDot) {
+ NewChangesDot(Modifier.padding(start = 8.dp))
+ }
+
+ if (onClick != null) {
+ Icon(
+ painterResource(com.android.internal.R.drawable.ic_chevron_end),
+ contentDescription = null,
+ Modifier.padding(start = 8.dp).size(20.dp),
+ )
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 2d756ae..f0cc42e 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -743,27 +743,6 @@
<!-- How long in milliseconds before full burn-in protection is achieved. -->
<integer name="config_dreamOverlayMillisUntilFullJitter">240000</integer>
- <!-- The duration in milliseconds of the y-translation animation when waking up from
- the dream -->
- <integer name="config_dreamOverlayOutTranslationYDurationMs">333</integer>
- <!-- The delay in milliseconds of the y-translation animation when waking up from
- the dream for the complications at the bottom of the screen -->
- <integer name="config_dreamOverlayOutTranslationYDelayBottomMs">33</integer>
- <!-- The delay in milliseconds of the y-translation animation when waking up from
- the dream for the complications at the top of the screen -->
- <integer name="config_dreamOverlayOutTranslationYDelayTopMs">117</integer>
- <!-- The duration in milliseconds of the alpha animation when waking up from the dream -->
- <integer name="config_dreamOverlayOutAlphaDurationMs">200</integer>
- <!-- The delay in milliseconds of the alpha animation when waking up from the dream for the
- complications at the top of the screen -->
- <integer name="config_dreamOverlayOutAlphaDelayTopMs">217</integer>
- <!-- The delay in milliseconds of the alpha animation when waking up from the dream for the
- complications at the bottom of the screen -->
- <integer name="config_dreamOverlayOutAlphaDelayBottomMs">133</integer>
- <!-- The duration in milliseconds of the blur animation when waking up from
- the dream -->
- <integer name="config_dreamOverlayOutBlurDurationMs">250</integer>
-
<integer name="complicationFadeOutMs">500</integer>
<integer name="complicationFadeInMs">500</integer>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
index baaef19..7da27b1 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
@@ -103,7 +103,6 @@
@Override
public void reset() {
- super.reset();
// start fresh
mDismissing = false;
mView.resetPasswordText(false /* animate */, false /* announce */);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
index b143c5b..d1c9a30 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
@@ -121,7 +121,6 @@
@Override
public void reset() {
- mMessageAreaController.setMessage("", false);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
index f902252..2260e35 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
@@ -71,6 +71,7 @@
import android.os.PowerExemptionManager;
import android.os.PowerManager;
import android.os.ServiceManager;
+import android.os.SystemUpdateManager;
import android.os.UserManager;
import android.os.Vibrator;
import android.os.storage.StorageManager;
@@ -139,6 +140,12 @@
return context.getSystemService(AlarmManager.class);
}
+ @Provides
+ @Singleton
+ static Optional<SystemUpdateManager> provideSystemUpdateManager(Context context) {
+ return Optional.ofNullable(context.getSystemService(SystemUpdateManager.class));
+ }
+
/** */
@Provides
public AmbientDisplayConfiguration provideAmbientDisplayConfiguration(Context context) {
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamCallbackController.kt b/packages/SystemUI/src/com/android/systemui/dreams/DreamCallbackController.kt
new file mode 100644
index 0000000..ab4632b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamCallbackController.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.dreams
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.policy.CallbackController
+import javax.inject.Inject
+
+/** Dream-related callback information */
+@SysUISingleton
+class DreamCallbackController @Inject constructor() :
+ CallbackController<DreamCallbackController.DreamCallback> {
+
+ private val callbacks = mutableSetOf<DreamCallbackController.DreamCallback>()
+
+ override fun addCallback(callback: DreamCallbackController.DreamCallback) {
+ callbacks.add(callback)
+ }
+
+ override fun removeCallback(callback: DreamCallbackController.DreamCallback) {
+ callbacks.remove(callback)
+ }
+
+ fun onWakeUp() {
+ callbacks.forEach { it.onWakeUp() }
+ }
+
+ interface DreamCallback {
+ fun onWakeUp()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
index 9b8ef71..abe9355 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
@@ -22,6 +22,9 @@
import android.view.View
import android.view.animation.Interpolator
import androidx.core.animation.doOnEnd
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.R
import com.android.systemui.animation.Interpolators
import com.android.systemui.dreams.complication.ComplicationHostViewController
import com.android.systemui.dreams.complication.ComplicationLayoutParams
@@ -29,10 +32,20 @@
import com.android.systemui.dreams.complication.ComplicationLayoutParams.POSITION_TOP
import com.android.systemui.dreams.complication.ComplicationLayoutParams.Position
import com.android.systemui.dreams.dagger.DreamOverlayModule
+import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel.Companion.DREAM_ANIMATION_DURATION
+import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.statusbar.BlurUtils
import com.android.systemui.statusbar.CrossFadeHelper
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener
+import com.android.systemui.util.concurrency.DelayableExecutor
import javax.inject.Inject
import javax.inject.Named
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.launch
/** Controller for dream overlay animations. */
class DreamOverlayAnimationsController
@@ -43,6 +56,8 @@
private val mStatusBarViewController: DreamOverlayStatusBarViewController,
private val mOverlayStateController: DreamOverlayStateController,
@Named(DreamOverlayModule.DREAM_BLUR_RADIUS) private val mDreamBlurRadius: Int,
+ private val transitionViewModel: DreamingToLockscreenTransitionViewModel,
+ private val configController: ConfigurationController,
@Named(DreamOverlayModule.DREAM_IN_BLUR_ANIMATION_DURATION)
private val mDreamInBlurAnimDurationMs: Long,
@Named(DreamOverlayModule.DREAM_IN_COMPLICATIONS_ANIMATION_DURATION)
@@ -51,22 +66,10 @@
private val mDreamInTranslationYDistance: Int,
@Named(DreamOverlayModule.DREAM_IN_TRANSLATION_Y_DURATION)
private val mDreamInTranslationYDurationMs: Long,
- @Named(DreamOverlayModule.DREAM_OUT_TRANSLATION_Y_DISTANCE)
- private val mDreamOutTranslationYDistance: Int,
- @Named(DreamOverlayModule.DREAM_OUT_TRANSLATION_Y_DURATION)
- private val mDreamOutTranslationYDurationMs: Long,
- @Named(DreamOverlayModule.DREAM_OUT_TRANSLATION_Y_DELAY_BOTTOM)
- private val mDreamOutTranslationYDelayBottomMs: Long,
- @Named(DreamOverlayModule.DREAM_OUT_TRANSLATION_Y_DELAY_TOP)
- private val mDreamOutTranslationYDelayTopMs: Long,
- @Named(DreamOverlayModule.DREAM_OUT_ALPHA_DURATION) private val mDreamOutAlphaDurationMs: Long,
- @Named(DreamOverlayModule.DREAM_OUT_ALPHA_DELAY_BOTTOM)
- private val mDreamOutAlphaDelayBottomMs: Long,
- @Named(DreamOverlayModule.DREAM_OUT_ALPHA_DELAY_TOP) private val mDreamOutAlphaDelayTopMs: Long,
- @Named(DreamOverlayModule.DREAM_OUT_BLUR_DURATION) private val mDreamOutBlurDurationMs: Long
) {
private var mAnimator: Animator? = null
+ private lateinit var view: View
/**
* Store the current alphas at the various positions. This is so that we may resume an animation
@@ -76,9 +79,63 @@
private var mCurrentBlurRadius: Float = 0f
+ fun init(view: View) {
+ this.view = view
+
+ view.repeatWhenAttached {
+ val configurationBasedDimensions = MutableStateFlow(loadFromResources(view))
+ val configCallback =
+ object : ConfigurationListener {
+ override fun onDensityOrFontScaleChanged() {
+ configurationBasedDimensions.value = loadFromResources(view)
+ }
+ }
+
+ configController.addCallback(configCallback)
+
+ repeatOnLifecycle(Lifecycle.State.CREATED) {
+ /* Translation animations, when moving from DREAMING->LOCKSCREEN state */
+ launch {
+ configurationBasedDimensions
+ .flatMapLatest {
+ transitionViewModel.dreamOverlayTranslationY(it.translationYPx)
+ }
+ .collect { px ->
+ setElementsTranslationYAtPosition(
+ px,
+ ComplicationLayoutParams.POSITION_TOP
+ )
+ setElementsTranslationYAtPosition(
+ px,
+ ComplicationLayoutParams.POSITION_BOTTOM
+ )
+ }
+ }
+
+ /* Alpha animations, when moving from DREAMING->LOCKSCREEN state */
+ launch {
+ transitionViewModel.dreamOverlayAlpha.collect { alpha ->
+ setElementsAlphaAtPosition(
+ alpha = alpha,
+ position = ComplicationLayoutParams.POSITION_TOP,
+ fadingOut = true,
+ )
+ setElementsAlphaAtPosition(
+ alpha = alpha,
+ position = ComplicationLayoutParams.POSITION_BOTTOM,
+ fadingOut = true,
+ )
+ }
+ }
+ }
+
+ configController.removeCallback(configCallback)
+ }
+ }
+
/** Starts the dream content and dream overlay entry animations. */
@JvmOverloads
- fun startEntryAnimations(view: View, animatorBuilder: () -> AnimatorSet = { AnimatorSet() }) {
+ fun startEntryAnimations(animatorBuilder: () -> AnimatorSet = { AnimatorSet() }) {
cancelAnimations()
mAnimator =
@@ -113,73 +170,9 @@
}
/** Starts the dream content and dream overlay exit animations. */
- @JvmOverloads
- fun startExitAnimations(
- view: View,
- doneCallback: () -> Unit,
- animatorBuilder: () -> AnimatorSet = { AnimatorSet() }
- ) {
+ fun wakeUp(doneCallback: Runnable, executor: DelayableExecutor) {
cancelAnimations()
-
- mAnimator =
- animatorBuilder().apply {
- playTogether(
- blurAnimator(
- view = view,
- // Start the blurring wherever the entry animation ended, in
- // case it was cancelled early.
- fromBlurRadius = mCurrentBlurRadius,
- toBlurRadius = mDreamBlurRadius.toFloat(),
- durationMs = mDreamOutBlurDurationMs,
- interpolator = Interpolators.EMPHASIZED_ACCELERATE
- ),
- translationYAnimator(
- from = 0f,
- to = mDreamOutTranslationYDistance.toFloat(),
- durationMs = mDreamOutTranslationYDurationMs,
- delayMs = mDreamOutTranslationYDelayBottomMs,
- positions = POSITION_BOTTOM,
- interpolator = Interpolators.EMPHASIZED_ACCELERATE
- ),
- translationYAnimator(
- from = 0f,
- to = mDreamOutTranslationYDistance.toFloat(),
- durationMs = mDreamOutTranslationYDurationMs,
- delayMs = mDreamOutTranslationYDelayTopMs,
- positions = POSITION_TOP,
- interpolator = Interpolators.EMPHASIZED_ACCELERATE
- ),
- alphaAnimator(
- from =
- mCurrentAlphaAtPosition.getOrDefault(
- key = POSITION_BOTTOM,
- defaultValue = 1f
- ),
- to = 0f,
- durationMs = mDreamOutAlphaDurationMs,
- delayMs = mDreamOutAlphaDelayBottomMs,
- positions = POSITION_BOTTOM
- ),
- alphaAnimator(
- from =
- mCurrentAlphaAtPosition.getOrDefault(
- key = POSITION_TOP,
- defaultValue = 1f
- ),
- to = 0f,
- durationMs = mDreamOutAlphaDurationMs,
- delayMs = mDreamOutAlphaDelayTopMs,
- positions = POSITION_TOP
- )
- )
- doOnEnd {
- mAnimator = null
- mOverlayStateController.setExitAnimationsRunning(false)
- doneCallback()
- }
- start()
- }
- mOverlayStateController.setExitAnimationsRunning(true)
+ executor.executeDelayed(doneCallback, DREAM_ANIMATION_DURATION.inWholeMilliseconds)
}
/** Cancels the dream content and dream overlay animations, if they're currently running. */
@@ -288,4 +281,15 @@
mStatusBarViewController.setTranslationY(translationY)
}
}
+
+ private fun loadFromResources(view: View): ConfigurationBasedDimensions {
+ return ConfigurationBasedDimensions(
+ translationYPx =
+ view.resources.getDimensionPixelSize(R.dimen.dream_overlay_exit_y_offset),
+ )
+ }
+
+ private data class ConfigurationBasedDimensions(
+ val translationYPx: Int,
+ )
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
index 9d7ad30..3106173 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
@@ -42,9 +42,9 @@
import com.android.systemui.statusbar.phone.KeyguardBouncer;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.util.ViewController;
+import com.android.systemui.util.concurrency.DelayableExecutor;
import java.util.Arrays;
-import java.util.concurrent.Executor;
import javax.inject.Inject;
import javax.inject.Named;
@@ -170,6 +170,7 @@
protected void onInit() {
mStatusBarViewController.init();
mComplicationHostViewController.init();
+ mDreamOverlayAnimationsController.init(mView);
}
@Override
@@ -184,7 +185,7 @@
// Start dream entry animations. Skip animations for low light clock.
if (!mStateController.isLowLightActive()) {
- mDreamOverlayAnimationsController.startEntryAnimations(mView);
+ mDreamOverlayAnimationsController.startEntryAnimations();
}
}
@@ -261,10 +262,8 @@
* @param onAnimationEnd Callback to trigger once animations are finished.
* @param callbackExecutor Executor to execute the callback on.
*/
- public void wakeUp(@NonNull Runnable onAnimationEnd, @NonNull Executor callbackExecutor) {
- mDreamOverlayAnimationsController.startExitAnimations(mView, () -> {
- callbackExecutor.execute(onAnimationEnd);
- return null;
- });
+ public void wakeUp(@NonNull Runnable onAnimationEnd,
+ @NonNull DelayableExecutor callbackExecutor) {
+ mDreamOverlayAnimationsController.wakeUp(onAnimationEnd, callbackExecutor);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
index e76d5b3..1be9cd1 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
@@ -43,8 +43,7 @@
import com.android.systemui.dreams.complication.Complication;
import com.android.systemui.dreams.dagger.DreamOverlayComponent;
import com.android.systemui.dreams.touch.DreamOverlayTouchMonitor;
-
-import java.util.concurrent.Executor;
+import com.android.systemui.util.concurrency.DelayableExecutor;
import javax.inject.Inject;
import javax.inject.Named;
@@ -61,10 +60,11 @@
// The Context is used to construct the hosting constraint layout and child overlay views.
private final Context mContext;
// The Executor ensures actions and ui updates happen on the same thread.
- private final Executor mExecutor;
+ private final DelayableExecutor mExecutor;
// A controller for the dream overlay container view (which contains both the status bar and the
// content area).
private DreamOverlayContainerViewController mDreamOverlayContainerViewController;
+ private final DreamCallbackController mDreamCallbackController;
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@Nullable
private final ComponentName mLowLightDreamComponent;
@@ -126,14 +126,15 @@
@Inject
public DreamOverlayService(
Context context,
- @Main Executor executor,
+ @Main DelayableExecutor executor,
WindowManager windowManager,
DreamOverlayComponent.Factory dreamOverlayComponentFactory,
DreamOverlayStateController stateController,
KeyguardUpdateMonitor keyguardUpdateMonitor,
UiEventLogger uiEventLogger,
@Nullable @Named(LowLightDreamModule.LOW_LIGHT_DREAM_COMPONENT)
- ComponentName lowLightDreamComponent) {
+ ComponentName lowLightDreamComponent,
+ DreamCallbackController dreamCallbackController) {
mContext = context;
mExecutor = executor;
mWindowManager = windowManager;
@@ -142,6 +143,7 @@
mKeyguardUpdateMonitor.registerCallback(mKeyguardCallback);
mStateController = stateController;
mUiEventLogger = uiEventLogger;
+ mDreamCallbackController = dreamCallbackController;
final ViewModelStore viewModelStore = new ViewModelStore();
final Complication.Host host =
@@ -217,6 +219,7 @@
public void onWakeUp(@NonNull Runnable onCompletedCallback) {
mExecutor.execute(() -> {
if (mDreamOverlayContainerViewController != null) {
+ mDreamCallbackController.onWakeUp();
mDreamOverlayContainerViewController.wakeUp(onCompletedCallback, mExecutor);
}
});
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
index 4f1ac1a..0d58a69 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
@@ -55,22 +55,6 @@
"dream_in_complications_translation_y";
public static final String DREAM_IN_TRANSLATION_Y_DURATION =
"dream_in_complications_translation_y_duration";
- public static final String DREAM_OUT_TRANSLATION_Y_DISTANCE =
- "dream_out_complications_translation_y";
- public static final String DREAM_OUT_TRANSLATION_Y_DURATION =
- "dream_out_complications_translation_y_duration";
- public static final String DREAM_OUT_TRANSLATION_Y_DELAY_BOTTOM =
- "dream_out_complications_translation_y_delay_bottom";
- public static final String DREAM_OUT_TRANSLATION_Y_DELAY_TOP =
- "dream_out_complications_translation_y_delay_top";
- public static final String DREAM_OUT_ALPHA_DURATION =
- "dream_out_complications_alpha_duration";
- public static final String DREAM_OUT_ALPHA_DELAY_BOTTOM =
- "dream_out_complications_alpha_delay_bottom";
- public static final String DREAM_OUT_ALPHA_DELAY_TOP =
- "dream_out_complications_alpha_delay_top";
- public static final String DREAM_OUT_BLUR_DURATION =
- "dream_out_blur_duration";
/** */
@Provides
@@ -185,66 +169,6 @@
return (long) resources.getInteger(R.integer.config_dreamOverlayInTranslationYDurationMs);
}
- /**
- * Provides the number of pixels to translate complications when waking up from dream.
- */
- @Provides
- @Named(DREAM_OUT_TRANSLATION_Y_DISTANCE)
- @DreamOverlayComponent.DreamOverlayScope
- static int providesDreamOutComplicationsTranslationY(@Main Resources resources) {
- return resources.getDimensionPixelSize(R.dimen.dream_overlay_exit_y_offset);
- }
-
- @Provides
- @Named(DREAM_OUT_TRANSLATION_Y_DURATION)
- @DreamOverlayComponent.DreamOverlayScope
- static long providesDreamOutComplicationsTranslationYDuration(@Main Resources resources) {
- return (long) resources.getInteger(R.integer.config_dreamOverlayOutTranslationYDurationMs);
- }
-
- @Provides
- @Named(DREAM_OUT_TRANSLATION_Y_DELAY_BOTTOM)
- @DreamOverlayComponent.DreamOverlayScope
- static long providesDreamOutComplicationsTranslationYDelayBottom(@Main Resources resources) {
- return (long) resources.getInteger(
- R.integer.config_dreamOverlayOutTranslationYDelayBottomMs);
- }
-
- @Provides
- @Named(DREAM_OUT_TRANSLATION_Y_DELAY_TOP)
- @DreamOverlayComponent.DreamOverlayScope
- static long providesDreamOutComplicationsTranslationYDelayTop(@Main Resources resources) {
- return (long) resources.getInteger(R.integer.config_dreamOverlayOutTranslationYDelayTopMs);
- }
-
- @Provides
- @Named(DREAM_OUT_ALPHA_DURATION)
- @DreamOverlayComponent.DreamOverlayScope
- static long providesDreamOutComplicationsAlphaDuration(@Main Resources resources) {
- return (long) resources.getInteger(R.integer.config_dreamOverlayOutAlphaDurationMs);
- }
-
- @Provides
- @Named(DREAM_OUT_ALPHA_DELAY_BOTTOM)
- @DreamOverlayComponent.DreamOverlayScope
- static long providesDreamOutComplicationsAlphaDelayBottom(@Main Resources resources) {
- return (long) resources.getInteger(R.integer.config_dreamOverlayOutAlphaDelayBottomMs);
- }
-
- @Provides
- @Named(DREAM_OUT_ALPHA_DELAY_TOP)
- @DreamOverlayComponent.DreamOverlayScope
- static long providesDreamOutComplicationsAlphaDelayTop(@Main Resources resources) {
- return (long) resources.getInteger(R.integer.config_dreamOverlayOutAlphaDelayTopMs);
- }
-
- @Provides
- @Named(DREAM_OUT_BLUR_DURATION)
- @DreamOverlayComponent.DreamOverlayScope
- static long providesDreamOutBlurDuration(@Main Resources resources) {
- return (long) resources.getInteger(R.integer.config_dreamOverlayOutBlurDurationMs);
- }
-
@Provides
@DreamOverlayComponent.DreamOverlayScope
static LifecycleOwner providesLifecycleOwner(Lazy<LifecycleRegistry> lifecycleRegistryLazy) {
diff --git a/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt b/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt
index c982131..276a290 100644
--- a/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt
@@ -51,6 +51,11 @@
registerDumpable(name, module, DumpPriority.CRITICAL)
}
+ /** See [registerNormalDumpable]. */
+ fun registerNormalDumpable(module: Dumpable) {
+ registerNormalDumpable(module::class.java.simpleName, module)
+ }
+
/**
* Registers a dumpable to be called during the NORMAL section of the bug report.
*
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index d231870..ae714fb 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -33,6 +33,7 @@
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE;
import static com.android.systemui.DejankUtils.whitelistIpcs;
+import static com.android.systemui.keyguard.domain.interactor.DreamingTransitionInteractor.TO_LOCKSCREEN_DURATION_MS;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -1231,8 +1232,7 @@
mDreamOpenAnimationDuration = context.getResources().getInteger(
com.android.internal.R.integer.config_dreamOpenAnimationDuration);
- mDreamCloseAnimationDuration = context.getResources().getInteger(
- com.android.internal.R.integer.config_dreamCloseAnimationDuration);
+ mDreamCloseAnimationDuration = (int) TO_LOCKSCREEN_DURATION_MS;
}
public void userActivity() {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
index 148792b..9a0fbbf 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
@@ -29,6 +29,8 @@
import com.android.systemui.doze.DozeMachine
import com.android.systemui.doze.DozeTransitionCallback
import com.android.systemui.doze.DozeTransitionListener
+import com.android.systemui.dreams.DreamCallbackController
+import com.android.systemui.dreams.DreamCallbackController.DreamCallback
import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
@@ -47,6 +49,7 @@
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.merge
/** Defines interface for classes that encapsulate application state for the keyguard. */
interface KeyguardRepository {
@@ -176,6 +179,7 @@
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
private val dozeTransitionListener: DozeTransitionListener,
private val authController: AuthController,
+ private val dreamCallbackController: DreamCallbackController,
) : KeyguardRepository {
private val _animateBottomAreaDozingTransitions = MutableStateFlow(false)
override val animateBottomAreaDozingTransitions =
@@ -276,22 +280,35 @@
.distinctUntilChanged()
override val isDreaming: Flow<Boolean> =
- conflatedCallbackFlow {
- val callback =
- object : KeyguardUpdateMonitorCallback() {
- override fun onDreamingStateChanged(isDreaming: Boolean) {
- trySendWithFailureLogging(isDreaming, TAG, "updated isDreaming")
+ merge(
+ conflatedCallbackFlow {
+ val callback =
+ object : KeyguardUpdateMonitorCallback() {
+ override fun onDreamingStateChanged(isDreaming: Boolean) {
+ trySendWithFailureLogging(isDreaming, TAG, "updated isDreaming")
+ }
}
- }
- keyguardUpdateMonitor.registerCallback(callback)
- trySendWithFailureLogging(
- keyguardUpdateMonitor.isDreaming,
- TAG,
- "initial isDreaming",
- )
+ keyguardUpdateMonitor.registerCallback(callback)
+ trySendWithFailureLogging(
+ keyguardUpdateMonitor.isDreaming,
+ TAG,
+ "initial isDreaming",
+ )
- awaitClose { keyguardUpdateMonitor.removeCallback(callback) }
- }
+ awaitClose { keyguardUpdateMonitor.removeCallback(callback) }
+ },
+ conflatedCallbackFlow {
+ val callback =
+ object : DreamCallback {
+ override fun onWakeUp() {
+ trySendWithFailureLogging(false, TAG, "updated isDreaming")
+ }
+ }
+ dreamCallbackController.addCallback(callback)
+
+ awaitClose { dreamCallbackController.removeCallback(callback) }
+ }
+ )
.distinctUntilChanged()
override val linearDozeAmount: Flow<Float> = conflatedCallbackFlow {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
index 5bb586e..d72d7183b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
@@ -66,8 +66,8 @@
}
/**
- * Begin a transition from one state to another. Will not start if another transition is in
- * progress.
+ * Begin a transition from one state to another. Transitions are interruptible, and will issue a
+ * [TransitionStep] with state = [TransitionState.CANCELED] before beginning the next one.
*/
fun startTransition(info: TransitionInfo): UUID?
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingTransitionInteractor.kt
index b73ce9e..4d60579 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingTransitionInteractor.kt
@@ -28,6 +28,8 @@
import com.android.systemui.keyguard.shared.model.TransitionInfo
import com.android.systemui.util.kotlin.sample
import javax.inject.Inject
+import kotlin.time.Duration
+import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.combine
@@ -110,7 +112,7 @@
name,
KeyguardState.DREAMING,
KeyguardState.LOCKSCREEN,
- getAnimator(),
+ getAnimator(TO_LOCKSCREEN_DURATION),
)
)
}
@@ -167,14 +169,16 @@
}
}
- private fun getAnimator(): ValueAnimator {
+ private fun getAnimator(duration: Duration = DEFAULT_DURATION): ValueAnimator {
return ValueAnimator().apply {
setInterpolator(Interpolators.LINEAR)
- setDuration(TRANSITION_DURATION_MS)
+ setDuration(duration.inWholeMilliseconds)
}
}
companion object {
- private const val TRANSITION_DURATION_MS = 500L
+ private val DEFAULT_DURATION = 500.milliseconds
+ val TO_LOCKSCREEN_DURATION = 1183.milliseconds
+ @JvmField val TO_LOCKSCREEN_DURATION_MS = TO_LOCKSCREEN_DURATION.inWholeMilliseconds
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
index 54a4f49..3b9d6f5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
@@ -19,12 +19,15 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.AnimationParams
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
+import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
import javax.inject.Inject
+import kotlin.time.Duration
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map
@@ -43,6 +46,10 @@
/** LOCKSCREEN->AOD transition information. */
val lockscreenToAodTransition: Flow<TransitionStep> = repository.transition(LOCKSCREEN, AOD)
+ /** DREAMING->LOCKSCREEN transition information. */
+ val dreamingToLockscreenTransition: Flow<TransitionStep> =
+ repository.transition(DREAMING, LOCKSCREEN)
+
/** (any)->AOD transition information */
val anyStateToAodTransition: Flow<TransitionStep> =
repository.transitions.filter { step -> step.to == KeyguardState.AOD }
@@ -72,4 +79,21 @@
/* The last completed [KeyguardState] transition */
val finishedKeyguardState: Flow<KeyguardState> =
finishedKeyguardTransitionStep.map { step -> step.to }
+
+ /**
+ * Transitions will occur over a [totalDuration] with [TransitionStep]s being emitted in the
+ * range of [0, 1]. View animations should begin and end within a subset of this range. This
+ * function maps the [startTime] and [duration] into [0, 1], when this subset is valid.
+ */
+ fun transitionStepAnimation(
+ flow: Flow<TransitionStep>,
+ params: AnimationParams,
+ totalDuration: Duration,
+ ): Flow<Float> {
+ val start = (params.startTime / totalDuration).toFloat()
+ val chunks = (totalDuration / params.duration).toFloat()
+ return flow
+ .map { step -> (step.value - start) * chunks }
+ .filter { value -> value >= 0f && value <= 1f }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/AnimationParams.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/AnimationParams.kt
new file mode 100644
index 0000000..67733e9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/AnimationParams.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.systemui.keyguard.shared.model
+
+import kotlin.time.Duration
+import kotlin.time.Duration.Companion.milliseconds
+
+/** Animation parameters */
+data class AnimationParams(
+ val startTime: Duration = 0.milliseconds,
+ val duration: Duration,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt
index 3d5985c5..f772b17 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt
@@ -105,6 +105,14 @@
}
launch {
+ viewModel.showWithFullExpansion.collect { model ->
+ hostViewController.resetSecurityContainer()
+ hostViewController.showPromptReason(model.promptReason)
+ hostViewController.onResume()
+ }
+ }
+
+ launch {
viewModel.hide.collect {
hostViewController.cancelDismissAction()
hostViewController.cleanUp()
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt
new file mode 100644
index 0000000..9b36e8c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.DreamingTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.AnimationParams
+import javax.inject.Inject
+import kotlin.time.Duration.Companion.milliseconds
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+
+/**
+ * Breaks down DREAMING->LOCKSCREEN transition into discrete steps for corresponding views to
+ * consume.
+ */
+@SysUISingleton
+class DreamingToLockscreenTransitionViewModel
+@Inject
+constructor(
+ private val interactor: KeyguardTransitionInteractor,
+) {
+
+ /** Dream overlay y-translation on exit */
+ fun dreamOverlayTranslationY(translatePx: Int): Flow<Float> {
+ return flowForAnimation(DREAM_OVERLAY_TRANSLATION_Y).map { value ->
+ EMPHASIZED_ACCELERATE.getInterpolation(value) * translatePx
+ }
+ }
+ /** Dream overlay views alpha - fade out */
+ val dreamOverlayAlpha: Flow<Float> = flowForAnimation(DREAM_OVERLAY_ALPHA).map { 1f - it }
+
+ /** Dream background alpha - fade out */
+ val dreamBackgroundAlpha: Flow<Float> = flowForAnimation(DREAM_BACKGROUND_ALPHA).map { 1f - it }
+
+ /** Lockscreen views y-translation */
+ fun lockscreenTranslationY(translatePx: Int): Flow<Float> {
+ return flowForAnimation(LOCKSCREEN_TRANSLATION_Y).map { it * translatePx }
+ }
+
+ /** Lockscreen views alpha */
+ val lockscreenAlpha: Flow<Float> = flowForAnimation(LOCKSCREEN_ALPHA)
+
+ private fun flowForAnimation(params: AnimationParams): Flow<Float> {
+ return interactor.transitionStepAnimation(
+ interactor.dreamingToLockscreenTransition,
+ params,
+ totalDuration = TO_LOCKSCREEN_DURATION
+ )
+ }
+
+ companion object {
+ /* Length of time before ending the dream activity, in order to start unoccluding */
+ val DREAM_ANIMATION_DURATION = 250.milliseconds
+
+ val DREAM_OVERLAY_TRANSLATION_Y = AnimationParams(duration = 500.milliseconds)
+ val DREAM_OVERLAY_ALPHA = AnimationParams(duration = 250.milliseconds)
+ val DREAM_BACKGROUND_ALPHA = AnimationParams(duration = 250.milliseconds)
+ val LOCKSCREEN_TRANSLATION_Y = AnimationParams(duration = TO_LOCKSCREEN_DURATION)
+ val LOCKSCREEN_ALPHA =
+ AnimationParams(startTime = 233.milliseconds, duration = 250.milliseconds)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt
index 737c35d..e5d4e49 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt
@@ -22,8 +22,10 @@
import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel
import com.android.systemui.keyguard.shared.model.KeyguardBouncerModel
+import com.android.systemui.statusbar.phone.KeyguardBouncer.EXPANSION_VISIBLE
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map
/** Models UI state for the lock screen bouncer; handles user input. */
@@ -42,6 +44,10 @@
/** Observe whether bouncer is showing. */
val show: Flow<KeyguardBouncerModel> = interactor.show
+ /** Observe visible expansion when bouncer is showing. */
+ val showWithFullExpansion: Flow<KeyguardBouncerModel> =
+ interactor.show.filter { it.expansionAmount == EXPANSION_VISIBLE }
+
/** Observe whether bouncer is hiding. */
val hide: Flow<Unit> = interactor.hide
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaSessionBasedFilter.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaSessionBasedFilter.kt
index ab93b29..d6f941d 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaSessionBasedFilter.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaSessionBasedFilter.kt
@@ -58,10 +58,10 @@
// Keep track of the key used for the session tokens. This information is used to know when to
// dispatch a removed event so that a media object for a local session will be removed.
- private val keyedTokens: MutableMap<String, MutableSet<MediaSession.Token>> = mutableMapOf()
+ private val keyedTokens: MutableMap<String, MutableSet<TokenId>> = mutableMapOf()
// Keep track of which media session tokens have associated notifications.
- private val tokensWithNotifications: MutableSet<MediaSession.Token> = mutableSetOf()
+ private val tokensWithNotifications: MutableSet<TokenId> = mutableSetOf()
private val sessionListener =
object : MediaSessionManager.OnActiveSessionsChangedListener {
@@ -101,15 +101,15 @@
isSsReactivated: Boolean
) {
backgroundExecutor.execute {
- data.token?.let { tokensWithNotifications.add(it) }
+ data.token?.let { tokensWithNotifications.add(TokenId(it)) }
val isMigration = oldKey != null && key != oldKey
if (isMigration) {
keyedTokens.remove(oldKey)?.let { removed -> keyedTokens.put(key, removed) }
}
if (data.token != null) {
- keyedTokens.get(key)?.let { tokens -> tokens.add(data.token) }
+ keyedTokens.get(key)?.let { tokens -> tokens.add(TokenId(data.token)) }
?: run {
- val tokens = mutableSetOf(data.token)
+ val tokens = mutableSetOf(TokenId(data.token))
keyedTokens.put(key, tokens)
}
}
@@ -125,7 +125,7 @@
isMigration ||
remote == null ||
remote.sessionToken == data.token ||
- !tokensWithNotifications.contains(remote.sessionToken)
+ !tokensWithNotifications.contains(TokenId(remote.sessionToken))
) {
// Not filtering in this case. Passing the event along to listeners.
dispatchMediaDataLoaded(key, oldKey, data, immediately)
@@ -136,7 +136,7 @@
// If the local session uses a different notification key, then lets go a step
// farther and dismiss the media data so that media controls for the local session
// don't hang around while casting.
- if (!keyedTokens.get(key)!!.contains(remote.sessionToken)) {
+ if (!keyedTokens.get(key)!!.contains(TokenId(remote.sessionToken))) {
dispatchMediaDataRemoved(key)
}
}
@@ -199,6 +199,15 @@
packageControllers.put(controller.packageName, tokens)
}
}
- tokensWithNotifications.retainAll(controllers.map { it.sessionToken })
+ tokensWithNotifications.retainAll(controllers.map { TokenId(it.sessionToken) })
+ }
+
+ /**
+ * Represents a unique identifier for a [MediaSession.Token].
+ *
+ * It's used to avoid storing strong binders for media session tokens.
+ */
+ private data class TokenId(val id: Int) {
+ constructor(token: MediaSession.Token) : this(token.hashCode())
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
index 7b9d0b4..358534c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
@@ -37,6 +37,7 @@
import com.android.systemui.common.ui.binder.TintedIconViewBinder
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.dump.DumpManager
import com.android.systemui.media.taptotransfer.MediaTttFlags
import com.android.systemui.media.taptotransfer.common.MediaTttIcon
import com.android.systemui.media.taptotransfer.common.MediaTttLogger
@@ -69,6 +70,7 @@
mainExecutor: DelayableExecutor,
accessibilityManager: AccessibilityManager,
configurationController: ConfigurationController,
+ dumpManager: DumpManager,
powerManager: PowerManager,
@Main private val mainHandler: Handler,
private val mediaTttFlags: MediaTttFlags,
@@ -83,6 +85,7 @@
mainExecutor,
accessibilityManager,
configurationController,
+ dumpManager,
powerManager,
R.layout.media_ttt_chip_receiver,
wakeLockBuilder,
@@ -162,6 +165,7 @@
}
override fun start() {
+ super.start()
if (mediaTttFlags.isMediaTttEnabled()) {
commandQueue.addCallback(commandQueueCallbacks)
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/binder/FooterActionsViewBinder.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/binder/FooterActionsViewBinder.kt
index 6db3c99..30f8124 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/binder/FooterActionsViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/binder/FooterActionsViewBinder.kt
@@ -237,7 +237,13 @@
return
}
- buttonView.setBackgroundResource(model.background)
+ val backgroundResource =
+ when (model.backgroundColor) {
+ R.attr.offStateColor -> R.drawable.qs_footer_action_circle
+ com.android.internal.R.attr.colorAccent -> R.drawable.qs_footer_action_circle_color
+ else -> error("Unsupported icon background resource ${model.backgroundColor}")
+ }
+ buttonView.setBackgroundResource(backgroundResource)
buttonView.setOnClickListener { model.onClick(Expandable.fromView(buttonView)) }
val icon = model.icon
diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsButtonViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsButtonViewModel.kt
index 8d819da..2670787 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsButtonViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsButtonViewModel.kt
@@ -16,7 +16,8 @@
package com.android.systemui.qs.footer.ui.viewmodel
-import android.annotation.DrawableRes
+import android.annotation.AttrRes
+import android.annotation.ColorInt
import com.android.systemui.animation.Expandable
import com.android.systemui.common.shared.model.Icon
@@ -27,7 +28,7 @@
data class FooterActionsButtonViewModel(
val id: Int,
val icon: Icon,
- val iconTint: Int?,
- @DrawableRes val background: Int,
+ @ColorInt val iconTint: Int?,
+ @AttrRes val backgroundColor: Int,
val onClick: (Expandable) -> Unit,
)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt
index dee6fad..fbf32b3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt
@@ -18,6 +18,7 @@
import android.content.Context
import android.util.Log
+import android.view.ContextThemeWrapper
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
@@ -49,12 +50,15 @@
/** A ViewModel for the footer actions. */
class FooterActionsViewModel(
- @Application private val context: Context,
+ @Application appContext: Context,
private val footerActionsInteractor: FooterActionsInteractor,
private val falsingManager: FalsingManager,
private val globalActionsDialogLite: GlobalActionsDialogLite,
showPowerButton: Boolean,
) {
+ /** The context themed with the Quick Settings colors. */
+ private val context = ContextThemeWrapper(appContext, R.style.Theme_SystemUI_QuickSettings)
+
/**
* Whether the UI rendering this ViewModel should be visible. Note that even when this is false,
* the UI should still participate to the layout it is included in (i.e. in the View world it
@@ -142,7 +146,7 @@
ContentDescription.Resource(R.string.accessibility_quick_settings_settings)
),
iconTint = null,
- R.drawable.qs_footer_action_circle,
+ backgroundColor = R.attr.offStateColor,
this::onSettingsButtonClicked,
)
@@ -160,7 +164,7 @@
context,
com.android.internal.R.attr.textColorOnAccent,
),
- R.drawable.qs_footer_action_circle_color,
+ backgroundColor = com.android.internal.R.attr.colorAccent,
this::onPowerButtonClicked,
)
} else {
@@ -260,7 +264,7 @@
),
),
iconTint = null,
- background = R.drawable.qs_footer_action_circle,
+ backgroundColor = R.attr.offStateColor,
onClick = this::onUserSwitcherClicked,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 770a236..b7001e4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -930,8 +930,7 @@
if (mStatusBarKeyguardViewManager.isBouncerShowing()) {
if (mStatusBarKeyguardViewManager.isShowingAlternateBouncer()) {
return; // udfps affordance is highlighted, no need to show action to unlock
- } else if (!mKeyguardUpdateMonitor.getIsFaceAuthenticated()
- && mKeyguardUpdateMonitor.isFaceEnrolled()) {
+ } else if (mKeyguardUpdateMonitor.isFaceEnrolled()) {
String message = mContext.getString(R.string.keyguard_retry);
mStatusBarKeyguardViewManager.setKeyguardMessage(message, mInitialTextColorState);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 5db95d7..ca1e397 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -146,7 +146,8 @@
private static final int DELAY_BEFORE_SHADE_CLOSE = 200;
private boolean mShadeNeedsToClose = false;
- private static final float RUBBER_BAND_FACTOR_NORMAL = 0.35f;
+ @VisibleForTesting
+ static final float RUBBER_BAND_FACTOR_NORMAL = 0.35f;
private static final float RUBBER_BAND_FACTOR_AFTER_EXPAND = 0.15f;
private static final float RUBBER_BAND_FACTOR_ON_PANEL_EXPAND = 0.21f;
/**
@@ -1326,8 +1327,11 @@
* @param listenerNeedsAnimation does the listener need to animate?
*/
private void updateStackPosition(boolean listenerNeedsAnimation) {
+ float topOverscrollAmount = mShouldUseSplitNotificationShade
+ ? getCurrentOverScrollAmount(true /* top */) : 0f;
final float endTopPosition = mTopPadding + mExtraTopInsetForFullShadeTransition
+ mAmbientState.getOverExpansion()
+ + topOverscrollAmount
- getCurrentOverScrollAmount(false /* top */);
float fraction = mAmbientState.getExpansionFraction();
// If we are on quick settings, we need to quickly hide it to show the bouncer to avoid an
@@ -2613,8 +2617,10 @@
float bottomAmount = getCurrentOverScrollAmount(false);
if (velocityY < 0 && topAmount > 0) {
setOwnScrollY(mOwnScrollY - (int) topAmount);
- mDontReportNextOverScroll = true;
- setOverScrollAmount(0, true, false);
+ if (!mShouldUseSplitNotificationShade) {
+ mDontReportNextOverScroll = true;
+ setOverScrollAmount(0, true, false);
+ }
mMaxOverScroll = Math.abs(velocityY) / 1000f * getRubberBandFactor(true /* onTop */)
* mOverflingDistance + topAmount;
} else if (velocityY > 0 && bottomAmount > 0) {
@@ -2648,6 +2654,7 @@
float topOverScroll = getCurrentOverScrollAmount(true);
return mScrolledToTopOnFirstDown
&& !mExpandedInThisMotion
+ && !mShouldUseSplitNotificationShade
&& (initialVelocity > mMinimumVelocity
|| (topOverScroll > mMinTopOverScrollToEscape && initialVelocity > 0));
}
@@ -2713,7 +2720,7 @@
return RUBBER_BAND_FACTOR_AFTER_EXPAND;
} else if (mIsExpansionChanging || mPanelTracking) {
return RUBBER_BAND_FACTOR_ON_PANEL_EXPAND;
- } else if (mScrolledToTopOnFirstDown) {
+ } else if (mScrolledToTopOnFirstDown && !mShouldUseSplitNotificationShade) {
return 1.0f;
}
return RUBBER_BAND_FACTOR_NORMAL;
@@ -5705,7 +5712,8 @@
}
}
- private void updateSplitNotificationShade() {
+ @VisibleForTesting
+ void updateSplitNotificationShade() {
boolean split = LargeScreenUtils.shouldUseSplitNotificationShade(getResources());
if (split != mShouldUseSplitNotificationShade) {
mShouldUseSplitNotificationShade = split;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectionModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectionModel.kt
index 1d00c33..a6b04e4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectionModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectionModel.kt
@@ -41,6 +41,7 @@
data class MobileConnectionModel(
/** From [ServiceStateListener.onServiceStateChanged] */
val isEmergencyOnly: Boolean = false,
+ val isRoaming: Boolean = false,
/** From [SignalStrengthsListener.onSignalStrengthsChanged] */
val isGsm: Boolean = false,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
index 2621f997..fc59f6e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
@@ -50,4 +50,12 @@
* [SubscriptionManager.getDefaultDataSubscriptionId]
*/
val isDefaultDataSubscription: StateFlow<Boolean>
+
+ /**
+ * See [TelephonyManager.getCdmaEnhancedRoamingIndicatorDisplayNumber]. This bit only matters if
+ * the connection type is CDMA.
+ *
+ * True if the Enhanced Roaming Indicator (ERI) display number is not [TelephonyManager.ERI_OFF]
+ */
+ val cdmaRoaming: StateFlow<Boolean>
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt
index 1c08525..98b47e4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt
@@ -186,6 +186,7 @@
connection.dataEnabled.value = true
connection.isDefaultDataSubscription.value = state.dataType != null
+ connection.cdmaRoaming.value = state.roaming
connection.connectionInfo.value = state.toMobileConnectionModel()
}
@@ -229,6 +230,7 @@
private fun Mobile.toMobileConnectionModel(): MobileConnectionModel {
return MobileConnectionModel(
isEmergencyOnly = false, // TODO(b/261029387): not yet supported
+ isRoaming = roaming,
isGsm = false, // TODO(b/261029387): not yet supported
cdmaLevel = level ?: 0,
primaryLevel = level ?: 0,
@@ -260,4 +262,6 @@
override val dataEnabled = MutableStateFlow(true)
override val isDefaultDataSubscription = MutableStateFlow(true)
+
+ override val cdmaRoaming = MutableStateFlow(false)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoModeMobileConnectionDataSource.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoModeMobileConnectionDataSource.kt
index da55787..2cdbc19 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoModeMobileConnectionDataSource.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoModeMobileConnectionDataSource.kt
@@ -98,6 +98,7 @@
val inflateStrength = getString("inflate")?.toBoolean()
val activity = getString("activity")?.toActivity()
val carrierNetworkChange = getString("carriernetworkchange") == "show"
+ val roaming = getString("roam") == "show"
return Mobile(
level = level,
@@ -107,6 +108,7 @@
inflateStrength = inflateStrength,
activity = activity,
carrierNetworkChange = carrierNetworkChange,
+ roaming = roaming,
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/model/FakeNetworkEventModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/model/FakeNetworkEventModel.kt
index 3f3acaf..b8543ec 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/model/FakeNetworkEventModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/model/FakeNetworkEventModel.kt
@@ -34,6 +34,7 @@
val inflateStrength: Boolean?,
@DataActivityType val activity: Int?,
val carrierNetworkChange: Boolean,
+ val roaming: Boolean,
) : FakeNetworkEventModel
data class MobileDisabled(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
index 15505fd..295e0dc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
@@ -27,6 +27,7 @@
import android.telephony.TelephonyDisplayInfo
import android.telephony.TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE
import android.telephony.TelephonyManager
+import android.telephony.TelephonyManager.ERI_OFF
import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.qualifiers.Application
@@ -95,7 +96,11 @@
TelephonyCallback.CarrierNetworkListener,
TelephonyCallback.DisplayInfoListener {
override fun onServiceStateChanged(serviceState: ServiceState) {
- state = state.copy(isEmergencyOnly = serviceState.isEmergencyOnly)
+ state =
+ state.copy(
+ isEmergencyOnly = serviceState.isEmergencyOnly,
+ isRoaming = serviceState.roaming,
+ )
trySend(state)
}
@@ -208,6 +213,11 @@
globalMobileDataSettingChangedEvent,
)
+ override val cdmaRoaming: StateFlow<Boolean> =
+ telephonyPollingEvent
+ .mapLatest { telephonyManager.cdmaEnhancedRoamingIndicatorDisplayNumber != ERI_OFF }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+
override val dataEnabled: StateFlow<Boolean> =
telephonyPollingEvent
.mapLatest { dataConnectionAllowed() }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
index a26f28a..15b70f9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
@@ -54,6 +54,13 @@
/** True if this line of service is emergency-only */
val isEmergencyOnly: StateFlow<Boolean>
+ /**
+ * True if this connection is considered roaming. The roaming bit can come from [ServiceState],
+ * or directly from the telephony manager's CDMA ERI number value. Note that we don't consider a
+ * connection to be roaming while carrier network change is active
+ */
+ val isRoaming: StateFlow<Boolean>
+
/** Int describing the connection strength. 0-4 OR 1-5. See [numberOfLevels] */
val level: StateFlow<Int>
@@ -95,6 +102,18 @@
.mapLatest { it.isEmergencyOnly }
.stateIn(scope, SharingStarted.WhileSubscribed(), false)
+ override val isRoaming: StateFlow<Boolean> =
+ combine(connectionInfo, connectionRepository.cdmaRoaming) { connection, cdmaRoaming ->
+ if (connection.carrierNetworkChangeActive) {
+ false
+ } else if (connection.isGsm) {
+ connection.isRoaming
+ } else {
+ cdmaRoaming
+ }
+ }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+
override val level: StateFlow<Int> =
connectionInfo
.mapLatest { connection ->
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
index 67ea139..4455801 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
@@ -21,6 +21,7 @@
import android.view.View.VISIBLE
import android.view.ViewGroup
import android.widget.ImageView
+import android.widget.Space
import androidx.core.view.isVisible
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
@@ -29,7 +30,6 @@
import com.android.systemui.common.ui.binder.IconViewBinder
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconViewModel
-import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.launch
@@ -43,6 +43,8 @@
val networkTypeView = view.requireViewById<ImageView>(R.id.mobile_type)
val iconView = view.requireViewById<ImageView>(R.id.mobile_signal)
val mobileDrawable = SignalDrawable(view.context).also { iconView.setImageDrawable(it) }
+ val roamingView = view.requireViewById<ImageView>(R.id.mobile_roaming)
+ val roamingSpace = view.requireViewById<Space>(R.id.mobile_roaming_space)
view.isVisible = true
iconView.isVisible = true
@@ -64,12 +66,21 @@
}
}
+ // Set the roaming indicator
+ launch {
+ viewModel.roaming.distinctUntilChanged().collect { isRoaming ->
+ roamingView.isVisible = isRoaming
+ roamingSpace.isVisible = isRoaming
+ }
+ }
+
// Set the tint
launch {
viewModel.tint.collect { tint ->
val tintList = ColorStateList.valueOf(tint)
iconView.imageTintList = tintList
networkTypeView.imageTintList = tintList
+ roamingView.imageTintList = tintList
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
index 8ebd718..f4d6111 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
@@ -87,5 +87,7 @@
}
}
+ val roaming: Flow<Boolean> = iconInteractor.isRoaming
+
val tint: Flow<Int> = flowOf(Color.CYAN)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiActivityModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/model/DataActivityModel.kt
similarity index 75%
rename from packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiActivityModel.kt
rename to packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/model/DataActivityModel.kt
index a4ca41c..9b41567 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiActivityModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/model/DataActivityModel.kt
@@ -14,20 +14,19 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.pipeline.wifi.shared.model
+package com.android.systemui.statusbar.pipeline.shared.data.model
import com.android.systemui.log.table.Diffable
import com.android.systemui.log.table.TableRowLogger
-/** Provides information on the current wifi activity. */
-data class WifiActivityModel(
- /** True if the wifi has activity in (download). */
+/** Provides information about the current data activity direction */
+data class DataActivityModel(
+ /** True if the connection has activity in (download). */
val hasActivityIn: Boolean,
- /** True if the wifi has activity out (upload). */
+ /** True if the connection has activity out (upload). */
val hasActivityOut: Boolean,
-) : Diffable<WifiActivityModel> {
-
- override fun logDiffs(prevVal: WifiActivityModel, row: TableRowLogger) {
+) : Diffable<DataActivityModel> {
+ override fun logDiffs(prevVal: DataActivityModel, row: TableRowLogger) {
if (prevVal.hasActivityIn != hasActivityIn) {
row.logChange(COL_ACTIVITY_IN, hasActivityIn)
}
@@ -42,6 +41,6 @@
}
}
-const val ACTIVITY_PREFIX = "wifiActivity"
+const val ACTIVITY_PREFIX = "dataActivity"
private const val COL_ACTIVITY_IN = "in"
private const val COL_ACTIVITY_OUT = "out"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt
index 0c9c1cc..8144198 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt
@@ -42,9 +42,8 @@
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.SB_LOGGING_TAG
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logInputChange
+import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
-import com.android.systemui.statusbar.pipeline.wifi.shared.model.ACTIVITY_PREFIX
-import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiActivityModel
import java.util.concurrent.Executor
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
@@ -74,7 +73,7 @@
val wifiNetwork: StateFlow<WifiNetworkModel>
/** Observable for the current wifi network activity. */
- val wifiActivity: StateFlow<WifiActivityModel>
+ val wifiActivity: StateFlow<DataActivityModel>
}
/** Real implementation of [WifiRepository]. */
@@ -230,7 +229,7 @@
initialValue = WIFI_NETWORK_DEFAULT
)
- override val wifiActivity: StateFlow<WifiActivityModel> =
+ override val wifiActivity: StateFlow<DataActivityModel> =
if (wifiManager == null) {
Log.w(SB_LOGGING_TAG, "Null WifiManager; skipping activity callback")
flowOf(ACTIVITY_DEFAULT)
@@ -238,7 +237,7 @@
conflatedCallbackFlow {
val callback = TrafficStateCallback { state ->
logger.logInputChange("onTrafficStateChange", prettyPrintActivity(state))
- trySend(trafficStateToWifiActivityModel(state))
+ trySend(trafficStateToDataActivityModel(state))
}
wifiManager.registerTrafficStateCallback(mainExecutor, callback)
awaitClose { wifiManager.unregisterTrafficStateCallback(callback) }
@@ -256,7 +255,9 @@
)
companion object {
- val ACTIVITY_DEFAULT = WifiActivityModel(hasActivityIn = false, hasActivityOut = false)
+ private const val ACTIVITY_PREFIX = "wifiActivity"
+
+ val ACTIVITY_DEFAULT = DataActivityModel(hasActivityIn = false, hasActivityOut = false)
// Start out with no known wifi network.
// Note: [WifiStatusTracker] (the old implementation of connectivity logic) does do an
// initial fetch to get a starting wifi network. But, it uses a deprecated API
@@ -265,8 +266,8 @@
// NetworkCallback inside [wifiNetwork] for our wifi network information.
val WIFI_NETWORK_DEFAULT = WifiNetworkModel.Inactive
- private fun trafficStateToWifiActivityModel(state: Int): WifiActivityModel {
- return WifiActivityModel(
+ private fun trafficStateToDataActivityModel(state: Int): DataActivityModel {
+ return DataActivityModel(
hasActivityIn = state == TrafficStateCallback.DATA_ACTIVITY_IN ||
state == TrafficStateCallback.DATA_ACTIVITY_INOUT,
hasActivityOut = state == TrafficStateCallback.DATA_ACTIVITY_OUT ||
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt
index ec935fe..93041ce 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt
@@ -19,10 +19,10 @@
import android.net.wifi.WifiManager
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot
+import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository
import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
-import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiActivityModel
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
@@ -50,8 +50,8 @@
/** Our current wifi network. See [WifiNetworkModel]. */
val wifiNetwork: Flow<WifiNetworkModel>
- /** Our current wifi activity. See [WifiActivityModel]. */
- val activity: StateFlow<WifiActivityModel>
+ /** Our current wifi activity. See [DataActivityModel]. */
+ val activity: StateFlow<DataActivityModel>
/** True if we're configured to force-hide the wifi icon and false otherwise. */
val isForceHidden: Flow<Boolean>
@@ -82,7 +82,7 @@
override val wifiNetwork: Flow<WifiNetworkModel> = wifiRepository.wifiNetwork
- override val activity: StateFlow<WifiActivityModel> = wifiRepository.wifiActivity
+ override val activity: StateFlow<DataActivityModel> = wifiRepository.wifiActivity
override val isForceHidden: Flow<Boolean> = connectivityRepository.forceHiddenSlots.map {
it.contains(ConnectivitySlot.WIFI)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt
index ec7ba65..07a7595 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt
@@ -37,10 +37,10 @@
import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logOutputChange
+import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor
import com.android.systemui.statusbar.pipeline.wifi.shared.WifiConstants
-import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiActivityModel
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
@@ -147,7 +147,7 @@
)
/** The wifi activity status. Null if we shouldn't display the activity status. */
- private val activity: Flow<WifiActivityModel?> =
+ private val activity: Flow<DataActivityModel?> =
if (!wifiConstants.shouldShowActivityConfig) {
flowOf(null)
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/stylus/StylusFirstUsageListener.kt b/packages/SystemUI/src/com/android/systemui/stylus/StylusFirstUsageListener.kt
new file mode 100644
index 0000000..154c6e2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/stylus/StylusFirstUsageListener.kt
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.stylus
+
+import android.content.Context
+import android.hardware.BatteryState
+import android.hardware.input.InputManager
+import android.os.Handler
+import android.util.Log
+import android.view.InputDevice
+import androidx.annotation.VisibleForTesting
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import java.util.concurrent.Executor
+import javax.inject.Inject
+
+/**
+ * A listener that detects when a stylus has first been used, by detecting 1) the presence of an
+ * internal SOURCE_STYLUS with a battery, or 2) any added SOURCE_STYLUS device with a bluetooth
+ * address.
+ */
+@SysUISingleton
+class StylusFirstUsageListener
+@Inject
+constructor(
+ private val context: Context,
+ private val inputManager: InputManager,
+ private val stylusManager: StylusManager,
+ private val featureFlags: FeatureFlags,
+ @Background private val executor: Executor,
+ @Background private val handler: Handler,
+) : CoreStartable, StylusManager.StylusCallback, InputManager.InputDeviceBatteryListener {
+
+ // Set must be only accessed from the background handler, which is the same handler that
+ // runs the StylusManager callbacks.
+ private val internalStylusDeviceIds: MutableSet<Int> = mutableSetOf()
+ @VisibleForTesting var hasStarted = false
+
+ override fun start() {
+ if (true) return // TODO(b/261826950): remove on main
+ if (hasStarted) return
+ if (!featureFlags.isEnabled(Flags.TRACK_STYLUS_EVER_USED)) return
+ if (inputManager.isStylusEverUsed(context)) return
+ if (!hostDeviceSupportsStylusInput()) return
+
+ hasStarted = true
+ inputManager.inputDeviceIds.forEach(this::onStylusAdded)
+ stylusManager.registerCallback(this)
+ stylusManager.startListener()
+ }
+
+ override fun onStylusAdded(deviceId: Int) {
+ if (!hasStarted) return
+
+ val device = inputManager.getInputDevice(deviceId) ?: return
+ if (device.isExternal || !device.supportsSource(InputDevice.SOURCE_STYLUS)) return
+
+ try {
+ inputManager.addInputDeviceBatteryListener(deviceId, executor, this)
+ internalStylusDeviceIds += deviceId
+ } catch (e: SecurityException) {
+ Log.e(TAG, "$e: Failed to register battery listener for $deviceId ${device.name}.")
+ }
+ }
+
+ override fun onStylusRemoved(deviceId: Int) {
+ if (!hasStarted) return
+
+ if (!internalStylusDeviceIds.contains(deviceId)) return
+ try {
+ inputManager.removeInputDeviceBatteryListener(deviceId, this)
+ internalStylusDeviceIds.remove(deviceId)
+ } catch (e: SecurityException) {
+ Log.e(TAG, "$e: Failed to remove registered battery listener for $deviceId.")
+ }
+ }
+
+ override fun onStylusBluetoothConnected(deviceId: Int, btAddress: String) {
+ if (!hasStarted) return
+
+ onRemoteDeviceFound()
+ }
+
+ override fun onBatteryStateChanged(
+ deviceId: Int,
+ eventTimeMillis: Long,
+ batteryState: BatteryState
+ ) {
+ if (!hasStarted) return
+
+ if (batteryState.isPresent) {
+ onRemoteDeviceFound()
+ }
+ }
+
+ private fun onRemoteDeviceFound() {
+ inputManager.setStylusEverUsed(context, true)
+ cleanupListeners()
+ }
+
+ private fun cleanupListeners() {
+ stylusManager.unregisterCallback(this)
+ handler.post {
+ internalStylusDeviceIds.forEach {
+ inputManager.removeInputDeviceBatteryListener(it, this)
+ }
+ }
+ }
+
+ private fun hostDeviceSupportsStylusInput(): Boolean {
+ return inputManager.inputDeviceIds
+ .asSequence()
+ .mapNotNull { inputManager.getInputDevice(it) }
+ .any { it.supportsSource(InputDevice.SOURCE_STYLUS) && !it.isExternal }
+ }
+
+ companion object {
+ private val TAG = StylusFirstUsageListener::class.simpleName.orEmpty()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
index db7315f..532fbaa 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
@@ -30,12 +30,16 @@
import android.view.accessibility.AccessibilityManager.FLAG_CONTENT_CONTROLS
import android.view.accessibility.AccessibilityManager.FLAG_CONTENT_ICONS
import android.view.accessibility.AccessibilityManager.FLAG_CONTENT_TEXT
+import androidx.annotation.CallSuper
import com.android.systemui.CoreStartable
+import com.android.systemui.Dumpable
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.dump.DumpManager
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.systemui.util.time.SystemClock
import com.android.systemui.util.wakelock.WakeLock
+import java.io.PrintWriter
/**
* A generic controller that can temporarily display a new view in a new window.
@@ -69,11 +73,12 @@
@Main private val mainExecutor: DelayableExecutor,
private val accessibilityManager: AccessibilityManager,
private val configurationController: ConfigurationController,
+ private val dumpManager: DumpManager,
private val powerManager: PowerManager,
@LayoutRes private val viewLayoutRes: Int,
private val wakeLockBuilder: WakeLock.Builder,
private val systemClock: SystemClock,
-) : CoreStartable {
+) : CoreStartable, Dumpable {
/**
* Window layout params that will be used as a starting point for the [windowLayoutParams] of
* all subclasses.
@@ -109,6 +114,11 @@
return activeViews.getOrNull(0)
}
+ @CallSuper
+ override fun start() {
+ dumpManager.registerNormalDumpable(this)
+ }
+
/**
* Displays the view with the provided [newInfo].
*
@@ -382,6 +392,19 @@
}
}
+ @Synchronized
+ @CallSuper
+ override fun dump(pw: PrintWriter, args: Array<out String>) {
+ pw.println("Current time millis: ${systemClock.currentTimeMillis()}")
+ pw.println("Active views size: ${activeViews.size}")
+ activeViews.forEachIndexed { index, displayInfo ->
+ pw.println("View[$index]:")
+ pw.println(" info=${displayInfo.info}")
+ pw.println(" hasView=${displayInfo.view != null}")
+ pw.println(" timeExpiration=${displayInfo.timeExpirationMillis}")
+ }
+ }
+
/**
* A method implemented by subclasses to update [currentView] based on [newInfo].
*/
@@ -445,8 +468,6 @@
*/
var cancelViewTimeout: Runnable?,
)
-
- // TODO(b/258019006): Add a dump method that dumps the currently active views.
}
private const val REMOVAL_REASON_TIMEOUT = "TIMEOUT"
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
index f37ef82..fd2c705 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
@@ -41,6 +41,7 @@
import com.android.systemui.common.ui.binder.TintedIconViewBinder
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.statusbar.policy.ConfigurationController
@@ -75,6 +76,7 @@
@Main mainExecutor: DelayableExecutor,
accessibilityManager: AccessibilityManager,
configurationController: ConfigurationController,
+ dumpManager: DumpManager,
powerManager: PowerManager,
private val falsingManager: FalsingManager,
private val falsingCollector: FalsingCollector,
@@ -89,6 +91,7 @@
mainExecutor,
accessibilityManager,
configurationController,
+ dumpManager,
powerManager,
R.layout.chipbar,
wakeLockBuilder,
@@ -225,8 +228,6 @@
return requireViewById(R.id.chipbar_inner)
}
- override fun start() {}
-
override fun getTouchableRegion(view: View, outRect: Rect) {
viewUtil.setRectToViewWindowLocation(view, outRect)
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java
index fa9bab2..1059543 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java
@@ -150,10 +150,4 @@
getContext().getResources().getString(R.string.kg_prompt_reason_restart_password),
false);
}
-
- @Test
- public void testReset() {
- mKeyguardAbsKeyInputViewController.reset();
- verify(mKeyguardMessageAreaController).setMessage("", false);
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamCallbackControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamCallbackControllerTest.kt
new file mode 100644
index 0000000..003efbf
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamCallbackControllerTest.kt
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.dreams
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.reset
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class DreamCallbackControllerTest : SysuiTestCase() {
+
+ @Mock private lateinit var callback: DreamCallbackController.DreamCallback
+
+ private lateinit var underTest: DreamCallbackController
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ underTest = DreamCallbackController()
+ }
+
+ @Test
+ fun testOnWakeUpInvokesCallback() {
+ underTest.addCallback(callback)
+ underTest.onWakeUp()
+ verify(callback).onWakeUp()
+
+ // Adding twice should not invoke twice
+ reset(callback)
+ underTest.addCallback(callback)
+ underTest.onWakeUp()
+ verify(callback, times(1)).onWakeUp()
+
+ // After remove, no call to callback
+ reset(callback)
+ underTest.removeCallback(callback)
+ underTest.onWakeUp()
+ verify(callback, never()).onWakeUp()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt
index 8e689cf..6c23254 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt
@@ -1,18 +1,25 @@
package com.android.systemui.dreams
-import android.animation.Animator
import android.animation.AnimatorSet
import android.testing.AndroidTestingRunner
+import android.view.View
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.dreams.complication.ComplicationHostViewController
+import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel
import com.android.systemui.statusbar.BlurUtils
-import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.runBlocking
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
+import org.mockito.Mockito.anyLong
+import org.mockito.Mockito.eq
import org.mockito.Mockito.never
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
@@ -28,14 +35,6 @@
private const val DREAM_IN_COMPLICATIONS_ANIMATION_DURATION = 3L
private const val DREAM_IN_TRANSLATION_Y_DISTANCE = 6
private const val DREAM_IN_TRANSLATION_Y_DURATION = 7L
- private const val DREAM_OUT_TRANSLATION_Y_DISTANCE = 6
- private const val DREAM_OUT_TRANSLATION_Y_DURATION = 7L
- private const val DREAM_OUT_TRANSLATION_Y_DELAY_BOTTOM = 8L
- private const val DREAM_OUT_TRANSLATION_Y_DELAY_TOP = 9L
- private const val DREAM_OUT_ALPHA_DURATION = 10L
- private const val DREAM_OUT_ALPHA_DELAY_BOTTOM = 11L
- private const val DREAM_OUT_ALPHA_DELAY_TOP = 12L
- private const val DREAM_OUT_BLUR_DURATION = 13L
}
@Mock private lateinit var mockAnimator: AnimatorSet
@@ -43,6 +42,8 @@
@Mock private lateinit var hostViewController: ComplicationHostViewController
@Mock private lateinit var statusBarViewController: DreamOverlayStatusBarViewController
@Mock private lateinit var stateController: DreamOverlayStateController
+ @Mock private lateinit var configController: ConfigurationController
+ @Mock private lateinit var transitionViewModel: DreamingToLockscreenTransitionViewModel
private lateinit var controller: DreamOverlayAnimationsController
@Before
@@ -55,71 +56,48 @@
statusBarViewController,
stateController,
DREAM_BLUR_RADIUS,
+ transitionViewModel,
+ configController,
DREAM_IN_BLUR_ANIMATION_DURATION,
DREAM_IN_COMPLICATIONS_ANIMATION_DURATION,
DREAM_IN_TRANSLATION_Y_DISTANCE,
DREAM_IN_TRANSLATION_Y_DURATION,
- DREAM_OUT_TRANSLATION_Y_DISTANCE,
- DREAM_OUT_TRANSLATION_Y_DURATION,
- DREAM_OUT_TRANSLATION_Y_DELAY_BOTTOM,
- DREAM_OUT_TRANSLATION_Y_DELAY_TOP,
- DREAM_OUT_ALPHA_DURATION,
- DREAM_OUT_ALPHA_DELAY_BOTTOM,
- DREAM_OUT_ALPHA_DELAY_TOP,
- DREAM_OUT_BLUR_DURATION
)
+
+ val mockView: View = mock()
+ whenever(mockView.resources).thenReturn(mContext.resources)
+
+ runBlocking(Dispatchers.Main.immediate) { controller.init(mockView) }
}
@Test
- fun testExitAnimationOnEnd() {
- val mockCallback: () -> Unit = mock()
+ fun testWakeUpCallsExecutor() {
+ val mockExecutor: DelayableExecutor = mock()
+ val mockCallback: Runnable = mock()
- controller.startExitAnimations(
- view = mock(),
+ controller.wakeUp(
doneCallback = mockCallback,
- animatorBuilder = { mockAnimator }
+ executor = mockExecutor,
)
- val captor = argumentCaptor<Animator.AnimatorListener>()
- verify(mockAnimator).addListener(captor.capture())
- val listener = captor.value
-
- verify(mockCallback, never()).invoke()
- listener.onAnimationEnd(mockAnimator)
- verify(mockCallback, times(1)).invoke()
+ verify(mockExecutor).executeDelayed(eq(mockCallback), anyLong())
}
@Test
- fun testCancellation() {
- controller.startExitAnimations(
- view = mock(),
- doneCallback = mock(),
- animatorBuilder = { mockAnimator }
- )
-
- verify(mockAnimator, never()).cancel()
- controller.cancelAnimations()
- verify(mockAnimator, times(1)).cancel()
- }
-
- @Test
- fun testExitAfterStartWillCancel() {
+ fun testWakeUpAfterStartWillCancel() {
val mockStartAnimator: AnimatorSet = mock()
- val mockExitAnimator: AnimatorSet = mock()
- controller.startEntryAnimations(view = mock(), animatorBuilder = { mockStartAnimator })
+ controller.startEntryAnimations(animatorBuilder = { mockStartAnimator })
verify(mockStartAnimator, never()).cancel()
- controller.startExitAnimations(
- view = mock(),
+ controller.wakeUp(
doneCallback = mock(),
- animatorBuilder = { mockExitAnimator }
+ executor = mock(),
)
// Verify that we cancelled the start animator in favor of the exit
// animator.
verify(mockStartAnimator, times(1)).cancel()
- verify(mockExitAnimator, never()).cancel()
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
index 73c226d..2799a25 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
@@ -203,7 +203,7 @@
mController.onViewAttached();
- verify(mAnimationsController).startEntryAnimations(mDreamOverlayContainerView);
+ verify(mAnimationsController).startEntryAnimations();
verify(mAnimationsController, never()).cancelAnimations();
}
@@ -213,7 +213,7 @@
mController.onViewAttached();
- verify(mAnimationsController, never()).startEntryAnimations(mDreamOverlayContainerView);
+ verify(mAnimationsController, never()).startEntryAnimations();
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
index ffb8342..d6f8dea 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
@@ -113,6 +113,9 @@
@Mock
UiEventLogger mUiEventLogger;
+ @Mock
+ DreamCallbackController mDreamCallbackController;
+
@Captor
ArgumentCaptor<View> mViewCaptor;
@@ -141,7 +144,8 @@
mStateController,
mKeyguardUpdateMonitor,
mUiEventLogger,
- LOW_LIGHT_COMPONENT);
+ LOW_LIGHT_COMPONENT,
+ mDreamCallbackController);
}
@Test
@@ -353,6 +357,7 @@
mService.onWakeUp(callback);
mMainExecutor.runAllReady();
verify(mDreamOverlayContainerViewController).wakeUp(callback, mMainExecutor);
+ verify(mDreamCallbackController).onWakeUp();
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
index 5deac19..563d44d3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
@@ -28,6 +28,8 @@
import com.android.systemui.doze.DozeMachine
import com.android.systemui.doze.DozeTransitionCallback
import com.android.systemui.doze.DozeTransitionListener
+import com.android.systemui.dreams.DreamCallbackController
+import com.android.systemui.dreams.DreamCallbackController.DreamCallback
import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
@@ -66,6 +68,7 @@
@Mock private lateinit var dozeTransitionListener: DozeTransitionListener
@Mock private lateinit var authController: AuthController
@Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+ @Mock private lateinit var dreamCallbackController: DreamCallbackController
private lateinit var underTest: KeyguardRepositoryImpl
@@ -83,6 +86,7 @@
keyguardUpdateMonitor,
dozeTransitionListener,
authController,
+ dreamCallbackController,
)
}
@@ -318,7 +322,7 @@
}
@Test
- fun isDreaming() =
+ fun isDreamingFromKeyguardUpdateMonitor() =
runTest(UnconfinedTestDispatcher()) {
whenever(keyguardUpdateMonitor.isDreaming()).thenReturn(false)
var latest: Boolean? = null
@@ -339,6 +343,26 @@
}
@Test
+ fun isDreamingFromDreamCallbackController() =
+ runTest(UnconfinedTestDispatcher()) {
+ whenever(keyguardUpdateMonitor.isDreaming()).thenReturn(true)
+ var latest: Boolean? = null
+ val job = underTest.isDreaming.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isTrue()
+
+ val listener =
+ withArgCaptor<DreamCallbackController.DreamCallback> {
+ verify(dreamCallbackController).addCallback(capture())
+ }
+
+ listener.onWakeUp()
+ assertThat(latest).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
fun biometricUnlockState() =
runTest(UnconfinedTestDispatcher()) {
val values = mutableListOf<BiometricUnlockModel>()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt
new file mode 100644
index 0000000..81950f1
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.domain.interactor.DreamingTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.AnimationParams
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel.Companion.DREAM_OVERLAY_ALPHA
+import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel.Companion.DREAM_OVERLAY_TRANSLATION_Y
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@SmallTest
+@RunWith(JUnit4::class)
+class DreamingToLockscreenTransitionViewModelTest : SysuiTestCase() {
+ private lateinit var underTest: DreamingToLockscreenTransitionViewModel
+ private lateinit var repository: FakeKeyguardTransitionRepository
+
+ @Before
+ fun setUp() {
+ repository = FakeKeyguardTransitionRepository()
+ val interactor = KeyguardTransitionInteractor(repository)
+ underTest = DreamingToLockscreenTransitionViewModel(interactor)
+ }
+
+ @Test
+ fun dreamOverlayTranslationY() =
+ runTest(UnconfinedTestDispatcher()) {
+ val values = mutableListOf<Float>()
+
+ val pixels = 100
+ val job =
+ underTest.dreamOverlayTranslationY(pixels).onEach { values.add(it) }.launchIn(this)
+
+ repository.sendTransitionStep(step(0f))
+ repository.sendTransitionStep(step(0.3f))
+ repository.sendTransitionStep(step(0.5f))
+ repository.sendTransitionStep(step(1f))
+
+ // Only two values should be present, since the dream overlay runs for a small fraction
+ // of
+ // the overall animation time
+ assertThat(values.size).isEqualTo(2)
+ assertThat(values[0])
+ .isEqualTo(
+ EMPHASIZED_ACCELERATE.getInterpolation(
+ animValue(0f, DREAM_OVERLAY_TRANSLATION_Y)
+ ) * pixels
+ )
+ assertThat(values[1])
+ .isEqualTo(
+ EMPHASIZED_ACCELERATE.getInterpolation(
+ animValue(0.3f, DREAM_OVERLAY_TRANSLATION_Y)
+ ) * pixels
+ )
+
+ job.cancel()
+ }
+
+ @Test
+ fun dreamOverlayFadeOut() =
+ runTest(UnconfinedTestDispatcher()) {
+ val values = mutableListOf<Float>()
+
+ val job = underTest.dreamOverlayAlpha.onEach { values.add(it) }.launchIn(this)
+
+ repository.sendTransitionStep(step(0f))
+ repository.sendTransitionStep(step(0.1f))
+ repository.sendTransitionStep(step(0.5f))
+ repository.sendTransitionStep(step(1f))
+
+ // Only two values should be present, since the dream overlay runs for a small fraction
+ // of
+ // the overall animation time
+ assertThat(values.size).isEqualTo(2)
+ assertThat(values[0]).isEqualTo(1f - animValue(0f, DREAM_OVERLAY_ALPHA))
+ assertThat(values[1]).isEqualTo(1f - animValue(0.1f, DREAM_OVERLAY_ALPHA))
+
+ job.cancel()
+ }
+
+ private fun animValue(stepValue: Float, params: AnimationParams): Float {
+ val totalDuration = TO_LOCKSCREEN_DURATION
+ val startValue = (params.startTime / totalDuration).toFloat()
+
+ val multiplier = (totalDuration / params.duration).toFloat()
+ return (stepValue - startValue) * multiplier
+ }
+
+ private fun step(value: Float): TransitionStep {
+ return TransitionStep(
+ from = KeyguardState.DREAMING,
+ to = KeyguardState.LOCKSCREEN,
+ value = value,
+ transitionState = TransitionState.RUNNING,
+ ownerName = "DreamingToLockscreenTransitionViewModelTest"
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/FakeMediaTttChipControllerReceiver.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/FakeMediaTttChipControllerReceiver.kt
index bad3f03..07a3109 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/FakeMediaTttChipControllerReceiver.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/FakeMediaTttChipControllerReceiver.kt
@@ -22,6 +22,7 @@
import android.view.ViewGroup
import android.view.WindowManager
import android.view.accessibility.AccessibilityManager
+import com.android.systemui.dump.DumpManager
import com.android.systemui.media.taptotransfer.MediaTttFlags
import com.android.systemui.media.taptotransfer.common.MediaTttLogger
import com.android.systemui.statusbar.CommandQueue
@@ -39,6 +40,7 @@
mainExecutor: DelayableExecutor,
accessibilityManager: AccessibilityManager,
configurationController: ConfigurationController,
+ dumpManager: DumpManager,
powerManager: PowerManager,
mainHandler: Handler,
mediaTttFlags: MediaTttFlags,
@@ -55,6 +57,7 @@
mainExecutor,
accessibilityManager,
configurationController,
+ dumpManager,
powerManager,
mainHandler,
mediaTttFlags,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
index ef0bfb7..03ba3d3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
@@ -34,6 +34,7 @@
import com.android.internal.logging.testing.UiEventLoggerFake
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
+import com.android.systemui.dump.DumpManager
import com.android.systemui.media.taptotransfer.MediaTttFlags
import com.android.systemui.media.taptotransfer.common.MediaTttLogger
import com.android.systemui.statusbar.CommandQueue
@@ -73,6 +74,8 @@
@Mock
private lateinit var configurationController: ConfigurationController
@Mock
+ private lateinit var dumpManager: DumpManager
+ @Mock
private lateinit var mediaTttFlags: MediaTttFlags
@Mock
private lateinit var powerManager: PowerManager
@@ -122,6 +125,7 @@
fakeExecutor,
accessibilityManager,
configurationController,
+ dumpManager,
powerManager,
Handler.getMain(),
mediaTttFlags,
@@ -150,6 +154,7 @@
FakeExecutor(FakeSystemClock()),
accessibilityManager,
configurationController,
+ dumpManager,
powerManager,
Handler.getMain(),
mediaTttFlags,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt
index b03a545..4cc12c7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt
@@ -38,6 +38,7 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.common.shared.model.Text.Companion.loadText
+import com.android.systemui.dump.DumpManager
import com.android.systemui.media.taptotransfer.MediaTttFlags
import com.android.systemui.media.taptotransfer.common.MediaTttLogger
import com.android.systemui.plugins.FalsingManager
@@ -81,6 +82,7 @@
@Mock private lateinit var applicationInfo: ApplicationInfo
@Mock private lateinit var commandQueue: CommandQueue
@Mock private lateinit var configurationController: ConfigurationController
+ @Mock private lateinit var dumpManager: DumpManager
@Mock private lateinit var falsingManager: FalsingManager
@Mock private lateinit var falsingCollector: FalsingCollector
@Mock private lateinit var chipbarLogger: ChipbarLogger
@@ -137,6 +139,7 @@
fakeExecutor,
accessibilityManager,
configurationController,
+ dumpManager,
powerManager,
falsingManager,
falsingCollector,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt
index 01411c9..0b9fbd9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt
@@ -84,7 +84,7 @@
ContentDescription.Resource(R.string.accessibility_quick_settings_settings)
)
)
- assertThat(settings.background).isEqualTo(R.drawable.qs_footer_action_circle)
+ assertThat(settings.backgroundColor).isEqualTo(R.attr.offStateColor)
assertThat(settings.iconTint).isNull()
}
@@ -105,7 +105,7 @@
ContentDescription.Resource(R.string.accessibility_quick_settings_power_menu)
)
)
- assertThat(power.background).isEqualTo(R.drawable.qs_footer_action_circle_color)
+ assertThat(power.backgroundColor).isEqualTo(com.android.internal.R.attr.colorAccent)
assertThat(power.iconTint)
.isEqualTo(
Utils.getColorAttrDefaultColor(
@@ -170,7 +170,7 @@
assertThat(userSwitcher).isNotNull()
assertThat(userSwitcher!!.icon)
.isEqualTo(Icon.Loaded(picture, ContentDescription.Loaded("Signed in as foo")))
- assertThat(userSwitcher.background).isEqualTo(R.drawable.qs_footer_action_circle)
+ assertThat(userSwitcher.backgroundColor).isEqualTo(R.attr.offStateColor)
// Change the current user name.
userSwitcherControllerWrapper.currentUserName = "bar"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
index 99b58a3..8d96932 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
@@ -660,7 +660,6 @@
createController();
String message = mContext.getString(R.string.keyguard_retry);
when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(true);
- when(mKeyguardUpdateMonitor.getIsFaceAuthenticated()).thenReturn(false);
when(mKeyguardUpdateMonitor.isFaceEnrolled()).thenReturn(true);
mController.setVisible(true);
@@ -671,21 +670,6 @@
}
@Test
- public void transientIndication_swipeUpToRetry_faceAuthenticated() {
- createController();
- String message = mContext.getString(R.string.keyguard_retry);
- when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(true);
- when(mKeyguardUpdateMonitor.getIsFaceAuthenticated()).thenReturn(true);
- when(mKeyguardUpdateMonitor.isFaceEnrolled()).thenReturn(true);
-
- mController.setVisible(true);
- mController.getKeyguardCallback().onBiometricError(FACE_ERROR_TIMEOUT,
- "A message", BiometricSourceType.FACE);
-
- verify(mStatusBarKeyguardViewManager, never()).setKeyguardMessage(eq(message), any());
- }
-
- @Test
public void faceErrorTimeout_whenFingerprintEnrolled_doesNotShowMessage() {
createController();
when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index 07ea630..7622549 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -20,6 +20,7 @@
import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_ALL;
import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_GENTLE;
+import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.RUBBER_BAND_FACTOR_NORMAL;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
@@ -47,6 +48,7 @@
import android.graphics.Rect;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
+import android.testing.TestableResources;
import android.util.MathUtils;
import android.view.MotionEvent;
import android.view.View;
@@ -99,6 +101,7 @@
private NotificationStackScrollLayout mStackScroller; // Normally test this
private NotificationStackScrollLayout mStackScrollerInternal; // See explanation below
private AmbientState mAmbientState;
+ private TestableResources mTestableResources;
@Rule public MockitoRule mockito = MockitoJUnit.rule();
@Mock private CentralSurfaces mCentralSurfaces;
@@ -123,6 +126,7 @@
@UiThreadTest
public void setUp() throws Exception {
allowTestableLooperAsMainThread();
+ mTestableResources = mContext.getOrCreateTestableResources();
// Interact with real instance of AmbientState.
mAmbientState = spy(new AmbientState(
@@ -394,7 +398,7 @@
@Test
@UiThreadTest
public void testSetExpandedHeight_withSplitShade_doesntInterpolateStackHeight() {
- mContext.getOrCreateTestableResources()
+ mTestableResources
.addOverride(R.bool.config_use_split_notification_shade, /* value= */ true);
final int[] expectedStackHeight = {0};
@@ -405,12 +409,12 @@
.isEqualTo(expectedStackHeight[0]);
});
- mContext.getOrCreateTestableResources()
+ mTestableResources
.addOverride(R.bool.config_use_split_notification_shade, /* value= */ false);
expectedStackHeight[0] = 0;
mStackScroller.setExpandedHeight(100f);
- mContext.getOrCreateTestableResources()
+ mTestableResources
.addOverride(R.bool.config_use_split_notification_shade, /* value= */ true);
expectedStackHeight[0] = 100;
mStackScroller.setExpandedHeight(100f);
@@ -781,6 +785,39 @@
assertEquals(mAmbientState.getScrollY(), 0);
}
+ @Test
+ public void testSplitShade_hasTopOverscroll() {
+ mTestableResources
+ .addOverride(R.bool.config_use_split_notification_shade, /* value= */ true);
+ mStackScroller.updateSplitNotificationShade();
+ mAmbientState.setExpansionFraction(1f);
+
+ int topOverscrollPixels = 100;
+ mStackScroller.setOverScrolledPixels(topOverscrollPixels, true, false);
+
+ float expectedTopOverscrollAmount = topOverscrollPixels * RUBBER_BAND_FACTOR_NORMAL;
+ assertEquals(expectedTopOverscrollAmount, mStackScroller.getCurrentOverScrollAmount(true));
+ assertEquals(expectedTopOverscrollAmount, mAmbientState.getStackY());
+ }
+
+ @Test
+ public void testNormalShade_hasNoTopOverscroll() {
+ mTestableResources
+ .addOverride(R.bool.config_use_split_notification_shade, /* value= */ false);
+ mStackScroller.updateSplitNotificationShade();
+ mAmbientState.setExpansionFraction(1f);
+
+ int topOverscrollPixels = 100;
+ mStackScroller.setOverScrolledPixels(topOverscrollPixels, true, false);
+
+ float expectedTopOverscrollAmount = topOverscrollPixels * RUBBER_BAND_FACTOR_NORMAL;
+ assertEquals(expectedTopOverscrollAmount, mStackScroller.getCurrentOverScrollAmount(true));
+ // When not in split shade mode, then the overscroll effect is handled in
+ // NotificationPanelViewController and not in NotificationStackScrollLayout. Therefore
+ // mAmbientState must have stackY set to 0
+ assertEquals(0f, mAmbientState.getStackY());
+ }
+
private void setBarStateForTest(int state) {
// Can't inject this through the listener or we end up on the actual implementation
// rather than the mock because the spy just coppied the anonymous inner /shruggie.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
index 5265ec6..7b9929d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
@@ -30,6 +30,8 @@
private val _isDefaultDataSubscription = MutableStateFlow(true)
override val isDefaultDataSubscription = _isDefaultDataSubscription
+ override val cdmaRoaming = MutableStateFlow(false)
+
fun setConnectionInfo(model: MobileConnectionModel) {
_connectionInfo.value = model
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt
index e943de2..b2423da 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt
@@ -95,6 +95,7 @@
inflateStrength = testCase.inflateStrength,
activity = testCase.activity,
carrierNetworkChange = testCase.carrierNetworkChange,
+ roaming = testCase.roaming,
)
fakeNetworkEventFlow.value = networkModel
@@ -116,6 +117,7 @@
assertThat(connectionInfo.dataActivityDirection).isEqualTo(model.activity)
assertThat(connectionInfo.carrierNetworkChangeActive)
.isEqualTo(model.carrierNetworkChange)
+ assertThat(connectionInfo.isRoaming).isEqualTo(model.roaming)
// TODO(b/261029387): check these once we start handling them
assertThat(connectionInfo.isEmergencyOnly).isFalse()
@@ -138,6 +140,7 @@
val inflateStrength: Boolean,
@Annotation.DataActivityType val activity: Int,
val carrierNetworkChange: Boolean,
+ val roaming: Boolean,
) {
override fun toString(): String {
return "INPUT(level=$level, " +
@@ -146,7 +149,8 @@
"carrierId=$carrierId, " +
"inflateStrength=$inflateStrength, " +
"activity=$activity, " +
- "carrierNetworkChange=$carrierNetworkChange)"
+ "carrierNetworkChange=$carrierNetworkChange, " +
+ "roaming=$roaming)"
}
// Convenience for iterating test data and creating new cases
@@ -158,6 +162,7 @@
inflateStrength: Boolean? = null,
@Annotation.DataActivityType activity: Int? = null,
carrierNetworkChange: Boolean? = null,
+ roaming: Boolean? = null,
): TestCase =
TestCase(
level = level ?: this.level,
@@ -166,7 +171,8 @@
carrierId = carrierId ?: this.carrierId,
inflateStrength = inflateStrength ?: this.inflateStrength,
activity = activity ?: this.activity,
- carrierNetworkChange = carrierNetworkChange ?: this.carrierNetworkChange
+ carrierNetworkChange = carrierNetworkChange ?: this.carrierNetworkChange,
+ roaming = roaming ?: this.roaming,
)
}
@@ -193,6 +199,8 @@
TelephonyManager.DATA_ACTIVITY_INOUT
)
private val carrierNetworkChange = booleanList
+ // false first so the base case doesn't have roaming set (more common)
+ private val roaming = listOf(false, true)
@Parameters(name = "{0}") @JvmStatic fun data() = testData()
@@ -226,7 +234,8 @@
carrierIds.first(),
inflateStrength.first(),
activity.first(),
- carrierNetworkChange.first()
+ carrierNetworkChange.first(),
+ roaming.first(),
)
val tail =
@@ -237,6 +246,7 @@
inflateStrength.map { baseCase.modifiedBy(inflateStrength = it) },
activity.map { baseCase.modifiedBy(activity = it) },
carrierNetworkChange.map { baseCase.modifiedBy(carrierNetworkChange = it) },
+ roaming.map { baseCase.modifiedBy(roaming = it) }
)
.flatten()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt
index 32d0410..e4f29e2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt
@@ -292,6 +292,7 @@
assertThat(connectionInfo.dataActivityDirection).isEqualTo(model.activity)
assertThat(connectionInfo.carrierNetworkChangeActive)
.isEqualTo(model.carrierNetworkChange)
+ assertThat(connectionInfo.isRoaming).isEqualTo(model.roaming)
// TODO(b/261029387) check these once we start handling them
assertThat(connectionInfo.isEmergencyOnly).isFalse()
@@ -313,6 +314,7 @@
inflateStrength: Boolean? = false,
activity: Int? = null,
carrierNetworkChange: Boolean = false,
+ roaming: Boolean = false,
): FakeNetworkEventModel =
FakeNetworkEventModel.Mobile(
level = level,
@@ -322,4 +324,5 @@
inflateStrength = inflateStrength,
activity = activity,
carrierNetworkChange = carrierNetworkChange,
+ roaming = roaming,
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
index 1fc9c60..0b3e5b5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
@@ -32,6 +32,8 @@
import android.telephony.TelephonyManager.DATA_DISCONNECTED
import android.telephony.TelephonyManager.DATA_DISCONNECTING
import android.telephony.TelephonyManager.DATA_UNKNOWN
+import android.telephony.TelephonyManager.ERI_OFF
+import android.telephony.TelephonyManager.ERI_ON
import android.telephony.TelephonyManager.NETWORK_TYPE_LTE
import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN
import androidx.test.filters.SmallTest
@@ -402,6 +404,61 @@
job.cancel()
}
+ @Test
+ fun `roaming - cdma - queries telephony manager`() =
+ runBlocking(IMMEDIATE) {
+ var latest: Boolean? = null
+ // Start the telephony collection job so that cdmaRoaming starts updating
+ val telephonyJob = underTest.connectionInfo.launchIn(this)
+ val job = underTest.cdmaRoaming.onEach { latest = it }.launchIn(this)
+
+ val cb = getTelephonyCallbackForType<ServiceStateListener>()
+
+ val serviceState = ServiceState()
+ serviceState.roaming = false
+
+ // CDMA roaming is off, GSM roaming is off
+ whenever(telephonyManager.cdmaEnhancedRoamingIndicatorDisplayNumber).thenReturn(ERI_OFF)
+ cb.onServiceStateChanged(serviceState)
+
+ assertThat(latest).isFalse()
+
+ // CDMA roaming is off, GSM roaming is on
+ whenever(telephonyManager.cdmaEnhancedRoamingIndicatorDisplayNumber).thenReturn(ERI_ON)
+ cb.onServiceStateChanged(serviceState)
+
+ assertThat(latest).isTrue()
+
+ telephonyJob.cancel()
+ job.cancel()
+ }
+
+ @Test
+ fun `roaming - gsm - queries service state`() =
+ runBlocking(IMMEDIATE) {
+ var latest: Boolean? = null
+ val job = underTest.connectionInfo.onEach { latest = it.isRoaming }.launchIn(this)
+
+ val serviceState = ServiceState()
+ serviceState.roaming = false
+
+ val cb = getTelephonyCallbackForType<ServiceStateListener>()
+
+ // CDMA roaming is off, GSM roaming is off
+ whenever(telephonyManager.cdmaEnhancedRoamingIndicatorDisplayNumber).thenReturn(ERI_OFF)
+ cb.onServiceStateChanged(serviceState)
+
+ assertThat(latest).isFalse()
+
+ // CDMA roaming is off, GSM roaming is on
+ serviceState.roaming = true
+ cb.onServiceStateChanged(serviceState)
+
+ assertThat(latest).isTrue()
+
+ job.cancel()
+ }
+
private fun getTelephonyCallbacks(): List<TelephonyCallback> {
val callbackCaptor = argumentCaptor<TelephonyCallback>()
Mockito.verify(telephonyManager).registerTelephonyCallback(any(), callbackCaptor.capture())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
index 1ff1636a..0e2c38e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
@@ -30,6 +30,8 @@
private val _isEmergencyOnly = MutableStateFlow(false)
override val isEmergencyOnly = _isEmergencyOnly
+ override val isRoaming = MutableStateFlow(false)
+
private val _isFailedConnection = MutableStateFlow(false)
override val isDefaultConnectionFailed = _isFailedConnection
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
index 2281e89b..9b6f6df 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
@@ -298,6 +298,100 @@
job.cancel()
}
+ @Test
+ fun `roaming - is gsm - uses connection model`() =
+ runBlocking(IMMEDIATE) {
+ var latest: Boolean? = null
+ val job = underTest.isRoaming.onEach { latest = it }.launchIn(this)
+
+ connectionRepository.cdmaRoaming.value = true
+ connectionRepository.setConnectionInfo(
+ MobileConnectionModel(
+ isGsm = true,
+ isRoaming = false,
+ )
+ )
+ yield()
+
+ assertThat(latest).isFalse()
+
+ connectionRepository.setConnectionInfo(
+ MobileConnectionModel(
+ isGsm = true,
+ isRoaming = true,
+ )
+ )
+ yield()
+
+ assertThat(latest).isTrue()
+
+ job.cancel()
+ }
+
+ @Test
+ fun `roaming - is cdma - uses cdma roaming bit`() =
+ runBlocking(IMMEDIATE) {
+ var latest: Boolean? = null
+ val job = underTest.isRoaming.onEach { latest = it }.launchIn(this)
+
+ connectionRepository.cdmaRoaming.value = false
+ connectionRepository.setConnectionInfo(
+ MobileConnectionModel(
+ isGsm = false,
+ isRoaming = true,
+ )
+ )
+ yield()
+
+ assertThat(latest).isFalse()
+
+ connectionRepository.cdmaRoaming.value = true
+ connectionRepository.setConnectionInfo(
+ MobileConnectionModel(
+ isGsm = false,
+ isRoaming = false,
+ )
+ )
+ yield()
+
+ assertThat(latest).isTrue()
+
+ job.cancel()
+ }
+
+ @Test
+ fun `roaming - false while carrierNetworkChangeActive`() =
+ runBlocking(IMMEDIATE) {
+ var latest: Boolean? = null
+ val job = underTest.isRoaming.onEach { latest = it }.launchIn(this)
+
+ connectionRepository.cdmaRoaming.value = true
+ connectionRepository.setConnectionInfo(
+ MobileConnectionModel(
+ isGsm = false,
+ isRoaming = true,
+ carrierNetworkChangeActive = true,
+ )
+ )
+ yield()
+
+ assertThat(latest).isFalse()
+
+ connectionRepository.cdmaRoaming.value = true
+ connectionRepository.setConnectionInfo(
+ MobileConnectionModel(
+ isGsm = true,
+ isRoaming = true,
+ carrierNetworkChangeActive = true,
+ )
+ )
+ yield()
+
+ assertThat(latest).isFalse()
+
+ job.cancel()
+ }
+
companion object {
private val IMMEDIATE = Dispatchers.Main.immediate
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
index f2533a9..2c8f0a7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
@@ -234,6 +234,22 @@
job.cancel()
}
+ @Test
+ fun roaming() =
+ runBlocking(IMMEDIATE) {
+ interactor.isRoaming.value = true
+ var latest: Boolean? = null
+ val job = underTest.roaming.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isTrue()
+
+ interactor.isRoaming.value = false
+
+ assertThat(latest).isFalse()
+
+ job.cancel()
+ }
+
/** Convenience constructor for these tests */
private fun defaultSignal(
level: Int = 1,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt
index 2f18ce3..4e15b4a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt
@@ -16,9 +16,9 @@
package com.android.systemui.statusbar.pipeline.wifi.data.repository
+import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryImpl.Companion.ACTIVITY_DEFAULT
-import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiActivityModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@@ -35,7 +35,7 @@
override val wifiNetwork: StateFlow<WifiNetworkModel> = _wifiNetwork
private val _wifiActivity = MutableStateFlow(ACTIVITY_DEFAULT)
- override val wifiActivity: StateFlow<WifiActivityModel> = _wifiActivity
+ override val wifiActivity: StateFlow<DataActivityModel> = _wifiActivity
fun setIsWifiEnabled(enabled: Boolean) {
_isWifiEnabled.value = enabled
@@ -49,7 +49,7 @@
_wifiNetwork.value = wifiNetworkModel
}
- fun setWifiActivity(activity: WifiActivityModel) {
+ fun setWifiActivity(activity: DataActivityModel) {
_wifiActivity.value = activity
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryImplTest.kt
index 800f3c0..5d0d87b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryImplTest.kt
@@ -31,10 +31,10 @@
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
+import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryImpl.Companion.ACTIVITY_DEFAULT
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryImpl.Companion.WIFI_NETWORK_DEFAULT
-import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiActivityModel
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
@@ -724,7 +724,7 @@
fun wifiActivity_nullWifiManager_receivesDefault() = runBlocking(IMMEDIATE) {
underTest = createRepo(wifiManagerToUse = null)
- var latest: WifiActivityModel? = null
+ var latest: DataActivityModel? = null
val job = underTest
.wifiActivity
.onEach { latest = it }
@@ -737,7 +737,7 @@
@Test
fun wifiActivity_callbackGivesNone_activityFlowHasNone() = runBlocking(IMMEDIATE) {
- var latest: WifiActivityModel? = null
+ var latest: DataActivityModel? = null
val job = underTest
.wifiActivity
.onEach { latest = it }
@@ -746,7 +746,7 @@
getTrafficStateCallback().onStateChanged(TrafficStateCallback.DATA_ACTIVITY_NONE)
assertThat(latest).isEqualTo(
- WifiActivityModel(hasActivityIn = false, hasActivityOut = false)
+ DataActivityModel(hasActivityIn = false, hasActivityOut = false)
)
job.cancel()
@@ -754,7 +754,7 @@
@Test
fun wifiActivity_callbackGivesIn_activityFlowHasIn() = runBlocking(IMMEDIATE) {
- var latest: WifiActivityModel? = null
+ var latest: DataActivityModel? = null
val job = underTest
.wifiActivity
.onEach { latest = it }
@@ -763,7 +763,7 @@
getTrafficStateCallback().onStateChanged(TrafficStateCallback.DATA_ACTIVITY_IN)
assertThat(latest).isEqualTo(
- WifiActivityModel(hasActivityIn = true, hasActivityOut = false)
+ DataActivityModel(hasActivityIn = true, hasActivityOut = false)
)
job.cancel()
@@ -771,7 +771,7 @@
@Test
fun wifiActivity_callbackGivesOut_activityFlowHasOut() = runBlocking(IMMEDIATE) {
- var latest: WifiActivityModel? = null
+ var latest: DataActivityModel? = null
val job = underTest
.wifiActivity
.onEach { latest = it }
@@ -780,7 +780,7 @@
getTrafficStateCallback().onStateChanged(TrafficStateCallback.DATA_ACTIVITY_OUT)
assertThat(latest).isEqualTo(
- WifiActivityModel(hasActivityIn = false, hasActivityOut = true)
+ DataActivityModel(hasActivityIn = false, hasActivityOut = true)
)
job.cancel()
@@ -788,7 +788,7 @@
@Test
fun wifiActivity_callbackGivesInout_activityFlowHasInAndOut() = runBlocking(IMMEDIATE) {
- var latest: WifiActivityModel? = null
+ var latest: DataActivityModel? = null
val job = underTest
.wifiActivity
.onEach { latest = it }
@@ -796,7 +796,7 @@
getTrafficStateCallback().onStateChanged(TrafficStateCallback.DATA_ACTIVITY_INOUT)
- assertThat(latest).isEqualTo(WifiActivityModel(hasActivityIn = true, hasActivityOut = true))
+ assertThat(latest).isEqualTo(DataActivityModel(hasActivityIn = true, hasActivityOut = true))
job.cancel()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorImplTest.kt
index b38497a..2ecb17b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorImplTest.kt
@@ -20,10 +20,10 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot
+import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository
-import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiActivityModel
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -225,23 +225,23 @@
@Test
fun activity_matchesRepoWifiActivity() = runBlocking(IMMEDIATE) {
- var latest: WifiActivityModel? = null
+ var latest: DataActivityModel? = null
val job = underTest
.activity
.onEach { latest = it }
.launchIn(this)
- val activity1 = WifiActivityModel(hasActivityIn = true, hasActivityOut = true)
+ val activity1 = DataActivityModel(hasActivityIn = true, hasActivityOut = true)
wifiRepository.setWifiActivity(activity1)
yield()
assertThat(latest).isEqualTo(activity1)
- val activity2 = WifiActivityModel(hasActivityIn = false, hasActivityOut = false)
+ val activity2 = DataActivityModel(hasActivityIn = false, hasActivityOut = false)
wifiRepository.setWifiActivity(activity2)
yield()
assertThat(latest).isEqualTo(activity2)
- val activity3 = WifiActivityModel(hasActivityIn = true, hasActivityOut = false)
+ val activity3 = DataActivityModel(hasActivityIn = true, hasActivityOut = false)
wifiRepository.setWifiActivity(activity3)
yield()
assertThat(latest).isEqualTo(activity3)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
index 7502020..b47f177 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
@@ -27,13 +27,13 @@
import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot
+import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository
import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor
import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractorImpl
import com.android.systemui.statusbar.pipeline.wifi.shared.WifiConstants
-import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiActivityModel
import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineScope
@@ -209,7 +209,7 @@
.launchIn(this)
// WHEN we update the repo to have activity
- val activity = WifiActivityModel(hasActivityIn = true, hasActivityOut = true)
+ val activity = DataActivityModel(hasActivityIn = true, hasActivityOut = true)
wifiRepository.setWifiActivity(activity)
yield()
@@ -252,7 +252,7 @@
.launchIn(this)
// WHEN we update the repo to have activity
- val activity = WifiActivityModel(hasActivityIn = true, hasActivityOut = true)
+ val activity = DataActivityModel(hasActivityIn = true, hasActivityOut = true)
wifiRepository.setWifiActivity(activity)
yield()
@@ -293,7 +293,7 @@
.onEach { latestQs = it }
.launchIn(this)
- val activity = WifiActivityModel(hasActivityIn = true, hasActivityOut = true)
+ val activity = DataActivityModel(hasActivityIn = true, hasActivityOut = true)
wifiRepository.setWifiActivity(activity)
yield()
@@ -319,7 +319,7 @@
.onEach { latest = it }
.launchIn(this)
- val activity = WifiActivityModel(hasActivityIn = true, hasActivityOut = false)
+ val activity = DataActivityModel(hasActivityIn = true, hasActivityOut = false)
wifiRepository.setWifiActivity(activity)
yield()
@@ -341,7 +341,7 @@
.onEach { latest = it }
.launchIn(this)
- val activity = WifiActivityModel(hasActivityIn = false, hasActivityOut = true)
+ val activity = DataActivityModel(hasActivityIn = false, hasActivityOut = true)
wifiRepository.setWifiActivity(activity)
yield()
@@ -363,7 +363,7 @@
.onEach { latest = it }
.launchIn(this)
- val activity = WifiActivityModel(hasActivityIn = false, hasActivityOut = true)
+ val activity = DataActivityModel(hasActivityIn = false, hasActivityOut = true)
wifiRepository.setWifiActivity(activity)
yield()
@@ -385,7 +385,7 @@
.onEach { latest = it }
.launchIn(this)
- val activity = WifiActivityModel(hasActivityIn = true, hasActivityOut = false)
+ val activity = DataActivityModel(hasActivityIn = true, hasActivityOut = false)
wifiRepository.setWifiActivity(activity)
yield()
@@ -407,7 +407,7 @@
.onEach { latest = it }
.launchIn(this)
- val activity = WifiActivityModel(hasActivityIn = true, hasActivityOut = false)
+ val activity = DataActivityModel(hasActivityIn = true, hasActivityOut = false)
wifiRepository.setWifiActivity(activity)
yield()
@@ -429,7 +429,7 @@
.onEach { latest = it }
.launchIn(this)
- val activity = WifiActivityModel(hasActivityIn = false, hasActivityOut = true)
+ val activity = DataActivityModel(hasActivityIn = false, hasActivityOut = true)
wifiRepository.setWifiActivity(activity)
yield()
@@ -451,7 +451,7 @@
.onEach { latest = it }
.launchIn(this)
- val activity = WifiActivityModel(hasActivityIn = true, hasActivityOut = true)
+ val activity = DataActivityModel(hasActivityIn = true, hasActivityOut = true)
wifiRepository.setWifiActivity(activity)
yield()
@@ -473,7 +473,7 @@
.onEach { latest = it }
.launchIn(this)
- val activity = WifiActivityModel(hasActivityIn = false, hasActivityOut = false)
+ val activity = DataActivityModel(hasActivityIn = false, hasActivityOut = false)
wifiRepository.setWifiActivity(activity)
yield()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusFirstUsageListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusFirstUsageListenerTest.kt
new file mode 100644
index 0000000..8dd088f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusFirstUsageListenerTest.kt
@@ -0,0 +1,289 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.stylus
+
+import android.content.Context
+import android.hardware.BatteryState
+import android.hardware.input.InputManager
+import android.os.Handler
+import android.testing.AndroidTestingRunner
+import android.view.InputDevice
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.time.FakeSystemClock
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.Mockito.verifyZeroInteractions
+import org.mockito.MockitoAnnotations
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+@Ignore("TODO(b/20579491): unignore on main")
+class StylusFirstUsageListenerTest : SysuiTestCase() {
+ @Mock lateinit var context: Context
+ @Mock lateinit var inputManager: InputManager
+ @Mock lateinit var stylusManager: StylusManager
+ @Mock lateinit var featureFlags: FeatureFlags
+ @Mock lateinit var internalStylusDevice: InputDevice
+ @Mock lateinit var otherDevice: InputDevice
+ @Mock lateinit var externalStylusDevice: InputDevice
+ @Mock lateinit var batteryState: BatteryState
+ @Mock lateinit var handler: Handler
+
+ private lateinit var stylusListener: StylusFirstUsageListener
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ whenever(featureFlags.isEnabled(Flags.TRACK_STYLUS_EVER_USED)).thenReturn(true)
+ whenever(inputManager.isStylusEverUsed(context)).thenReturn(false)
+
+ stylusListener =
+ StylusFirstUsageListener(
+ context,
+ inputManager,
+ stylusManager,
+ featureFlags,
+ EXECUTOR,
+ handler
+ )
+ stylusListener.hasStarted = false
+
+ whenever(handler.post(any())).thenAnswer {
+ (it.arguments[0] as Runnable).run()
+ true
+ }
+
+ whenever(otherDevice.supportsSource(InputDevice.SOURCE_STYLUS)).thenReturn(false)
+ whenever(internalStylusDevice.supportsSource(InputDevice.SOURCE_STYLUS)).thenReturn(true)
+ whenever(internalStylusDevice.isExternal).thenReturn(false)
+ whenever(externalStylusDevice.supportsSource(InputDevice.SOURCE_STYLUS)).thenReturn(true)
+ whenever(externalStylusDevice.isExternal).thenReturn(true)
+
+ whenever(inputManager.inputDeviceIds).thenReturn(intArrayOf())
+ whenever(inputManager.getInputDevice(OTHER_DEVICE_ID)).thenReturn(otherDevice)
+ whenever(inputManager.getInputDevice(INTERNAL_STYLUS_DEVICE_ID))
+ .thenReturn(internalStylusDevice)
+ whenever(inputManager.getInputDevice(EXTERNAL_STYLUS_DEVICE_ID))
+ .thenReturn(externalStylusDevice)
+ }
+
+ @Test
+ fun start_flagDisabled_doesNotRegister() {
+ whenever(featureFlags.isEnabled(Flags.TRACK_STYLUS_EVER_USED)).thenReturn(false)
+
+ stylusListener.start()
+
+ verify(stylusManager, never()).registerCallback(any())
+ verify(inputManager, never()).setStylusEverUsed(context, true)
+ }
+
+ @Test
+ fun start_toggleHasStarted() {
+ stylusListener.start()
+
+ assert(stylusListener.hasStarted)
+ }
+
+ @Test
+ fun start_hasStarted_doesNotRegister() {
+ stylusListener.hasStarted = true
+
+ stylusListener.start()
+
+ verify(stylusManager, never()).registerCallback(any())
+ }
+
+ @Test
+ fun start_hostDeviceDoesNotSupportStylus_doesNotRegister() {
+ whenever(inputManager.inputDeviceIds).thenReturn(intArrayOf(OTHER_DEVICE_ID))
+
+ stylusListener.start()
+
+ verify(stylusManager, never()).registerCallback(any())
+ verify(inputManager, never()).setStylusEverUsed(context, true)
+ }
+
+ @Test
+ fun start_stylusEverUsed_doesNotRegister() {
+ whenever(inputManager.inputDeviceIds)
+ .thenReturn(intArrayOf(OTHER_DEVICE_ID, INTERNAL_STYLUS_DEVICE_ID))
+ whenever(inputManager.isStylusEverUsed(context)).thenReturn(true)
+
+ stylusListener.start()
+
+ verify(stylusManager, never()).registerCallback(any())
+ verify(inputManager, never()).setStylusEverUsed(context, true)
+ }
+
+ @Test
+ fun start_hostDeviceSupportsStylus_registersListener() {
+ whenever(inputManager.inputDeviceIds)
+ .thenReturn(intArrayOf(OTHER_DEVICE_ID, INTERNAL_STYLUS_DEVICE_ID))
+
+ stylusListener.start()
+
+ verify(stylusManager).registerCallback(any())
+ verify(inputManager, never()).setStylusEverUsed(context, true)
+ }
+
+ @Test
+ fun onStylusAdded_hasNotStarted_doesNotRegisterListener() {
+ stylusListener.hasStarted = false
+
+ stylusListener.onStylusAdded(INTERNAL_STYLUS_DEVICE_ID)
+
+ verifyZeroInteractions(inputManager)
+ }
+
+ @Test
+ fun onStylusAdded_internalStylus_registersListener() {
+ stylusListener.hasStarted = true
+
+ stylusListener.onStylusAdded(INTERNAL_STYLUS_DEVICE_ID)
+
+ verify(inputManager, times(1))
+ .addInputDeviceBatteryListener(INTERNAL_STYLUS_DEVICE_ID, EXECUTOR, stylusListener)
+ }
+
+ @Test
+ fun onStylusAdded_externalStylus_doesNotRegisterListener() {
+ stylusListener.hasStarted = true
+
+ stylusListener.onStylusAdded(EXTERNAL_STYLUS_DEVICE_ID)
+
+ verify(inputManager, never()).addInputDeviceBatteryListener(any(), any(), any())
+ }
+
+ @Test
+ fun onStylusAdded_otherDevice_doesNotRegisterListener() {
+ stylusListener.onStylusAdded(OTHER_DEVICE_ID)
+
+ verify(inputManager, never()).addInputDeviceBatteryListener(any(), any(), any())
+ }
+
+ @Test
+ fun onStylusRemoved_registeredDevice_unregistersListener() {
+ stylusListener.hasStarted = true
+ stylusListener.onStylusAdded(INTERNAL_STYLUS_DEVICE_ID)
+
+ stylusListener.onStylusRemoved(INTERNAL_STYLUS_DEVICE_ID)
+
+ verify(inputManager, times(1))
+ .removeInputDeviceBatteryListener(INTERNAL_STYLUS_DEVICE_ID, stylusListener)
+ }
+
+ @Test
+ fun onStylusRemoved_hasNotStarted_doesNotUnregisterListener() {
+ stylusListener.hasStarted = false
+ stylusListener.onStylusAdded(INTERNAL_STYLUS_DEVICE_ID)
+
+ stylusListener.onStylusRemoved(INTERNAL_STYLUS_DEVICE_ID)
+
+ verifyZeroInteractions(inputManager)
+ }
+
+ @Test
+ fun onStylusRemoved_unregisteredDevice_doesNotUnregisterListener() {
+ stylusListener.hasStarted = true
+
+ stylusListener.onStylusRemoved(INTERNAL_STYLUS_DEVICE_ID)
+
+ verifyNoMoreInteractions(inputManager)
+ }
+
+ @Test
+ fun onStylusBluetoothConnected_updateStylusFlagAndUnregisters() {
+ stylusListener.hasStarted = true
+ stylusListener.onStylusAdded(INTERNAL_STYLUS_DEVICE_ID)
+
+ stylusListener.onStylusBluetoothConnected(EXTERNAL_STYLUS_DEVICE_ID, "ANY")
+
+ verify(inputManager).setStylusEverUsed(context, true)
+ verify(inputManager, times(1))
+ .removeInputDeviceBatteryListener(INTERNAL_STYLUS_DEVICE_ID, stylusListener)
+ verify(stylusManager).unregisterCallback(stylusListener)
+ }
+
+ @Test
+ fun onStylusBluetoothConnected_hasNotStarted_doesNoting() {
+ stylusListener.hasStarted = false
+ stylusListener.onStylusAdded(INTERNAL_STYLUS_DEVICE_ID)
+
+ stylusListener.onStylusBluetoothConnected(EXTERNAL_STYLUS_DEVICE_ID, "ANY")
+
+ verifyZeroInteractions(inputManager)
+ verifyZeroInteractions(stylusManager)
+ }
+
+ @Test
+ fun onBatteryStateChanged_batteryPresent_updateStylusFlagAndUnregisters() {
+ stylusListener.hasStarted = true
+ stylusListener.onStylusAdded(INTERNAL_STYLUS_DEVICE_ID)
+ whenever(batteryState.isPresent).thenReturn(true)
+
+ stylusListener.onBatteryStateChanged(0, 1, batteryState)
+
+ verify(inputManager).setStylusEverUsed(context, true)
+ verify(inputManager, times(1))
+ .removeInputDeviceBatteryListener(INTERNAL_STYLUS_DEVICE_ID, stylusListener)
+ verify(stylusManager).unregisterCallback(stylusListener)
+ }
+
+ @Test
+ fun onBatteryStateChanged_batteryNotPresent_doesNotUpdateFlagOrUnregister() {
+ stylusListener.hasStarted = true
+ stylusListener.onStylusAdded(INTERNAL_STYLUS_DEVICE_ID)
+ whenever(batteryState.isPresent).thenReturn(false)
+
+ stylusListener.onBatteryStateChanged(0, 1, batteryState)
+
+ verifyZeroInteractions(stylusManager)
+ verify(inputManager, never())
+ .removeInputDeviceBatteryListener(INTERNAL_STYLUS_DEVICE_ID, stylusListener)
+ }
+
+ @Test
+ fun onBatteryStateChanged_hasNotStarted_doesNothing() {
+ stylusListener.hasStarted = false
+ stylusListener.onStylusAdded(INTERNAL_STYLUS_DEVICE_ID)
+ whenever(batteryState.isPresent).thenReturn(false)
+
+ stylusListener.onBatteryStateChanged(0, 1, batteryState)
+
+ verifyZeroInteractions(inputManager)
+ verifyZeroInteractions(stylusManager)
+ }
+
+ companion object {
+ private const val OTHER_DEVICE_ID = 0
+ private const val INTERNAL_STYLUS_DEVICE_ID = 1
+ private const val EXTERNAL_STYLUS_DEVICE_ID = 2
+ private val EXECUTOR = FakeExecutor(FakeSystemClock())
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt
index 82153d5..99e2012 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt
@@ -27,6 +27,7 @@
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.dump.DumpManager
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener
import com.android.systemui.util.concurrency.DelayableExecutor
@@ -66,6 +67,8 @@
@Mock
private lateinit var configurationController: ConfigurationController
@Mock
+ private lateinit var dumpManager: DumpManager
+ @Mock
private lateinit var windowManager: WindowManager
@Mock
private lateinit var powerManager: PowerManager
@@ -91,6 +94,7 @@
fakeExecutor,
accessibilityManager,
configurationController,
+ dumpManager,
powerManager,
fakeWakeLockBuilder,
fakeClock,
@@ -989,6 +993,7 @@
@Main mainExecutor: DelayableExecutor,
accessibilityManager: AccessibilityManager,
configurationController: ConfigurationController,
+ dumpManager: DumpManager,
powerManager: PowerManager,
wakeLockBuilder: WakeLock.Builder,
systemClock: SystemClock,
@@ -999,6 +1004,7 @@
mainExecutor,
accessibilityManager,
configurationController,
+ dumpManager,
powerManager,
R.layout.chipbar,
wakeLockBuilder,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt
index 2e4d8e7..d3411c2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt
@@ -36,6 +36,7 @@
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.common.shared.model.Text
import com.android.systemui.common.shared.model.TintedIcon
+import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.statusbar.policy.ConfigurationController
@@ -66,6 +67,7 @@
@Mock private lateinit var logger: ChipbarLogger
@Mock private lateinit var accessibilityManager: AccessibilityManager
@Mock private lateinit var configurationController: ConfigurationController
+ @Mock private lateinit var dumpManager: DumpManager
@Mock private lateinit var powerManager: PowerManager
@Mock private lateinit var windowManager: WindowManager
@Mock private lateinit var falsingManager: FalsingManager
@@ -100,6 +102,7 @@
fakeExecutor,
accessibilityManager,
configurationController,
+ dumpManager,
powerManager,
falsingManager,
falsingCollector,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/FakeChipbarCoordinator.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/FakeChipbarCoordinator.kt
index d5167b3..b9a5bd7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/FakeChipbarCoordinator.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/FakeChipbarCoordinator.kt
@@ -22,6 +22,7 @@
import android.view.WindowManager
import android.view.accessibility.AccessibilityManager
import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.statusbar.policy.ConfigurationController
@@ -38,6 +39,7 @@
mainExecutor: DelayableExecutor,
accessibilityManager: AccessibilityManager,
configurationController: ConfigurationController,
+ dumpManager: DumpManager,
powerManager: PowerManager,
falsingManager: FalsingManager,
falsingCollector: FalsingCollector,
@@ -53,6 +55,7 @@
mainExecutor,
accessibilityManager,
configurationController,
+ dumpManager,
powerManager,
falsingManager,
falsingCollector,
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index 401d184..5bdfa70 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -46,6 +46,7 @@
import com.android.server.display.config.DisplayQuirks;
import com.android.server.display.config.HbmTiming;
import com.android.server.display.config.HighBrightnessMode;
+import com.android.server.display.config.IntegerArray;
import com.android.server.display.config.NitsMap;
import com.android.server.display.config.Point;
import com.android.server.display.config.RefreshRateConfigs;
@@ -213,6 +214,10 @@
* <type>android.sensor.light</type>
* <name>1234 Ambient Light Sensor</name>
* </lightSensor>
+ * <screenOffBrightnessSensor>
+ * <type>com.google.sensor.binned_brightness</type>
+ * <name>Binned Brightness 0 (wake-up)</name>
+ * </screenOffBrightnessSensor>
* <proxSensor>
* <type>android.sensor.proximity</type>
* <name>1234 Proximity Sensor</name>
@@ -368,6 +373,13 @@
* </brightnessThresholdPoints>
* </darkeningThresholds>
* </displayBrightnessChangeThresholdsIdle>
+ * <screenOffBrightnessSensorValueToLux>
+ * <item>-1</item>
+ * <item>0</item>
+ * <item>5</item>
+ * <item>80</item>
+ * <item>1500</item>
+ * </screenOffBrightnessSensorValueToLux>
* </displayConfiguration>
* }
* </pre>
@@ -428,6 +440,9 @@
// The details of the ambient light sensor associated with this display.
private final SensorData mAmbientLightSensor = new SensorData();
+ // The details of the doze brightness sensor associated with this display.
+ private final SensorData mScreenOffBrightnessSensor = new SensorData();
+
// The details of the proximity sensor associated with this display.
private final SensorData mProximitySensor = new SensorData();
@@ -523,6 +538,9 @@
private float[] mAmbientDarkeningLevelsIdle = DEFAULT_AMBIENT_THRESHOLD_LEVELS;
private float[] mAmbientDarkeningPercentagesIdle = DEFAULT_AMBIENT_DARKENING_THRESHOLDS;
+ // A mapping between screen off sensor values and lux values
+ private int[] mScreenOffBrightnessSensorValueToLux;
+
private Spline mBrightnessToBacklightSpline;
private Spline mBacklightToBrightnessSpline;
private Spline mBacklightToNitsSpline;
@@ -1198,6 +1216,10 @@
return mAmbientLightSensor;
}
+ SensorData getScreenOffBrightnessSensor() {
+ return mScreenOffBrightnessSensor;
+ }
+
SensorData getProximitySensor() {
return mProximitySensor;
}
@@ -1321,6 +1343,14 @@
return mHighAmbientBrightnessThresholds;
}
+ /**
+ * @return A mapping from screen off brightness sensor readings to lux values. This estimates
+ * the ambient lux when the screen is off to determine the initial brightness
+ */
+ public int[] getScreenOffBrightnessSensorValueToLux() {
+ return mScreenOffBrightnessSensorValueToLux;
+ }
+
@Override
public String toString() {
return "DisplayDeviceConfig{"
@@ -1399,6 +1429,7 @@
mScreenDarkeningPercentagesIdle)
+ "\n"
+ ", mAmbientLightSensor=" + mAmbientLightSensor
+ + ", mScreenOffBrightnessSensor=" + mScreenOffBrightnessSensor
+ ", mProximitySensor=" + mProximitySensor
+ ", mRefreshRateLimitations= " + Arrays.toString(mRefreshRateLimitations.toArray())
+ ", mDensityMapping= " + mDensityMapping
@@ -1421,6 +1452,9 @@
+ Arrays.toString(mHighDisplayBrightnessThresholds)
+ ", mHighAmbientBrightnessThresholds= "
+ Arrays.toString(mHighAmbientBrightnessThresholds)
+ + "\n"
+ + ", mScreenOffBrightnessSensorValueToLux=" + Arrays.toString(
+ mScreenOffBrightnessSensorValueToLux)
+ "}";
}
@@ -1474,11 +1508,13 @@
loadQuirks(config);
loadBrightnessRamps(config);
loadAmbientLightSensorFromDdc(config);
+ loadScreenOffBrightnessSensorFromDdc(config);
loadProxSensorFromDdc(config);
loadAmbientHorizonFromDdc(config);
loadBrightnessChangeThresholds(config);
loadAutoBrightnessConfigValues(config);
loadRefreshRateSetting(config);
+ loadScreenOffBrightnessSensorValueToLuxFromDdc(config);
} else {
Slog.w(TAG, "DisplayDeviceConfig file is null");
}
@@ -2175,6 +2211,14 @@
mProximitySensor.type = "";
}
+ private void loadScreenOffBrightnessSensorFromDdc(DisplayConfiguration config) {
+ final SensorDetails sensorDetails = config.getScreenOffBrightnessSensor();
+ if (sensorDetails != null) {
+ mScreenOffBrightnessSensor.type = sensorDetails.getType();
+ mScreenOffBrightnessSensor.name = sensorDetails.getName();
+ }
+ }
+
private void loadProxSensorFromDdc(DisplayConfiguration config) {
SensorDetails sensorDetails = config.getProxSensor();
if (sensorDetails != null) {
@@ -2587,6 +2631,19 @@
&& mDdcAutoBrightnessAvailable;
}
+ private void loadScreenOffBrightnessSensorValueToLuxFromDdc(DisplayConfiguration config) {
+ IntegerArray sensorValueToLux = config.getScreenOffBrightnessSensorValueToLux();
+ if (sensorValueToLux == null) {
+ return;
+ }
+
+ List<BigInteger> items = sensorValueToLux.getItem();
+ mScreenOffBrightnessSensorValueToLux = new int[items.size()];
+ for (int i = 0; i < items.size(); i++) {
+ mScreenOffBrightnessSensorValueToLux[i] = items.get(i).intValue();
+ }
+ }
+
static class SensorData {
public String type;
public String name;
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index c740b98..d791c06 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -414,7 +414,12 @@
@Nullable
private AutomaticBrightnessController mAutomaticBrightnessController;
+ // The controller for the sensor used to estimate ambient lux while the display is off.
+ @Nullable
+ private ScreenOffBrightnessSensorController mScreenOffBrightnessSensorController;
+
private Sensor mLightSensor;
+ private Sensor mScreenOffBrightnessSensor;
// The mappers between ambient lux, display backlight values, and display brightness.
// We will switch between the idle mapper and active mapper in AutomaticBrightnessController.
@@ -1098,6 +1103,19 @@
mBrightnessEventRingBuffer =
new RingBuffer<>(BrightnessEvent.class, RINGBUFFER_MAX);
+
+ loadScreenOffBrightnessSensor();
+ int[] sensorValueToLux = mDisplayDeviceConfig.getScreenOffBrightnessSensorValueToLux();
+ if (mScreenOffBrightnessSensor != null && sensorValueToLux != null) {
+ mScreenOffBrightnessSensorController = new ScreenOffBrightnessSensorController(
+ mSensorManager,
+ mScreenOffBrightnessSensor,
+ mHandler,
+ SystemClock::uptimeMillis,
+ sensorValueToLux,
+ mInteractiveModeBrightnessMapper
+ );
+ }
} else {
mUseSoftwareAutoBrightnessConfig = false;
}
@@ -1275,6 +1293,12 @@
}
assert(state != Display.STATE_UNKNOWN);
+ if (mScreenOffBrightnessSensorController != null) {
+ mScreenOffBrightnessSensorController.setLightSensorEnabled(mUseAutoBrightness
+ && (state == Display.STATE_OFF || (state == Display.STATE_DOZE
+ && !mAllowAutoBrightnessWhileDozingConfig)));
+ }
+
boolean skipRampBecauseOfProximityChangeToNegative = false;
// Apply the proximity sensor.
if (mProximitySensor != null) {
@@ -1454,6 +1478,9 @@
updateScreenBrightnessSetting = mCurrentScreenBrightnessSetting != brightnessState;
mAppliedAutoBrightness = true;
mBrightnessReasonTemp.setReason(BrightnessReason.REASON_AUTOMATIC);
+ if (mScreenOffBrightnessSensorController != null) {
+ mScreenOffBrightnessSensorController.setLightSensorEnabled(false);
+ }
} else {
mAppliedAutoBrightness = false;
}
@@ -1481,6 +1508,19 @@
mBrightnessReasonTemp.setReason(BrightnessReason.REASON_DOZE_DEFAULT);
}
+ // The ALS is not available yet - use the screen off sensor to determine the initial
+ // brightness
+ if (Float.isNaN(brightnessState) && autoBrightnessEnabled
+ && mScreenOffBrightnessSensorController != null) {
+ brightnessState = mScreenOffBrightnessSensorController.getAutomaticScreenBrightness();
+ if (isValidBrightnessValue(brightnessState)) {
+ brightnessState = clampScreenBrightness(brightnessState);
+ updateScreenBrightnessSetting = mCurrentScreenBrightnessSetting != brightnessState;
+ mBrightnessReasonTemp.setReason(
+ BrightnessReason.REASON_SCREEN_OFF_BRIGHTNESS_SENSOR);
+ }
+ }
+
// Apply manual brightness.
if (Float.isNaN(brightnessState)) {
brightnessState = clampScreenBrightness(mCurrentScreenBrightnessSetting);
@@ -2052,6 +2092,14 @@
fallbackType);
}
+ private void loadScreenOffBrightnessSensor() {
+ DisplayDeviceConfig.SensorData screenOffBrightnessSensor =
+ mDisplayDeviceConfig.getScreenOffBrightnessSensor();
+ mScreenOffBrightnessSensor = SensorUtils.findSensor(mSensorManager,
+ screenOffBrightnessSensor.type, screenOffBrightnessSensor.name,
+ SensorUtils.NO_FALLBACK);
+ }
+
private void loadProximitySensor() {
if (DEBUG_PRETEND_PROXIMITY_SENSOR_ABSENT) {
return;
@@ -3201,7 +3249,8 @@
static final int REASON_OVERRIDE = 7;
static final int REASON_TEMPORARY = 8;
static final int REASON_BOOST = 9;
- static final int REASON_MAX = REASON_BOOST;
+ static final int REASON_SCREEN_OFF_BRIGHTNESS_SENSOR = 10;
+ static final int REASON_MAX = REASON_SCREEN_OFF_BRIGHTNESS_SENSOR;
static final int MODIFIER_DIMMED = 0x1;
static final int MODIFIER_LOW_POWER = 0x2;
@@ -3311,6 +3360,7 @@
case REASON_OVERRIDE: return "override";
case REASON_TEMPORARY: return "temporary";
case REASON_BOOST: return "boost";
+ case REASON_SCREEN_OFF_BRIGHTNESS_SENSOR: return "screen_off_brightness_sensor";
default: return Integer.toString(reason);
}
}
diff --git a/services/core/java/com/android/server/display/ScreenOffBrightnessSensorController.java b/services/core/java/com/android/server/display/ScreenOffBrightnessSensorController.java
new file mode 100644
index 0000000..6f50dac
--- /dev/null
+++ b/services/core/java/com/android/server/display/ScreenOffBrightnessSensorController.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display;
+
+import android.annotation.Nullable;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.os.Handler;
+import android.os.PowerManager;
+import android.util.IndentingPrintWriter;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.PrintWriter;
+
+/**
+ * Controls the light sensor when the screen is off. The sensor used here does not report lux values
+ * but an index that needs to be mapped to a lux value.
+ */
+public class ScreenOffBrightnessSensorController implements SensorEventListener {
+ private static final String TAG = "ScreenOffBrightnessSensorController";
+
+ private static final int SENSOR_INVALID_VALUE = -1;
+ private static final long SENSOR_VALUE_VALID_TIME_MILLIS = 1500;
+
+ private final Handler mHandler;
+ private final Clock mClock;
+ private final SensorManager mSensorManager;
+ private final Sensor mLightSensor;
+ private final int[] mSensorValueToLux;
+
+ private boolean mRegistered;
+ private int mLastSensorValue = SENSOR_INVALID_VALUE;
+ private long mSensorDisableTime = -1;
+
+ // The mapper to translate ambient lux to screen brightness in the range [0, 1.0].
+ @Nullable
+ private final BrightnessMappingStrategy mBrightnessMapper;
+
+ public ScreenOffBrightnessSensorController(
+ SensorManager sensorManager,
+ Sensor lightSensor,
+ Handler handler,
+ Clock clock,
+ int[] sensorValueToLux,
+ BrightnessMappingStrategy brightnessMapper) {
+ mSensorManager = sensorManager;
+ mLightSensor = lightSensor;
+ mHandler = handler;
+ mClock = clock;
+ mSensorValueToLux = sensorValueToLux;
+ mBrightnessMapper = brightnessMapper;
+ }
+
+ @Override
+ public void onSensorChanged(SensorEvent event) {
+ if (mRegistered) {
+ mLastSensorValue = (int) event.values[0];
+ }
+ }
+
+ @Override
+ public void onAccuracyChanged(Sensor sensor, int accuracy) {
+ }
+
+ void setLightSensorEnabled(boolean enabled) {
+ if (enabled && !mRegistered) {
+ // Wait until we get an event from the sensor indicating ready.
+ mRegistered = mSensorManager.registerListener(this, mLightSensor,
+ SensorManager.SENSOR_DELAY_NORMAL, mHandler);
+ mLastSensorValue = SENSOR_INVALID_VALUE;
+ } else if (!enabled && mRegistered) {
+ mSensorManager.unregisterListener(this);
+ mRegistered = false;
+ mSensorDisableTime = mClock.uptimeMillis();
+ }
+ }
+
+ float getAutomaticScreenBrightness() {
+ if (mLastSensorValue < 0 || mLastSensorValue >= mSensorValueToLux.length
+ || (!mRegistered
+ && mClock.uptimeMillis() - mSensorDisableTime > SENSOR_VALUE_VALID_TIME_MILLIS)) {
+ return PowerManager.BRIGHTNESS_INVALID_FLOAT;
+ }
+
+ int lux = mSensorValueToLux[mLastSensorValue];
+ if (lux < 0) {
+ return PowerManager.BRIGHTNESS_INVALID_FLOAT;
+ }
+
+ return mBrightnessMapper.getBrightness(lux);
+ }
+
+ /** Dump current state */
+ public void dump(PrintWriter pw) {
+ pw.println("ScreenOffBrightnessSensorController:");
+ IndentingPrintWriter idpw = new IndentingPrintWriter(pw);
+ idpw.increaseIndent();
+ idpw.println("registered=" + mRegistered);
+ idpw.println("lastSensorValue=" + mLastSensorValue);
+ }
+
+ /** Functional interface for providing time. */
+ @VisibleForTesting
+ interface Clock {
+ /**
+ * Returns current time in milliseconds since boot, not counting time spent in deep sleep.
+ */
+ long uptimeMillis();
+ }
+}
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index dbe049a..de0d1f8 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -1049,13 +1049,6 @@
return;
}
- // Don't dream if the user isn't user zero.
- // TODO(b/261907079): Move this check to DreamManagerService#canStartDreamingInternal().
- if (ActivityManager.getCurrentUser() != UserHandle.USER_SYSTEM) {
- noDreamAction.run();
- return;
- }
-
final DreamManagerInternal dreamManagerInternal = getDreamManagerInternal();
if (dreamManagerInternal == null || !dreamManagerInternal.canStartDreaming(isScreenOn)) {
noDreamAction.run();
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 4c49986..4c19322a 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -1125,7 +1125,7 @@
}
mDisplayPolicy = new DisplayPolicy(mWmService, this);
- mDisplayRotation = new DisplayRotation(mWmService, this);
+ mDisplayRotation = new DisplayRotation(mWmService, this, mDisplayInfo.address);
mDeviceStateController = new DeviceStateController(mWmService.mContext, mWmService.mH,
newFoldState -> {
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index 185e06e..4fb137b 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -58,6 +58,7 @@
import android.util.Slog;
import android.util.TimeUtils;
import android.util.proto.ProtoOutputStream;
+import android.view.DisplayAddress;
import android.view.IWindowManager;
import android.view.Surface;
import android.window.TransitionRequestInfo;
@@ -211,15 +212,16 @@
private boolean mDemoHdmiRotationLock;
private boolean mDemoRotationLock;
- DisplayRotation(WindowManagerService service, DisplayContent displayContent) {
- this(service, displayContent, displayContent.getDisplayPolicy(),
+ DisplayRotation(WindowManagerService service, DisplayContent displayContent,
+ DisplayAddress displayAddress) {
+ this(service, displayContent, displayAddress, displayContent.getDisplayPolicy(),
service.mDisplayWindowSettings, service.mContext, service.getWindowManagerLock());
}
@VisibleForTesting
DisplayRotation(WindowManagerService service, DisplayContent displayContent,
- DisplayPolicy displayPolicy, DisplayWindowSettings displayWindowSettings,
- Context context, Object lock) {
+ DisplayAddress displayAddress, DisplayPolicy displayPolicy,
+ DisplayWindowSettings displayWindowSettings, Context context, Object lock) {
mService = service;
mDisplayContent = displayContent;
mDisplayPolicy = displayPolicy;
@@ -235,6 +237,8 @@
mDeskDockRotation = readRotation(R.integer.config_deskDockRotation);
mUndockedHdmiRotation = readRotation(R.integer.config_undockedHdmiRotation);
+ mRotation = readDefaultDisplayRotation(displayAddress);
+
if (isDefaultDisplay) {
final Handler uiHandler = UiThread.getHandler();
mOrientationListener = new OrientationListener(mContext, uiHandler);
@@ -248,6 +252,33 @@
}
}
+ // Change the default value to the value specified in the sysprop
+ // ro.bootanim.set_orientation_<display_id>. Four values are supported: ORIENTATION_0,
+ // ORIENTATION_90, ORIENTATION_180 and ORIENTATION_270.
+ // If the value isn't specified or is ORIENTATION_0, nothing will be changed.
+ // This is needed to support having default orientation different from the natural
+ // device orientation. For example, on tablets that may want to keep natural orientation
+ // portrait for applications compatibility but have landscape orientation as a default choice
+ // from the UX perspective.
+ @Surface.Rotation
+ private int readDefaultDisplayRotation(DisplayAddress displayAddress) {
+ if (!(displayAddress instanceof DisplayAddress.Physical)) {
+ return Surface.ROTATION_0;
+ }
+ final DisplayAddress.Physical physicalAddress = (DisplayAddress.Physical) displayAddress;
+ String syspropValue = SystemProperties.get(
+ "ro.bootanim.set_orientation_" + physicalAddress.getPhysicalDisplayId(),
+ "ORIENTATION_0");
+ if (syspropValue.equals("ORIENTATION_90")) {
+ return Surface.ROTATION_90;
+ } else if (syspropValue.equals("ORIENTATION_180")) {
+ return Surface.ROTATION_180;
+ } else if (syspropValue.equals("ORIENTATION_270")) {
+ return Surface.ROTATION_270;
+ }
+ return Surface.ROTATION_0;
+ }
+
private int readRotation(int resID) {
try {
final int rotation = mContext.getResources().getInteger(resID);
diff --git a/services/core/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd
index 7bc8931..f628fba 100644
--- a/services/core/xsd/display-device-config/display-device-config.xsd
+++ b/services/core/xsd/display-device-config/display-device-config.xsd
@@ -74,6 +74,9 @@
<xs:element type="sensorDetails" name="lightSensor">
<xs:annotation name="final"/>
</xs:element>
+ <xs:element type="sensorDetails" name="screenOffBrightnessSensor">
+ <xs:annotation name="final"/>
+ </xs:element>
<xs:element type="sensorDetails" name="proxSensor">
<xs:annotation name="final"/>
</xs:element>
@@ -109,6 +112,11 @@
<xs:element type="thresholds" name="ambientBrightnessChangeThresholdsIdle" minOccurs="0" maxOccurs="1">
<xs:annotation name="final"/>
</xs:element>
+ <!-- Table that translates sensor values from the screenOffBrightnessSensor
+ to lux values; -1 means the lux reading is not available. -->
+ <xs:element type="integer-array" name="screenOffBrightnessSensorValueToLux">
+ <xs:annotation name="final"/>
+ </xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
@@ -485,4 +493,10 @@
</xs:element>
</xs:sequence>
</xs:complexType>
+
+ <xs:complexType name="integer-array">
+ <xs:sequence>
+ <xs:element name="item" type="xs:nonNegativeInteger" minOccurs="0" maxOccurs="unbounded"/>
+ </xs:sequence>
+ </xs:complexType>
</xs:schema>
diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt
index 6276eda..cb08179 100644
--- a/services/core/xsd/display-device-config/schema/current.txt
+++ b/services/core/xsd/display-device-config/schema/current.txt
@@ -98,6 +98,8 @@
method public final java.math.BigInteger getScreenBrightnessRampIncreaseMaxMillis();
method public final java.math.BigDecimal getScreenBrightnessRampSlowDecrease();
method public final java.math.BigDecimal getScreenBrightnessRampSlowIncrease();
+ method public final com.android.server.display.config.SensorDetails getScreenOffBrightnessSensor();
+ method public final com.android.server.display.config.IntegerArray getScreenOffBrightnessSensorValueToLux();
method @NonNull public final com.android.server.display.config.ThermalThrottling getThermalThrottling();
method public final void setAmbientBrightnessChangeThresholds(@NonNull com.android.server.display.config.Thresholds);
method public final void setAmbientBrightnessChangeThresholdsIdle(com.android.server.display.config.Thresholds);
@@ -120,6 +122,8 @@
method public final void setScreenBrightnessRampIncreaseMaxMillis(java.math.BigInteger);
method public final void setScreenBrightnessRampSlowDecrease(java.math.BigDecimal);
method public final void setScreenBrightnessRampSlowIncrease(java.math.BigDecimal);
+ method public final void setScreenOffBrightnessSensor(com.android.server.display.config.SensorDetails);
+ method public final void setScreenOffBrightnessSensorValueToLux(com.android.server.display.config.IntegerArray);
method public final void setThermalThrottling(@NonNull com.android.server.display.config.ThermalThrottling);
}
@@ -160,6 +164,11 @@
method public final void setTransitionPoint_all(@NonNull java.math.BigDecimal);
}
+ public class IntegerArray {
+ ctor public IntegerArray();
+ method public java.util.List<java.math.BigInteger> getItem();
+ }
+
public class NitsMap {
ctor public NitsMap();
method public String getInterpolation();
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java
index 6b705aa..86c5937 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java
@@ -161,6 +161,14 @@
assertArrayEquals(new int[]{70, 80},
mDisplayDeviceConfig.getHighAmbientBrightnessThresholds());
+ assertEquals("sensor_12345",
+ mDisplayDeviceConfig.getScreenOffBrightnessSensor().type);
+ assertEquals("Sensor 12345",
+ mDisplayDeviceConfig.getScreenOffBrightnessSensor().name);
+
+ assertArrayEquals(new int[]{-1, 10, 20, 30, 40},
+ mDisplayDeviceConfig.getScreenOffBrightnessSensorValueToLux());
+
// Todo(brup): Add asserts for BrightnessThrottlingData, DensityMapping,
// HighBrightnessModeData AmbientLightSensor, RefreshRateLimitations and ProximitySensor.
}
@@ -232,6 +240,7 @@
HIGH_BRIGHTNESS_THRESHOLD_OF_PEAK_REFRESH_RATE);
assertArrayEquals(mDisplayDeviceConfig.getHighAmbientBrightnessThresholds(),
HIGH_AMBIENT_THRESHOLD_OF_PEAK_REFRESH_RATE);
+
// Todo(brup): Add asserts for BrightnessThrottlingData, DensityMapping,
// HighBrightnessModeData AmbientLightSensor, RefreshRateLimitations and ProximitySensor.
}
@@ -283,6 +292,10 @@
+ "<thermalStatusLimit>light</thermalStatusLimit>\n"
+ "<allowInLowPowerMode>false</allowInLowPowerMode>\n"
+ "</highBrightnessMode>\n"
+ + "<screenOffBrightnessSensor>\n"
+ + "<type>sensor_12345</type>\n"
+ + "<name>Sensor 12345</name>\n"
+ + "</screenOffBrightnessSensor>\n"
+ "<ambientBrightnessChangeThresholds>\n"
+ "<brighteningThresholds>\n"
+ "<minimum>10</minimum>\n"
@@ -467,6 +480,13 @@
+ "</blockingZoneThreshold>\n"
+ "</higherBlockingZoneConfigs>\n"
+ "</refreshRate>\n"
+ + "<screenOffBrightnessSensorValueToLux>\n"
+ + "<item>-1</item>\n"
+ + "<item>10</item>\n"
+ + "<item>20</item>\n"
+ + "<item>30</item>\n"
+ + "<item>40</item>\n"
+ + "</screenOffBrightnessSensorValueToLux>\n"
+ "</displayConfiguration>\n";
}
@@ -544,6 +564,7 @@
when(mResources.getIntArray(
R.array.config_highAmbientBrightnessThresholdsOfFixedRefreshRate))
.thenReturn(HIGH_AMBIENT_THRESHOLD_OF_PEAK_REFRESH_RATE);
+
mDisplayDeviceConfig = DisplayDeviceConfig.create(mContext, true);
}
diff --git a/services/tests/servicestests/src/com/android/server/display/ScreenOffBrightnessSensorControllerTest.java b/services/tests/servicestests/src/com/android/server/display/ScreenOffBrightnessSensorControllerTest.java
new file mode 100644
index 0000000..ea04a19
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/display/ScreenOffBrightnessSensorControllerTest.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.hardware.Sensor;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.os.Handler;
+import android.os.PowerManager;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.testutils.OffsettableClock;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class ScreenOffBrightnessSensorControllerTest {
+
+ private static final int[] SENSOR_TO_LUX = new int[]{-1, 10, 20, 30, 40};
+
+ private ScreenOffBrightnessSensorController mController;
+ private OffsettableClock mClock;
+ private Sensor mLightSensor;
+
+ @Mock SensorManager mSensorManager;
+ @Mock Handler mNoOpHandler;
+ @Mock BrightnessMappingStrategy mBrightnessMappingStrategy;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ mClock = new OffsettableClock.Stopped();
+ mLightSensor = TestUtils.createSensor(Sensor.TYPE_LIGHT, "Light Sensor");
+ mController = new ScreenOffBrightnessSensorController(
+ mSensorManager,
+ mLightSensor,
+ mNoOpHandler,
+ mClock::now,
+ SENSOR_TO_LUX,
+ mBrightnessMappingStrategy
+ );
+ }
+
+ @Test
+ public void testBrightness() throws Exception {
+ when(mSensorManager.registerListener(any(SensorEventListener.class), eq(mLightSensor),
+ eq(SensorManager.SENSOR_DELAY_NORMAL), any(Handler.class)))
+ .thenReturn(true);
+ mController.setLightSensorEnabled(true);
+ ArgumentCaptor<SensorEventListener> listenerCaptor =
+ ArgumentCaptor.forClass(SensorEventListener.class);
+ verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
+ eq(SensorManager.SENSOR_DELAY_NORMAL), any(Handler.class));
+ SensorEventListener listener = listenerCaptor.getValue();
+
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 0));
+ assertEquals(PowerManager.BRIGHTNESS_INVALID_FLOAT,
+ mController.getAutomaticScreenBrightness(), 0);
+
+ int sensorValue = 1;
+ float brightness = 0.2f;
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, sensorValue));
+ when(mBrightnessMappingStrategy.getBrightness(SENSOR_TO_LUX[sensorValue]))
+ .thenReturn(brightness);
+ assertEquals(brightness, mController.getAutomaticScreenBrightness(), 0);
+
+ sensorValue = 2;
+ brightness = 0.4f;
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, sensorValue));
+ when(mBrightnessMappingStrategy.getBrightness(SENSOR_TO_LUX[sensorValue]))
+ .thenReturn(brightness);
+ assertEquals(brightness, mController.getAutomaticScreenBrightness(), 0);
+
+ sensorValue = 3;
+ brightness = 0.6f;
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, sensorValue));
+ when(mBrightnessMappingStrategy.getBrightness(SENSOR_TO_LUX[sensorValue]))
+ .thenReturn(brightness);
+ assertEquals(brightness, mController.getAutomaticScreenBrightness(), 0);
+
+ sensorValue = 4;
+ brightness = 0.8f;
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, sensorValue));
+ when(mBrightnessMappingStrategy.getBrightness(SENSOR_TO_LUX[sensorValue]))
+ .thenReturn(brightness);
+ assertEquals(brightness, mController.getAutomaticScreenBrightness(), 0);
+
+ sensorValue = 5;
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, sensorValue));
+ assertEquals(PowerManager.BRIGHTNESS_INVALID_FLOAT,
+ mController.getAutomaticScreenBrightness(), 0);
+ }
+
+ @Test
+ public void testSensorValueValidTime() throws Exception {
+ when(mSensorManager.registerListener(any(SensorEventListener.class), eq(mLightSensor),
+ eq(SensorManager.SENSOR_DELAY_NORMAL), any(Handler.class)))
+ .thenReturn(true);
+ mController.setLightSensorEnabled(true);
+ ArgumentCaptor<SensorEventListener> listenerCaptor =
+ ArgumentCaptor.forClass(SensorEventListener.class);
+ verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
+ eq(SensorManager.SENSOR_DELAY_NORMAL), any(Handler.class));
+ SensorEventListener listener = listenerCaptor.getValue();
+
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1));
+ mController.setLightSensorEnabled(false);
+ assertNotEquals(PowerManager.BRIGHTNESS_INVALID_FLOAT,
+ mController.getAutomaticScreenBrightness(), 0);
+
+ mClock.fastForward(2000);
+ mController.setLightSensorEnabled(false);
+ assertEquals(PowerManager.BRIGHTNESS_INVALID_FLOAT,
+ mController.getAutomaticScreenBrightness(), 0);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/display/TestUtils.java b/services/tests/servicestests/src/com/android/server/display/TestUtils.java
index 0454587..f3f04b8 100644
--- a/services/tests/servicestests/src/com/android/server/display/TestUtils.java
+++ b/services/tests/servicestests/src/com/android/server/display/TestUtils.java
@@ -28,13 +28,13 @@
public final class TestUtils {
- public static SensorEvent createSensorEvent(Sensor sensor, int lux) throws Exception {
+ public static SensorEvent createSensorEvent(Sensor sensor, int value) throws Exception {
final Constructor<SensorEvent> constructor =
SensorEvent.class.getDeclaredConstructor(int.class);
constructor.setAccessible(true);
final SensorEvent event = constructor.newInstance(1);
event.sensor = sensor;
- event.values[0] = lux;
+ event.values[0] = value;
event.timestamp = SystemClock.elapsedRealtimeNanos();
return event;
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
index b45c37f..491f876d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
@@ -60,6 +60,7 @@
import android.os.SystemClock;
import android.platform.test.annotations.Presubmit;
import android.provider.Settings;
+import android.view.DisplayAddress;
import android.view.Surface;
import android.view.WindowManager;
@@ -101,6 +102,7 @@
private static WindowManagerService sMockWm;
private DisplayContent mMockDisplayContent;
private DisplayPolicy mMockDisplayPolicy;
+ private DisplayAddress mMockDisplayAddress;
private Context mMockContext;
private Resources mMockRes;
private SensorManager mMockSensorManager;
@@ -1091,9 +1093,11 @@
when(mMockResolver.acquireProvider(Settings.AUTHORITY))
.thenReturn(mFakeSettingsProvider.getIContentProvider());
+ mMockDisplayAddress = mock(DisplayAddress.class);
+
mMockDisplayWindowSettings = mock(DisplayWindowSettings.class);
- mTarget = new DisplayRotation(sMockWm, mMockDisplayContent, mMockDisplayPolicy,
- mMockDisplayWindowSettings, mMockContext, new Object());
+ mTarget = new DisplayRotation(sMockWm, mMockDisplayContent, mMockDisplayAddress,
+ mMockDisplayPolicy, mMockDisplayWindowSettings, mMockContext, new Object());
reset(sMockWm);
captureObservers();