Merge "Remove unnecessary view ID" 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/os/PowerManager.java b/core/java/android/os/PowerManager.java
index 68e1acb..132bd66 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -608,7 +608,10 @@
WAKE_REASON_DISPLAY_GROUP_ADDED,
WAKE_REASON_DISPLAY_GROUP_TURNED_ON,
WAKE_REASON_UNFOLD_DEVICE,
- WAKE_REASON_DREAM_FINISHED
+ WAKE_REASON_DREAM_FINISHED,
+ WAKE_REASON_TAP,
+ WAKE_REASON_LIFT,
+ WAKE_REASON_BIOMETRIC,
})
@Retention(RetentionPolicy.SOURCE)
public @interface WakeReason{}
@@ -660,8 +663,9 @@
public static final int WAKE_REASON_PLUGGED_IN = 3;
/**
- * Wake up reason code: Waking up due to a user performed gesture (e.g. double tapping on the
- * screen).
+ * Wake up reason code: Waking up due to a user performed gesture. This includes user
+ * interactions with UI on the screen such as the notification shade. This does not include
+ * {@link WAKE_REASON_TAP} or {@link WAKE_REASON_LIFT}.
* @hide
*/
public static final int WAKE_REASON_GESTURE = 4;
@@ -723,6 +727,26 @@
public static final int WAKE_REASON_DREAM_FINISHED = 13;
/**
+ * Wake up reason code: Waking up due to the user single or double tapping on the screen. This
+ * wake reason is used when the user is not tapping on a specific UI element; rather, the device
+ * wakes up due to a generic tap on the screen.
+ * @hide
+ */
+ public static final int WAKE_REASON_TAP = 15;
+
+ /**
+ * Wake up reason code: Waking up due to a user performed lift gesture.
+ * @hide
+ */
+ public static final int WAKE_REASON_LIFT = 16;
+
+ /**
+ * Wake up reason code: Waking up due to a user interacting with a biometric.
+ * @hide
+ */
+ public static final int WAKE_REASON_BIOMETRIC = 17;
+
+ /**
* Convert the wake reason to a string for debugging purposes.
* @hide
*/
@@ -742,6 +766,9 @@
case WAKE_REASON_DISPLAY_GROUP_TURNED_ON: return "WAKE_REASON_DISPLAY_GROUP_TURNED_ON";
case WAKE_REASON_UNFOLD_DEVICE: return "WAKE_REASON_UNFOLD_DEVICE";
case WAKE_REASON_DREAM_FINISHED: return "WAKE_REASON_DREAM_FINISHED";
+ case WAKE_REASON_TAP: return "WAKE_REASON_TAP";
+ case WAKE_REASON_LIFT: return "WAKE_REASON_LIFT";
+ case WAKE_REASON_BIOMETRIC: return "WAKE_REASON_BIOMETRIC";
default: return Integer.toString(wakeReason);
}
}
diff --git a/core/java/android/service/controls/ControlsProviderService.java b/core/java/android/service/controls/ControlsProviderService.java
index 950c8ac..ed24740 100644
--- a/core/java/android/service/controls/ControlsProviderService.java
+++ b/core/java/android/service/controls/ControlsProviderService.java
@@ -58,19 +58,24 @@
* Manifest metadata to show a custom embedded activity as part of device controls.
*
* The value of this metadata must be the {@link ComponentName} as a string of an activity in
- * the same package that will be launched as part of a TaskView.
+ * the same package that will be launched embedded in the device controls space.
*
* The activity must be exported, enabled and protected by
* {@link Manifest.permission.BIND_CONTROLS}.
*
+ * It is recommended that the activity is declared {@code android:resizeableActivity="true"}.
+ *
* @hide
*/
public static final String META_DATA_PANEL_ACTIVITY =
"android.service.controls.META_DATA_PANEL_ACTIVITY";
/**
- * Boolean extra containing the value of
- * {@link android.provider.Settings.Secure#LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS}.
+ * Boolean extra containing the value of the setting allowing actions on a locked device.
+ *
+ * This corresponds to the setting that indicates whether the user has
+ * consented to allow actions on devices that declare {@link Control#isAuthRequired()} as
+ * {@code false} when the device is locked.
*
* This is passed with the intent when the panel specified by {@link #META_DATA_PANEL_ACTIVITY}
* is launched.
@@ -78,7 +83,7 @@
* @hide
*/
public static final String EXTRA_LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS =
- "android.service.controls.extra.EXTRA_LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS";
+ "android.service.controls.extra.LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS";
/**
* @hide
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/config.xml b/core/res/res/values/config.xml
index 22f4298..50761bf 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -5204,6 +5204,15 @@
the format [System DeviceState]:[WM Jetpack Posture], for example: "0:1". -->
<string-array name="config_device_state_postures" translatable="false" />
+ <!-- Which Surface rotations are considered as tabletop posture (horizontal hinge) when the
+ device is half-folded. Other half-folded postures will be assumed to be book (vertical
+ hinge) mode. Units: degrees; valid values: 0, 90, 180, 270. -->
+ <integer-array name="config_deviceTabletopRotations" />
+
+ <!-- This flag indicates that a display with fold-state FLAT should always be considered as
+ having a separating hinge. -->
+ <bool name="config_isDisplayHingeAlwaysSeparating">false</bool>
+
<!-- Aspect ratio of letterboxing for fixed orientation. Values <= 1.0 will be ignored.
Note: Activity min/max aspect ratio restrictions will still be respected.
Therefore this override can control the maximum screen area that can be occupied by
@@ -5252,14 +5261,26 @@
<!-- Horizontal position of a center of the letterboxed app window.
0 corresponds to the left side of the screen and 1 to the right side. If given value < 0
- or > 1, it is ignored and central position is used (0.5). -->
+ or > 1 it is ignored and for non-book mode central position is used (0.5); for book mode
+ left is used (0.0). -->
<item name="config_letterboxHorizontalPositionMultiplier" format="float" type="dimen">0.5</item>
<!-- Vertical position of a center of the letterboxed app window.
0 corresponds to the upper side of the screen and 1 to the lower side. If given value < 0
- or > 1, it is ignored and central position is used (0.5). -->
+ or > 1 it is ignored and for non-tabletop mode central position is used (0.5); for
+ tabletop mode top (0.0) is used. -->
<item name="config_letterboxVerticalPositionMultiplier" format="float" type="dimen">0.0</item>
+ <!-- Horizontal position of a center of the letterboxed app window when in book mode.
+ 0 corresponds to the left side of the screen and 1 to the right side. If given value < 0
+ or > 1, it is ignored and left position is used (0.0). -->
+ <item name="config_letterboxBookModePositionMultiplier" format="float" type="dimen">0.0</item>
+
+ <!-- Vertical position of a center of the letterboxed app window when in tabletop mode.
+ 0 corresponds to the upper side of the screen and 1 to the lower side. If given value < 0
+ or > 1, it is ignored and top position is used (0.0). -->
+ <item name="config_letterboxTabletopModePositionMultiplier" format="float" type="dimen">0.0</item>
+
<!-- Whether horizontal reachability repositioning is allowed for letterboxed fullscreen apps.
-->
<bool name="config_letterboxIsHorizontalReachabilityEnabled">false</bool>
@@ -5287,6 +5308,26 @@
If given value is outside of this range, the option 1 (center) is assummed. -->
<integer name="config_letterboxDefaultPositionForVerticalReachability">1</integer>
+ <!-- Default horizontal position of the letterboxed app window when reachability is
+ enabled and an app is fullscreen in landscape device orientation and in book mode. When
+ reachability is enabled, the position can change between left, center and right. This config
+ defines the default one:
+ - Option 0 - Left.
+ - Option 1 - Center.
+ - Option 2 - Right.
+ If given value is outside of this range, the option 0 (left) is assummed. -->
+ <integer name="config_letterboxDefaultPositionForBookModeReachability">0</integer>
+
+ <!-- Default vertical position of the letterboxed app window when reachability is
+ enabled and an app is fullscreen in portrait device orientation and in tabletop mode. When
+ reachability is enabled, the position can change between top, center and bottom. This config
+ defines the default one:
+ - Option 0 - Top.
+ - Option 1 - Center.
+ - Option 2 - Bottom.
+ If given value is outside of this range, the option 0 (top) is assummed. -->
+ <integer name="config_letterboxDefaultPositionForTabletopModeReachability">0</integer>
+
<!-- Whether displaying letterbox education is enabled for letterboxed fullscreen apps. -->
<bool name="config_letterboxIsEducationEnabled">false</bool>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index ba274a7..fb77b3b 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -4435,6 +4435,8 @@
<java-symbol type="array" name="config_keep_warming_services" />
<java-symbol type="string" name="config_display_features" />
<java-symbol type="array" name="config_device_state_postures" />
+ <java-symbol type="array" name="config_deviceTabletopRotations" />
+ <java-symbol type="bool" name="config_isDisplayHingeAlwaysSeparating" />
<java-symbol type="dimen" name="controls_thumbnail_image_max_height" />
<java-symbol type="dimen" name="controls_thumbnail_image_max_width" />
@@ -4447,10 +4449,14 @@
<java-symbol type="color" name="config_letterboxBackgroundColor" />
<java-symbol type="dimen" name="config_letterboxHorizontalPositionMultiplier" />
<java-symbol type="dimen" name="config_letterboxVerticalPositionMultiplier" />
+ <java-symbol type="dimen" name="config_letterboxBookModePositionMultiplier" />
+ <java-symbol type="dimen" name="config_letterboxTabletopModePositionMultiplier" />
<java-symbol type="bool" name="config_letterboxIsHorizontalReachabilityEnabled" />
<java-symbol type="bool" name="config_letterboxIsVerticalReachabilityEnabled" />
<java-symbol type="integer" name="config_letterboxDefaultPositionForHorizontalReachability" />
<java-symbol type="integer" name="config_letterboxDefaultPositionForVerticalReachability" />
+ <java-symbol type="integer" name="config_letterboxDefaultPositionForBookModeReachability" />
+ <java-symbol type="integer" name="config_letterboxDefaultPositionForTabletopModeReachability" />
<java-symbol type="bool" name="config_letterboxIsEducationEnabled" />
<java-symbol type="dimen" name="config_letterboxDefaultMinAspectRatioForUnresizableApps" />
<java-symbol type="bool" name="config_letterboxIsSplitScreenAspectRatioForUnresizableAppsEnabled" />
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 86c8097..49704d9 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -1525,6 +1525,12 @@
"group": "WM_DEBUG_FOCUS_LIGHT",
"at": "com\/android\/server\/wm\/DisplayContent.java"
},
+ "-637815408": {
+ "message": "Invalid surface rotation angle in config_deviceTabletopRotations: %d",
+ "level": "ERROR",
+ "group": "WM_DEBUG_ORIENTATION",
+ "at": "com\/android\/server\/wm\/DisplayRotation.java"
+ },
"-636553602": {
"message": "commitVisibility: %s: visible=%b visibleRequested=%b, isInTransition=%b, runningAnimation=%b, caller=%s",
"level": "VERBOSE",
@@ -3193,6 +3199,12 @@
"group": "WM_DEBUG_STATES",
"at": "com\/android\/server\/wm\/TaskFragment.java"
},
+ "939638078": {
+ "message": "config_deviceTabletopRotations is not defined. Half-fold letterboxing will work inconsistently.",
+ "level": "WARN",
+ "group": "WM_DEBUG_ORIENTATION",
+ "at": "com\/android\/server\/wm\/DisplayRotation.java"
+ },
"948208142": {
"message": "Setting Activity.mLauncherTaskBehind to true. Activity=%s",
"level": "DEBUG",
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/animation/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt
index f55fb97..9058510 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt
@@ -169,11 +169,9 @@
setFloatUniform("in_progress", value)
val curvedProg = 1 - (1 - value) * (1 - value) * (1 - value)
- setFloatUniform(
- "in_size",
- /* width= */ maxSize.x * curvedProg,
- /* height= */ maxSize.y * curvedProg
- )
+ currentWidth = maxSize.x * curvedProg
+ currentHeight = maxSize.y * curvedProg
+ setFloatUniform("in_size", /* width= */ currentWidth, /* height= */ currentHeight)
setFloatUniform("in_thickness", maxSize.y * curvedProg * 0.5f)
// radius should not exceed width and height values.
setFloatUniform("in_cornerRadius", Math.min(maxSize.x, maxSize.y) * curvedProg)
@@ -237,4 +235,10 @@
* False for a ring effect.
*/
var rippleFill: Boolean = false
+
+ var currentWidth: Float = 0f
+ private set
+
+ var currentHeight: Float = 0f
+ private set
}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt
index ae28a8b..b37c734 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt
@@ -36,7 +36,7 @@
*/
open class RippleView(context: Context?, attrs: AttributeSet?) : View(context, attrs) {
- private lateinit var rippleShader: RippleShader
+ protected lateinit var rippleShader: RippleShader
lateinit var rippleShape: RippleShape
private set
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/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 87d2c51..f37d221 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1272,6 +1272,9 @@
translate into their final position. -->
<dimen name="lockscreen_shade_keyguard_transition_distance">@dimen/lockscreen_shade_media_transition_distance</dimen>
+ <!-- DREAMING -> LOCKSCREEN transition: Amount to shift lockscreen content on entering -->
+ <dimen name="dreaming_to_lockscreen_transition_lockscreen_translation_y">40dp</dimen>
+
<!-- The amount of vertical offset for the keyguard during the full shade transition. -->
<dimen name="lockscreen_shade_keyguard_transition_vertical_offset">0dp</dimen>
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/keyguard/KeyguardStatusView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
index 8b9823b..b8e196f 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
@@ -16,6 +16,8 @@
package com.android.keyguard;
+import static java.util.Collections.emptySet;
+
import android.content.Context;
import android.os.Trace;
import android.util.AttributeSet;
@@ -88,8 +90,9 @@
}
/** Sets a translationY value on every child view except for the media view. */
- public void setChildrenTranslationYExcludingMediaView(float translationY) {
- setChildrenTranslationYExcluding(translationY, Set.of(mMediaHostContainer));
+ public void setChildrenTranslationY(float translationY, boolean excludeMedia) {
+ setChildrenTranslationYExcluding(translationY,
+ excludeMedia ? Set.of(mMediaHostContainer) : emptySet());
}
/** Sets a translationY value on every view except for the views in the provided set. */
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index 7849747..aec3063 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -20,6 +20,8 @@
import android.util.Slog;
import com.android.keyguard.KeyguardClockSwitch.ClockSize;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.ClockAnimations;
import com.android.systemui.statusbar.notification.AnimatableProperty;
import com.android.systemui.statusbar.notification.PropertyAnimator;
@@ -59,6 +61,7 @@
KeyguardUpdateMonitor keyguardUpdateMonitor,
ConfigurationController configurationController,
DozeParameters dozeParameters,
+ FeatureFlags featureFlags,
ScreenOffAnimationController screenOffAnimationController) {
super(keyguardStatusView);
mKeyguardSliceViewController = keyguardSliceViewController;
@@ -67,6 +70,8 @@
mConfigurationController = configurationController;
mKeyguardVisibilityHelper = new KeyguardVisibilityHelper(mView, keyguardStateController,
dozeParameters, screenOffAnimationController, /* animateYPos= */ true);
+ mKeyguardVisibilityHelper.setOcclusionTransitionFlagEnabled(
+ featureFlags.isEnabled(Flags.UNOCCLUSION_TRANSITION));
}
@Override
@@ -115,8 +120,8 @@
/**
* Sets a translationY on the views on the keyguard, except on the media view.
*/
- public void setTranslationYExcludingMedia(float translationY) {
- mView.setChildrenTranslationYExcludingMediaView(translationY);
+ public void setTranslationY(float translationY, boolean excludeMedia) {
+ mView.setChildrenTranslationY(translationY, excludeMedia);
}
/**
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
index 498304b..bde0692 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
@@ -44,6 +44,7 @@
private boolean mAnimateYPos;
private boolean mKeyguardViewVisibilityAnimating;
private boolean mLastOccludedState = false;
+ private boolean mIsUnoccludeTransitionFlagEnabled = false;
private final AnimationProperties mAnimationProperties = new AnimationProperties();
public KeyguardVisibilityHelper(View view,
@@ -62,6 +63,10 @@
return mKeyguardViewVisibilityAnimating;
}
+ public void setOcclusionTransitionFlagEnabled(boolean enabled) {
+ mIsUnoccludeTransitionFlagEnabled = enabled;
+ }
+
/**
* Set the visibility of a keyguard view based on some new state.
*/
@@ -129,7 +134,7 @@
// since it may need to be cancelled due to keyguard lifecycle events.
mScreenOffAnimationController.animateInKeyguard(
mView, mAnimateKeyguardStatusViewVisibleEndRunnable);
- } else if (mLastOccludedState && !isOccluded) {
+ } else if (!mIsUnoccludeTransitionFlagEnabled && mLastOccludedState && !isOccluded) {
// An activity was displayed over the lock screen, and has now gone away
mView.setVisibility(View.VISIBLE);
mView.setAlpha(0f);
@@ -142,7 +147,9 @@
.start();
} else {
mView.setVisibility(View.VISIBLE);
- mView.setAlpha(1f);
+ if (!mIsUnoccludeTransitionFlagEnabled) {
+ mView.setAlpha(1f);
+ }
}
} else {
mView.setVisibility(View.GONE);
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
index 833ff3f..37183e8 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
@@ -22,6 +22,7 @@
import static com.android.systemui.plugins.SensorManagerPlugin.Sensor.TYPE_WAKE_LOCK_SCREEN;
import android.annotation.AnyThread;
+import android.content.res.Resources;
import android.database.ContentObserver;
import android.hardware.Sensor;
import android.hardware.SensorManager;
@@ -40,6 +41,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
+import com.android.internal.R;
import com.android.internal.logging.UiEvent;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.logging.UiEventLoggerImpl;
@@ -140,6 +142,7 @@
}
DozeSensors(
+ Resources resources,
AsyncSensorManager sensorManager,
DozeParameters dozeParameters,
AmbientDisplayConfiguration config,
@@ -185,7 +188,8 @@
new TriggerSensor(
mSensorManager.getDefaultSensor(Sensor.TYPE_PICK_UP_GESTURE),
Settings.Secure.DOZE_PICK_UP_GESTURE,
- true /* settingDef */,
+ resources.getBoolean(
+ R.bool.config_dozePickupGestureEnabled) /* settingDef */,
config.dozePickupSensorAvailable(),
DozeLog.REASON_SENSOR_PICKUP, false /* touchCoords */,
false /* touchscreen */,
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
index 3f9f14c..b95c3f3 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
@@ -210,7 +210,7 @@
mAllowPulseTriggers = true;
mSessionTracker = sessionTracker;
- mDozeSensors = new DozeSensors(mSensorManager, dozeParameters,
+ mDozeSensors = new DozeSensors(mContext.getResources(), mSensorManager, dozeParameters,
config, wakeLock, this::onSensor, this::onProximityFar, dozeLog, proximitySensor,
secureSettings, authController, devicePostureController, userTracker);
mDockManager = dockManager;
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/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 339fd13..1f80b5a 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -181,10 +181,6 @@
@JvmField
val LIGHT_REVEAL_MIGRATION = unreleasedFlag(218, "light_reveal_migration", teamfood = false)
- // TODO(b/262780002): Tracking Bug
- @JvmField
- val REVAMPED_WALLPAPER_UI = unreleasedFlag(222, "revamped_wallpaper_ui", teamfood = false)
-
/** Flag to control the migration of face auth to modern architecture. */
// TODO(b/262838215): Tracking bug
@JvmField val FACE_AUTH_REFACTOR = unreleasedFlag(220, "face_auth_refactor")
@@ -193,6 +189,15 @@
// TODO(b/244313043): Tracking bug
@JvmField val BIOMETRICS_ANIMATION_REVAMP = unreleasedFlag(221, "biometrics_animation_revamp")
+ // TODO(b/262780002): Tracking Bug
+ @JvmField
+ val REVAMPED_WALLPAPER_UI = unreleasedFlag(222, "revamped_wallpaper_ui", teamfood = false)
+
+ /** A different path for unocclusion transitions back to keyguard */
+ // TODO(b/262859270): Tracking Bug
+ @JvmField
+ val UNOCCLUSION_TRANSITION = unreleasedFlag(223, "unocclusion_transition", teamfood = false)
+
// 300 - power menu
// TODO(b/254512600): Tracking Bug
@JvmField val POWER_MENU_LITE = releasedFlag(300, "power_menu_lite")
@@ -310,6 +315,10 @@
// TODO(b/261734857): Tracking Bug
@JvmField val UMO_TURBULENCE_NOISE = unreleasedFlag(909, "umo_turbulence_noise")
+ // TODO(b/263272731): Tracking Bug
+ val MEDIA_TTT_RECEIVER_SUCCESS_RIPPLE =
+ unreleasedFlag(910, "media_ttt_receiver_success_ripple", teamfood = true)
+
// 1000 - dock
val SIMULATE_DOCK_THROUGH_CHARGING = releasedFlag(1000, "simulate_dock_through_charging")
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index d231870..8aada1f 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.ui.viewmodel.DreamingToLockscreenTransitionViewModel.LOCKSCREEN_ANIMATION_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) LOCKSCREEN_ANIMATION_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..188930c 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,15 @@
}
}
- 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
}
}
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..402fac1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt
@@ -0,0 +1,81 @@
+/*
+ * 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.animation.Interpolators.EMPHASIZED_DECELERATE
+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 }
+
+ /** Lockscreen views y-translation */
+ fun lockscreenTranslationY(translatePx: Int): Flow<Float> {
+ return flowForAnimation(LOCKSCREEN_TRANSLATION_Y).map { value ->
+ -translatePx + (EMPHASIZED_DECELERATE.getInterpolation(value) * 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
+ @JvmField
+ val LOCKSCREEN_ANIMATION_DURATION_MS =
+ (TO_LOCKSCREEN_DURATION - DREAM_ANIMATION_DURATION).inWholeMilliseconds
+
+ val DREAM_OVERLAY_TRANSLATION_Y = AnimationParams(duration = 600.milliseconds)
+ val DREAM_OVERLAY_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/MediaTttFlags.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttFlags.kt
index 03bc935..8a565fa 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttFlags.kt
@@ -26,4 +26,8 @@
class MediaTttFlags @Inject constructor(private val featureFlags: FeatureFlags) {
/** */
fun isMediaTttEnabled(): Boolean = featureFlags.isEnabled(Flags.MEDIA_TAP_TO_TRANSFER)
+
+ /** Check whether the flag for the receiver success state is enabled. */
+ fun isMediaTttReceiverSuccessRippleEnabled(): Boolean =
+ featureFlags.isEnabled(Flags.MEDIA_TTT_RECEIVER_SUCCESS_RIPPLE)
}
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..889147b 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,
@@ -111,6 +114,9 @@
}
}
+ private var maxRippleWidth: Float = 0f
+ private var maxRippleHeight: Float = 0f
+
private fun updateMediaTapToTransferReceiverDisplay(
@StatusBarManager.MediaTransferReceiverState displayState: Int,
routeInfo: MediaRoute2Info,
@@ -162,6 +168,7 @@
}
override fun start() {
+ super.start()
if (mediaTttFlags.isMediaTttEnabled()) {
commandQueue.addCallback(commandQueueCallbacks)
}
@@ -212,7 +219,7 @@
expandRipple(view.requireViewById(R.id.ripple))
}
- override fun animateViewOut(view: ViewGroup, onAnimationEnd: Runnable) {
+ override fun animateViewOut(view: ViewGroup, removalReason: String?, onAnimationEnd: Runnable) {
val appIconView = view.getAppIconView()
appIconView.animate()
.translationYBy(getTranslationAmount().toFloat())
@@ -222,7 +229,14 @@
.alpha(0f)
.setDuration(ICON_ALPHA_ANIM_DURATION)
.start()
- (view.requireViewById(R.id.ripple) as ReceiverChipRippleView).collapseRipple(onAnimationEnd)
+
+ val rippleView: ReceiverChipRippleView = view.requireViewById(R.id.ripple)
+ if (removalReason == ChipStateReceiver.TRANSFER_TO_RECEIVER_SUCCEEDED.name &&
+ mediaTttFlags.isMediaTttReceiverSuccessRippleEnabled()) {
+ expandRippleToFull(rippleView, onAnimationEnd)
+ } else {
+ rippleView.collapseRipple(onAnimationEnd)
+ }
}
override fun getTouchableRegion(view: View, outRect: Rect) {
@@ -267,12 +281,19 @@
})
}
- private fun layoutRipple(rippleView: ReceiverChipRippleView) {
+ private fun layoutRipple(rippleView: ReceiverChipRippleView, isFullScreen: Boolean = false) {
val windowBounds = windowManager.currentWindowMetrics.bounds
val height = windowBounds.height().toFloat()
val width = windowBounds.width().toFloat()
- rippleView.setMaxSize(width / 2f, height / 2f)
+ if (isFullScreen) {
+ maxRippleHeight = height * 2f
+ maxRippleWidth = width * 2f
+ } else {
+ maxRippleHeight = height / 2f
+ maxRippleWidth = width / 2f
+ }
+ rippleView.setMaxSize(maxRippleWidth, maxRippleHeight)
// Center the ripple on the bottom of the screen in the middle.
rippleView.setCenter(width * 0.5f, height)
val color = Utils.getColorAttrDefaultColor(context, R.attr.wallpaperTextColorAccent)
@@ -282,6 +303,11 @@
private fun View.getAppIconView(): CachingIconView {
return this.requireViewById(R.id.app_icon)
}
+
+ private fun expandRippleToFull(rippleView: ReceiverChipRippleView, onAnimationEnd: Runnable?) {
+ layoutRipple(rippleView, true)
+ rippleView.expandToFull(maxRippleHeight, onAnimationEnd)
+ }
}
val ICON_TRANSLATION_ANIM_DURATION = 30.frames
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt
index 6e9fc5c..87b2528 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt
@@ -22,6 +22,7 @@
import android.util.AttributeSet
import com.android.systemui.surfaceeffects.ripple.RippleShader
import com.android.systemui.surfaceeffects.ripple.RippleView
+import kotlin.math.pow
/**
* An expanding ripple effect for the media tap-to-transfer receiver chip.
@@ -59,4 +60,44 @@
})
animator.reverse()
}
+
+ // Expands the ripple to cover full screen.
+ fun expandToFull(newHeight: Float, onAnimationEnd: Runnable? = null) {
+ if (!isStarted) {
+ return
+ }
+ // Reset all listeners to animator.
+ animator.removeAllListeners()
+ animator.removeAllUpdateListeners()
+
+ // Only show the outline as ripple expands and disappears when animation ends.
+ setRippleFill(false)
+
+ val startingPercentage = calculateStartingPercentage(newHeight)
+ animator.addUpdateListener { updateListener ->
+ val now = updateListener.currentPlayTime
+ val progress = updateListener.animatedValue as Float
+ rippleShader.progress = startingPercentage + (progress * (1 - startingPercentage))
+ rippleShader.distortionStrength = 1 - rippleShader.progress
+ rippleShader.pixelDensity = 1 - rippleShader.progress
+ rippleShader.time = now.toFloat()
+ invalidate()
+ }
+ animator.addListener(object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator?) {
+ animation?.let { visibility = GONE }
+ onAnimationEnd?.run()
+ isStarted = false
+ }
+ })
+ animator.start()
+ }
+
+ // Calculates the actual starting percentage according to ripple shader progress set method.
+ // Check calculations in [RippleShader.progress]
+ fun calculateStartingPercentage(newHeight: Float): Float {
+ val ratio = rippleShader.currentHeight / newHeight
+ val remainingPercentage = (1 - ratio).toDouble().pow(1 / 3.toDouble()).toFloat()
+ return 1 - remainingPercentage
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index fcdde79..8054f27 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -44,6 +44,7 @@
import static com.android.systemui.statusbar.VibratorHelper.TOUCH_VIBRATION_ATTRIBUTES;
import static com.android.systemui.statusbar.notification.stack.StackStateAnimator.ANIMATION_DURATION_FOLD_TO_AOD;
import static com.android.systemui.util.DumpUtilsKt.asIndenting;
+import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
import static java.lang.Float.isNaN;
@@ -138,6 +139,10 @@
import com.android.systemui.fragments.FragmentService;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor;
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
+import com.android.systemui.keyguard.shared.model.TransitionState;
+import com.android.systemui.keyguard.shared.model.TransitionStep;
+import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel;
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel;
import com.android.systemui.media.controls.pipeline.MediaDataManager;
import com.android.systemui.media.controls.ui.KeyguardMediaController;
@@ -677,6 +682,12 @@
private boolean mGestureWaitForTouchSlop;
private boolean mIgnoreXTouchSlop;
private boolean mExpandLatencyTracking;
+ private DreamingToLockscreenTransitionViewModel mDreamingToLockscreenTransitionViewModel;
+
+ private KeyguardTransitionInteractor mKeyguardTransitionInteractor;
+ private boolean mIsDreamToLockscreenTransitionRunning = false;
+ private int mDreamingToLockscreenTransitionTranslationY;
+ private boolean mUnocclusionTransitionFlagEnabled = false;
private final Runnable mFlingCollapseRunnable = () -> fling(0, false /* expand */,
mNextCollapseSpeedUpFactor, false /* expandBecauseOfFalsing */);
@@ -692,6 +703,12 @@
}
};
+ private final Consumer<TransitionStep> mDreamingToLockscreenTransition =
+ (TransitionStep step) -> {
+ mIsDreamToLockscreenTransitionRunning =
+ step.getTransitionState() == TransitionState.RUNNING;
+ };
+
@Inject
public NotificationPanelViewController(NotificationPanelView view,
@Main Handler handler,
@@ -760,6 +777,8 @@
SystemClock systemClock,
KeyguardBottomAreaViewModel keyguardBottomAreaViewModel,
KeyguardBottomAreaInteractor keyguardBottomAreaInteractor,
+ DreamingToLockscreenTransitionViewModel dreamingToLockscreenTransitionViewModel,
+ KeyguardTransitionInteractor keyguardTransitionInteractor,
DumpManager dumpManager) {
keyguardStateController.addCallback(new KeyguardStateController.Callback() {
@Override
@@ -775,6 +794,8 @@
mShadeLog = shadeLogger;
mShadeHeightLogger = shadeHeightLogger;
mGutsManager = gutsManager;
+ mDreamingToLockscreenTransitionViewModel = dreamingToLockscreenTransitionViewModel;
+ mKeyguardTransitionInteractor = keyguardTransitionInteractor;
mView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
@Override
public void onViewAttachedToWindow(View v) {
@@ -920,6 +941,8 @@
mNotificationPanelUnfoldAnimationController = unfoldComponent.map(
SysUIUnfoldComponent::getNotificationPanelUnfoldAnimationController);
+ mUnocclusionTransitionFlagEnabled = featureFlags.isEnabled(Flags.UNOCCLUSION_TRANSITION);
+
mQsFrameTranslateController = qsFrameTranslateController;
updateUserSwitcherFlags();
mKeyguardBottomAreaViewModel = keyguardBottomAreaViewModel;
@@ -1072,6 +1095,18 @@
mKeyguardUnfoldTransition.ifPresent(u -> u.setup(mView));
mNotificationPanelUnfoldAnimationController.ifPresent(controller ->
controller.setup(mNotificationContainerParent));
+
+ if (mUnocclusionTransitionFlagEnabled) {
+ collectFlow(mView, mDreamingToLockscreenTransitionViewModel.getLockscreenAlpha(),
+ dreamingToLockscreenTransitionAlpha(mNotificationStackScrollLayoutController));
+
+ collectFlow(mView, mDreamingToLockscreenTransitionViewModel.lockscreenTranslationY(
+ mDreamingToLockscreenTransitionTranslationY),
+ dreamingToLockscreenTransitionY(mNotificationStackScrollLayoutController));
+
+ collectFlow(mView, mKeyguardTransitionInteractor.getDreamingToLockscreenTransition(),
+ mDreamingToLockscreenTransition);
+ }
}
@VisibleForTesting
@@ -1106,6 +1141,8 @@
mUdfpsMaxYBurnInOffset = mResources.getDimensionPixelSize(R.dimen.udfps_burn_in_offset_y);
mSplitShadeScrimTransitionDistance = mResources.getDimensionPixelSize(
R.dimen.split_shade_scrim_transition_distance);
+ mDreamingToLockscreenTransitionTranslationY = mResources.getDimensionPixelSize(
+ R.dimen.dreaming_to_lockscreen_transition_lockscreen_translation_y);
}
private void updateViewControllers(KeyguardStatusView keyguardStatusView,
@@ -1769,10 +1806,14 @@
}
private void updateClock() {
+ if (mIsDreamToLockscreenTransitionRunning) {
+ return;
+ }
float alpha = mClockPositionResult.clockAlpha * mKeyguardOnlyContentAlpha;
mKeyguardStatusViewController.setAlpha(alpha);
mKeyguardStatusViewController
- .setTranslationYExcludingMedia(mKeyguardOnlyTransitionTranslationY);
+ .setTranslationY(mKeyguardOnlyTransitionTranslationY, /* excludeMedia= */true);
+
if (mKeyguardQsUserSwitchController != null) {
mKeyguardQsUserSwitchController.setAlpha(alpha);
}
@@ -2656,7 +2697,9 @@
} else if (statusBarState == KEYGUARD
|| statusBarState == StatusBarState.SHADE_LOCKED) {
mKeyguardBottomArea.setVisibility(View.VISIBLE);
- mKeyguardBottomArea.setAlpha(1f);
+ if (!mIsDreamToLockscreenTransitionRunning) {
+ mKeyguardBottomArea.setAlpha(1f);
+ }
} else {
mKeyguardBottomArea.setVisibility(View.GONE);
}
@@ -3523,6 +3566,9 @@
}
private void updateNotificationTranslucency() {
+ if (mIsDreamToLockscreenTransitionRunning) {
+ return;
+ }
float alpha = 1f;
if (mClosingWithAlphaFadeOut && !mExpandingFromHeadsUp
&& !mHeadsUpManager.hasPinnedHeadsUp()) {
@@ -3578,6 +3624,9 @@
}
private void updateKeyguardBottomAreaAlpha() {
+ if (mIsDreamToLockscreenTransitionRunning) {
+ return;
+ }
// There are two possible panel expansion behaviors:
// • User dragging up to unlock: we want to fade out as quick as possible
// (ALPHA_EXPANSION_THRESHOLD) to avoid seeing the bouncer over the bottom area.
@@ -3610,7 +3659,9 @@
}
private void onExpandingFinished() {
- mScrimController.onExpandingFinished();
+ if (!mUnocclusionTransitionFlagEnabled) {
+ mScrimController.onExpandingFinished();
+ }
mNotificationStackScrollLayoutController.onExpansionStopped();
mHeadsUpManager.onExpandingFinished();
mConversationNotificationManager.onNotificationPanelExpandStateChanged(isFullyCollapsed());
@@ -5805,6 +5856,32 @@
mCurrentPanelState = state;
}
+ private Consumer<Float> dreamingToLockscreenTransitionAlpha(
+ NotificationStackScrollLayoutController stackScroller) {
+ return (Float alpha) -> {
+ mKeyguardStatusViewController.setAlpha(alpha);
+ stackScroller.setAlpha(alpha);
+
+ mKeyguardBottomAreaInteractor.setAlpha(alpha);
+ mLockIconViewController.setAlpha(alpha);
+
+ if (mKeyguardQsUserSwitchController != null) {
+ mKeyguardQsUserSwitchController.setAlpha(alpha);
+ }
+ if (mKeyguardUserSwitcherController != null) {
+ mKeyguardUserSwitcherController.setAlpha(alpha);
+ }
+ };
+ }
+
+ private Consumer<Float> dreamingToLockscreenTransitionY(
+ NotificationStackScrollLayoutController stackScroller) {
+ return (Float translationY) -> {
+ mKeyguardStatusViewController.setTranslationY(translationY, /* excludeMedia= */false);
+ stackScroller.setTranslationY(translationY);
+ };
+ }
+
@VisibleForTesting
StatusBarStateController getStatusBarStateController() {
return mStatusBarStateController;
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/LightRevealScrim.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
index 2334a4c..9421524 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
@@ -90,8 +90,13 @@
class LinearLightRevealEffect(private val isVertical: Boolean) : LightRevealEffect {
// Interpolator that reveals >80% of the content at 0.5 progress, makes revealing faster
- private val interpolator = PathInterpolator(/* controlX1= */ 0.4f, /* controlY1= */ 0f,
- /* controlX2= */ 0.2f, /* controlY2= */ 1f)
+ private val interpolator =
+ PathInterpolator(
+ /* controlX1= */ 0.4f,
+ /* controlY1= */ 0f,
+ /* controlX2= */ 0.2f,
+ /* controlY2= */ 1f
+ )
override fun setRevealAmountOnScrim(amount: Float, scrim: LightRevealScrim) {
val interpolatedAmount = interpolator.getInterpolation(amount)
@@ -116,17 +121,17 @@
if (isVertical) {
scrim.setRevealGradientBounds(
- left = scrim.width / 2 - (scrim.width / 2) * gradientBoundsAmount,
+ left = scrim.viewWidth / 2 - (scrim.viewWidth / 2) * gradientBoundsAmount,
top = 0f,
- right = scrim.width / 2 + (scrim.width / 2) * gradientBoundsAmount,
- bottom = scrim.height.toFloat()
+ right = scrim.viewWidth / 2 + (scrim.viewWidth / 2) * gradientBoundsAmount,
+ bottom = scrim.viewHeight.toFloat()
)
} else {
scrim.setRevealGradientBounds(
left = 0f,
- top = scrim.height / 2 - (scrim.height / 2) * gradientBoundsAmount,
- right = scrim.width.toFloat(),
- bottom = scrim.height / 2 + (scrim.height / 2) * gradientBoundsAmount
+ top = scrim.viewHeight / 2 - (scrim.viewHeight / 2) * gradientBoundsAmount,
+ right = scrim.viewWidth.toFloat(),
+ bottom = scrim.viewHeight / 2 + (scrim.viewHeight / 2) * gradientBoundsAmount
)
}
}
@@ -234,7 +239,14 @@
* transparent center. The center position, size, and stops of the gradient can be manipulated to
* reveal views below the scrim as if they are being 'lit up'.
*/
-class LightRevealScrim(context: Context?, attrs: AttributeSet?) : View(context, attrs) {
+class LightRevealScrim
+@JvmOverloads
+constructor(
+ context: Context?,
+ attrs: AttributeSet?,
+ initialWidth: Int? = null,
+ initialHeight: Int? = null
+) : View(context, attrs) {
/** Listener that is called if the scrim's opaqueness changes */
lateinit var isScrimOpaqueChangedListener: Consumer<Boolean>
@@ -278,6 +290,17 @@
var revealGradientHeight: Float = 0f
/**
+ * Keeps the initial value until the view is measured. See [LightRevealScrim.onMeasure].
+ *
+ * Needed as the view dimensions are used before the onMeasure pass happens, and without preset
+ * width and height some flicker during fold/unfold happens.
+ */
+ internal var viewWidth: Int = initialWidth ?: 0
+ private set
+ internal var viewHeight: Int = initialHeight ?: 0
+ private set
+
+ /**
* Alpha of the fill that can be used in the beginning of the animation to hide the content.
* Normally the gradient bounds are animated from small size so the content is not visible, but
* if the start gradient bounds allow to see some content this could be used to make the reveal
@@ -375,6 +398,11 @@
invalidate()
}
+ override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec)
+ viewWidth = measuredWidth
+ viewHeight = measuredHeight
+ }
/**
* Sets bounds for the transparent oval gradient that reveals the views below the scrim. This is
* simply a helper method that sets [revealGradientCenter], [revealGradientWidth], and
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 4bcc0b6..c2c38a7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -916,6 +916,11 @@
return mView.getTranslationX();
}
+ /** Set view y-translation */
+ public void setTranslationY(float translationY) {
+ mView.setTranslationY(translationY);
+ }
+
public int indexOfChild(View view) {
return mView.indexOfChild(view);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index d500f99..ee8b861 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -232,7 +232,6 @@
private boolean mExpansionAffectsAlpha = true;
private boolean mAnimateChange;
private boolean mUpdatePending;
- private boolean mTracking;
private long mAnimationDuration = -1;
private long mAnimationDelay;
private Animator.AnimatorListener mAnimatorListener;
@@ -526,7 +525,6 @@
}
public void onTrackingStarted() {
- mTracking = true;
mDarkenWhileDragging = !mKeyguardStateController.canDismissLockScreen();
if (!mKeyguardUnlockAnimationController.isPlayingCannedUnlockAnimation()) {
mAnimatingPanelExpansionOnUnlock = false;
@@ -534,7 +532,6 @@
}
public void onExpandingFinished() {
- mTracking = false;
setUnocclusionAnimationRunning(false);
}
@@ -1450,8 +1447,6 @@
pw.print(" expansionProgress=");
pw.println(mTransitionToLockScreenFullShadeNotificationsProgress);
- pw.print(" mTracking=");
- pw.println(mTracking);
pw.print(" mDefaultScrimAlpha=");
pw.println(mDefaultScrimAlpha);
pw.print(" mPanelExpansionFraction=");
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index f196505..d480fab 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -135,7 +135,7 @@
private final PrimaryBouncerCallbackInteractor mPrimaryBouncerCallbackInteractor;
private final PrimaryBouncerInteractor mPrimaryBouncerInteractor;
private final BouncerView mPrimaryBouncerView;
- private final Lazy<com.android.systemui.shade.ShadeController> mShadeController;
+ private final Lazy<ShadeController> mShadeController;
// Local cache of expansion events, to avoid duplicates
private float mFraction = -1f;
@@ -252,6 +252,7 @@
private float mQsExpansion;
final Set<KeyguardViewManagerCallback> mCallbacks = new HashSet<>();
private boolean mIsModernBouncerEnabled;
+ private boolean mIsUnoccludeTransitionFlagEnabled;
private OnDismissAction mAfterKeyguardGoneAction;
private Runnable mKeyguardGoneCancelAction;
@@ -329,6 +330,7 @@
mFoldAodAnimationController = sysUIUnfoldComponent
.map(SysUIUnfoldComponent::getFoldAodAnimationController).orElse(null);
mIsModernBouncerEnabled = featureFlags.isEnabled(Flags.MODERN_BOUNCER);
+ mIsUnoccludeTransitionFlagEnabled = featureFlags.isEnabled(Flags.UNOCCLUSION_TRANSITION);
}
@Override
@@ -867,8 +869,10 @@
// by a FLAG_DISMISS_KEYGUARD_ACTIVITY.
reset(isOccluding /* hideBouncerWhenShowing*/);
}
- if (animate && !isOccluded && isShowing && !primaryBouncerIsShowing()) {
- mCentralSurfaces.animateKeyguardUnoccluding();
+ if (!mIsUnoccludeTransitionFlagEnabled) {
+ if (animate && !isOccluded && isShowing && !primaryBouncerIsShowing()) {
+ mCentralSurfaces.animateKeyguardUnoccluding();
+ }
}
}
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..6c37f94 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
@@ -17,7 +17,6 @@
package com.android.systemui.statusbar.pipeline.mobile.data.model
import android.annotation.IntRange
-import android.telephony.Annotation.DataActivityType
import android.telephony.CellSignalStrength
import android.telephony.TelephonyCallback.CarrierNetworkListener
import android.telephony.TelephonyCallback.DataActivityListener
@@ -28,6 +27,7 @@
import android.telephony.TelephonyDisplayInfo
import android.telephony.TelephonyManager
import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState.Disconnected
+import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
/**
* Data class containing all of the relevant information for a particular line of service, known as
@@ -39,28 +39,42 @@
* threading complex system objects through the pipeline.
*/
data class MobileConnectionModel(
- /** From [ServiceStateListener.onServiceStateChanged] */
+ /** Fields below are from [ServiceStateListener.onServiceStateChanged] */
val isEmergencyOnly: Boolean = false,
+ val isRoaming: Boolean = false,
+ /**
+ * See [android.telephony.ServiceState.getOperatorAlphaShort], this value is defined as the
+ * current registered operator name in short alphanumeric format. In some cases this name might
+ * be preferred over other methods of calculating the network name
+ */
+ val operatorAlphaShort: String? = null,
- /** From [SignalStrengthsListener.onSignalStrengthsChanged] */
+ /** Fields below from [SignalStrengthsListener.onSignalStrengthsChanged] */
val isGsm: Boolean = false,
@IntRange(from = 0, to = 4)
val cdmaLevel: Int = CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN,
@IntRange(from = 0, to = 4)
val primaryLevel: Int = CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN,
- /** Mapped from [DataConnectionStateListener.onDataConnectionStateChanged] */
+ /** Fields below from [DataConnectionStateListener.onDataConnectionStateChanged] */
val dataConnectionState: DataConnectionState = Disconnected,
- /** From [DataActivityListener.onDataActivity]. See [TelephonyManager] for the values */
- @DataActivityType val dataActivityDirection: Int? = null,
+ /**
+ * Fields below from [DataActivityListener.onDataActivity]. See [TelephonyManager] for the
+ * values
+ */
+ val dataActivityDirection: DataActivityModel =
+ DataActivityModel(
+ hasActivityIn = false,
+ hasActivityOut = false,
+ ),
- /** From [CarrierNetworkListener.onCarrierNetworkChange] */
+ /** Fields below from [CarrierNetworkListener.onCarrierNetworkChange] */
val carrierNetworkChangeActive: Boolean = false,
+ /** Fields below from [DisplayInfoListener.onDisplayInfoChanged]. */
+
/**
- * From [DisplayInfoListener.onDisplayInfoChanged].
- *
* [resolvedNetworkType] is the [TelephonyDisplayInfo.getOverrideNetworkType] if it exists or
* [TelephonyDisplayInfo.getNetworkType]. This is used to look up the proper network type icon
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/NetworkNameModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/NetworkNameModel.kt
new file mode 100644
index 0000000..a8cf35a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/NetworkNameModel.kt
@@ -0,0 +1,62 @@
+/*
+ * 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.statusbar.pipeline.mobile.data.model
+
+import android.content.Intent
+import android.telephony.TelephonyManager.EXTRA_DATA_SPN
+import android.telephony.TelephonyManager.EXTRA_PLMN
+import android.telephony.TelephonyManager.EXTRA_SHOW_PLMN
+import android.telephony.TelephonyManager.EXTRA_SHOW_SPN
+
+/**
+ * Encapsulates the data needed to show a network name for a mobile network. The data is parsed from
+ * the intent sent by [android.telephony.TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED].
+ */
+sealed interface NetworkNameModel {
+ val name: String
+
+ /** The default name is read from [com.android.internal.R.string.lockscreen_carrier_default] */
+ data class Default(override val name: String) : NetworkNameModel
+
+ /**
+ * This name has been derived from telephony intents. see
+ * [android.telephony.TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED]
+ */
+ data class Derived(override val name: String) : NetworkNameModel
+}
+
+fun Intent.toNetworkNameModel(separator: String): NetworkNameModel? {
+ val showSpn = getBooleanExtra(EXTRA_SHOW_SPN, false)
+ val spn = getStringExtra(EXTRA_DATA_SPN)
+ val showPlmn = getBooleanExtra(EXTRA_SHOW_PLMN, false)
+ val plmn = getStringExtra(EXTRA_PLMN)
+
+ val str = StringBuilder()
+ val strData = StringBuilder()
+ if (showPlmn && plmn != null) {
+ str.append(plmn)
+ strData.append(plmn)
+ }
+ if (showSpn && spn != null) {
+ if (str.isNotEmpty()) {
+ str.append(separator)
+ }
+ str.append(spn)
+ }
+
+ return if (str.isNotEmpty()) NetworkNameModel.Derived(str.toString()) else null
+}
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..2fd415e6 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
@@ -21,6 +21,7 @@
import android.telephony.TelephonyCallback
import android.telephony.TelephonyManager
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
+import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
@@ -50,4 +51,15 @@
* [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>
+
+ /** The service provider name for this network connection, or the default name */
+ val networkName: StateFlow<NetworkNameModel>
}
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..d3ee85f 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
@@ -18,6 +18,7 @@
import android.content.Context
import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
+import android.telephony.TelephonyManager.DATA_ACTIVITY_NONE
import android.util.Log
import com.android.settingslib.SignalIcon
import com.android.settingslib.mobile.MobileMappings
@@ -26,6 +27,7 @@
import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel
+import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType
import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.DefaultNetworkType
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
@@ -34,6 +36,7 @@
import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel.Mobile
import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel.MobileDisabled
+import com.android.systemui.statusbar.pipeline.shared.data.model.toMobileDataActivityModel
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -185,7 +188,9 @@
// This is always true here, because we split out disabled states at the data-source level
connection.dataEnabled.value = true
connection.isDefaultDataSubscription.value = state.dataType != null
+ connection.networkName.value = NetworkNameModel.Derived(state.name)
+ connection.cdmaRoaming.value = state.roaming
connection.connectionInfo.value = state.toMobileConnectionModel()
}
@@ -229,12 +234,13 @@
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,
dataConnectionState =
DataConnectionState.Connected, // TODO(b/261029387): not yet supported
- dataActivityDirection = activity,
+ dataActivityDirection = (activity ?: DATA_ACTIVITY_NONE).toMobileDataActivityModel(),
carrierNetworkChangeActive = carrierNetworkChange,
resolvedNetworkType = dataType.toResolvedNetworkType()
)
@@ -260,4 +266,8 @@
override val dataEnabled = MutableStateFlow(true)
override val isDefaultDataSubscription = MutableStateFlow(true)
+
+ override val cdmaRoaming = MutableStateFlow(false)
+
+ override val networkName = MutableStateFlow(NetworkNameModel.Derived("demo network"))
}
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..a1ae8ed 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,8 @@
val inflateStrength = getString("inflate")?.toBoolean()
val activity = getString("activity")?.toActivity()
val carrierNetworkChange = getString("carriernetworkchange") == "show"
+ val roaming = getString("roam") == "show"
+ val name = getString("networkname") ?: "demo mode"
return Mobile(
level = level,
@@ -107,6 +109,8 @@
inflateStrength = inflateStrength,
activity = activity,
carrierNetworkChange = carrierNetworkChange,
+ roaming = roaming,
+ name = name,
)
}
}
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..8b03f71 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,8 @@
val inflateStrength: Boolean?,
@DataActivityType val activity: Int?,
val carrierNetworkChange: Boolean,
+ val roaming: Boolean,
+ val name: String,
) : 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..7e9a9ce 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
@@ -17,29 +17,37 @@
package com.android.systemui.statusbar.pipeline.mobile.data.repository.prod
import android.content.Context
+import android.content.IntentFilter
import android.database.ContentObserver
import android.provider.Settings.Global
import android.telephony.CellSignalStrength
import android.telephony.CellSignalStrengthCdma
import android.telephony.ServiceState
import android.telephony.SignalStrength
+import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
import android.telephony.TelephonyCallback
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.EXTRA_SUBSCRIPTION_ID
import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN
+import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
+import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.DefaultNetworkType
import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.OverrideNetworkType
import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.UnknownNetworkType
import com.android.systemui.statusbar.pipeline.mobile.data.model.toDataConnectionType
+import com.android.systemui.statusbar.pipeline.mobile.data.model.toNetworkNameModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
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.toMobileDataActivityModel
import com.android.systemui.util.settings.GlobalSettings
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
@@ -61,8 +69,11 @@
class MobileConnectionRepositoryImpl(
private val context: Context,
override val subId: Int,
+ defaultNetworkName: NetworkNameModel,
+ networkNameSeparator: String,
private val telephonyManager: TelephonyManager,
private val globalSettings: GlobalSettings,
+ broadcastDispatcher: BroadcastDispatcher,
defaultDataSubId: StateFlow<Int>,
globalMobileDataSettingChangedEvent: Flow<Unit>,
mobileMappingsProxy: MobileMappingsProxy,
@@ -95,7 +106,12 @@
TelephonyCallback.CarrierNetworkListener,
TelephonyCallback.DisplayInfoListener {
override fun onServiceStateChanged(serviceState: ServiceState) {
- state = state.copy(isEmergencyOnly = serviceState.isEmergencyOnly)
+ state =
+ state.copy(
+ isEmergencyOnly = serviceState.isEmergencyOnly,
+ isRoaming = serviceState.roaming,
+ operatorAlphaShort = serviceState.operatorAlphaShort,
+ )
trySend(state)
}
@@ -132,7 +148,10 @@
}
override fun onDataActivity(direction: Int) {
- state = state.copy(dataActivityDirection = direction)
+ state =
+ state.copy(
+ dataActivityDirection = direction.toMobileDataActivityModel()
+ )
trySend(state)
}
@@ -208,6 +227,24 @@
globalMobileDataSettingChangedEvent,
)
+ override val cdmaRoaming: StateFlow<Boolean> =
+ telephonyPollingEvent
+ .mapLatest { telephonyManager.cdmaEnhancedRoamingIndicatorDisplayNumber != ERI_OFF }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+
+ override val networkName: StateFlow<NetworkNameModel> =
+ broadcastDispatcher
+ .broadcastFlow(IntentFilter(TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED)) {
+ intent,
+ _ ->
+ if (intent.getIntExtra(EXTRA_SUBSCRIPTION_ID, INVALID_SUBSCRIPTION_ID) != subId) {
+ defaultNetworkName
+ } else {
+ intent.toNetworkNameModel(networkNameSeparator) ?: defaultNetworkName
+ }
+ }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), defaultNetworkName)
+
override val dataEnabled: StateFlow<Boolean> =
telephonyPollingEvent
.mapLatest { dataConnectionAllowed() }
@@ -223,6 +260,7 @@
class Factory
@Inject
constructor(
+ private val broadcastDispatcher: BroadcastDispatcher,
private val context: Context,
private val telephonyManager: TelephonyManager,
private val logger: ConnectivityPipelineLogger,
@@ -233,14 +271,19 @@
) {
fun build(
subId: Int,
+ defaultNetworkName: NetworkNameModel,
+ networkNameSeparator: String,
defaultDataSubId: StateFlow<Int>,
globalMobileDataSettingChangedEvent: Flow<Unit>,
): MobileConnectionRepository {
return MobileConnectionRepositoryImpl(
context,
subId,
+ defaultNetworkName,
+ networkNameSeparator,
telephonyManager.createForSubscriptionId(subId),
globalSettings,
+ broadcastDispatcher,
defaultDataSubId,
globalMobileDataSettingChangedEvent,
mobileMappingsProxy,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
index 483df47..a9b3d18 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
@@ -38,12 +38,14 @@
import com.android.internal.telephony.PhoneConstants
import com.android.settingslib.SignalIcon.MobileIconGroup
import com.android.settingslib.mobile.MobileMappings.Config
+import com.android.systemui.R
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel
+import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository
@@ -88,6 +90,14 @@
) : MobileConnectionsRepository {
private var subIdRepositoryCache: MutableMap<Int, MobileConnectionRepository> = mutableMapOf()
+ private val defaultNetworkName =
+ NetworkNameModel.Default(
+ context.getString(com.android.internal.R.string.lockscreen_carrier_default)
+ )
+
+ private val networkNameSeparator: String =
+ context.getString(R.string.status_bar_network_name_separator)
+
/**
* State flow that emits the set of mobile data subscriptions, each represented by its own
* [SubscriptionInfo]. We probably only need the [SubscriptionInfo.getSubscriptionId] of each
@@ -243,6 +253,8 @@
private fun createRepositoryForSubId(subId: Int): MobileConnectionRepository {
return mobileConnectionRepositoryFactory.build(
subId,
+ defaultNetworkName,
+ networkNameSeparator,
defaultDataSubId,
globalMobileDataSettingChangedEvent,
)
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..76e6a96a 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
@@ -20,10 +20,13 @@
import com.android.settingslib.SignalIcon.MobileIconGroup
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState.Connected
+import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
+import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
import com.android.systemui.util.CarrierConfigTracker
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
@@ -32,6 +35,9 @@
import kotlinx.coroutines.flow.stateIn
interface MobileIconInteractor {
+ /** The current mobile data activity */
+ val activity: Flow<DataActivityModel>
+
/** Only true if mobile is the default transport but is not validated, otherwise false */
val isDefaultConnectionFailed: StateFlow<Boolean>
@@ -51,9 +57,25 @@
/** Observable for RAT type (network type) indicator */
val networkTypeIconGroup: StateFlow<MobileIconGroup>
+ /**
+ * Provider name for this network connection. The name can be one of 3 values:
+ * 1. The default network name, if one is configured
+ * 2. A derived name based off of the intent [ACTION_SERVICE_PROVIDERS_UPDATED]
+ * 3. Or, in the case where the repository sends us the default network name, we check for an
+ * override in [connectionInfo.operatorAlphaShort], a value that is derived from [ServiceState]
+ */
+ val networkName: StateFlow<NetworkNameModel>
+
/** 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>
@@ -75,10 +97,28 @@
) : MobileIconInteractor {
private val connectionInfo = connectionRepository.connectionInfo
+ override val activity = connectionInfo.mapLatest { it.dataActivityDirection }
+
override val isDataEnabled: StateFlow<Boolean> = connectionRepository.dataEnabled
override val isDefaultDataEnabled = defaultSubscriptionHasDataEnabled
+ override val networkName =
+ combine(connectionInfo, connectionRepository.networkName) { connection, networkName ->
+ if (
+ networkName is NetworkNameModel.Default && connection.operatorAlphaShort != null
+ ) {
+ NetworkNameModel.Derived(connection.operatorAlphaShort)
+ } else {
+ networkName
+ }
+ }
+ .stateIn(
+ scope,
+ SharingStarted.WhileSubscribed(),
+ connectionRepository.networkName.value
+ )
+
/** Observable for the current RAT indicator icon ([MobileIconGroup]) */
override val networkTypeIconGroup: StateFlow<MobileIconGroup> =
combine(
@@ -95,6 +135,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..545e624 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
@@ -17,10 +17,12 @@
package com.android.systemui.statusbar.pipeline.mobile.ui.binder
import android.content.res.ColorStateList
+import android.view.View
import android.view.View.GONE
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 +31,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
@@ -40,9 +41,14 @@
view: ViewGroup,
viewModel: MobileIconViewModel,
) {
+ val activityContainer = view.requireViewById<View>(R.id.inout_container)
+ val activityIn = view.requireViewById<ImageView>(R.id.mobile_in)
+ val activityOut = view.requireViewById<ImageView>(R.id.mobile_out)
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 +70,32 @@
}
}
+ // Set the roaming indicator
+ launch {
+ viewModel.roaming.distinctUntilChanged().collect { isRoaming ->
+ roamingView.isVisible = isRoaming
+ roamingSpace.isVisible = isRoaming
+ }
+ }
+
+ // Set the activity indicators
+ launch { viewModel.activityInVisible.collect { activityIn.isVisible = it } }
+
+ launch { viewModel.activityOutVisible.collect { activityOut.isVisible = it } }
+
+ launch {
+ viewModel.activityContainerVisible.collect { activityContainer.isVisible = it }
+ }
+
// Set the tint
launch {
viewModel.tint.collect { tint ->
val tintList = ColorStateList.valueOf(tint)
iconView.imageTintList = tintList
networkTypeView.imageTintList = tintList
+ roamingView.imageTintList = tintList
+ activityIn.imageTintList = tintList
+ activityOut.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..961283f 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
@@ -22,13 +22,16 @@
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconInteractor
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
+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 kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapLatest
/**
@@ -48,6 +51,7 @@
val subscriptionId: Int,
iconInteractor: MobileIconInteractor,
logger: ConnectivityPipelineLogger,
+ constants: ConnectivityConstants,
) {
/** Whether or not to show the error state of [SignalDrawable] */
private val showExclamationMark: Flow<Boolean> =
@@ -87,5 +91,19 @@
}
}
+ val roaming: Flow<Boolean> = iconInteractor.isRoaming
+
+ private val activity: Flow<DataActivityModel?> =
+ if (!constants.shouldShowActivityConfig) {
+ flowOf(null)
+ } else {
+ iconInteractor.activity
+ }
+
+ val activityInVisible: Flow<Boolean> = activity.map { it?.hasActivityIn ?: false }
+ val activityOutVisible: Flow<Boolean> = activity.map { it?.hasActivityOut ?: false }
+ val activityContainerVisible: Flow<Boolean> =
+ activity.map { it != null && (it.hasActivityIn || it.hasActivityOut) }
+
val tint: Flow<Int> = flowOf(Color.CYAN)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt
index 2349cb7..0b41d31 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt
@@ -20,6 +20,7 @@
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
import com.android.systemui.statusbar.pipeline.mobile.ui.view.ModernStatusBarMobileView
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
import javax.inject.Inject
import kotlinx.coroutines.InternalCoroutinesApi
@@ -36,13 +37,15 @@
val subscriptionIdsFlow: StateFlow<List<Int>>,
private val interactor: MobileIconsInteractor,
private val logger: ConnectivityPipelineLogger,
+ private val constants: ConnectivityConstants,
) {
/** TODO: do we need to cache these? */
fun viewModelForSub(subId: Int): MobileIconViewModel =
MobileIconViewModel(
subId,
interactor.createMobileConnectionInteractorForSubId(subId),
- logger
+ logger,
+ constants,
)
class Factory
@@ -50,12 +53,14 @@
constructor(
private val interactor: MobileIconsInteractor,
private val logger: ConnectivityPipelineLogger,
+ private val constants: ConnectivityConstants,
) {
fun create(subscriptionIdsFlow: StateFlow<List<Int>>): MobileIconsViewModel {
return MobileIconsViewModel(
subscriptionIdsFlow,
interactor,
logger,
+ constants,
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityConstants.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityConstants.kt
index 6efb10f..0c9b86c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityConstants.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityConstants.kt
@@ -16,8 +16,10 @@
package com.android.systemui.statusbar.pipeline.shared
+import android.content.Context
import android.telephony.TelephonyManager
import com.android.systemui.Dumpable
+import com.android.systemui.R
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dump.DumpManager
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.SB_LOGGING_TAG
@@ -32,15 +34,25 @@
@SysUISingleton
class ConnectivityConstants
@Inject
-constructor(dumpManager: DumpManager, telephonyManager: TelephonyManager) : Dumpable {
+constructor(
+ context: Context,
+ dumpManager: DumpManager,
+ telephonyManager: TelephonyManager,
+) : Dumpable {
init {
- dumpManager.registerDumpable("${SB_LOGGING_TAG}Constants", this)
+ dumpManager.registerNormalDumpable("${SB_LOGGING_TAG}Constants", this)
}
/** True if this device has the capability for data connections and false otherwise. */
val hasDataCapabilities = telephonyManager.isDataCapable
+ /** True if we should show the activityIn/activityOut icons and false otherwise */
+ val shouldShowActivityConfig = context.resources.getBoolean(R.bool.config_showActivity)
+
override fun dump(pw: PrintWriter, args: Array<out String>) {
- pw.apply { println("hasDataCapabilities=$hasDataCapabilities") }
+ pw.apply {
+ println("hasDataCapabilities=$hasDataCapabilities")
+ println("shouldShowActivityConfig=$shouldShowActivityConfig")
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/model/DataActivityModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/model/DataActivityModel.kt
new file mode 100644
index 0000000..05d0714
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/model/DataActivityModel.kt
@@ -0,0 +1,71 @@
+/*
+ * 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.statusbar.pipeline.shared.data.model
+
+import android.net.wifi.WifiManager
+import android.telephony.Annotation
+import android.telephony.TelephonyManager
+import com.android.systemui.log.table.Diffable
+import com.android.systemui.log.table.TableRowLogger
+
+/** 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 connection has activity out (upload). */
+ val hasActivityOut: Boolean,
+) : Diffable<DataActivityModel> {
+ override fun logDiffs(prevVal: DataActivityModel, row: TableRowLogger) {
+ if (prevVal.hasActivityIn != hasActivityIn) {
+ row.logChange(COL_ACTIVITY_IN, hasActivityIn)
+ }
+ if (prevVal.hasActivityOut != hasActivityOut) {
+ row.logChange(COL_ACTIVITY_OUT, hasActivityOut)
+ }
+ }
+
+ override fun logFull(row: TableRowLogger) {
+ row.logChange(COL_ACTIVITY_IN, hasActivityIn)
+ row.logChange(COL_ACTIVITY_OUT, hasActivityOut)
+ }
+}
+
+const val ACTIVITY_PREFIX = "dataActivity"
+private const val COL_ACTIVITY_IN = "in"
+private const val COL_ACTIVITY_OUT = "out"
+
+fun @receiver:Annotation.DataActivityType Int.toMobileDataActivityModel(): DataActivityModel =
+ when (this) {
+ TelephonyManager.DATA_ACTIVITY_IN ->
+ DataActivityModel(hasActivityIn = true, hasActivityOut = false)
+ TelephonyManager.DATA_ACTIVITY_OUT ->
+ DataActivityModel(hasActivityIn = false, hasActivityOut = true)
+ TelephonyManager.DATA_ACTIVITY_INOUT ->
+ DataActivityModel(hasActivityIn = true, hasActivityOut = true)
+ else -> DataActivityModel(hasActivityIn = false, hasActivityOut = false)
+ }
+
+fun Int.toWifiDataActivityModel(): DataActivityModel =
+ when (this) {
+ WifiManager.TrafficStateCallback.DATA_ACTIVITY_IN ->
+ DataActivityModel(hasActivityIn = true, hasActivityOut = false)
+ WifiManager.TrafficStateCallback.DATA_ACTIVITY_OUT ->
+ DataActivityModel(hasActivityIn = false, hasActivityOut = true)
+ WifiManager.TrafficStateCallback.DATA_ACTIVITY_INOUT ->
+ DataActivityModel(hasActivityIn = true, hasActivityOut = true)
+ else -> DataActivityModel(hasActivityIn = false, hasActivityOut = false)
+ }
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..5ccd6f4 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,9 @@
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.shared.data.model.toWifiDataActivityModel
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 +74,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 +230,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 +238,7 @@
conflatedCallbackFlow {
val callback = TrafficStateCallback { state ->
logger.logInputChange("onTrafficStateChange", prettyPrintActivity(state))
- trySend(trafficStateToWifiActivityModel(state))
+ trySend(state.toWifiDataActivityModel())
}
wifiManager.registerTrafficStateCallback(mainExecutor, callback)
awaitClose { wifiManager.unregisterTrafficStateCallback(callback) }
@@ -256,7 +256,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,15 +267,6 @@
// NetworkCallback inside [wifiNetwork] for our wifi network information.
val WIFI_NETWORK_DEFAULT = WifiNetworkModel.Inactive
- private fun trafficStateToWifiActivityModel(state: Int): WifiActivityModel {
- return WifiActivityModel(
- hasActivityIn = state == TrafficStateCallback.DATA_ACTIVITY_IN ||
- state == TrafficStateCallback.DATA_ACTIVITY_INOUT,
- hasActivityOut = state == TrafficStateCallback.DATA_ACTIVITY_OUT ||
- state == TrafficStateCallback.DATA_ACTIVITY_INOUT,
- )
- }
-
private fun networkCapabilitiesToWifiInfo(
networkCapabilities: NetworkCapabilities
): WifiInfo? {
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/shared/model/WifiActivityModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiActivityModel.kt
deleted file mode 100644
index a4ca41c..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiActivityModel.kt
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * 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.statusbar.pipeline.wifi.shared.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). */
- val hasActivityIn: Boolean,
- /** True if the wifi has activity out (upload). */
- val hasActivityOut: Boolean,
-) : Diffable<WifiActivityModel> {
-
- override fun logDiffs(prevVal: WifiActivityModel, row: TableRowLogger) {
- if (prevVal.hasActivityIn != hasActivityIn) {
- row.logChange(COL_ACTIVITY_IN, hasActivityIn)
- }
- if (prevVal.hasActivityOut != hasActivityOut) {
- row.logChange(COL_ACTIVITY_OUT, hasActivityOut)
- }
- }
-
- override fun logFull(row: TableRowLogger) {
- row.logChange(COL_ACTIVITY_IN, hasActivityIn)
- row.logChange(COL_ACTIVITY_OUT, hasActivityOut)
- }
-}
-
-const val ACTIVITY_PREFIX = "wifiActivity"
-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/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/temporarydisplay/TemporaryViewDisplayController.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
index db7315f..ad48e21 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].
*
@@ -321,7 +331,7 @@
return
}
- removeViewFromWindow(displayInfo)
+ removeViewFromWindow(displayInfo, removalReason)
// Prune anything that's already timed out before determining if we should re-display a
// different chipbar.
@@ -348,14 +358,14 @@
removeViewFromWindow(displayInfo)
}
- private fun removeViewFromWindow(displayInfo: DisplayInfo) {
+ private fun removeViewFromWindow(displayInfo: DisplayInfo, removalReason: String? = null) {
val view = displayInfo.view
if (view == null) {
logger.logViewRemovalIgnored(displayInfo.info.id, "View is null")
return
}
displayInfo.view = null // Need other places??
- animateViewOut(view) {
+ animateViewOut(view, removalReason) {
windowManager.removeView(view)
displayInfo.wakeLock?.release(displayInfo.info.wakeReason)
}
@@ -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].
*/
@@ -405,7 +428,11 @@
*
* @param onAnimationEnd an action that *must* be run once the animation finishes successfully.
*/
- internal open fun animateViewOut(view: ViewGroup, onAnimationEnd: Runnable) {
+ internal open fun animateViewOut(
+ view: ViewGroup,
+ removalReason: String? = null,
+ onAnimationEnd: Runnable
+ ) {
onAnimationEnd.run()
}
@@ -445,8 +472,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..52980c3 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,
@@ -208,7 +211,7 @@
)
}
- override fun animateViewOut(view: ViewGroup, onAnimationEnd: Runnable) {
+ override fun animateViewOut(view: ViewGroup, removalReason: String?, onAnimationEnd: Runnable) {
val innerView = view.getInnerView()
innerView.accessibilityLiveRegion = ACCESSIBILITY_LIVE_REGION_NONE
ViewHierarchyAnimator.animateRemoval(
@@ -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/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
index 9cca950..523cf68 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
@@ -159,18 +159,24 @@
ensureOverlayRemoved()
val newRoot = SurfaceControlViewHost(context, context.display!!, wwm)
- val newView =
- LightRevealScrim(context, null).apply {
- revealEffect = createLightRevealEffect()
- isScrimOpaqueChangedListener = Consumer {}
- revealAmount =
- when (reason) {
- FOLD -> TRANSPARENT
- UNFOLD -> BLACK
- }
- }
-
val params = getLayoutParams()
+ val newView =
+ LightRevealScrim(
+ context,
+ attrs = null,
+ initialWidth = params.width,
+ initialHeight = params.height
+ )
+ .apply {
+ revealEffect = createLightRevealEffect()
+ isScrimOpaqueChangedListener = Consumer {}
+ revealAmount =
+ when (reason) {
+ FOLD -> TRANSPARENT
+ UNFOLD -> BLACK
+ }
+ }
+
newRoot.setView(newView, params)
if (onOverlayReady != null) {
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/keyguard/KeyguardStatusViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
index c94c97c..be4bbdf 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
@@ -25,6 +25,7 @@
import android.testing.AndroidTestingRunner;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.plugins.ClockAnimations;
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
@@ -59,6 +60,8 @@
@Mock
DozeParameters mDozeParameters;
@Mock
+ FeatureFlags mFeatureFlags;
+ @Mock
ScreenOffAnimationController mScreenOffAnimationController;
@Captor
private ArgumentCaptor<KeyguardUpdateMonitorCallback> mKeyguardUpdateMonitorCallbackCaptor;
@@ -77,6 +80,7 @@
mKeyguardUpdateMonitor,
mConfigurationController,
mDozeParameters,
+ mFeatureFlags,
mScreenOffAnimationController);
}
@@ -96,9 +100,9 @@
public void setTranslationYExcludingMedia_forwardsCallToView() {
float translationY = 123f;
- mController.setTranslationYExcludingMedia(translationY);
+ mController.setTranslationY(translationY, /* excludeMedia= */true);
- verify(mKeyguardStatusView).setChildrenTranslationYExcludingMediaView(translationY);
+ verify(mKeyguardStatusView).setChildrenTranslationY(translationY, /* excludeMedia= */true);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.kt
index ce44f4d..508aea5 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.kt
@@ -37,19 +37,23 @@
fun setChildrenTranslationYExcludingMediaView_mediaViewIsNotTranslated() {
val translationY = 1234f
- keyguardStatusView.setChildrenTranslationYExcludingMediaView(translationY)
+ keyguardStatusView.setChildrenTranslationY(translationY, /* excludeMedia= */true)
assertThat(mediaView.translationY).isEqualTo(0)
- }
-
- @Test
- fun setChildrenTranslationYExcludingMediaView_childrenAreTranslated() {
- val translationY = 1234f
-
- keyguardStatusView.setChildrenTranslationYExcludingMediaView(translationY)
childrenExcludingMedia.forEach {
assertThat(it.translationY).isEqualTo(translationY)
}
}
-}
\ No newline at end of file
+
+ @Test
+ fun setChildrenTranslationYIncludeMediaView() {
+ val translationY = 1234f
+
+ keyguardStatusView.setChildrenTranslationY(translationY, /* excludeMedia= */false)
+
+ statusViewContainer.children.forEach {
+ assertThat(it.translationY).isEqualTo(translationY)
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
index 20d3cd5..d910a27 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
@@ -35,6 +35,7 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.content.res.Resources;
import android.database.ContentObserver;
import android.hardware.Sensor;
import android.hardware.display.AmbientDisplayConfiguration;
@@ -76,7 +77,8 @@
@RunWithLooper
@SmallTest
public class DozeSensorsTest extends SysuiTestCase {
-
+ @Mock
+ private Resources mResources;
@Mock
private AsyncSensorManager mSensorManager;
@Mock
@@ -426,7 +428,7 @@
@Test
public void testGesturesAllInitiallyRespectSettings() {
- DozeSensors dozeSensors = new DozeSensors(mSensorManager, mDozeParameters,
+ DozeSensors dozeSensors = new DozeSensors(mResources, mSensorManager, mDozeParameters,
mAmbientDisplayConfiguration, mWakeLock, mCallback, mProxCallback, mDozeLog,
mProximitySensor, mFakeSettings, mAuthController,
mDevicePostureController, mUserTracker);
@@ -436,9 +438,47 @@
}
}
+ @Test
+ public void liftToWake_defaultSetting_configDefaultFalse() {
+ // WHEN the default lift to wake gesture setting is false
+ when(mResources.getBoolean(
+ com.android.internal.R.bool.config_dozePickupGestureEnabled)).thenReturn(false);
+
+ DozeSensors dozeSensors = new DozeSensors(mResources, mSensorManager, mDozeParameters,
+ mAmbientDisplayConfiguration, mWakeLock, mCallback, mProxCallback, mDozeLog,
+ mProximitySensor, mFakeSettings, mAuthController,
+ mDevicePostureController, mUserTracker);
+
+ for (TriggerSensor sensor : dozeSensors.mTriggerSensors) {
+ // THEN lift to wake's TriggerSensor enabledBySettings is false
+ if (sensor.mPulseReason == DozeLog.REASON_SENSOR_PICKUP) {
+ assertFalse(sensor.enabledBySetting());
+ }
+ }
+ }
+
+ @Test
+ public void liftToWake_defaultSetting_configDefaultTrue() {
+ // WHEN the default lift to wake gesture setting is true
+ when(mResources.getBoolean(
+ com.android.internal.R.bool.config_dozePickupGestureEnabled)).thenReturn(true);
+
+ DozeSensors dozeSensors = new DozeSensors(mResources, mSensorManager, mDozeParameters,
+ mAmbientDisplayConfiguration, mWakeLock, mCallback, mProxCallback, mDozeLog,
+ mProximitySensor, mFakeSettings, mAuthController,
+ mDevicePostureController, mUserTracker);
+
+ for (TriggerSensor sensor : dozeSensors.mTriggerSensors) {
+ // THEN lift to wake's TriggerSensor enabledBySettings is true
+ if (sensor.mPulseReason == DozeLog.REASON_SENSOR_PICKUP) {
+ assertTrue(sensor.enabledBySetting());
+ }
+ }
+ }
+
private class TestableDozeSensors extends DozeSensors {
TestableDozeSensors() {
- super(mSensorManager, mDozeParameters,
+ super(mResources, mSensorManager, mDozeParameters,
mAmbientDisplayConfiguration, mWakeLock, mCallback, mProxCallback, mDozeLog,
mProximitySensor, mFakeSettings, mAuthController,
mDevicePostureController, mUserTracker);
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..c54e456
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt
@@ -0,0 +1,205 @@
+/*
+ * 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.animation.Interpolators.EMPHASIZED_DECELERATE
+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.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel.Companion.LOCKSCREEN_ALPHA
+import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel.Companion.LOCKSCREEN_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 3 values should be present, since the dream overlay runs for a small fraction
+ // of the overall animation time
+ assertThat(values.size).isEqualTo(3)
+ 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
+ )
+ assertThat(values[2])
+ .isEqualTo(
+ EMPHASIZED_ACCELERATE.getInterpolation(
+ animValue(0.5f, 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()
+ }
+
+ @Test
+ fun lockscreenFadeIn() =
+ runTest(UnconfinedTestDispatcher()) {
+ val values = mutableListOf<Float>()
+
+ val job = underTest.lockscreenAlpha.onEach { values.add(it) }.launchIn(this)
+
+ repository.sendTransitionStep(step(0f))
+ repository.sendTransitionStep(step(0.1f))
+ // Should start running here...
+ repository.sendTransitionStep(step(0.2f))
+ repository.sendTransitionStep(step(0.3f))
+ // ...up to here
+ 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(animValue(0.2f, LOCKSCREEN_ALPHA))
+ assertThat(values[1]).isEqualTo(animValue(0.3f, LOCKSCREEN_ALPHA))
+
+ job.cancel()
+ }
+
+ @Test
+ fun lockscreenTranslationY() =
+ runTest(UnconfinedTestDispatcher()) {
+ val values = mutableListOf<Float>()
+
+ val pixels = 100
+ val job =
+ underTest.lockscreenTranslationY(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))
+
+ assertThat(values.size).isEqualTo(4)
+ assertThat(values[0])
+ .isEqualTo(
+ -pixels +
+ EMPHASIZED_DECELERATE.getInterpolation(
+ animValue(0f, LOCKSCREEN_TRANSLATION_Y)
+ ) * pixels
+ )
+ assertThat(values[1])
+ .isEqualTo(
+ -pixels +
+ EMPHASIZED_DECELERATE.getInterpolation(
+ animValue(0.3f, LOCKSCREEN_TRANSLATION_Y)
+ ) * pixels
+ )
+ assertThat(values[2])
+ .isEqualTo(
+ -pixels +
+ EMPHASIZED_DECELERATE.getInterpolation(
+ animValue(0.5f, LOCKSCREEN_TRANSLATION_Y)
+ ) * pixels
+ )
+ assertThat(values[3])
+ .isEqualTo(
+ -pixels +
+ EMPHASIZED_DECELERATE.getInterpolation(
+ animValue(1f, LOCKSCREEN_TRANSLATION_Y)
+ ) * pixels
+ )
+
+ 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..9c4e849 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,
@@ -63,7 +66,7 @@
wakeLockBuilder,
systemClock,
) {
- override fun animateViewOut(view: ViewGroup, onAnimationEnd: Runnable) {
+ override fun animateViewOut(view: ViewGroup, removalReason: String?, onAnimationEnd: Runnable) {
// Just bypass the animation in tests
onAnimationEnd.run()
}
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..cefc742 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
@@ -95,6 +98,7 @@
fun setUp() {
MockitoAnnotations.initMocks(this)
whenever(mediaTttFlags.isMediaTttEnabled()).thenReturn(true)
+ whenever(mediaTttFlags.isMediaTttReceiverSuccessRippleEnabled()).thenReturn(true)
fakeAppIconDrawable = context.getDrawable(R.drawable.ic_cake)!!
whenever(packageManager.getApplicationIcon(PACKAGE_NAME)).thenReturn(fakeAppIconDrawable)
@@ -122,6 +126,7 @@
fakeExecutor,
accessibilityManager,
configurationController,
+ dumpManager,
powerManager,
Handler.getMain(),
mediaTttFlags,
@@ -150,6 +155,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/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
index 01f13e66..ad993c3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -104,6 +104,8 @@
import com.android.systemui.fragments.FragmentService;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor;
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
+import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel;
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel;
import com.android.systemui.media.controls.pipeline.MediaDataManager;
import com.android.systemui.media.controls.ui.KeyguardMediaController;
@@ -286,6 +288,8 @@
@Mock private ViewTreeObserver mViewTreeObserver;
@Mock private KeyguardBottomAreaViewModel mKeyguardBottomAreaViewModel;
@Mock private KeyguardBottomAreaInteractor mKeyguardBottomAreaInteractor;
+ @Mock private DreamingToLockscreenTransitionViewModel mDreamingToLockscreenTransitionViewModel;
+ @Mock private KeyguardTransitionInteractor mKeyguardTransitionInteractor;
@Mock private MotionEvent mDownMotionEvent;
@Captor
private ArgumentCaptor<NotificationStackScrollLayout.OnEmptySpaceClickListener>
@@ -502,6 +506,8 @@
systemClock,
mKeyguardBottomAreaViewModel,
mKeyguardBottomAreaInteractor,
+ mDreamingToLockscreenTransitionViewModel,
+ mKeyguardTransitionInteractor,
mDumpManager);
mNotificationPanelViewController.initDependencies(
mCentralSurfaces,
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/LightRevealScrimTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LightRevealScrimTest.kt
index 97fe25d..d3befb4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LightRevealScrimTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LightRevealScrimTest.kt
@@ -20,6 +20,7 @@
import android.view.View
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Before
@@ -36,7 +37,7 @@
@Before
fun setUp() {
- scrim = LightRevealScrim(context, null)
+ scrim = LightRevealScrim(context, null, DEFAULT_WIDTH, DEFAULT_HEIGHT)
scrim.isScrimOpaqueChangedListener = Consumer { opaque ->
isOpaque = opaque
}
@@ -63,4 +64,25 @@
scrim.revealAmount = 0.5f
assertFalse("Scrim is opaque even though it's revealed", scrim.isScrimOpaque)
}
+
+ @Test
+ fun testBeforeOnMeasure_defaultDimensions() {
+ assertThat(scrim.viewWidth).isEqualTo(DEFAULT_WIDTH)
+ assertThat(scrim.viewHeight).isEqualTo(DEFAULT_HEIGHT)
+ }
+
+ @Test
+ fun testAfterOnMeasure_measuredDimensions() {
+ scrim.measure(/* widthMeasureSpec= */ exact(1), /* heightMeasureSpec= */ exact(2))
+
+ assertThat(scrim.viewWidth).isEqualTo(1)
+ assertThat(scrim.viewHeight).isEqualTo(2)
+ }
+
+ private fun exact(value: Int) = View.MeasureSpec.makeMeasureSpec(value, View.MeasureSpec.EXACTLY)
+
+ private companion object {
+ private const val DEFAULT_WIDTH = 42
+ private const val DEFAULT_HEIGHT = 24
+ }
}
\ No newline at end of file
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..59eec53 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
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.pipeline.mobile.data.repository
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
+import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
import kotlinx.coroutines.flow.MutableStateFlow
// TODO(b/261632894): remove this in favor of the real impl or DemoMobileConnectionRepository
@@ -30,6 +31,11 @@
private val _isDefaultDataSubscription = MutableStateFlow(true)
override val isDefaultDataSubscription = _isDefaultDataSubscription
+ override val cdmaRoaming = MutableStateFlow(false)
+
+ override val networkName =
+ MutableStateFlow<NetworkNameModel>(NetworkNameModel.Default("default"))
+
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..3d5316d 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
@@ -18,13 +18,16 @@
import android.telephony.Annotation
import android.telephony.TelephonyManager
+import android.telephony.TelephonyManager.DATA_ACTIVITY_NONE
import androidx.test.filters.SmallTest
import com.android.settingslib.SignalIcon
import com.android.settingslib.mobile.TelephonyIcons
import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
+import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel
+import com.android.systemui.statusbar.pipeline.shared.data.model.toMobileDataActivityModel
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
@@ -95,6 +98,8 @@
inflateStrength = testCase.inflateStrength,
activity = testCase.activity,
carrierNetworkChange = testCase.carrierNetworkChange,
+ roaming = testCase.roaming,
+ name = "demo name",
)
fakeNetworkEventFlow.value = networkModel
@@ -113,9 +118,12 @@
assertThat(conn.subId).isEqualTo(model.subId)
assertThat(connectionInfo.cdmaLevel).isEqualTo(model.level)
assertThat(connectionInfo.primaryLevel).isEqualTo(model.level)
- assertThat(connectionInfo.dataActivityDirection).isEqualTo(model.activity)
+ assertThat(connectionInfo.dataActivityDirection)
+ .isEqualTo((model.activity ?: DATA_ACTIVITY_NONE).toMobileDataActivityModel())
assertThat(connectionInfo.carrierNetworkChangeActive)
.isEqualTo(model.carrierNetworkChange)
+ assertThat(connectionInfo.isRoaming).isEqualTo(model.roaming)
+ assertThat(conn.networkName.value).isEqualTo(NetworkNameModel.Derived(model.name))
// TODO(b/261029387): check these once we start handling them
assertThat(connectionInfo.isEmergencyOnly).isFalse()
@@ -138,6 +146,8 @@
val inflateStrength: Boolean,
@Annotation.DataActivityType val activity: Int,
val carrierNetworkChange: Boolean,
+ val roaming: Boolean,
+ val name: String,
) {
override fun toString(): String {
return "INPUT(level=$level, " +
@@ -146,7 +156,9 @@
"carrierId=$carrierId, " +
"inflateStrength=$inflateStrength, " +
"activity=$activity, " +
- "carrierNetworkChange=$carrierNetworkChange)"
+ "carrierNetworkChange=$carrierNetworkChange, " +
+ "roaming=$roaming, " +
+ "name=$name)"
}
// Convenience for iterating test data and creating new cases
@@ -158,6 +170,8 @@
inflateStrength: Boolean? = null,
@Annotation.DataActivityType activity: Int? = null,
carrierNetworkChange: Boolean? = null,
+ roaming: Boolean? = null,
+ name: String? = null,
): TestCase =
TestCase(
level = level ?: this.level,
@@ -166,7 +180,9 @@
carrierId = carrierId ?: this.carrierId,
inflateStrength = inflateStrength ?: this.inflateStrength,
activity = activity ?: this.activity,
- carrierNetworkChange = carrierNetworkChange ?: this.carrierNetworkChange
+ carrierNetworkChange = carrierNetworkChange ?: this.carrierNetworkChange,
+ roaming = roaming ?: this.roaming,
+ name = name ?: this.name,
)
}
@@ -193,6 +209,9 @@
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)
+ private val names = listOf("name 1", "name 2")
@Parameters(name = "{0}") @JvmStatic fun data() = testData()
@@ -226,7 +245,9 @@
carrierIds.first(),
inflateStrength.first(),
activity.first(),
- carrierNetworkChange.first()
+ carrierNetworkChange.first(),
+ roaming.first(),
+ names.first(),
)
val tail =
@@ -237,6 +258,8 @@
inflateStrength.map { baseCase.modifiedBy(inflateStrength = it) },
activity.map { baseCase.modifiedBy(activity = it) },
carrierNetworkChange.map { baseCase.modifiedBy(carrierNetworkChange = it) },
+ roaming.map { baseCase.modifiedBy(roaming = it) },
+ names.map { baseCase.modifiedBy(name = 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..34f30eb 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
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.pipeline.mobile.data.repository.demo
import android.telephony.TelephonyManager.DATA_ACTIVITY_INOUT
+import android.telephony.TelephonyManager.DATA_ACTIVITY_NONE
import android.telephony.TelephonyManager.UNKNOWN_CARRIER_ID
import androidx.test.filters.SmallTest
import com.android.settingslib.SignalIcon
@@ -24,9 +25,11 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
+import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel.MobileDisabled
+import com.android.systemui.statusbar.pipeline.shared.data.model.toMobileDataActivityModel
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
@@ -289,9 +292,12 @@
assertThat(conn.subId).isEqualTo(model.subId)
assertThat(connectionInfo.cdmaLevel).isEqualTo(model.level)
assertThat(connectionInfo.primaryLevel).isEqualTo(model.level)
- assertThat(connectionInfo.dataActivityDirection).isEqualTo(model.activity)
+ assertThat(connectionInfo.dataActivityDirection)
+ .isEqualTo((model.activity ?: DATA_ACTIVITY_NONE).toMobileDataActivityModel())
assertThat(connectionInfo.carrierNetworkChangeActive)
.isEqualTo(model.carrierNetworkChange)
+ assertThat(connectionInfo.isRoaming).isEqualTo(model.roaming)
+ assertThat(conn.networkName.value).isEqualTo(NetworkNameModel.Derived(model.name))
// TODO(b/261029387) check these once we start handling them
assertThat(connectionInfo.isEmergencyOnly).isFalse()
@@ -313,6 +319,7 @@
inflateStrength: Boolean? = false,
activity: Int? = null,
carrierNetworkChange: Boolean = false,
+ roaming: Boolean = false,
): FakeNetworkEventModel =
FakeNetworkEventModel.Mobile(
level = level,
@@ -322,4 +329,6 @@
inflateStrength = inflateStrength,
activity = activity,
carrierNetworkChange = carrierNetworkChange,
+ roaming = roaming,
+ name = "demo name",
)
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..7fa8065 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
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.pipeline.mobile.data.repository.prod
+import android.content.Intent
import android.os.UserHandle
import android.provider.Settings
import android.telephony.CellSignalStrengthCdma
@@ -23,27 +24,44 @@
import android.telephony.SignalStrength
import android.telephony.SubscriptionInfo
import android.telephony.TelephonyCallback
+import android.telephony.TelephonyCallback.DataActivityListener
import android.telephony.TelephonyCallback.ServiceStateListener
import android.telephony.TelephonyDisplayInfo
import android.telephony.TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_CA
import android.telephony.TelephonyManager
+import android.telephony.TelephonyManager.DATA_ACTIVITY_DORMANT
+import android.telephony.TelephonyManager.DATA_ACTIVITY_IN
+import android.telephony.TelephonyManager.DATA_ACTIVITY_INOUT
+import android.telephony.TelephonyManager.DATA_ACTIVITY_NONE
+import android.telephony.TelephonyManager.DATA_ACTIVITY_OUT
import android.telephony.TelephonyManager.DATA_CONNECTED
import android.telephony.TelephonyManager.DATA_CONNECTING
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.EXTRA_PLMN
+import android.telephony.TelephonyManager.EXTRA_SHOW_PLMN
+import android.telephony.TelephonyManager.EXTRA_SHOW_SPN
+import android.telephony.TelephonyManager.EXTRA_SPN
+import android.telephony.TelephonyManager.EXTRA_SUBSCRIPTION_ID
import android.telephony.TelephonyManager.NETWORK_TYPE_LTE
import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
+import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.DefaultNetworkType
import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.OverrideNetworkType
import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.UnknownNetworkType
+import com.android.systemui.statusbar.pipeline.mobile.data.model.toNetworkNameModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
+import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
+import com.android.systemui.statusbar.pipeline.shared.data.model.toMobileDataActivityModel
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.mock
@@ -88,8 +106,11 @@
MobileConnectionRepositoryImpl(
context,
SUB_1_ID,
+ DEFAULT_NAME,
+ SEP,
telephonyManager,
globalSettings,
+ fakeBroadcastDispatcher,
connectionsRepo.defaultDataSubId,
connectionsRepo.globalMobileDataSettingChangedEvent,
mobileMappings,
@@ -247,10 +268,11 @@
var latest: MobileConnectionModel? = null
val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
- val callback = getTelephonyCallbackForType<TelephonyCallback.DataActivityListener>()
- callback.onDataActivity(3)
+ val callback = getTelephonyCallbackForType<DataActivityListener>()
+ callback.onDataActivity(DATA_ACTIVITY_INOUT)
- assertThat(latest?.dataActivityDirection).isEqualTo(3)
+ assertThat(latest?.dataActivityDirection)
+ .isEqualTo(DATA_ACTIVITY_INOUT.toMobileDataActivityModel())
job.cancel()
}
@@ -402,6 +424,167 @@
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()
+ }
+
+ @Test
+ fun `activity - updates from callback`() =
+ runBlocking(IMMEDIATE) {
+ var latest: DataActivityModel? = null
+ val job =
+ underTest.connectionInfo.onEach { latest = it.dataActivityDirection }.launchIn(this)
+
+ assertThat(latest)
+ .isEqualTo(DataActivityModel(hasActivityIn = false, hasActivityOut = false))
+
+ val cb = getTelephonyCallbackForType<DataActivityListener>()
+ cb.onDataActivity(DATA_ACTIVITY_IN)
+ assertThat(latest)
+ .isEqualTo(DataActivityModel(hasActivityIn = true, hasActivityOut = false))
+
+ cb.onDataActivity(DATA_ACTIVITY_OUT)
+ assertThat(latest)
+ .isEqualTo(DataActivityModel(hasActivityIn = false, hasActivityOut = true))
+
+ cb.onDataActivity(DATA_ACTIVITY_INOUT)
+ assertThat(latest)
+ .isEqualTo(DataActivityModel(hasActivityIn = true, hasActivityOut = true))
+
+ cb.onDataActivity(DATA_ACTIVITY_NONE)
+ assertThat(latest)
+ .isEqualTo(DataActivityModel(hasActivityIn = false, hasActivityOut = false))
+
+ cb.onDataActivity(DATA_ACTIVITY_DORMANT)
+ assertThat(latest)
+ .isEqualTo(DataActivityModel(hasActivityIn = false, hasActivityOut = false))
+
+ cb.onDataActivity(1234)
+ assertThat(latest)
+ .isEqualTo(DataActivityModel(hasActivityIn = false, hasActivityOut = false))
+
+ job.cancel()
+ }
+
+ @Test
+ fun `network name - default`() =
+ runBlocking(IMMEDIATE) {
+ var latest: NetworkNameModel? = null
+ val job = underTest.networkName.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isEqualTo(DEFAULT_NAME)
+
+ job.cancel()
+ }
+
+ @Test
+ fun `network name - uses broadcast info - returns derived`() =
+ runBlocking(IMMEDIATE) {
+ var latest: NetworkNameModel? = null
+ val job = underTest.networkName.onEach { latest = it }.launchIn(this)
+
+ val intent = spnIntent()
+
+ fakeBroadcastDispatcher.registeredReceivers.forEach { receiver ->
+ receiver.onReceive(context, intent)
+ }
+
+ assertThat(latest).isEqualTo(intent.toNetworkNameModel(SEP))
+
+ job.cancel()
+ }
+
+ @Test
+ fun `network name - broadcast not for this sub id - returns default`() =
+ runBlocking(IMMEDIATE) {
+ var latest: NetworkNameModel? = null
+ val job = underTest.networkName.onEach { latest = it }.launchIn(this)
+
+ val intent = spnIntent(subId = 101)
+
+ fakeBroadcastDispatcher.registeredReceivers.forEach { receiver ->
+ receiver.onReceive(context, intent)
+ }
+
+ assertThat(latest).isEqualTo(DEFAULT_NAME)
+
+ job.cancel()
+ }
+
+ @Test
+ fun `network name - operatorAlphaShort - tracked`() =
+ runBlocking(IMMEDIATE) {
+ var latest: String? = null
+
+ val job =
+ underTest.connectionInfo.onEach { latest = it.operatorAlphaShort }.launchIn(this)
+
+ val shortName = "short name"
+ val serviceState = ServiceState()
+ serviceState.setOperatorName(
+ /* longName */ "long name",
+ /* shortName */ shortName,
+ /* numeric */ "12345",
+ )
+
+ getTelephonyCallbackForType<ServiceStateListener>().onServiceStateChanged(serviceState)
+
+ assertThat(latest).isEqualTo(shortName)
+
+ job.cancel()
+ }
+
private fun getTelephonyCallbacks(): List<TelephonyCallback> {
val callbackCaptor = argumentCaptor<TelephonyCallback>()
Mockito.verify(telephonyManager).registerTelephonyCallback(any(), callbackCaptor.capture())
@@ -427,10 +610,31 @@
return signalStrength
}
+ private fun spnIntent(
+ subId: Int = SUB_1_ID,
+ showSpn: Boolean = true,
+ spn: String = SPN,
+ showPlmn: Boolean = true,
+ plmn: String = PLMN,
+ ): Intent =
+ Intent(TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED).apply {
+ putExtra(EXTRA_SUBSCRIPTION_ID, subId)
+ putExtra(EXTRA_SHOW_SPN, showSpn)
+ putExtra(EXTRA_SPN, spn)
+ putExtra(EXTRA_SHOW_PLMN, showPlmn)
+ putExtra(EXTRA_PLMN, plmn)
+ }
+
companion object {
private val IMMEDIATE = Dispatchers.Main.immediate
private const val SUB_1_ID = 1
private val SUB_1 =
mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_1_ID) }
+
+ private val DEFAULT_NAME = NetworkNameModel.Default("default name")
+ private const val SEP = "-"
+
+ private const val SPN = "testSpn"
+ private const val PLMN = "testPlmn"
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
index 6d80acb..3cc1e8b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
@@ -91,6 +91,7 @@
connectionFactory =
MobileConnectionRepositoryImpl.Factory(
+ fakeBroadcastDispatcher,
context = context,
telephonyManager = telephonyManager,
bgDispatcher = IMMEDIATE,
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..c3519b7 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
@@ -19,17 +19,31 @@
import android.telephony.CellSignalStrength
import com.android.settingslib.SignalIcon
import com.android.settingslib.mobile.TelephonyIcons
+import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
+import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
import kotlinx.coroutines.flow.MutableStateFlow
class FakeMobileIconInteractor : MobileIconInteractor {
override val alwaysShowDataRatIcon = MutableStateFlow(false)
+ override val activity =
+ MutableStateFlow(
+ DataActivityModel(
+ hasActivityIn = false,
+ hasActivityOut = false,
+ )
+ )
+
private val _iconGroup = MutableStateFlow<SignalIcon.MobileIconGroup>(TelephonyIcons.THREE_G)
override val networkTypeIconGroup = _iconGroup
+ override val networkName = MutableStateFlow(NetworkNameModel.Derived("demo mode"))
+
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..4dca780 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
@@ -25,6 +25,7 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
+import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.DefaultNetworkType
import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.OverrideNetworkType
import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionRepository
@@ -298,6 +299,135 @@
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()
+ }
+
+ @Test
+ fun `network name - uses operatorAlphaShot when non null and repo is default`() =
+ runBlocking(IMMEDIATE) {
+ var latest: NetworkNameModel? = null
+ val job = underTest.networkName.onEach { latest = it }.launchIn(this)
+
+ val testOperatorName = "operatorAlphaShort"
+
+ // Default network name, operator name is non-null, uses the operator name
+ connectionRepository.networkName.value = DEFAULT_NAME
+ connectionRepository.setConnectionInfo(
+ MobileConnectionModel(operatorAlphaShort = testOperatorName)
+ )
+ yield()
+
+ assertThat(latest).isEqualTo(NetworkNameModel.Derived(testOperatorName))
+
+ // Default network name, operator name is null, uses the default
+ connectionRepository.setConnectionInfo(MobileConnectionModel(operatorAlphaShort = null))
+ yield()
+
+ assertThat(latest).isEqualTo(DEFAULT_NAME)
+
+ // Derived network name, operator name non-null, uses the derived name
+ connectionRepository.networkName.value = DERIVED_NAME
+ connectionRepository.setConnectionInfo(
+ MobileConnectionModel(operatorAlphaShort = testOperatorName)
+ )
+ yield()
+
+ assertThat(latest).isEqualTo(DERIVED_NAME)
+
+ job.cancel()
+ }
+
companion object {
private val IMMEDIATE = Dispatchers.Main.immediate
@@ -307,5 +437,8 @@
private const val SUB_1_ID = 1
private val SUB_1 =
mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_1_ID) }
+
+ private val DEFAULT_NAME = NetworkNameModel.Default("test default name")
+ private val DERIVED_NAME = NetworkNameModel.Derived("test derived name")
}
}
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..415ce75 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
@@ -23,7 +23,10 @@
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconInteractor
+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.DataActivityModel
+import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.launchIn
@@ -40,6 +43,7 @@
private lateinit var underTest: MobileIconViewModel
private val interactor = FakeMobileIconInteractor()
@Mock private lateinit var logger: ConnectivityPipelineLogger
+ @Mock private lateinit var constants: ConnectivityConstants
@Before
fun setUp() {
@@ -53,7 +57,7 @@
setNumberOfLevels(4)
isDataConnected.value = true
}
- underTest = MobileIconViewModel(SUB_1_ID, interactor, logger)
+ underTest = MobileIconViewModel(SUB_1_ID, interactor, logger, constants)
}
@Test
@@ -234,6 +238,108 @@
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()
+ }
+
+ @Test
+ fun `data activity - null when config is off`() =
+ runBlocking(IMMEDIATE) {
+ // Create a new view model here so the constants are properly read
+ whenever(constants.shouldShowActivityConfig).thenReturn(false)
+ underTest = MobileIconViewModel(SUB_1_ID, interactor, logger, constants)
+
+ var inVisible: Boolean? = null
+ val inJob = underTest.activityInVisible.onEach { inVisible = it }.launchIn(this)
+
+ var outVisible: Boolean? = null
+ val outJob = underTest.activityInVisible.onEach { outVisible = it }.launchIn(this)
+
+ var containerVisible: Boolean? = null
+ val containerJob =
+ underTest.activityInVisible.onEach { containerVisible = it }.launchIn(this)
+
+ interactor.activity.value =
+ DataActivityModel(
+ hasActivityIn = true,
+ hasActivityOut = true,
+ )
+
+ assertThat(inVisible).isFalse()
+ assertThat(outVisible).isFalse()
+ assertThat(containerVisible).isFalse()
+
+ inJob.cancel()
+ outJob.cancel()
+ containerJob.cancel()
+ }
+
+ @Test
+ fun `data activity - config on - test indicators`() =
+ runBlocking(IMMEDIATE) {
+ // Create a new view model here so the constants are properly read
+ whenever(constants.shouldShowActivityConfig).thenReturn(true)
+ underTest = MobileIconViewModel(SUB_1_ID, interactor, logger, constants)
+
+ var inVisible: Boolean? = null
+ val inJob = underTest.activityInVisible.onEach { inVisible = it }.launchIn(this)
+
+ var outVisible: Boolean? = null
+ val outJob = underTest.activityOutVisible.onEach { outVisible = it }.launchIn(this)
+
+ var containerVisible: Boolean? = null
+ val containerJob =
+ underTest.activityContainerVisible.onEach { containerVisible = it }.launchIn(this)
+
+ interactor.activity.value =
+ DataActivityModel(
+ hasActivityIn = true,
+ hasActivityOut = false,
+ )
+
+ yield()
+
+ assertThat(inVisible).isTrue()
+ assertThat(outVisible).isFalse()
+ assertThat(containerVisible).isTrue()
+
+ interactor.activity.value =
+ DataActivityModel(
+ hasActivityIn = false,
+ hasActivityOut = true,
+ )
+
+ assertThat(inVisible).isFalse()
+ assertThat(outVisible).isTrue()
+ assertThat(containerVisible).isTrue()
+
+ interactor.activity.value =
+ DataActivityModel(
+ hasActivityIn = false,
+ hasActivityOut = false,
+ )
+
+ assertThat(inVisible).isFalse()
+ assertThat(outVisible).isFalse()
+ assertThat(containerVisible).isFalse()
+
+ inJob.cancel()
+ outJob.cancel()
+ containerJob.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/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..4ef4e6c 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,
@@ -61,7 +64,7 @@
wakeLockBuilder,
systemClock,
) {
- override fun animateViewOut(view: ViewGroup, onAnimationEnd: Runnable) {
+ override fun animateViewOut(view: ViewGroup, removalReason: String?, onAnimationEnd: Runnable) {
// Just bypass the animation in tests
onAnimationEnd.run()
}
diff --git a/proto/src/windowmanager.proto b/proto/src/windowmanager.proto
index f26404c6..da4dfa9 100644
--- a/proto/src/windowmanager.proto
+++ b/proto/src/windowmanager.proto
@@ -68,4 +68,8 @@
LetterboxHorizontalReachability letterbox_position_for_horizontal_reachability = 1;
// Represents the current vertical position for the letterboxed activity
LetterboxVerticalReachability letterbox_position_for_vertical_reachability = 2;
+ // Represents the current horizontal position for the letterboxed activity in book mode
+ LetterboxHorizontalReachability letterbox_position_for_book_mode_reachability = 3;
+ // Represents the current vertical position for the letterboxed activity in tabletop mode
+ LetterboxVerticalReachability letterbox_position_for_tabletop_mode_reachability = 4;
}
\ No newline at end of file
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/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java
index fa6f4ee..890c891 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackage.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackage.java
@@ -1470,15 +1470,9 @@
}
// Then make sure none of the activities have more than the max number of shortcuts.
- int total = 0;
for (int i = counts.size() - 1; i >= 0; i--) {
- int count = counts.valueAt(i);
- service.enforceMaxActivityShortcuts(count);
- total += count;
+ service.enforceMaxActivityShortcuts(counts.valueAt(i));
}
-
- // Finally make sure that the app doesn't have more than the max number of shortcuts.
- service.enforceMaxAppShortcuts(total);
}
/**
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 014a77b..0b20683 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -181,9 +181,6 @@
static final int DEFAULT_MAX_SHORTCUTS_PER_ACTIVITY = 15;
@VisibleForTesting
- static final int DEFAULT_MAX_SHORTCUTS_PER_APP = 60;
-
- @VisibleForTesting
static final int DEFAULT_MAX_ICON_DIMENSION_DP = 96;
@VisibleForTesting
@@ -260,11 +257,6 @@
String KEY_MAX_SHORTCUTS = "max_shortcuts";
/**
- * Key name for the max dynamic shortcuts per app. (int)
- */
- String KEY_MAX_SHORTCUTS_PER_APP = "max_shortcuts_per_app";
-
- /**
* Key name for icon compression quality, 0-100.
*/
String KEY_ICON_QUALITY = "icon_quality";
@@ -337,14 +329,9 @@
new SparseArray<>();
/**
- * Max number of dynamic + manifest shortcuts that each activity can have at a time.
- */
- private int mMaxShortcutsPerActivity;
-
- /**
* Max number of dynamic + manifest shortcuts that each application can have at a time.
*/
- private int mMaxShortcutsPerApp;
+ private int mMaxShortcuts;
/**
* Max number of updating API calls that each application can make during the interval.
@@ -817,12 +804,9 @@
mMaxUpdatesPerInterval = Math.max(0, (int) parser.getLong(
ConfigConstants.KEY_MAX_UPDATES_PER_INTERVAL, DEFAULT_MAX_UPDATES_PER_INTERVAL));
- mMaxShortcutsPerActivity = Math.max(0, (int) parser.getLong(
+ mMaxShortcuts = Math.max(0, (int) parser.getLong(
ConfigConstants.KEY_MAX_SHORTCUTS, DEFAULT_MAX_SHORTCUTS_PER_ACTIVITY));
- mMaxShortcutsPerApp = Math.max(0, (int) parser.getLong(
- ConfigConstants.KEY_MAX_SHORTCUTS_PER_APP, DEFAULT_MAX_SHORTCUTS_PER_APP));
-
final int iconDimensionDp = Math.max(1, injectIsLowRamDevice()
? (int) parser.getLong(
ConfigConstants.KEY_MAX_ICON_DIMENSION_DP_LOWRAM,
@@ -1762,33 +1746,16 @@
* {@link #getMaxActivityShortcuts()}.
*/
void enforceMaxActivityShortcuts(int numShortcuts) {
- if (numShortcuts > mMaxShortcutsPerActivity) {
+ if (numShortcuts > mMaxShortcuts) {
throw new IllegalArgumentException("Max number of dynamic shortcuts exceeded");
}
}
/**
- * @throws IllegalArgumentException if {@code numShortcuts} is bigger than
- * {@link #getMaxAppShortcuts()}.
- */
- void enforceMaxAppShortcuts(int numShortcuts) {
- if (numShortcuts > mMaxShortcutsPerApp) {
- throw new IllegalArgumentException("Max number of dynamic shortcuts per app exceeded");
- }
- }
-
- /**
* Return the max number of dynamic + manifest shortcuts for each launcher icon.
*/
int getMaxActivityShortcuts() {
- return mMaxShortcutsPerActivity;
- }
-
- /**
- * Return the max number of dynamic + manifest shortcuts for each launcher icon.
- */
- int getMaxAppShortcuts() {
- return mMaxShortcutsPerApp;
+ return mMaxShortcuts;
}
/**
@@ -2221,8 +2188,6 @@
ps.ensureNotImmutable(shortcut.getId(), /*ignoreInvisible=*/ true);
fillInDefaultActivity(Arrays.asList(shortcut));
- enforceMaxAppShortcuts(ps.getShortcutCount());
-
if (!shortcut.hasRank()) {
shortcut.setRank(0);
}
@@ -2610,7 +2575,7 @@
throws RemoteException {
verifyCaller(packageName, userId);
- return mMaxShortcutsPerActivity;
+ return mMaxShortcuts;
}
@Override
@@ -4758,7 +4723,7 @@
pw.print(" maxUpdatesPerInterval: ");
pw.println(mMaxUpdatesPerInterval);
pw.print(" maxShortcutsPerActivity: ");
- pw.println(mMaxShortcutsPerActivity);
+ pw.println(mMaxShortcuts);
pw.println();
mStatLogger.dump(pw, " ");
@@ -5245,7 +5210,7 @@
@VisibleForTesting
int getMaxShortcutsForTest() {
- return mMaxShortcutsPerActivity;
+ return mMaxShortcuts;
}
@VisibleForTesting
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/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 418e1ed..57eeb9a 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -8149,7 +8149,7 @@
}
/**
- * Adjusts position of resolved bounds if they doesn't fill the parent using gravity
+ * Adjusts position of resolved bounds if they don't fill the parent using gravity
* requested in the config or via an ADB command. For more context see {@link
* LetterboxUiController#getHorizontalPositionMultiplier(Configuration)} and
* {@link LetterboxUiController#getVerticalPositionMultiplier(Configuration)}
@@ -8168,9 +8168,8 @@
int offsetX = 0;
if (parentBounds.width() != screenResolvedBounds.width()) {
if (screenResolvedBounds.width() <= parentAppBounds.width()) {
- float positionMultiplier =
- mLetterboxUiController.getHorizontalPositionMultiplier(
- newParentConfiguration);
+ float positionMultiplier = mLetterboxUiController.getHorizontalPositionMultiplier(
+ newParentConfiguration);
offsetX = (int) Math.ceil((parentAppBounds.width() - screenResolvedBounds.width())
* positionMultiplier);
}
@@ -8180,9 +8179,8 @@
int offsetY = 0;
if (parentBounds.height() != screenResolvedBounds.height()) {
if (screenResolvedBounds.height() <= parentAppBounds.height()) {
- float positionMultiplier =
- mLetterboxUiController.getVerticalPositionMultiplier(
- newParentConfiguration);
+ float positionMultiplier = mLetterboxUiController.getVerticalPositionMultiplier(
+ newParentConfiguration);
offsetY = (int) Math.ceil((parentAppBounds.height() - screenResolvedBounds.height())
* positionMultiplier);
}
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..34bdb7a 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -16,6 +16,7 @@
package com.android.server.wm;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
import static android.util.RotationUtils.deltaRotation;
@@ -55,9 +56,11 @@
import android.os.SystemProperties;
import android.os.UserHandle;
import android.provider.Settings;
+import android.util.ArraySet;
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;
@@ -75,6 +78,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayDeque;
+import java.util.Set;
/**
* Defines the mapping between orientation and rotation of a display.
@@ -211,15 +215,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 +240,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 +255,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);
@@ -1521,6 +1555,15 @@
proto.end(token);
}
+ boolean isDeviceInPosture(DeviceStateController.FoldState state, boolean isTabletop) {
+ if (mFoldController == null) return false;
+ return mFoldController.isDeviceInPosture(state, isTabletop);
+ }
+
+ boolean isDisplaySeparatingHinge() {
+ return mFoldController != null && mFoldController.isSeparatingHinge();
+ }
+
/**
* Called by the DeviceStateManager callback when the device state changes.
*/
@@ -1538,6 +1581,63 @@
private DeviceStateController.FoldState mFoldState =
DeviceStateController.FoldState.UNKNOWN;
private boolean mInHalfFoldTransition = false;
+ private final boolean mIsDisplayAlwaysSeparatingHinge;
+ private final Set<Integer> mTabletopRotations;
+
+ FoldController() {
+ mTabletopRotations = new ArraySet<>();
+ int[] tabletop_rotations = mContext.getResources().getIntArray(
+ R.array.config_deviceTabletopRotations);
+ if (tabletop_rotations != null) {
+ for (int angle : tabletop_rotations) {
+ switch (angle) {
+ case 0:
+ mTabletopRotations.add(Surface.ROTATION_0);
+ break;
+ case 90:
+ mTabletopRotations.add(Surface.ROTATION_90);
+ break;
+ case 180:
+ mTabletopRotations.add(Surface.ROTATION_180);
+ break;
+ case 270:
+ mTabletopRotations.add(Surface.ROTATION_270);
+ break;
+ default:
+ ProtoLog.e(WM_DEBUG_ORIENTATION,
+ "Invalid surface rotation angle in "
+ + "config_deviceTabletopRotations: %d",
+ angle);
+ }
+ }
+ } else {
+ ProtoLog.w(WM_DEBUG_ORIENTATION,
+ "config_deviceTabletopRotations is not defined. Half-fold "
+ + "letterboxing will work inconsistently.");
+ }
+ mIsDisplayAlwaysSeparatingHinge = mContext.getResources().getBoolean(
+ R.bool.config_isDisplayHingeAlwaysSeparating);
+ }
+
+ boolean isDeviceInPosture(DeviceStateController.FoldState state, boolean isTabletop) {
+ if (state != mFoldState) {
+ return false;
+ }
+ if (mFoldState == DeviceStateController.FoldState.HALF_FOLDED) {
+ return !(isTabletop ^ mTabletopRotations.contains(mRotation));
+ }
+ return true;
+ }
+
+ DeviceStateController.FoldState getFoldState() {
+ return mFoldState;
+ }
+
+ boolean isSeparatingHinge() {
+ return mFoldState == DeviceStateController.FoldState.HALF_FOLDED
+ || (mFoldState == DeviceStateController.FoldState.OPEN
+ && mIsDisplayAlwaysSeparatingHinge);
+ }
boolean overrideFrozenRotation() {
return mFoldState == DeviceStateController.FoldState.HALF_FOLDED;
@@ -1586,6 +1686,15 @@
mService.updateRotation(false /* alwaysSendConfiguration */,
false /* forceRelayout */);
}
+ // Alert the activity of possible new bounds.
+ final Task topFullscreenTask =
+ mDisplayContent.getTask(t -> t.getWindowingMode() == WINDOWING_MODE_FULLSCREEN);
+ if (topFullscreenTask != null) {
+ final ActivityRecord top = topFullscreenTask.topRunningActivity();
+ if (top != null) {
+ top.recomputeConfiguration();
+ }
+ }
}
}
diff --git a/services/core/java/com/android/server/wm/LetterboxConfiguration.java b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
index 127a7bf..3eca364 100644
--- a/services/core/java/com/android/server/wm/LetterboxConfiguration.java
+++ b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
@@ -16,12 +16,16 @@
package com.android.server.wm;
+import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
+import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
+
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.graphics.Color;
import android.provider.DeviceConfig;
+import android.util.Slog;
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
@@ -33,6 +37,8 @@
/** Reads letterbox configs from resources and controls their overrides at runtime. */
final class LetterboxConfiguration {
+ private static final String TAG = TAG_WITH_CLASS_NAME ? "LetterboxConfiguration" : TAG_ATM;
+
/**
* Override of aspect ratio for fixed orientation letterboxing that is set via ADB with
* set-fixed-orientation-letterbox-aspect-ratio or via {@link
@@ -144,6 +150,14 @@
// side of the screen and 1.0 to the bottom side.
private float mLetterboxVerticalPositionMultiplier;
+ // Horizontal position of a center of the letterboxed app window when the device is half-folded.
+ // 0 corresponds to the left side of the screen and 1.0 to the right side.
+ private float mLetterboxBookModePositionMultiplier;
+
+ // Vertical position of a center of the letterboxed app window when the device is half-folded.
+ // 0 corresponds to the top side of the screen and 1.0 to the bottom side.
+ private float mLetterboxTabletopModePositionMultiplier;
+
// Default horizontal position the letterboxed app window when horizontal reachability is
// enabled and an app is fullscreen in landscape device orientation.
// It is used as a starting point for mLetterboxPositionForHorizontalReachability.
@@ -179,8 +193,15 @@
LetterboxConfiguration(Context systemUiContext) {
this(systemUiContext, new LetterboxConfigurationPersister(systemUiContext,
- () -> readLetterboxHorizontalReachabilityPositionFromConfig(systemUiContext),
- () -> readLetterboxVerticalReachabilityPositionFromConfig(systemUiContext)));
+ () -> readLetterboxHorizontalReachabilityPositionFromConfig(systemUiContext,
+ /* forBookMode */ false),
+ () -> readLetterboxVerticalReachabilityPositionFromConfig(systemUiContext,
+ /* forTabletopMode */ false),
+ () -> readLetterboxHorizontalReachabilityPositionFromConfig(systemUiContext,
+ /* forBookMode */ true),
+ () -> readLetterboxVerticalReachabilityPositionFromConfig(systemUiContext,
+ /* forTabletopMode */ true)
+ ));
}
@VisibleForTesting
@@ -200,14 +221,18 @@
R.dimen.config_letterboxHorizontalPositionMultiplier);
mLetterboxVerticalPositionMultiplier = mContext.getResources().getFloat(
R.dimen.config_letterboxVerticalPositionMultiplier);
+ mLetterboxBookModePositionMultiplier = mContext.getResources().getFloat(
+ R.dimen.config_letterboxBookModePositionMultiplier);
+ mLetterboxTabletopModePositionMultiplier = mContext.getResources().getFloat(
+ R.dimen.config_letterboxTabletopModePositionMultiplier);
mIsHorizontalReachabilityEnabled = mContext.getResources().getBoolean(
R.bool.config_letterboxIsHorizontalReachabilityEnabled);
mIsVerticalReachabilityEnabled = mContext.getResources().getBoolean(
R.bool.config_letterboxIsVerticalReachabilityEnabled);
mDefaultPositionForHorizontalReachability =
- readLetterboxHorizontalReachabilityPositionFromConfig(mContext);
+ readLetterboxHorizontalReachabilityPositionFromConfig(mContext, false);
mDefaultPositionForVerticalReachability =
- readLetterboxVerticalReachabilityPositionFromConfig(mContext);
+ readLetterboxVerticalReachabilityPositionFromConfig(mContext, false);
mIsEducationEnabled = mContext.getResources().getBoolean(
R.bool.config_letterboxIsEducationEnabled);
setDefaultMinAspectRatioForUnresizableApps(mContext.getResources().getFloat(
@@ -460,11 +485,30 @@
* or via an ADB command. 0 corresponds to the left side of the screen and 1 to the
* right side.
*/
- float getLetterboxHorizontalPositionMultiplier() {
- return (mLetterboxHorizontalPositionMultiplier < 0.0f
- || mLetterboxHorizontalPositionMultiplier > 1.0f)
- // Default to central position if invalid value is provided.
- ? 0.5f : mLetterboxHorizontalPositionMultiplier;
+ float getLetterboxHorizontalPositionMultiplier(boolean isInBookMode) {
+ if (isInBookMode) {
+ if (mLetterboxBookModePositionMultiplier < 0.0f
+ || mLetterboxBookModePositionMultiplier > 1.0f) {
+ Slog.w(TAG,
+ "mLetterboxBookModePositionMultiplier out of bounds (isInBookMode=true): "
+ + mLetterboxBookModePositionMultiplier);
+ // Default to left position if invalid value is provided.
+ return 0.0f;
+ } else {
+ return mLetterboxBookModePositionMultiplier;
+ }
+ } else {
+ if (mLetterboxHorizontalPositionMultiplier < 0.0f
+ || mLetterboxHorizontalPositionMultiplier > 1.0f) {
+ Slog.w(TAG,
+ "mLetterboxBookModePositionMultiplier out of bounds (isInBookMode=false):"
+ + mLetterboxBookModePositionMultiplier);
+ // Default to central position if invalid value is provided.
+ return 0.5f;
+ } else {
+ return mLetterboxHorizontalPositionMultiplier;
+ }
+ }
}
/*
@@ -473,11 +517,18 @@
* or via an ADB command. 0 corresponds to the top side of the screen and 1 to the
* bottom side.
*/
- float getLetterboxVerticalPositionMultiplier() {
- return (mLetterboxVerticalPositionMultiplier < 0.0f
- || mLetterboxVerticalPositionMultiplier > 1.0f)
- // Default to central position if invalid value is provided.
- ? 0.5f : mLetterboxVerticalPositionMultiplier;
+ float getLetterboxVerticalPositionMultiplier(boolean isInTabletopMode) {
+ if (isInTabletopMode) {
+ return (mLetterboxTabletopModePositionMultiplier < 0.0f
+ || mLetterboxTabletopModePositionMultiplier > 1.0f)
+ // Default to top position if invalid value is provided.
+ ? 0.0f : mLetterboxTabletopModePositionMultiplier;
+ } else {
+ return (mLetterboxVerticalPositionMultiplier < 0.0f
+ || mLetterboxVerticalPositionMultiplier > 1.0f)
+ // Default to central position if invalid value is provided.
+ ? 0.5f : mLetterboxVerticalPositionMultiplier;
+ }
}
/**
@@ -618,7 +669,8 @@
*/
void resetDefaultPositionForHorizontalReachability() {
mDefaultPositionForHorizontalReachability =
- readLetterboxHorizontalReachabilityPositionFromConfig(mContext);
+ readLetterboxHorizontalReachabilityPositionFromConfig(mContext,
+ false /* forBookMode */);
}
/**
@@ -627,27 +679,34 @@
*/
void resetDefaultPositionForVerticalReachability() {
mDefaultPositionForVerticalReachability =
- readLetterboxVerticalReachabilityPositionFromConfig(mContext);
+ readLetterboxVerticalReachabilityPositionFromConfig(mContext,
+ false /* forTabletopMode */);
}
@LetterboxHorizontalReachabilityPosition
- private static int readLetterboxHorizontalReachabilityPositionFromConfig(Context context) {
+ private static int readLetterboxHorizontalReachabilityPositionFromConfig(Context context,
+ boolean forBookMode) {
int position = context.getResources().getInteger(
- R.integer.config_letterboxDefaultPositionForHorizontalReachability);
+ forBookMode
+ ? R.integer.config_letterboxDefaultPositionForBookModeReachability
+ : R.integer.config_letterboxDefaultPositionForHorizontalReachability);
return position == LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT
- || position == LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER
- || position == LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT
+ || position == LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER
+ || position == LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT
? position : LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER;
}
@LetterboxVerticalReachabilityPosition
- private static int readLetterboxVerticalReachabilityPositionFromConfig(Context context) {
+ private static int readLetterboxVerticalReachabilityPositionFromConfig(Context context,
+ boolean forTabletopMode) {
int position = context.getResources().getInteger(
- R.integer.config_letterboxDefaultPositionForVerticalReachability);
+ forTabletopMode
+ ? R.integer.config_letterboxDefaultPositionForTabletopModeReachability
+ : R.integer.config_letterboxDefaultPositionForVerticalReachability);
return position == LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP
|| position == LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER
|| position == LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM
- ? position : LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER;
+ ? position : LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER;
}
/*
@@ -656,9 +715,10 @@
*
* <p>The position multiplier is changed after each double tap in the letterbox area.
*/
- float getHorizontalMultiplierForReachability() {
+ float getHorizontalMultiplierForReachability(boolean isDeviceInBookMode) {
final int letterboxPositionForHorizontalReachability =
- mLetterboxConfigurationPersister.getLetterboxPositionForHorizontalReachability();
+ mLetterboxConfigurationPersister.getLetterboxPositionForHorizontalReachability(
+ isDeviceInBookMode);
switch (letterboxPositionForHorizontalReachability) {
case LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT:
return 0.0f;
@@ -679,9 +739,10 @@
*
* <p>The position multiplier is changed after each double tap in the letterbox area.
*/
- float getVerticalMultiplierForReachability() {
+ float getVerticalMultiplierForReachability(boolean isDeviceInTabletopMode) {
final int letterboxPositionForVerticalReachability =
- mLetterboxConfigurationPersister.getLetterboxPositionForVerticalReachability();
+ mLetterboxConfigurationPersister.getLetterboxPositionForVerticalReachability(
+ isDeviceInTabletopMode);
switch (letterboxPositionForVerticalReachability) {
case LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP:
return 0.0f;
@@ -701,8 +762,9 @@
* enabled.
*/
@LetterboxHorizontalReachabilityPosition
- int getLetterboxPositionForHorizontalReachability() {
- return mLetterboxConfigurationPersister.getLetterboxPositionForHorizontalReachability();
+ int getLetterboxPositionForHorizontalReachability(boolean isInFullScreenBookMode) {
+ return mLetterboxConfigurationPersister.getLetterboxPositionForHorizontalReachability(
+ isInFullScreenBookMode);
}
/*
@@ -710,8 +772,9 @@
* enabled.
*/
@LetterboxVerticalReachabilityPosition
- int getLetterboxPositionForVerticalReachability() {
- return mLetterboxConfigurationPersister.getLetterboxPositionForVerticalReachability();
+ int getLetterboxPositionForVerticalReachability(boolean isInFullScreenTabletopMode) {
+ return mLetterboxConfigurationPersister.getLetterboxPositionForVerticalReachability(
+ isInFullScreenTabletopMode);
}
/** Returns a string representing the given {@link LetterboxHorizontalReachabilityPosition}. */
@@ -750,34 +813,41 @@
* Changes letterbox position for horizontal reachability to the next available one on the
* right side.
*/
- void movePositionForHorizontalReachabilityToNextRightStop() {
- updatePositionForHorizontalReachability(prev -> Math.min(
- prev + 1, LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT));
+ void movePositionForHorizontalReachabilityToNextRightStop(boolean isDeviceInBookMode) {
+ updatePositionForHorizontalReachability(isDeviceInBookMode, prev -> Math.min(
+ prev + (isDeviceInBookMode ? 2 : 1), // Move 2 stops in book mode to avoid center.
+ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT));
}
/**
* Changes letterbox position for horizontal reachability to the next available one on the left
* side.
*/
- void movePositionForHorizontalReachabilityToNextLeftStop() {
- updatePositionForHorizontalReachability(prev -> Math.max(prev - 1, 0));
+ void movePositionForHorizontalReachabilityToNextLeftStop(boolean isDeviceInBookMode) {
+ updatePositionForHorizontalReachability(isDeviceInBookMode, prev -> Math.max(
+ prev - (isDeviceInBookMode ? 2 : 1), 0)); // Move 2 stops in book mode to avoid
+ // center.
}
/**
* Changes letterbox position for vertical reachability to the next available one on the bottom
* side.
*/
- void movePositionForVerticalReachabilityToNextBottomStop() {
- updatePositionForVerticalReachability(prev -> Math.min(
- prev + 1, LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM));
+ void movePositionForVerticalReachabilityToNextBottomStop(boolean isDeviceInTabletopMode) {
+ updatePositionForVerticalReachability(isDeviceInTabletopMode, prev -> Math.min(
+ prev + (isDeviceInTabletopMode ? 2 : 1), // Move 2 stops in tabletop mode to avoid
+ // center.
+ LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM));
}
/**
* Changes letterbox position for vertical reachability to the next available one on the top
* side.
*/
- void movePositionForVerticalReachabilityToNextTopStop() {
- updatePositionForVerticalReachability(prev -> Math.max(prev - 1, 0));
+ void movePositionForVerticalReachabilityToNextTopStop(boolean isDeviceInTabletopMode) {
+ updatePositionForVerticalReachability(isDeviceInTabletopMode, prev -> Math.max(
+ prev - (isDeviceInTabletopMode ? 2 : 1), 0)); // Move 2 stops in tabletop mode to
+ // avoid center.
}
/**
@@ -854,25 +924,27 @@
}
/** Calculates a new letterboxPositionForHorizontalReachability value and updates the store */
- private void updatePositionForHorizontalReachability(
+ private void updatePositionForHorizontalReachability(boolean isDeviceInBookMode,
Function<Integer, Integer> newHorizonalPositionFun) {
final int letterboxPositionForHorizontalReachability =
- mLetterboxConfigurationPersister.getLetterboxPositionForHorizontalReachability();
+ mLetterboxConfigurationPersister.getLetterboxPositionForHorizontalReachability(
+ isDeviceInBookMode);
final int nextHorizontalPosition = newHorizonalPositionFun.apply(
letterboxPositionForHorizontalReachability);
mLetterboxConfigurationPersister.setLetterboxPositionForHorizontalReachability(
- nextHorizontalPosition);
+ isDeviceInBookMode, nextHorizontalPosition);
}
/** Calculates a new letterboxPositionForVerticalReachability value and updates the store */
- private void updatePositionForVerticalReachability(
+ private void updatePositionForVerticalReachability(boolean isDeviceInTabletopMode,
Function<Integer, Integer> newVerticalPositionFun) {
final int letterboxPositionForVerticalReachability =
- mLetterboxConfigurationPersister.getLetterboxPositionForVerticalReachability();
+ mLetterboxConfigurationPersister.getLetterboxPositionForVerticalReachability(
+ isDeviceInTabletopMode);
final int nextVerticalPosition = newVerticalPositionFun.apply(
letterboxPositionForVerticalReachability);
mLetterboxConfigurationPersister.setLetterboxPositionForVerticalReachability(
- nextVerticalPosition);
+ isDeviceInTabletopMode, nextVerticalPosition);
}
// TODO(b/262378106): Cache runtime flag and implement DeviceConfig.OnPropertiesChangedListener
diff --git a/services/core/java/com/android/server/wm/LetterboxConfigurationPersister.java b/services/core/java/com/android/server/wm/LetterboxConfigurationPersister.java
index 70639b1..4a99db5 100644
--- a/services/core/java/com/android/server/wm/LetterboxConfigurationPersister.java
+++ b/services/core/java/com/android/server/wm/LetterboxConfigurationPersister.java
@@ -55,6 +55,8 @@
private final Context mContext;
private final Supplier<Integer> mDefaultHorizontalReachabilitySupplier;
private final Supplier<Integer> mDefaultVerticalReachabilitySupplier;
+ private final Supplier<Integer> mDefaultBookModeReachabilitySupplier;
+ private final Supplier<Integer> mDefaultTabletopModeReachabilitySupplier;
// Horizontal position of a center of the letterboxed app window which is global to prevent
// "jumps" when switching between letterboxed apps. It's updated to reposition the app window
@@ -64,6 +66,11 @@
@LetterboxHorizontalReachabilityPosition
private volatile int mLetterboxPositionForHorizontalReachability;
+ // The same as mLetterboxPositionForHorizontalReachability but used when the device is
+ // half-folded.
+ @LetterboxHorizontalReachabilityPosition
+ private volatile int mLetterboxPositionForBookModeReachability;
+
// Vertical position of a center of the letterboxed app window which is global to prevent
// "jumps" when switching between letterboxed apps. It's updated to reposition the app window
// in response to a double tap gesture (see LetterboxUiController#handleDoubleTap). Used in
@@ -72,6 +79,11 @@
@LetterboxVerticalReachabilityPosition
private volatile int mLetterboxPositionForVerticalReachability;
+ // The same as mLetterboxPositionForVerticalReachability but used when the device is
+ // half-folded.
+ @LetterboxVerticalReachabilityPosition
+ private volatile int mLetterboxPositionForTabletopModeReachability;
+
@NonNull
private final AtomicFile mConfigurationFile;
@@ -83,9 +95,13 @@
LetterboxConfigurationPersister(Context systemUiContext,
Supplier<Integer> defaultHorizontalReachabilitySupplier,
- Supplier<Integer> defaultVerticalReachabilitySupplier) {
+ Supplier<Integer> defaultVerticalReachabilitySupplier,
+ Supplier<Integer> defaultBookModeReachabilitySupplier,
+ Supplier<Integer> defaultTabletopModeReachabilitySupplier) {
this(systemUiContext, defaultHorizontalReachabilitySupplier,
defaultVerticalReachabilitySupplier,
+ defaultBookModeReachabilitySupplier,
+ defaultTabletopModeReachabilitySupplier,
Environment.getDataSystemDirectory(), new PersisterQueue(),
/* completionCallback */ null);
}
@@ -93,11 +109,18 @@
@VisibleForTesting
LetterboxConfigurationPersister(Context systemUiContext,
Supplier<Integer> defaultHorizontalReachabilitySupplier,
- Supplier<Integer> defaultVerticalReachabilitySupplier, File configFolder,
+ Supplier<Integer> defaultVerticalReachabilitySupplier,
+ Supplier<Integer> defaultBookModeReachabilitySupplier,
+ Supplier<Integer> defaultTabletopModeReachabilitySupplier,
+ File configFolder,
PersisterQueue persisterQueue, @Nullable Consumer<String> completionCallback) {
mContext = systemUiContext.createDeviceProtectedStorageContext();
mDefaultHorizontalReachabilitySupplier = defaultHorizontalReachabilitySupplier;
mDefaultVerticalReachabilitySupplier = defaultVerticalReachabilitySupplier;
+ mDefaultBookModeReachabilitySupplier =
+ defaultBookModeReachabilitySupplier;
+ mDefaultTabletopModeReachabilitySupplier =
+ defaultTabletopModeReachabilitySupplier;
mCompletionCallback = completionCallback;
final File prefFiles = new File(configFolder, LETTERBOX_CONFIGURATION_FILENAME);
mConfigurationFile = new AtomicFile(prefFiles);
@@ -117,8 +140,12 @@
* enabled.
*/
@LetterboxHorizontalReachabilityPosition
- int getLetterboxPositionForHorizontalReachability() {
- return mLetterboxPositionForHorizontalReachability;
+ int getLetterboxPositionForHorizontalReachability(boolean forBookMode) {
+ if (forBookMode) {
+ return mLetterboxPositionForBookModeReachability;
+ } else {
+ return mLetterboxPositionForHorizontalReachability;
+ }
}
/*
@@ -126,31 +153,55 @@
* enabled.
*/
@LetterboxVerticalReachabilityPosition
- int getLetterboxPositionForVerticalReachability() {
- return mLetterboxPositionForVerticalReachability;
- }
-
- /**
- * Updates letterboxPositionForVerticalReachability if different from the current value
- */
- void setLetterboxPositionForHorizontalReachability(
- int letterboxPositionForHorizontalReachability) {
- if (mLetterboxPositionForHorizontalReachability
- != letterboxPositionForHorizontalReachability) {
- mLetterboxPositionForHorizontalReachability =
- letterboxPositionForHorizontalReachability;
- updateConfiguration();
+ int getLetterboxPositionForVerticalReachability(boolean forTabletopMode) {
+ if (forTabletopMode) {
+ return mLetterboxPositionForTabletopModeReachability;
+ } else {
+ return mLetterboxPositionForVerticalReachability;
}
}
/**
* Updates letterboxPositionForVerticalReachability if different from the current value
*/
- void setLetterboxPositionForVerticalReachability(
+ void setLetterboxPositionForHorizontalReachability(boolean forBookMode,
+ int letterboxPositionForHorizontalReachability) {
+ if (forBookMode) {
+ if (mLetterboxPositionForBookModeReachability
+ != letterboxPositionForHorizontalReachability) {
+ mLetterboxPositionForBookModeReachability =
+ letterboxPositionForHorizontalReachability;
+ updateConfiguration();
+ }
+ } else {
+ if (mLetterboxPositionForHorizontalReachability
+ != letterboxPositionForHorizontalReachability) {
+ mLetterboxPositionForHorizontalReachability =
+ letterboxPositionForHorizontalReachability;
+ updateConfiguration();
+ }
+ }
+ }
+
+ /**
+ * Updates letterboxPositionForVerticalReachability if different from the current value
+ */
+ void setLetterboxPositionForVerticalReachability(boolean forTabletopMode,
int letterboxPositionForVerticalReachability) {
- if (mLetterboxPositionForVerticalReachability != letterboxPositionForVerticalReachability) {
- mLetterboxPositionForVerticalReachability = letterboxPositionForVerticalReachability;
- updateConfiguration();
+ if (forTabletopMode) {
+ if (mLetterboxPositionForTabletopModeReachability
+ != letterboxPositionForVerticalReachability) {
+ mLetterboxPositionForTabletopModeReachability =
+ letterboxPositionForVerticalReachability;
+ updateConfiguration();
+ }
+ } else {
+ if (mLetterboxPositionForVerticalReachability
+ != letterboxPositionForVerticalReachability) {
+ mLetterboxPositionForVerticalReachability =
+ letterboxPositionForVerticalReachability;
+ updateConfiguration();
+ }
}
}
@@ -158,6 +209,10 @@
void useDefaultValue() {
mLetterboxPositionForHorizontalReachability = mDefaultHorizontalReachabilitySupplier.get();
mLetterboxPositionForVerticalReachability = mDefaultVerticalReachabilitySupplier.get();
+ mLetterboxPositionForBookModeReachability =
+ mDefaultBookModeReachabilitySupplier.get();
+ mLetterboxPositionForTabletopModeReachability =
+ mDefaultTabletopModeReachabilitySupplier.get();
}
private void readCurrentConfiguration() {
@@ -171,6 +226,10 @@
letterboxData.letterboxPositionForHorizontalReachability;
mLetterboxPositionForVerticalReachability =
letterboxData.letterboxPositionForVerticalReachability;
+ mLetterboxPositionForBookModeReachability =
+ letterboxData.letterboxPositionForBookModeReachability;
+ mLetterboxPositionForTabletopModeReachability =
+ letterboxData.letterboxPositionForTabletopModeReachability;
} catch (IOException ioe) {
Slog.e(TAG,
"Error reading from LetterboxConfigurationPersister. "
@@ -192,6 +251,8 @@
mPersisterQueue.addItem(new UpdateValuesCommand(mConfigurationFile,
mLetterboxPositionForHorizontalReachability,
mLetterboxPositionForVerticalReachability,
+ mLetterboxPositionForBookModeReachability,
+ mLetterboxPositionForTabletopModeReachability,
mCompletionCallback), /* flush */ true);
}
@@ -221,13 +282,18 @@
private final int mHorizontalReachability;
private final int mVerticalReachability;
+ private final int mBookModeReachability;
+ private final int mTabletopModeReachability;
UpdateValuesCommand(@NonNull AtomicFile fileToUpdate,
int horizontalReachability, int verticalReachability,
+ int bookModeReachability, int tabletopModeReachability,
@Nullable Consumer<String> onComplete) {
mFileToUpdate = fileToUpdate;
mHorizontalReachability = horizontalReachability;
mVerticalReachability = verticalReachability;
+ mBookModeReachability = bookModeReachability;
+ mTabletopModeReachability = tabletopModeReachability;
mOnComplete = onComplete;
}
@@ -237,6 +303,10 @@
new WindowManagerProtos.LetterboxProto();
letterboxData.letterboxPositionForHorizontalReachability = mHorizontalReachability;
letterboxData.letterboxPositionForVerticalReachability = mVerticalReachability;
+ letterboxData.letterboxPositionForBookModeReachability =
+ mBookModeReachability;
+ letterboxData.letterboxPositionForTabletopModeReachability =
+ mTabletopModeReachability;
final byte[] bytes = WindowManagerProtos.LetterboxProto.toByteArray(letterboxData);
FileOutputStream fos = null;
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index a53a5fc..9cb94c6 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -275,30 +275,62 @@
&& mActivityRecord.fillsParent();
}
+ // Check if we are in the given pose and in fullscreen mode.
+ // Note that we check the task rather than the parent as with ActivityEmbedding the parent might
+ // be a TaskFragment, and its windowing mode is always MULTI_WINDOW, even if the task is
+ // actually fullscreen.
+ private boolean isDisplayFullScreenAndInPosture(DeviceStateController.FoldState state,
+ boolean isTabletop) {
+ Task task = mActivityRecord.getTask();
+ return mActivityRecord.mDisplayContent != null
+ && mActivityRecord.mDisplayContent.getDisplayRotation().isDeviceInPosture(state,
+ isTabletop)
+ && task != null
+ && task.getWindowingMode() == WINDOWING_MODE_FULLSCREEN;
+ }
+
+ // Note that we check the task rather than the parent as with ActivityEmbedding the parent might
+ // be a TaskFragment, and its windowing mode is always MULTI_WINDOW, even if the task is
+ // actually fullscreen.
+ private boolean isDisplayFullScreenAndSeparatingHinge() {
+ Task task = mActivityRecord.getTask();
+ return mActivityRecord.mDisplayContent != null
+ && mActivityRecord.mDisplayContent.getDisplayRotation().isDisplaySeparatingHinge()
+ && task != null
+ && task.getWindowingMode() == WINDOWING_MODE_FULLSCREEN;
+ }
+
+
float getHorizontalPositionMultiplier(Configuration parentConfiguration) {
// Don't check resolved configuration because it may not be updated yet during
// configuration change.
+ boolean bookMode = isDisplayFullScreenAndInPosture(
+ DeviceStateController.FoldState.HALF_FOLDED, false /* isTabletop */);
return isHorizontalReachabilityEnabled(parentConfiguration)
// Using the last global dynamic position to avoid "jumps" when moving
// between apps or activities.
- ? mLetterboxConfiguration.getHorizontalMultiplierForReachability()
- : mLetterboxConfiguration.getLetterboxHorizontalPositionMultiplier();
+ ? mLetterboxConfiguration.getHorizontalMultiplierForReachability(bookMode)
+ : mLetterboxConfiguration.getLetterboxHorizontalPositionMultiplier(bookMode);
}
float getVerticalPositionMultiplier(Configuration parentConfiguration) {
// Don't check resolved configuration because it may not be updated yet during
// configuration change.
+ boolean tabletopMode = isDisplayFullScreenAndInPosture(
+ DeviceStateController.FoldState.HALF_FOLDED, true /* isTabletop */);
return isVerticalReachabilityEnabled(parentConfiguration)
// Using the last global dynamic position to avoid "jumps" when moving
// between apps or activities.
- ? mLetterboxConfiguration.getVerticalMultiplierForReachability()
- : mLetterboxConfiguration.getLetterboxVerticalPositionMultiplier();
+ ? mLetterboxConfiguration.getVerticalMultiplierForReachability(tabletopMode)
+ : mLetterboxConfiguration.getLetterboxVerticalPositionMultiplier(tabletopMode);
}
float getFixedOrientationLetterboxAspectRatio() {
- return mActivityRecord.shouldCreateCompatDisplayInsets()
- ? getDefaultMinAspectRatioForUnresizableApps()
- : mLetterboxConfiguration.getFixedOrientationLetterboxAspectRatio();
+ return isDisplayFullScreenAndSeparatingHinge()
+ ? getSplitScreenAspectRatio()
+ : mActivityRecord.shouldCreateCompatDisplayInsets()
+ ? getDefaultMinAspectRatioForUnresizableApps()
+ : mLetterboxConfiguration.getFixedOrientationLetterboxAspectRatio();
}
private float getDefaultMinAspectRatioForUnresizableApps() {
@@ -351,11 +383,13 @@
return;
}
+ boolean isInFullScreenBookMode = isDisplayFullScreenAndSeparatingHinge();
int letterboxPositionForHorizontalReachability = mLetterboxConfiguration
- .getLetterboxPositionForHorizontalReachability();
+ .getLetterboxPositionForHorizontalReachability(isInFullScreenBookMode);
if (mLetterbox.getInnerFrame().left > x) {
// Moving to the next stop on the left side of the app window: right > center > left.
- mLetterboxConfiguration.movePositionForHorizontalReachabilityToNextLeftStop();
+ mLetterboxConfiguration.movePositionForHorizontalReachabilityToNextLeftStop(
+ isInFullScreenBookMode);
int changeToLog =
letterboxPositionForHorizontalReachability
== LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER
@@ -364,7 +398,8 @@
logLetterboxPositionChange(changeToLog);
} else if (mLetterbox.getInnerFrame().right < x) {
// Moving to the next stop on the right side of the app window: left > center > right.
- mLetterboxConfiguration.movePositionForHorizontalReachabilityToNextRightStop();
+ mLetterboxConfiguration.movePositionForHorizontalReachabilityToNextRightStop(
+ isInFullScreenBookMode);
int changeToLog =
letterboxPositionForHorizontalReachability
== LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER
@@ -388,11 +423,13 @@
// Only react to clicks at the top and bottom of the letterboxed app window.
return;
}
+ boolean isInFullScreenTabletopMode = isDisplayFullScreenAndSeparatingHinge();
int letterboxPositionForVerticalReachability = mLetterboxConfiguration
- .getLetterboxPositionForVerticalReachability();
+ .getLetterboxPositionForVerticalReachability(isInFullScreenTabletopMode);
if (mLetterbox.getInnerFrame().top > y) {
// Moving to the next stop on the top side of the app window: bottom > center > top.
- mLetterboxConfiguration.movePositionForVerticalReachabilityToNextTopStop();
+ mLetterboxConfiguration.movePositionForVerticalReachabilityToNextTopStop(
+ isInFullScreenTabletopMode);
int changeToLog =
letterboxPositionForVerticalReachability
== LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER
@@ -401,7 +438,8 @@
logLetterboxPositionChange(changeToLog);
} else if (mLetterbox.getInnerFrame().bottom < y) {
// Moving to the next stop on the bottom side of the app window: top > center > bottom.
- mLetterboxConfiguration.movePositionForVerticalReachabilityToNextBottomStop();
+ mLetterboxConfiguration.movePositionForVerticalReachabilityToNextBottomStop(
+ isInFullScreenTabletopMode);
int changeToLog =
letterboxPositionForVerticalReachability
== LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER
@@ -712,10 +750,10 @@
+ getVerticalPositionMultiplier(mActivityRecord.getParent().getConfiguration()));
pw.println(prefix + " letterboxPositionForHorizontalReachability="
+ LetterboxConfiguration.letterboxHorizontalReachabilityPositionToString(
- mLetterboxConfiguration.getLetterboxPositionForHorizontalReachability()));
+ mLetterboxConfiguration.getLetterboxPositionForHorizontalReachability(false)));
pw.println(prefix + " letterboxPositionForVerticalReachability="
+ LetterboxConfiguration.letterboxVerticalReachabilityPositionToString(
- mLetterboxConfiguration.getLetterboxPositionForVerticalReachability()));
+ mLetterboxConfiguration.getLetterboxPositionForVerticalReachability(false)));
pw.println(prefix + " fixedOrientationLetterboxAspectRatio="
+ mLetterboxConfiguration.getFixedOrientationLetterboxAspectRatio());
pw.println(prefix + " defaultMinAspectRatioForUnresizableApps="
@@ -780,14 +818,20 @@
int positionToLog = APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__UNKNOWN_POSITION;
if (isHorizontalReachabilityEnabled()) {
int letterboxPositionForHorizontalReachability = getLetterboxConfiguration()
- .getLetterboxPositionForHorizontalReachability();
+ .getLetterboxPositionForHorizontalReachability(
+ isDisplayFullScreenAndInPosture(
+ DeviceStateController.FoldState.HALF_FOLDED,
+ false /* isTabletop */));
positionToLog = letterboxHorizontalReachabilityPositionToLetterboxPosition(
- letterboxPositionForHorizontalReachability);
+ letterboxPositionForHorizontalReachability);
} else if (isVerticalReachabilityEnabled()) {
int letterboxPositionForVerticalReachability = getLetterboxConfiguration()
- .getLetterboxPositionForVerticalReachability();
+ .getLetterboxPositionForVerticalReachability(
+ isDisplayFullScreenAndInPosture(
+ DeviceStateController.FoldState.HALF_FOLDED,
+ true /* isTabletop */));
positionToLog = letterboxVerticalReachabilityPositionToLetterboxPosition(
- letterboxPositionForVerticalReachability);
+ letterboxPositionForVerticalReachability);
}
return positionToLog;
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
index 4e692e2d..85aa942 100644
--- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
+++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
@@ -1219,9 +1219,17 @@
pw.println("Corner radius: "
+ mLetterboxConfiguration.getLetterboxActivityCornersRadius());
pw.println("Horizontal position multiplier: "
- + mLetterboxConfiguration.getLetterboxHorizontalPositionMultiplier());
+ + mLetterboxConfiguration.getLetterboxHorizontalPositionMultiplier(
+ false /* isInBookMode */));
pw.println("Vertical position multiplier: "
- + mLetterboxConfiguration.getLetterboxVerticalPositionMultiplier());
+ + mLetterboxConfiguration.getLetterboxVerticalPositionMultiplier(
+ false /* isInTabletopMode */));
+ pw.println("Horizontal position multiplier (book mode): "
+ + mLetterboxConfiguration.getLetterboxHorizontalPositionMultiplier(
+ true /* isInBookMode */));
+ pw.println("Vertical position multiplier (tabletop mode): "
+ + mLetterboxConfiguration.getLetterboxVerticalPositionMultiplier(
+ true /* isInTabletopMode */));
pw.println("Aspect ratio: "
+ mLetterboxConfiguration.getFixedOrientationLetterboxAspectRatio());
pw.println("Default min aspect ratio for unresizable apps: "
@@ -1238,10 +1246,10 @@
mLetterboxConfiguration.getDefaultPositionForVerticalReachability()));
pw.println("Current position for horizontal reachability:"
+ LetterboxConfiguration.letterboxHorizontalReachabilityPositionToString(
- mLetterboxConfiguration.getLetterboxPositionForHorizontalReachability()));
+ mLetterboxConfiguration.getLetterboxPositionForHorizontalReachability(false)));
pw.println("Current position for vertical reachability:"
+ LetterboxConfiguration.letterboxVerticalReachabilityPositionToString(
- mLetterboxConfiguration.getLetterboxPositionForVerticalReachability()));
+ mLetterboxConfiguration.getLetterboxPositionForVerticalReachability(false)));
pw.println("Is education enabled: "
+ mLetterboxConfiguration.getIsEducationEnabled());
pw.println("Is using split screen aspect ratio as aspect ratio for unresizable apps: "
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();
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationPersisterTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationPersisterTest.java
index 1be9de7..51a7e74 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationPersisterTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationPersisterTest.java
@@ -67,6 +67,11 @@
R.integer.config_letterboxDefaultPositionForHorizontalReachability),
() -> mContext.getResources().getInteger(
R.integer.config_letterboxDefaultPositionForVerticalReachability),
+ () -> mContext.getResources().getInteger(
+ R.integer.config_letterboxDefaultPositionForBookModeReachability),
+ () -> mContext.getResources().getInteger(
+ R.integer.config_letterboxDefaultPositionForTabletopModeReachability
+ ),
mConfigFolder, mPersisterQueue, mQueueState);
mQueueListener = queueEmpty -> mQueueState.onItemAdded();
mPersisterQueue.addListener(mQueueListener);
@@ -84,14 +89,15 @@
@Test
public void test_whenStoreIsCreated_valuesAreDefaults() {
final int positionForHorizontalReachability =
- mLetterboxConfigurationPersister.getLetterboxPositionForHorizontalReachability();
+ mLetterboxConfigurationPersister.getLetterboxPositionForHorizontalReachability(
+ false);
final int defaultPositionForHorizontalReachability =
mContext.getResources().getInteger(
R.integer.config_letterboxDefaultPositionForHorizontalReachability);
Assert.assertEquals(defaultPositionForHorizontalReachability,
positionForHorizontalReachability);
final int positionForVerticalReachability =
- mLetterboxConfigurationPersister.getLetterboxPositionForVerticalReachability();
+ mLetterboxConfigurationPersister.getLetterboxPositionForVerticalReachability(false);
final int defaultPositionForVerticalReachability =
mContext.getResources().getInteger(
R.integer.config_letterboxDefaultPositionForVerticalReachability);
@@ -101,15 +107,16 @@
@Test
public void test_whenUpdatedWithNewValues_valuesAreWritten() {
- mLetterboxConfigurationPersister.setLetterboxPositionForHorizontalReachability(
+ mLetterboxConfigurationPersister.setLetterboxPositionForHorizontalReachability(false,
LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT);
- mLetterboxConfigurationPersister.setLetterboxPositionForVerticalReachability(
+ mLetterboxConfigurationPersister.setLetterboxPositionForVerticalReachability(false,
LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP);
waitForCompletion(mPersisterQueue);
final int newPositionForHorizontalReachability =
- mLetterboxConfigurationPersister.getLetterboxPositionForHorizontalReachability();
+ mLetterboxConfigurationPersister.getLetterboxPositionForHorizontalReachability(
+ false);
final int newPositionForVerticalReachability =
- mLetterboxConfigurationPersister.getLetterboxPositionForVerticalReachability();
+ mLetterboxConfigurationPersister.getLetterboxPositionForVerticalReachability(false);
Assert.assertEquals(LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT,
newPositionForHorizontalReachability);
Assert.assertEquals(LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP,
@@ -120,24 +127,24 @@
public void test_whenUpdatedWithNewValues_valuesAreReadAfterRestart() {
final PersisterQueue firstPersisterQueue = new PersisterQueue();
final LetterboxConfigurationPersister firstPersister = new LetterboxConfigurationPersister(
- mContext, () -> -1, () -> -1, mContext.getFilesDir(), firstPersisterQueue,
- mQueueState);
+ mContext, () -> -1, () -> -1, () -> -1, () -> -1, mContext.getFilesDir(),
+ firstPersisterQueue, mQueueState);
firstPersister.start();
- firstPersister.setLetterboxPositionForHorizontalReachability(
+ firstPersister.setLetterboxPositionForHorizontalReachability(false,
LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT);
- firstPersister.setLetterboxPositionForVerticalReachability(
+ firstPersister.setLetterboxPositionForVerticalReachability(false,
LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP);
waitForCompletion(firstPersisterQueue);
stopPersisterSafe(firstPersisterQueue);
final PersisterQueue secondPersisterQueue = new PersisterQueue();
final LetterboxConfigurationPersister secondPersister = new LetterboxConfigurationPersister(
- mContext, () -> -1, () -> -1, mContext.getFilesDir(), secondPersisterQueue,
- mQueueState);
+ mContext, () -> -1, () -> -1, () -> -1, () -> -1, mContext.getFilesDir(),
+ secondPersisterQueue, mQueueState);
secondPersister.start();
final int newPositionForHorizontalReachability =
- secondPersister.getLetterboxPositionForHorizontalReachability();
+ secondPersister.getLetterboxPositionForHorizontalReachability(false);
final int newPositionForVerticalReachability =
- secondPersister.getLetterboxPositionForVerticalReachability();
+ secondPersister.getLetterboxPositionForVerticalReachability(false);
Assert.assertEquals(LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT,
newPositionForHorizontalReachability);
Assert.assertEquals(LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP,
@@ -149,15 +156,16 @@
@Test
public void test_whenUpdatedWithNewValuesAndDeleted_valuesAreDefaults() {
- mLetterboxConfigurationPersister.setLetterboxPositionForHorizontalReachability(
+ mLetterboxConfigurationPersister.setLetterboxPositionForHorizontalReachability(false,
LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT);
- mLetterboxConfigurationPersister.setLetterboxPositionForVerticalReachability(
+ mLetterboxConfigurationPersister.setLetterboxPositionForVerticalReachability(false,
LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP);
waitForCompletion(mPersisterQueue);
final int newPositionForHorizontalReachability =
- mLetterboxConfigurationPersister.getLetterboxPositionForHorizontalReachability();
+ mLetterboxConfigurationPersister.getLetterboxPositionForHorizontalReachability(
+ false);
final int newPositionForVerticalReachability =
- mLetterboxConfigurationPersister.getLetterboxPositionForVerticalReachability();
+ mLetterboxConfigurationPersister.getLetterboxPositionForVerticalReachability(false);
Assert.assertEquals(LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT,
newPositionForHorizontalReachability);
Assert.assertEquals(LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP,
@@ -165,14 +173,15 @@
deleteConfiguration(mLetterboxConfigurationPersister, mPersisterQueue);
waitForCompletion(mPersisterQueue);
final int positionForHorizontalReachability =
- mLetterboxConfigurationPersister.getLetterboxPositionForHorizontalReachability();
+ mLetterboxConfigurationPersister.getLetterboxPositionForHorizontalReachability(
+ false);
final int defaultPositionForHorizontalReachability =
mContext.getResources().getInteger(
R.integer.config_letterboxDefaultPositionForHorizontalReachability);
Assert.assertEquals(defaultPositionForHorizontalReachability,
positionForHorizontalReachability);
final int positionForVerticalReachability =
- mLetterboxConfigurationPersister.getLetterboxPositionForVerticalReachability();
+ mLetterboxConfigurationPersister.getLetterboxPositionForVerticalReachability(false);
final int defaultPositionForVerticalReachability =
mContext.getResources().getInteger(
R.integer.config_letterboxDefaultPositionForVerticalReachability);
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationTest.java
index c927f9e..e196704 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationTest.java
@@ -26,6 +26,7 @@
import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -39,7 +40,8 @@
import org.junit.Before;
import org.junit.Test;
-import java.util.function.Consumer;
+import java.util.Arrays;
+import java.util.function.BiConsumer;
@SmallTest
@Presubmit
@@ -58,20 +60,28 @@
@Test
public void test_whenReadingValues_storeIsInvoked() {
- mLetterboxConfiguration.getLetterboxPositionForHorizontalReachability();
- verify(mLetterboxConfigurationPersister).getLetterboxPositionForHorizontalReachability();
- mLetterboxConfiguration.getLetterboxPositionForVerticalReachability();
- verify(mLetterboxConfigurationPersister).getLetterboxPositionForVerticalReachability();
+ for (boolean halfFoldPose : Arrays.asList(false, true)) {
+ mLetterboxConfiguration.getLetterboxPositionForHorizontalReachability(halfFoldPose);
+ verify(mLetterboxConfigurationPersister).getLetterboxPositionForHorizontalReachability(
+ halfFoldPose);
+ mLetterboxConfiguration.getLetterboxPositionForVerticalReachability(halfFoldPose);
+ verify(mLetterboxConfigurationPersister).getLetterboxPositionForVerticalReachability(
+ halfFoldPose);
+ }
}
@Test
public void test_whenSettingValues_updateConfigurationIsInvoked() {
- mLetterboxConfiguration.movePositionForHorizontalReachabilityToNextRightStop();
- verify(mLetterboxConfigurationPersister).setLetterboxPositionForHorizontalReachability(
- anyInt());
- mLetterboxConfiguration.movePositionForVerticalReachabilityToNextBottomStop();
- verify(mLetterboxConfigurationPersister).setLetterboxPositionForVerticalReachability(
- anyInt());
+ for (boolean halfFoldPose : Arrays.asList(false, true)) {
+ mLetterboxConfiguration.movePositionForHorizontalReachabilityToNextRightStop(
+ halfFoldPose);
+ verify(mLetterboxConfigurationPersister).setLetterboxPositionForHorizontalReachability(
+ eq(halfFoldPose), anyInt());
+ mLetterboxConfiguration.movePositionForVerticalReachabilityToNextBottomStop(
+ halfFoldPose);
+ verify(mLetterboxConfigurationPersister).setLetterboxPositionForVerticalReachability(
+ eq(halfFoldPose), anyInt());
+ }
}
@Test
@@ -81,33 +91,65 @@
/* from */ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER,
/* expected */ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT,
/* expectedTime */ 1,
+ /* halfFoldPose */ false,
LetterboxConfiguration::movePositionForHorizontalReachabilityToNextLeftStop);
assertForHorizontalMove(
/* from */ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER,
/* expected */ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT,
/* expectedTime */ 1,
+ /* halfFoldPose */ false,
LetterboxConfiguration::movePositionForHorizontalReachabilityToNextRightStop);
// Starting from left
assertForHorizontalMove(
/* from */ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT,
/* expected */ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT,
/* expectedTime */ 2,
+ /* halfFoldPose */ false,
LetterboxConfiguration::movePositionForHorizontalReachabilityToNextLeftStop);
assertForHorizontalMove(
/* from */ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT,
/* expected */ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER,
/* expectedTime */ 1,
+ /* halfFoldPose */ false,
LetterboxConfiguration::movePositionForHorizontalReachabilityToNextRightStop);
// Starting from right
assertForHorizontalMove(
/* from */ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT,
/* expected */ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT,
/* expectedTime */ 2,
+ /* halfFoldPose */ false,
LetterboxConfiguration::movePositionForHorizontalReachabilityToNextRightStop);
assertForHorizontalMove(
/* from */ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT,
/* expected */ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER,
/* expectedTime */ 2,
+ /* halfFoldPose */ false,
+ LetterboxConfiguration::movePositionForHorizontalReachabilityToNextLeftStop);
+ // Starting from left - book mode
+ assertForHorizontalMove(
+ /* from */ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT,
+ /* expected */ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT,
+ /* expectedTime */ 1,
+ /* halfFoldPose */ true,
+ LetterboxConfiguration::movePositionForHorizontalReachabilityToNextLeftStop);
+ assertForHorizontalMove(
+ /* from */ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT,
+ /* expected */ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT,
+ /* expectedTime */ 1,
+ /* halfFoldPose */ true,
+ LetterboxConfiguration::movePositionForHorizontalReachabilityToNextRightStop);
+ // Starting from right - book mode
+ assertForHorizontalMove(
+ /* from */ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT,
+ /* expected */ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT,
+ /* expectedTime */ 2,
+ /* halfFoldPose */ true,
+ LetterboxConfiguration::movePositionForHorizontalReachabilityToNextRightStop);
+ assertForHorizontalMove(
+ /* from */ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT,
+ /* expected */ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT,
+ /* expectedTime */ 2,
+ /* halfFoldPose */ true,
LetterboxConfiguration::movePositionForHorizontalReachabilityToNextLeftStop);
}
@@ -118,55 +160,87 @@
/* from */ LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER,
/* expected */ LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM,
/* expectedTime */ 1,
+ /* halfFoldPose */ false,
LetterboxConfiguration::movePositionForVerticalReachabilityToNextBottomStop);
assertForVerticalMove(
/* from */ LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER,
/* expected */ LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP,
/* expectedTime */ 1,
+ /* halfFoldPose */ false,
LetterboxConfiguration::movePositionForVerticalReachabilityToNextTopStop);
// Starting from top
assertForVerticalMove(
/* from */ LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP,
/* expected */ LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER,
/* expectedTime */ 1,
+ /* halfFoldPose */ false,
LetterboxConfiguration::movePositionForVerticalReachabilityToNextBottomStop);
assertForVerticalMove(
/* from */ LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP,
/* expected */ LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP,
/* expectedTime */ 2,
+ /* halfFoldPose */ false,
LetterboxConfiguration::movePositionForVerticalReachabilityToNextTopStop);
// Starting from bottom
assertForVerticalMove(
/* from */ LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM,
/* expected */ LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER,
/* expectedTime */ 2,
+ /* halfFoldPose */ false,
LetterboxConfiguration::movePositionForVerticalReachabilityToNextTopStop);
assertForVerticalMove(
/* from */ LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM,
/* expected */ LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM,
/* expectedTime */ 2,
+ /* halfFoldPose */ false,
+ LetterboxConfiguration::movePositionForVerticalReachabilityToNextBottomStop);
+ // Starting from top - tabletop mode
+ assertForVerticalMove(
+ /* from */ LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP,
+ /* expected */ LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM,
+ /* expectedTime */ 1,
+ /* halfFoldPose */ true,
+ LetterboxConfiguration::movePositionForVerticalReachabilityToNextBottomStop);
+ assertForVerticalMove(
+ /* from */ LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP,
+ /* expected */ LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP,
+ /* expectedTime */ 1,
+ /* halfFoldPose */ true,
+ LetterboxConfiguration::movePositionForVerticalReachabilityToNextTopStop);
+ // Starting from bottom - tabletop mode
+ assertForVerticalMove(
+ /* from */ LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM,
+ /* expected */ LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP,
+ /* expectedTime */ 2,
+ /* halfFoldPose */ true,
+ LetterboxConfiguration::movePositionForVerticalReachabilityToNextTopStop);
+ assertForVerticalMove(
+ /* from */ LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM,
+ /* expected */ LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM,
+ /* expectedTime */ 2,
+ /* halfFoldPose */ true,
LetterboxConfiguration::movePositionForVerticalReachabilityToNextBottomStop);
}
private void assertForHorizontalMove(int from, int expected, int expectedTime,
- Consumer<LetterboxConfiguration> move) {
+ boolean halfFoldPose, BiConsumer<LetterboxConfiguration, Boolean> move) {
// We are in the current position
- when(mLetterboxConfiguration.getLetterboxPositionForHorizontalReachability())
+ when(mLetterboxConfiguration.getLetterboxPositionForHorizontalReachability(halfFoldPose))
.thenReturn(from);
- move.accept(mLetterboxConfiguration);
+ move.accept(mLetterboxConfiguration, halfFoldPose);
verify(mLetterboxConfigurationPersister,
- times(expectedTime)).setLetterboxPositionForHorizontalReachability(
+ times(expectedTime)).setLetterboxPositionForHorizontalReachability(halfFoldPose,
expected);
}
private void assertForVerticalMove(int from, int expected, int expectedTime,
- Consumer<LetterboxConfiguration> move) {
+ boolean halfFoldPose, BiConsumer<LetterboxConfiguration, Boolean> move) {
// We are in the current position
- when(mLetterboxConfiguration.getLetterboxPositionForVerticalReachability())
+ when(mLetterboxConfiguration.getLetterboxPositionForVerticalReachability(halfFoldPose))
.thenReturn(from);
- move.accept(mLetterboxConfiguration);
+ move.accept(mLetterboxConfiguration, halfFoldPose);
verify(mLetterboxConfigurationPersister,
- times(expectedTime)).setLetterboxPositionForVerticalReachability(
+ times(expectedTime)).setLetterboxPositionForVerticalReachability(halfFoldPose,
expected);
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index 94c33f2..7488e1c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -16,6 +16,7 @@
package com.android.server.wm;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE;
import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
@@ -95,6 +96,7 @@
import com.android.internal.policy.SystemBarUtils;
import com.android.internal.statusbar.LetterboxDetails;
import com.android.server.statusbar.StatusBarManagerInternal;
+import com.android.server.wm.DeviceStateController.FoldState;
import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
@@ -2821,6 +2823,70 @@
}
@Test
+ public void testUpdateResolvedBoundsVerticalPosition_tabletop() {
+
+ // Set up a display in portrait with a fixed-orientation LANDSCAPE app
+ setUpDisplaySizeWithApp(1400, 2800);
+ mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+ mActivity.mWmService.mLetterboxConfiguration.setLetterboxVerticalPositionMultiplier(
+ 1.0f /*letterboxVerticalPositionMultiplier*/);
+ prepareUnresizable(mActivity, SCREEN_ORIENTATION_LANDSCAPE);
+
+ Rect letterboxNoFold = new Rect(0, 2100, 1400, 2800);
+ assertEquals(letterboxNoFold, mActivity.getBounds());
+
+ // Make the activity full-screen
+ mTask.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+
+ setFoldablePosture(true /* isHalfFolded */, true /* isTabletop */);
+
+ Rect letterboxHalfFold = new Rect(0, 0, 1400, 700);
+ assertEquals(letterboxHalfFold, mActivity.getBounds());
+
+ setFoldablePosture(false /* isHalfFolded */, false /* isTabletop */);
+
+ assertEquals(letterboxNoFold, mActivity.getBounds());
+
+ }
+
+ @Test
+ public void testUpdateResolvedBoundsHorizontalPosition_book() {
+
+ // Set up a display in landscape with a fixed-orientation PORTRAIT app
+ setUpDisplaySizeWithApp(2800, 1400);
+ mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+ mActivity.mWmService.mLetterboxConfiguration.setLetterboxHorizontalPositionMultiplier(
+ 1.0f /*letterboxVerticalPositionMultiplier*/);
+ prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);
+
+ Rect letterboxNoFold = new Rect(2100, 0, 2800, 1400);
+ assertEquals(letterboxNoFold, mActivity.getBounds());
+
+ // Make the activity full-screen
+ mTask.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+
+ setFoldablePosture(true /* isHalfFolded */, false /* isTabletop */);
+
+ Rect letterboxHalfFold = new Rect(0, 0, 700, 1400);
+ assertEquals(letterboxHalfFold, mActivity.getBounds());
+
+ setFoldablePosture(false /* isHalfFolded */, false /* isTabletop */);
+
+ assertEquals(letterboxNoFold, mActivity.getBounds());
+
+ }
+
+ private void setFoldablePosture(boolean isHalfFolded, boolean isTabletop) {
+ final DisplayRotation r = mActivity.mDisplayContent.getDisplayRotation();
+ doReturn(isHalfFolded).when(r).isDisplaySeparatingHinge();
+ doReturn(false).when(r).isDeviceInPosture(any(FoldState.class), anyBoolean());
+ if (isHalfFolded) {
+ doReturn(true).when(r).isDeviceInPosture(FoldState.HALF_FOLDED, isTabletop);
+ }
+ mActivity.recomputeConfiguration();
+ }
+
+ @Test
public void testUpdateResolvedBoundsPosition_alignToTop() {
final int notchHeight = 100;
final DisplayContent display = new TestDisplayContent.Builder(mAtm, 1000, 2800)