Merge "Remove VibrationAttributes#CATEGORY_KEYBOARD (in framework)" into main
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 42d66ce..f7745d1 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -325,17 +325,62 @@
private String mTag = TAG;
- private final ISurfaceControlViewHostParent mSurfaceControlViewHostParent =
- new ISurfaceControlViewHostParent.Stub() {
+ private static class SurfaceControlViewHostParent extends ISurfaceControlViewHostParent.Stub {
+
+ /**
+ * mSurfaceView is set in {@link #attach} and cleared in {@link #detach} to prevent
+ * temporary memory leaks. The remote process's ISurfaceControlViewHostParent binder
+ * reference extends this object's lifetime. If mSurfaceView is not cleared in
+ * {@link #detach}, then the SurfaceView and anything it references will not be promptly
+ * garbage collected.
+ */
+ @Nullable
+ private SurfaceView mSurfaceView;
+
+ void attach(SurfaceView sv) {
+ synchronized (this) {
+ try {
+ sv.mSurfacePackage.getRemoteInterface().attachParentInterface(this);
+ mSurfaceView = sv;
+ } catch (RemoteException e) {
+ Log.d(TAG, "Failed to attach parent interface to SCVH. Likely SCVH is alraedy "
+ + "dead.");
+ }
+ }
+ }
+
+ void detach() {
+ synchronized (this) {
+ if (mSurfaceView == null) {
+ return;
+ }
+ try {
+ mSurfaceView.mSurfacePackage.getRemoteInterface().attachParentInterface(null);
+ } catch (RemoteException e) {
+ Log.d(TAG, "Failed to remove parent interface from SCVH. Likely SCVH is "
+ + "already dead");
+ }
+ mSurfaceView = null;
+ }
+ }
+
@Override
public void updateParams(WindowManager.LayoutParams[] childAttrs) {
- mEmbeddedWindowParams.clear();
- mEmbeddedWindowParams.addAll(Arrays.asList(childAttrs));
+ SurfaceView sv;
+ synchronized (this) {
+ sv = mSurfaceView;
+ }
+ if (sv == null) {
+ return;
+ }
- if (isAttachedToWindow()) {
- runOnUiThread(() -> {
- if (mParent != null) {
- mParent.recomputeViewAttributes(SurfaceView.this);
+ sv.mEmbeddedWindowParams.clear();
+ sv.mEmbeddedWindowParams.addAll(Arrays.asList(childAttrs));
+
+ if (sv.isAttachedToWindow()) {
+ sv.runOnUiThread(() -> {
+ if (sv.mParent != null) {
+ sv.mParent.recomputeViewAttributes(sv);
}
});
}
@@ -343,34 +388,45 @@
@Override
public void forwardBackKeyToParent(@NonNull KeyEvent keyEvent) {
- runOnUiThread(() -> {
- if (!isAttachedToWindow() || keyEvent.getKeyCode() != KeyEvent.KEYCODE_BACK) {
- return;
- }
- final ViewRootImpl vri = getViewRootImpl();
- if (vri == null) {
- return;
- }
- final InputManager inputManager = mContext.getSystemService(InputManager.class);
- if (inputManager == null) {
- return;
- }
- // Check that the event was created recently.
- final long timeDiff = SystemClock.uptimeMillis() - keyEvent.getEventTime();
- if (timeDiff > FORWARD_BACK_KEY_TOLERANCE_MS) {
- Log.e(TAG, "Ignore the input event that exceed the tolerance time, "
- + "exceed " + timeDiff + "ms");
- return;
- }
- if (inputManager.verifyInputEvent(keyEvent) == null) {
- Log.e(TAG, "Received invalid input event");
- return;
- }
- vri.enqueueInputEvent(keyEvent, null /* receiver */, 0 /* flags */,
- true /* processImmediately */);
- });
+ SurfaceView sv;
+ synchronized (this) {
+ sv = mSurfaceView;
+ }
+ if (sv == null) {
+ return;
+ }
+
+ sv.runOnUiThread(() -> {
+ if (!sv.isAttachedToWindow() || keyEvent.getKeyCode() != KeyEvent.KEYCODE_BACK) {
+ return;
+ }
+ final ViewRootImpl vri = sv.getViewRootImpl();
+ if (vri == null) {
+ return;
+ }
+ final InputManager inputManager = sv.mContext.getSystemService(InputManager.class);
+ if (inputManager == null) {
+ return;
+ }
+ // Check that the event was created recently.
+ final long timeDiff = SystemClock.uptimeMillis() - keyEvent.getEventTime();
+ if (timeDiff > FORWARD_BACK_KEY_TOLERANCE_MS) {
+ Log.e(TAG, "Ignore the input event that exceed the tolerance time, "
+ + "exceed " + timeDiff + "ms");
+ return;
+ }
+ if (inputManager.verifyInputEvent(keyEvent) == null) {
+ Log.e(TAG, "Received invalid input event");
+ return;
+ }
+ vri.enqueueInputEvent(keyEvent, null /* receiver */, 0 /* flags */,
+ true /* processImmediately */);
+ });
}
- };
+ }
+
+ private final SurfaceControlViewHostParent mSurfaceControlViewHostParent =
+ new SurfaceControlViewHostParent();
private final boolean mRtDrivenClipping = Flags.clipSurfaceviews();
@@ -930,13 +986,8 @@
}
if (mSurfacePackage != null) {
- try {
- mSurfacePackage.getRemoteInterface().attachParentInterface(null);
- mEmbeddedWindowParams.clear();
- } catch (RemoteException e) {
- Log.d(TAG, "Failed to remove parent interface from SCVH. Likely SCVH is "
- + "already dead");
- }
+ mSurfaceControlViewHostParent.detach();
+ mEmbeddedWindowParams.clear();
if (releaseSurfacePackage) {
mSurfacePackage.release();
mSurfacePackage = null;
@@ -2067,12 +2118,7 @@
applyTransactionOnVriDraw(transaction);
}
mSurfacePackage = p;
- try {
- mSurfacePackage.getRemoteInterface().attachParentInterface(
- mSurfaceControlViewHostParent);
- } catch (RemoteException e) {
- Log.d(TAG, "Failed to attach parent interface to SCVH. Likely SCVH is already dead.");
- }
+ mSurfaceControlViewHostParent.attach(this);
if (isFocused()) {
requestEmbeddedFocus(true);
diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java
index 33610a0..c7e1fba 100644
--- a/core/java/com/android/internal/jank/InteractionJankMonitor.java
+++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java
@@ -396,6 +396,9 @@
int cujType = conf.mCujType;
if (!shouldMonitor()) {
return false;
+ } else if (!conf.hasValidView()) {
+ Log.w(TAG, "The view has since become invalid, aborting the CUJ.");
+ return false;
}
RunningTracker tracker = putTrackerIfNoCurrent(cujType, () ->
diff --git a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
index 4aeee6c..cbcbf2db 100644
--- a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
@@ -736,7 +736,7 @@
return UUID.nameUUIDFromBytes(fullStringIdentifier.getBytes()).getMostSignificantBits();
}
- private static final int STACK_SIZE_TO_PROTO_LOG_ENTRY_CALL = 12;
+ private static final int STACK_SIZE_TO_PROTO_LOG_ENTRY_CALL = 6;
private String collectStackTrace() {
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index 9112d37..284c299 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -19,14 +19,6 @@
#include "com_android_internal_os_Zygote.h"
-#include <async_safe/log.h>
-
-// sys/mount.h has to come before linux/fs.h due to redefinition of MS_RDONLY, MS_BIND, etc
-#include <sys/mount.h>
-#include <linux/fs.h>
-#include <sys/types.h>
-#include <dirent.h>
-
#include <algorithm>
#include <array>
#include <atomic>
@@ -41,19 +33,18 @@
#include <android/fdsan.h>
#include <arpa/inet.h>
+#include <dirent.h>
#include <fcntl.h>
#include <grp.h>
#include <inttypes.h>
#include <malloc.h>
#include <mntent.h>
-#include <paths.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
-#include <sys/auxv.h>
#include <sys/capability.h>
-#include <sys/cdefs.h>
#include <sys/eventfd.h>
+#include <sys/mount.h>
#include <sys/personality.h>
#include <sys/prctl.h>
#include <sys/resource.h>
@@ -66,6 +57,7 @@
#include <sys/wait.h>
#include <unistd.h>
+#include <async_safe/log.h>
#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/properties.h>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt
index 05c9d02..b6f2a25 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt
@@ -16,6 +16,7 @@
package com.android.wm.shell.desktopmode
+import com.android.internal.annotations.VisibleForTesting
import com.android.internal.protolog.ProtoLog
import com.android.internal.util.FrameworkStatsLog
import com.android.wm.shell.protolog.ShellProtoLogGroup
@@ -128,7 +129,10 @@
/* task_y */
taskUpdate.taskY,
/* session_id */
- sessionId
+ sessionId,
+ taskUpdate.minimizeReason?.reason ?: UNSET_MINIMIZE_REASON,
+ taskUpdate.unminimizeReason?.reason ?: UNSET_UNMINIMIZE_REASON,
+
)
}
@@ -142,6 +146,8 @@
* @property taskWidth width of the task in px
* @property taskX x-coordinate of the top-left corner
* @property taskY y-coordinate of the top-left corner
+ * @property minimizeReason the reason the task was minimized
+ * @property unminimizeEvent the reason the task was unminimized
*
*/
data class TaskUpdate(
@@ -151,8 +157,52 @@
val taskWidth: Int,
val taskX: Int,
val taskY: Int,
+ val minimizeReason: MinimizeReason? = null,
+ val unminimizeReason: UnminimizeReason? = null,
)
+ // Default value used when the task was not minimized.
+ @VisibleForTesting
+ const val UNSET_MINIMIZE_REASON =
+ FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__MINIMIZE_REASON__UNSET_MINIMIZE
+
+ /** The reason a task was minimized. */
+ enum class MinimizeReason (val reason: Int) {
+ TASK_LIMIT(
+ FrameworkStatsLog
+ .DESKTOP_MODE_SESSION_TASK_UPDATE__MINIMIZE_REASON__MINIMIZE_TASK_LIMIT
+ ),
+ MINIMIZE_BUTTON( // TODO(b/356843241): use this enum value
+ FrameworkStatsLog
+ .DESKTOP_MODE_SESSION_TASK_UPDATE__MINIMIZE_REASON__MINIMIZE_BUTTON
+ ),
+ }
+
+ // Default value used when the task was not unminimized.
+ @VisibleForTesting
+ const val UNSET_UNMINIMIZE_REASON =
+ FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__UNMINIMIZE_REASON__UNSET_UNMINIMIZE
+
+ /** The reason a task was unminimized. */
+ enum class UnminimizeReason (val reason: Int) {
+ UNKNOWN(
+ FrameworkStatsLog
+ .DESKTOP_MODE_SESSION_TASK_UPDATE__UNMINIMIZE_REASON__UNMINIMIZE_UNKNOWN
+ ),
+ TASKBAR_TAP(
+ FrameworkStatsLog
+ .DESKTOP_MODE_SESSION_TASK_UPDATE__UNMINIMIZE_REASON__UNMINIMIZE_TASKBAR_TAP
+ ),
+ ALT_TAB(
+ FrameworkStatsLog
+ .DESKTOP_MODE_SESSION_TASK_UPDATE__UNMINIMIZE_REASON__UNMINIMIZE_ALT_TAB
+ ),
+ TASK_LAUNCH(
+ FrameworkStatsLog
+ .DESKTOP_MODE_SESSION_TASK_UPDATE__UNMINIMIZE_REASON__UNMINIMIZE_TASK_LAUNCH
+ ),
+ }
+
/**
* Enum EnterReason mapped to the EnterReason definition in
* stats/atoms/desktopmode/desktopmode_extensions_atoms.proto
diff --git a/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/Android.bp b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/Android.bp
index 35b2f56..a231e38 100644
--- a/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/Android.bp
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/Android.bp
@@ -15,6 +15,7 @@
//
package {
+ default_team: "trendy_team_multitasking_windowing",
// See: http://go/android-license-faq
// A large-scale-change added 'default_applicable_licenses' to import
// all of the 'license_kinds' from "frameworks_base_license"
@@ -30,6 +31,46 @@
],
}
+java_library {
+ name: "WMShellFlickerTestsSplitScreenBase",
+ srcs: [
+ ":WMShellFlickerTestsSplitScreenBase-src",
+ ],
+ static_libs: [
+ "WMShellFlickerTestsBase",
+ "wm-shell-flicker-utils",
+ "androidx.test.ext.junit",
+ "flickertestapplib",
+ "flickerlib",
+ "flickerlib-helpers",
+ "flickerlib-trace_processor_shell",
+ "platform-test-annotations",
+ "wm-flicker-common-app-helpers",
+ "wm-flicker-common-assertions",
+ "launcher-helper-lib",
+ "launcher-aosp-tapl",
+ ],
+}
+
+android_test {
+ name: "WMShellFlickerTestsSplitScreen",
+ defaults: ["WMShellFlickerTestsDefault"],
+ manifest: "AndroidManifest.xml",
+ package_name: "com.android.wm.shell.flicker.splitscreen",
+ instrumentation_target_package: "com.android.wm.shell.flicker.splitscreen",
+ test_config_template: "AndroidTestTemplate.xml",
+ srcs: ["src/**/*.kt"],
+ exclude_srcs: ["src/**/benchmark/*.kt"],
+ static_libs: [
+ "WMShellFlickerTestsBase",
+ "WMShellFlickerTestsSplitScreenBase",
+ ],
+ data: ["trace_config/*"],
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Begin cleanup after gcl merges
+
filegroup {
name: "WMShellFlickerTestsSplitScreenGroup1-src",
srcs: [
@@ -61,27 +102,6 @@
],
}
-java_library {
- name: "WMShellFlickerTestsSplitScreenBase",
- srcs: [
- ":WMShellFlickerTestsSplitScreenBase-src",
- ],
- static_libs: [
- "WMShellFlickerTestsBase",
- "wm-shell-flicker-utils",
- "androidx.test.ext.junit",
- "flickertestapplib",
- "flickerlib",
- "flickerlib-helpers",
- "flickerlib-trace_processor_shell",
- "platform-test-annotations",
- "wm-flicker-common-app-helpers",
- "wm-flicker-common-assertions",
- "launcher-helper-lib",
- "launcher-aosp-tapl",
- ],
-}
-
android_test {
name: "WMShellFlickerTestsSplitScreenGroup1",
defaults: ["WMShellFlickerTestsDefault"],
@@ -154,3 +174,156 @@
],
data: ["trace_config/*"],
}
+
+////////////////////////////////////////////////////////////////////////////////
+// End cleanup after gcl merges
+
+////////////////////////////////////////////////////////////////////////////////
+// Begin breakdowns for FlickerTestsRotation module
+
+test_module_config {
+ name: "WMShellFlickerTestsSplitScreen-CatchAll",
+ base: "WMShellFlickerTestsSplitScreen",
+ exclude_filters: [
+ "com.android.wm.shell.flicker.splitscreen.CopyContentInSplit",
+ "com.android.wm.shell.flicker.splitscreen.DismissSplitScreenByDivider",
+ "com.android.wm.shell.flicker.splitscreen.DismissSplitScreenByGoHome",
+ "com.android.wm.shell.flicker.splitscreen.DragDividerToResize",
+ "com.android.wm.shell.flicker.splitscreen.EnterSplitScreenByDragFromAllApps",
+ "com.android.wm.shell.flicker.splitscreen.EnterSplitScreenByDragFromNotification",
+ "com.android.wm.shell.flicker.splitscreen.EnterSplitScreenByDragFromShortcut",
+ "com.android.wm.shell.flicker.splitscreen.EnterSplitScreenByDragFromTaskbar",
+ "com.android.wm.shell.flicker.splitscreen.EnterSplitScreenFromOverview",
+ "com.android.wm.shell.flicker.splitscreen.MultipleShowImeRequestsInSplitScreen",
+ "com.android.wm.shell.flicker.splitscreen.SwitchAppByDoubleTapDivider",
+ "com.android.wm.shell.flicker.splitscreen.SwitchBackToSplitFromAnotherApp",
+ "com.android.wm.shell.flicker.splitscreen.SwitchBackToSplitFromHome",
+ "com.android.wm.shell.flicker.splitscreen.SwitchBackToSplitFromRecent",
+ "com.android.wm.shell.flicker.splitscreen.SwitchBetweenSplitPairs",
+ "com.android.wm.shell.flicker.splitscreen.SwitchBetweenSplitPairsNoPip",
+ "com.android.wm.shell.flicker.splitscreen.",
+ ],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "WMShellFlickerTestsSplitScreen-CopyContentInSplit",
+ base: "WMShellFlickerTestsSplitScreen",
+ include_filters: ["com.android.wm.shell.flicker.splitscreen.CopyContentInSplit"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "WMShellFlickerTestsSplitScreen-DismissSplitScreenByDivider",
+ base: "WMShellFlickerTestsSplitScreen",
+ include_filters: ["com.android.wm.shell.flicker.splitscreen.DismissSplitScreenByDivider"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "WMShellFlickerTestsSplitScreen-DismissSplitScreenByGoHome",
+ base: "WMShellFlickerTestsSplitScreen",
+ include_filters: ["com.android.wm.shell.flicker.splitscreen.DismissSplitScreenByGoHome"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "WMShellFlickerTestsSplitScreen-DragDividerToResize",
+ base: "WMShellFlickerTestsSplitScreen",
+ include_filters: ["com.android.wm.shell.flicker.splitscreen.DragDividerToResize"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "WMShellFlickerTestsSplitScreen-EnterSplitScreenByDragFromAllApps",
+ base: "WMShellFlickerTestsSplitScreen",
+ include_filters: ["com.android.wm.shell.flicker.splitscreen.EnterSplitScreenByDragFromAllApps"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "WMShellFlickerTestsSplitScreen-EnterSplitScreenByDragFromNotification",
+ base: "WMShellFlickerTestsSplitScreen",
+ include_filters: ["com.android.wm.shell.flicker.splitscreen.EnterSplitScreenByDragFromNotification"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "WMShellFlickerTestsSplitScreen-EnterSplitScreenByDragFromShortcut",
+ base: "WMShellFlickerTestsSplitScreen",
+ include_filters: ["com.android.wm.shell.flicker.splitscreen.EnterSplitScreenByDragFromShortcut"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "WMShellFlickerTestsSplitScreen-EnterSplitScreenByDragFromTaskbar",
+ base: "WMShellFlickerTestsSplitScreen",
+ include_filters: ["com.android.wm.shell.flicker.splitscreen.EnterSplitScreenByDragFromTaskbar"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "WMShellFlickerTestsSplitScreen-EnterSplitScreenFromOverview",
+ base: "WMShellFlickerTestsSplitScreen",
+ include_filters: ["com.android.wm.shell.flicker.splitscreen.EnterSplitScreenFromOverview"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "WMShellFlickerTestsSplitScreen-MultipleShowImeRequestsInSplitScreen",
+ base: "WMShellFlickerTestsSplitScreen",
+ include_filters: ["com.android.wm.shell.flicker.splitscreen.MultipleShowImeRequestsInSplitScreen"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "WMShellFlickerTestsSplitScreen-SwitchAppByDoubleTapDivider",
+ base: "WMShellFlickerTestsSplitScreen",
+ include_filters: ["com.android.wm.shell.flicker.splitscreen.SwitchAppByDoubleTapDivider"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "WMShellFlickerTestsSplitScreen-SwitchBackToSplitFromAnotherApp",
+ base: "WMShellFlickerTestsSplitScreen",
+ include_filters: ["com.android.wm.shell.flicker.splitscreen.SwitchBackToSplitFromAnotherApp"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "WMShellFlickerTestsSplitScreen-SwitchBackToSplitFromHome",
+ base: "WMShellFlickerTestsSplitScreen",
+ include_filters: ["com.android.wm.shell.flicker.splitscreen.SwitchBackToSplitFromHome"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "WMShellFlickerTestsSplitScreen-SwitchBackToSplitFromRecent",
+ base: "WMShellFlickerTestsSplitScreen",
+ include_filters: ["com.android.wm.shell.flicker.splitscreen.SwitchBackToSplitFromRecent"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "WMShellFlickerTestsSplitScreen-SwitchBetweenSplitPairs",
+ base: "WMShellFlickerTestsSplitScreen",
+ include_filters: ["com.android.wm.shell.flicker.splitscreen.SwitchBetweenSplitPairs"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "WMShellFlickerTestsSplitScreen-SwitchBetweenSplitPairsNoPip",
+ base: "WMShellFlickerTestsSplitScreen",
+ include_filters: ["com.android.wm.shell.flicker.splitscreen.SwitchBetweenSplitPairsNoPip"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "WMShellFlickerTestsSplitScreen-UnlockKeyguardToSplitScreen",
+ base: "WMShellFlickerTestsSplitScreen",
+ include_filters: ["com.android.wm.shell.flicker.splitscreen.UnlockKeyguardToSplitScreen"],
+ test_suites: ["device-tests"],
+}
+
+// End breakdowns for FlickerTestsRotation module
+////////////////////////////////////////////////////////////////////////////////
diff --git a/libs/WindowManager/Shell/tests/flicker/appcompat/Android.bp b/libs/WindowManager/Shell/tests/flicker/appcompat/Android.bp
index e151ab2..29a9f10 100644
--- a/libs/WindowManager/Shell/tests/flicker/appcompat/Android.bp
+++ b/libs/WindowManager/Shell/tests/flicker/appcompat/Android.bp
@@ -15,6 +15,7 @@
//
package {
+ default_team: "trendy_team_app_compat",
// See: http://go/android-license-faq
// A large-scale-change added 'default_applicable_licenses' to import
// all of the 'license_kinds' from "frameworks_base_license"
@@ -23,6 +24,9 @@
default_applicable_licenses: ["frameworks_base_license"],
}
+////////////////////////////////////////////////////////////////////////////////
+// Begin cleanup after gcl merge
+
filegroup {
name: "WMShellFlickerTestsAppCompat-src",
srcs: [
@@ -41,3 +45,80 @@
static_libs: ["WMShellFlickerTestsBase"],
data: ["trace_config/*"],
}
+
+////////////////////////////////////////////////////////////////////////////////
+// End cleanup after gcl merge
+
+android_test {
+ name: "WMShellFlickerTestsAppCompat",
+ defaults: ["WMShellFlickerTestsDefault"],
+ manifest: "AndroidManifest.xml",
+ package_name: "com.android.wm.shell.flicker",
+ instrumentation_target_package: "com.android.wm.shell.flicker",
+ test_config_template: "AndroidTestTemplate.xml",
+ srcs: ["src/**/*.kt"],
+ static_libs: ["WMShellFlickerTestsBase"],
+ data: ["trace_config/*"],
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Begin breakdowns for WMShellFlickerTestsAppCompat module
+
+test_module_config {
+ name: "WMShellFlickerTestsAppCompat-CatchAll",
+ base: "WMShellFlickerTestsAppCompat",
+ exclude_filters: [
+ "com.android.wm.shell.flicker.appcompat.OpenAppInSizeCompatModeTest",
+ "com.android.wm.shell.flicker.appcompat.OpenTransparentActivityTest",
+ "com.android.wm.shell.flicker.appcompat.QuickSwitchLauncherToLetterboxAppTest",
+ "com.android.wm.shell.flicker.appcompat.RepositionFixedPortraitAppTest",
+ "com.android.wm.shell.flicker.appcompat.RestartAppInSizeCompatModeTest",
+ "com.android.wm.shell.flicker.appcompat.RotateImmersiveAppInFullscreenTest",
+ ],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "WMShellFlickerTestsAppCompat-OpenAppInSizeCompatModeTest",
+ base: "WMShellFlickerTestsAppCompat",
+ include_filters: ["com.android.wm.shell.flicker.appcompat.OpenAppInSizeCompatModeTest"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "WMShellFlickerTestsAppCompat-OpenTransparentActivityTest",
+ base: "WMShellFlickerTestsAppCompat",
+ include_filters: ["com.android.wm.shell.flicker.appcompat.OpenTransparentActivityTest"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "WMShellFlickerTestsAppCompat-QuickSwitchLauncherToLetterboxAppTest",
+ base: "WMShellFlickerTestsAppCompat",
+ include_filters: ["com.android.wm.shell.flicker.appcompat.QuickSwitchLauncherToLetterboxAppTest"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "WMShellFlickerTestsAppCompat-RepositionFixedPortraitAppTest",
+ base: "WMShellFlickerTestsAppCompat",
+ include_filters: ["com.android.wm.shell.flicker.appcompat.RepositionFixedPortraitAppTest"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "WMShellFlickerTestsAppCompat-RestartAppInSizeCompatModeTest",
+ base: "WMShellFlickerTestsAppCompat",
+ include_filters: ["com.android.wm.shell.flicker.appcompat.RestartAppInSizeCompatModeTest"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "WMShellFlickerTestsAppCompat-RotateImmersiveAppInFullscreenTest",
+ base: "WMShellFlickerTestsAppCompat",
+ include_filters: ["com.android.wm.shell.flicker.appcompat.RotateImmersiveAppInFullscreenTest"],
+ test_suites: ["device-tests"],
+}
+
+// End breakdowns for FlickerTestsRotation module
+////////////////////////////////////////////////////////////////////////////////
diff --git a/libs/WindowManager/Shell/tests/flicker/bubble/Android.bp b/libs/WindowManager/Shell/tests/flicker/bubble/Android.bp
index f0b4f1f..2ff7ab2 100644
--- a/libs/WindowManager/Shell/tests/flicker/bubble/Android.bp
+++ b/libs/WindowManager/Shell/tests/flicker/bubble/Android.bp
@@ -15,6 +15,7 @@
//
package {
+ default_team: "trendy_team_multitasking_windowing",
// See: http://go/android-license-faq
// A large-scale-change added 'default_applicable_licenses' to import
// all of the 'license_kinds' from "frameworks_base_license"
@@ -34,3 +35,57 @@
static_libs: ["WMShellFlickerTestsBase"],
data: ["trace_config/*"],
}
+
+////////////////////////////////////////////////////////////////////////////////
+// Begin breakdowns for WMShellFlickerTestsBubbles module
+
+test_module_config {
+ name: "WMShellFlickerTestsBubbles-CatchAll",
+ base: "WMShellFlickerTestsBubbles",
+ exclude_filters: [
+ "com.android.wm.shell.flicker.bubble.ChangeActiveActivityFromBubbleTest",
+ "com.android.wm.shell.flicker.bubble.DragToDismissBubbleScreenTest",
+ "com.android.wm.shell.flicker.bubble.OpenActivityFromBubbleOnLocksreenTest",
+ "com.android.wm.shell.flicker.bubble.OpenActivityFromBubbleTest",
+ "com.android.wm.shell.flicker.bubble.SendBubbleNotificationTest",
+ ],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "WMShellFlickerTestsBubbles-ChangeActiveActivityFromBubbleTest",
+ base: "WMShellFlickerTestsBubbles",
+ include_filters: ["com.android.wm.shell.flicker.bubble.ChangeActiveActivityFromBubbleTest"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "WMShellFlickerTestsBubbles-DragToDismissBubbleScreenTest",
+ base: "WMShellFlickerTestsBubbles",
+ include_filters: ["com.android.wm.shell.flicker.bubble.DragToDismissBubbleScreenTest"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "WMShellFlickerTestsBubbles-OpenActivityFromBubbleOnLocksreenTest",
+ base: "WMShellFlickerTestsBubbles",
+ include_filters: ["com.android.wm.shell.flicker.bubble.OpenActivityFromBubbleOnLocksreenTest"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "WMShellFlickerTestsBubbles-OpenActivityFromBubbleTest",
+ base: "WMShellFlickerTestsBubbles",
+ include_filters: ["com.android.wm.shell.flicker.bubble.OpenActivityFromBubbleTest"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "WMShellFlickerTestsBubbles-SendBubbleNotificationTest",
+ base: "WMShellFlickerTestsBubbles",
+ include_filters: ["com.android.wm.shell.flicker.bubble.SendBubbleNotificationTest"],
+ test_suites: ["device-tests"],
+}
+
+// End breakdowns for WMShellFlickerTestsBubbles module
+////////////////////////////////////////////////////////////////////////////////
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/Android.bp b/libs/WindowManager/Shell/tests/flicker/pip/Android.bp
index faeb342..4165ed0 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/Android.bp
+++ b/libs/WindowManager/Shell/tests/flicker/pip/Android.bp
@@ -15,6 +15,7 @@
//
package {
+ default_team: "trendy_team_multitasking_windowing",
// See: http://go/android-license-faq
// A large-scale-change added 'default_applicable_licenses' to import
// all of the 'license_kinds' from "frameworks_base_license"
@@ -24,6 +25,14 @@
}
filegroup {
+ name: "WMShellFlickerTestsPipApps-src",
+ srcs: ["src/**/apps/*.kt"],
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Begin cleanup after gcl merges
+
+filegroup {
name: "WMShellFlickerTestsPip1-src",
srcs: [
"src/**/A*.kt",
@@ -52,11 +61,6 @@
srcs: ["src/**/common/*.kt"],
}
-filegroup {
- name: "WMShellFlickerTestsPipApps-src",
- srcs: ["src/**/apps/*.kt"],
-}
-
android_test {
name: "WMShellFlickerTestsPip1",
defaults: ["WMShellFlickerTestsDefault"],
@@ -107,6 +111,21 @@
data: ["trace_config/*"],
}
+////////////////////////////////////////////////////////////////////////////////
+// End cleanup after gcl merges
+
+android_test {
+ name: "WMShellFlickerTestsPip",
+ defaults: ["WMShellFlickerTestsDefault"],
+ manifest: "AndroidManifest.xml",
+ package_name: "com.android.wm.shell.flicker.pip",
+ instrumentation_target_package: "com.android.wm.shell.flicker.pip",
+ test_config_template: "AndroidTestTemplate.xml",
+ srcs: ["src/**/*.kt"],
+ static_libs: ["WMShellFlickerTestsBase"],
+ data: ["trace_config/*"],
+}
+
android_test {
name: "WMShellFlickerTestsPipApps",
defaults: ["WMShellFlickerTestsDefault"],
@@ -146,3 +165,185 @@
test_plan_include: "csuitePlan.xml",
test_config_template: "csuiteDefaultTemplate.xml",
}
+
+////////////////////////////////////////////////////////////////////////////////
+// Begin breakdowns for WMShellFlickerTestsPip module
+
+test_module_config {
+ name: "WMShellFlickerTestsPip-CatchAll",
+ base: "WMShellFlickerTestsPip",
+ exclude_filters: [
+ "com.android.wm.shell.flicker.pip.AutoEnterPipOnGoToHomeTest",
+ "com.android.wm.shell.flicker.pip.AutoEnterPipWithSourceRectHintTest",
+ "com.android.wm.shell.flicker.pip.ClosePipBySwipingDownTest",
+ "com.android.wm.shell.flicker.pip.ClosePipWithDismissButtonTest",
+ "com.android.wm.shell.flicker.pip.EnterPipOnUserLeaveHintTest",
+ "com.android.wm.shell.flicker.pip.EnterPipViaAppUiButtonTest",
+ "com.android.wm.shell.flicker.pip.ExitPipToAppViaExpandButtonTest",
+ "com.android.wm.shell.flicker.pip.ExitPipToAppViaIntentTest",
+ "com.android.wm.shell.flicker.pip.ExpandPipOnDoubleClickTest",
+ "com.android.wm.shell.flicker.pip.ExpandPipOnPinchOpenTest",
+ "com.android.wm.shell.flicker.pip.FromSplitScreenAutoEnterPipOnGoToHomeTest",
+ "com.android.wm.shell.flicker.pip.FromSplitScreenEnterPipOnUserLeaveHintTest",
+ "com.android.wm.shell.flicker.pip.MovePipDownOnShelfHeightChange",
+ "com.android.wm.shell.flicker.pip.MovePipOnImeVisibilityChangeTest",
+ "com.android.wm.shell.flicker.pip.MovePipUpOnShelfHeightChangeTest",
+ "com.android.wm.shell.flicker.pip.PipAspectRatioChangeTest",
+ "com.android.wm.shell.flicker.pip.PipDragTest",
+ "com.android.wm.shell.flicker.pip.PipDragThenSnapTest",
+ "com.android.wm.shell.flicker.pip.PipPinchInTest",
+ "com.android.wm.shell.flicker.pip.SetRequestedOrientationWhilePinned",
+ "com.android.wm.shell.flicker.pip.ShowPipAndRotateDisplay",
+ ],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "WMShellFlickerTestsPip-AutoEnterPipOnGoToHomeTest",
+ base: "WMShellFlickerTestsPip",
+ include_filters: ["com.android.wm.shell.flicker.pip.AutoEnterPipOnGoToHomeTest"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "WMShellFlickerTestsPip-AutoEnterPipWithSourceRectHintTest",
+ base: "WMShellFlickerTestsPip",
+ include_filters: ["com.android.wm.shell.flicker.pip.AutoEnterPipWithSourceRectHintTest"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "WMShellFlickerTestsPip-ClosePipBySwipingDownTest",
+ base: "WMShellFlickerTestsPip",
+ include_filters: ["com.android.wm.shell.flicker.pip.ClosePipBySwipingDownTest"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "WMShellFlickerTestsPip-ClosePipWithDismissButtonTest",
+ base: "WMShellFlickerTestsPip",
+ include_filters: ["com.android.wm.shell.flicker.pip.ClosePipWithDismissButtonTest"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "WMShellFlickerTestsPip-EnterPipOnUserLeaveHintTest",
+ base: "WMShellFlickerTestsPip",
+ include_filters: ["com.android.wm.shell.flicker.pip.EnterPipOnUserLeaveHintTest"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "WMShellFlickerTestsPip-EnterPipViaAppUiButtonTest",
+ base: "WMShellFlickerTestsPip",
+ include_filters: ["com.android.wm.shell.flicker.pip.EnterPipViaAppUiButtonTest"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "WMShellFlickerTestsPip-ExitPipToAppViaExpandButtonTest",
+ base: "WMShellFlickerTestsPip",
+ include_filters: ["com.android.wm.shell.flicker.pip.ExitPipToAppViaExpandButtonTest"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "WMShellFlickerTestsPip-ExitPipToAppViaIntentTest",
+ base: "WMShellFlickerTestsPip",
+ include_filters: ["com.android.wm.shell.flicker.pip.ExitPipToAppViaIntentTest"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "WMShellFlickerTestsPip-ExpandPipOnDoubleClickTest",
+ base: "WMShellFlickerTestsPip",
+ include_filters: ["com.android.wm.shell.flicker.pip.ExpandPipOnDoubleClickTest"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "WMShellFlickerTestsPip-ExpandPipOnPinchOpenTest",
+ base: "WMShellFlickerTestsPip",
+ include_filters: ["com.android.wm.shell.flicker.pip.ExpandPipOnPinchOpenTest"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "WMShellFlickerTestsPip-FromSplitScreenAutoEnterPipOnGoToHomeTest",
+ base: "WMShellFlickerTestsPip",
+ include_filters: ["com.android.wm.shell.flicker.pip.FromSplitScreenAutoEnterPipOnGoToHomeTest"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "WMShellFlickerTestsPip-FromSplitScreenEnterPipOnUserLeaveHintTest",
+ base: "WMShellFlickerTestsPip",
+ include_filters: ["com.android.wm.shell.flicker.pip.FromSplitScreenEnterPipOnUserLeaveHintTest"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "WMShellFlickerTestsPip-MovePipDownOnShelfHeightChange",
+ base: "WMShellFlickerTestsPip",
+ include_filters: ["com.android.wm.shell.flicker.pip.MovePipDownOnShelfHeightChange"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "WMShellFlickerTestsPip-MovePipOnImeVisibilityChangeTest",
+ base: "WMShellFlickerTestsPip",
+ include_filters: ["com.android.wm.shell.flicker.pip.MovePipOnImeVisibilityChangeTest"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "WMShellFlickerTestsPip-MovePipUpOnShelfHeightChangeTest",
+ base: "WMShellFlickerTestsPip",
+ include_filters: ["com.android.wm.shell.flicker.pip.MovePipUpOnShelfHeightChangeTest"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "WMShellFlickerTestsPip-PipAspectRatioChangeTest",
+ base: "WMShellFlickerTestsPip",
+ include_filters: ["com.android.wm.shell.flicker.pip.PipAspectRatioChangeTest"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "WMShellFlickerTestsPip-PipDragTest",
+ base: "WMShellFlickerTestsPip",
+ include_filters: ["com.android.wm.shell.flicker.pip.PipDragTest"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "WMShellFlickerTestsPip-PipDragThenSnapTest",
+ base: "WMShellFlickerTestsPip",
+ include_filters: ["com.android.wm.shell.flicker.pip.PipDragThenSnapTest"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "WMShellFlickerTestsPip-PipPinchInTest",
+ base: "WMShellFlickerTestsPip",
+ include_filters: ["com.android.wm.shell.flicker.pip.PipPinchInTest"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "WMShellFlickerTestsPip-SetRequestedOrientationWhilePinned",
+ base: "WMShellFlickerTestsPip",
+ include_filters: ["com.android.wm.shell.flicker.pip.SetRequestedOrientationWhilePinned"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "WMShellFlickerTestsPip-ShowPipAndRotateDisplay",
+ base: "WMShellFlickerTestsPip",
+ include_filters: ["com.android.wm.shell.flicker.pip.ShowPipAndRotateDisplay"],
+ test_suites: ["device-tests"],
+}
+
+// End breakdowns for WMShellFlickerTestsPip module
+////////////////////////////////////////////////////////////////////////////////
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt
index 4548fcb..70b3661 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt
@@ -16,14 +16,17 @@
package com.android.wm.shell.desktopmode
-import com.android.dx.mockito.inline.extended.ExtendedMockito
+import com.android.dx.mockito.inline.extended.ExtendedMockito.verify
import com.android.internal.util.FrameworkStatsLog
import com.android.modules.utils.testing.ExtendedMockitoRule
import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.EnterReason
import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ExitReason
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.MinimizeReason
import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.TaskUpdate
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.UnminimizeReason
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.UNSET_MINIMIZE_REASON
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.UNSET_UNMINIMIZE_REASON
import kotlinx.coroutines.runBlocking
-import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.mockito.kotlin.eq
@@ -33,7 +36,7 @@
*/
class DesktopModeEventLoggerTest {
- private val desktopModeEventLogger = DesktopModeEventLogger()
+ private val desktopModeEventLogger = DesktopModeEventLogger()
@JvmField
@Rule
@@ -44,7 +47,7 @@
fun logSessionEnter_enterReason() = runBlocking {
desktopModeEventLogger.logSessionEnter(sessionId = SESSION_ID, EnterReason.UNKNOWN_ENTER)
- ExtendedMockito.verify {
+ verify {
FrameworkStatsLog.write(
eq(FrameworkStatsLog.DESKTOP_MODE_UI_CHANGED),
/* event */
@@ -63,7 +66,7 @@
fun logSessionExit_exitReason() = runBlocking {
desktopModeEventLogger.logSessionExit(sessionId = SESSION_ID, ExitReason.UNKNOWN_EXIT)
- ExtendedMockito.verify {
+ verify {
FrameworkStatsLog.write(
eq(FrameworkStatsLog.DESKTOP_MODE_UI_CHANGED),
/* event */
@@ -82,7 +85,7 @@
fun logTaskAdded_taskUpdate() = runBlocking {
desktopModeEventLogger.logTaskAdded(sessionId = SESSION_ID, TASK_UPDATE)
- ExtendedMockito.verify {
+ verify {
FrameworkStatsLog.write(eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE),
/* task_event */
eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_ADDED),
@@ -99,7 +102,9 @@
/* task_y */
eq(TASK_UPDATE.taskY),
/* session_id */
- eq(SESSION_ID))
+ eq(SESSION_ID),
+ eq(UNSET_MINIMIZE_REASON),
+ eq(UNSET_UNMINIMIZE_REASON))
}
}
@@ -107,7 +112,7 @@
fun logTaskRemoved_taskUpdate() = runBlocking {
desktopModeEventLogger.logTaskRemoved(sessionId = SESSION_ID, TASK_UPDATE)
- ExtendedMockito.verify {
+ verify {
FrameworkStatsLog.write(eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE),
/* task_event */
eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_REMOVED),
@@ -124,7 +129,9 @@
/* task_y */
eq(TASK_UPDATE.taskY),
/* session_id */
- eq(SESSION_ID))
+ eq(SESSION_ID),
+ eq(UNSET_MINIMIZE_REASON),
+ eq(UNSET_UNMINIMIZE_REASON))
}
}
@@ -132,10 +139,11 @@
fun logTaskInfoChanged_taskUpdate() = runBlocking {
desktopModeEventLogger.logTaskInfoChanged(sessionId = SESSION_ID, TASK_UPDATE)
- ExtendedMockito.verify {
+ verify {
FrameworkStatsLog.write(eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE),
/* task_event */
- eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_INFO_CHANGED),
+ eq(FrameworkStatsLog
+ .DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_INFO_CHANGED),
/* instance_id */
eq(TASK_UPDATE.instanceId),
/* uid */
@@ -149,7 +157,71 @@
/* task_y */
eq(TASK_UPDATE.taskY),
/* session_id */
- eq(SESSION_ID))
+ eq(SESSION_ID),
+ eq(UNSET_MINIMIZE_REASON),
+ eq(UNSET_UNMINIMIZE_REASON))
+ }
+ }
+
+ @Test
+ fun logTaskInfoChanged_logsTaskUpdateWithMinimizeReason() = runBlocking {
+ desktopModeEventLogger.logTaskInfoChanged(sessionId = SESSION_ID,
+ createTaskUpdate(minimizeReason = MinimizeReason.TASK_LIMIT))
+
+ verify {
+ FrameworkStatsLog.write(eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE),
+ /* task_event */
+ eq(FrameworkStatsLog
+ .DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_INFO_CHANGED),
+ /* instance_id */
+ eq(TASK_UPDATE.instanceId),
+ /* uid */
+ eq(TASK_UPDATE.uid),
+ /* task_height */
+ eq(TASK_UPDATE.taskHeight),
+ /* task_width */
+ eq(TASK_UPDATE.taskWidth),
+ /* task_x */
+ eq(TASK_UPDATE.taskX),
+ /* task_y */
+ eq(TASK_UPDATE.taskY),
+ /* session_id */
+ eq(SESSION_ID),
+ /* minimize_reason */
+ eq(MinimizeReason.TASK_LIMIT.reason),
+ /* unminimize_reason */
+ eq(UNSET_UNMINIMIZE_REASON))
+ }
+ }
+
+ @Test
+ fun logTaskInfoChanged_logsTaskUpdateWithUnminimizeReason() = runBlocking {
+ desktopModeEventLogger.logTaskInfoChanged(sessionId = SESSION_ID,
+ createTaskUpdate(unminimizeReason = UnminimizeReason.TASKBAR_TAP))
+
+ verify {
+ FrameworkStatsLog.write(eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE),
+ /* task_event */
+ eq(FrameworkStatsLog
+ .DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_INFO_CHANGED),
+ /* instance_id */
+ eq(TASK_UPDATE.instanceId),
+ /* uid */
+ eq(TASK_UPDATE.uid),
+ /* task_height */
+ eq(TASK_UPDATE.taskHeight),
+ /* task_width */
+ eq(TASK_UPDATE.taskWidth),
+ /* task_x */
+ eq(TASK_UPDATE.taskX),
+ /* task_y */
+ eq(TASK_UPDATE.taskY),
+ /* session_id */
+ eq(SESSION_ID),
+ /* minimize_reason */
+ eq(UNSET_MINIMIZE_REASON),
+ /* unminimize_reason */
+ eq(UnminimizeReason.TASKBAR_TAP.reason))
}
}
@@ -165,5 +237,11 @@
private val TASK_UPDATE = TaskUpdate(
TASK_ID, TASK_UID, TASK_HEIGHT, TASK_WIDTH, TASK_X, TASK_Y
)
+
+ private fun createTaskUpdate(
+ minimizeReason: MinimizeReason? = null,
+ unminimizeReason: UnminimizeReason? = null,
+ ) = TaskUpdate(TASK_ID, TASK_UID, TASK_HEIGHT, TASK_WIDTH, TASK_X, TASK_Y, minimizeReason,
+ unminimizeReason)
}
}
\ No newline at end of file
diff --git a/libs/hwui/aconfig/hwui_flags.aconfig b/libs/hwui/aconfig/hwui_flags.aconfig
index a1f5168..faea6d4 100644
--- a/libs/hwui/aconfig/hwui_flags.aconfig
+++ b/libs/hwui/aconfig/hwui_flags.aconfig
@@ -2,6 +2,13 @@
container: "system"
flag {
+ name: "runtime_color_filters_blenders"
+ namespace: "core_graphics"
+ description: "API for AGSL authored runtime color filters and blenders"
+ bug: "358126864"
+}
+
+flag {
name: "clip_shader"
is_exported: true
namespace: "core_graphics"
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 25c767a..c36eda9 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -84,6 +84,7 @@
import android.util.IntArray;
import android.util.Log;
import android.util.Pair;
+import android.util.Slog;
import android.view.KeyEvent;
import com.android.internal.annotations.GuardedBy;
@@ -4283,7 +4284,7 @@
final OnAudioFocusChangeListener listener =
fri.mRequest.getOnAudioFocusChangeListener();
if (listener != null) {
- Log.d(TAG, "dispatching onAudioFocusChange("
+ Slog.i(TAG, "dispatching onAudioFocusChange("
+ msg.arg1 + ") to " + msg.obj);
listener.onAudioFocusChange(msg.arg1);
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
index de60fdc2..b3ac54a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
@@ -21,6 +21,8 @@
import android.graphics.Canvas;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
+import android.media.AudioDeviceAttributes;
+import android.media.AudioDeviceInfo;
import android.media.AudioManager;
import android.net.Uri;
import android.provider.DeviceConfig;
@@ -41,9 +43,12 @@
import com.android.settingslib.widget.AdaptiveIcon;
import com.android.settingslib.widget.AdaptiveOutlineDrawable;
+import com.google.common.collect.ImmutableSet;
+
import java.io.IOException;
import java.util.List;
import java.util.Locale;
+import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -57,6 +62,9 @@
public static final String BT_ADVANCED_HEADER_ENABLED = "bt_advanced_header_enabled";
private static final int METADATA_FAST_PAIR_CUSTOMIZED_FIELDS = 25;
private static final String KEY_HEARABLE_CONTROL_SLICE = "HEARABLE_CONTROL_SLICE_WITH_WIDTH";
+ private static final Set<Integer> SA_PROFILES =
+ ImmutableSet.of(
+ BluetoothProfile.A2DP, BluetoothProfile.LE_AUDIO, BluetoothProfile.HEARING_AID);
private static ErrorListener sErrorListener;
@@ -895,4 +903,62 @@
}
return null;
}
+
+ /**
+ * Gets {@link AudioDeviceAttributes} of bluetooth device for spatial audio. Returns null if
+ * it's not an audio device(no A2DP, LE Audio and Hearing Aid profile).
+ */
+ @Nullable
+ public static AudioDeviceAttributes getAudioDeviceAttributesForSpatialAudio(
+ CachedBluetoothDevice cachedDevice,
+ @AudioManager.AudioDeviceCategory int audioDeviceCategory) {
+ AudioDeviceAttributes saDevice = null;
+ for (LocalBluetoothProfile profile : cachedDevice.getProfiles()) {
+ // pick first enabled profile that is compatible with spatial audio
+ if (SA_PROFILES.contains(profile.getProfileId())
+ && profile.isEnabled(cachedDevice.getDevice())) {
+ switch (profile.getProfileId()) {
+ case BluetoothProfile.A2DP:
+ saDevice =
+ new AudioDeviceAttributes(
+ AudioDeviceAttributes.ROLE_OUTPUT,
+ AudioDeviceInfo.TYPE_BLUETOOTH_A2DP,
+ cachedDevice.getAddress());
+ break;
+ case BluetoothProfile.LE_AUDIO:
+ if (audioDeviceCategory
+ == AudioManager.AUDIO_DEVICE_CATEGORY_SPEAKER) {
+ saDevice =
+ new AudioDeviceAttributes(
+ AudioDeviceAttributes.ROLE_OUTPUT,
+ AudioDeviceInfo.TYPE_BLE_SPEAKER,
+ cachedDevice.getAddress());
+ } else {
+ saDevice =
+ new AudioDeviceAttributes(
+ AudioDeviceAttributes.ROLE_OUTPUT,
+ AudioDeviceInfo.TYPE_BLE_HEADSET,
+ cachedDevice.getAddress());
+ }
+
+ break;
+ case BluetoothProfile.HEARING_AID:
+ saDevice =
+ new AudioDeviceAttributes(
+ AudioDeviceAttributes.ROLE_OUTPUT,
+ AudioDeviceInfo.TYPE_HEARING_AID,
+ cachedDevice.getAddress());
+ break;
+ default:
+ Log.i(
+ TAG,
+ "unrecognized profile for spatial audio: "
+ + profile.getProfileId());
+ break;
+ }
+ break;
+ }
+ }
+ return saDevice;
+ }
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingId.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingId.java
index 20a0339..58dc8c7 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingId.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingId.java
@@ -108,6 +108,12 @@
/** Device setting ID for device details footer. */
int DEVICE_SETTING_ID_DEVICE_DETAILS_FOOTER = 19;
+ /** Device setting ID for spatial audio group. */
+ int DEVICE_SETTING_ID_SPATIAL_AUDIO_MULTI_TOGGLE = 20;
+
+ /** Device setting ID for "More Settings" page. */
+ int DEVICE_SETTING_ID_MORE_SETTINGS = 21;
+
/** Device setting ID for ANC. */
int DEVICE_SETTING_ID_ANC = 1001;
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepository.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepository.kt
index a599dd1..ce7064c 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepository.kt
@@ -29,6 +29,7 @@
import com.android.settingslib.bluetooth.devicesettings.ToggleInfo
import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingConfigItemModel
import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingConfigModel
+import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingIcon
import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingModel
import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingStateModel
import com.android.settingslib.bluetooth.devicesettings.shared.model.ToggleModel
@@ -117,7 +118,7 @@
id = settingId,
title = pref.title,
summary = pref.summary,
- icon = pref.icon,
+ icon = pref.icon?.let { DeviceSettingIcon.BitmapIcon(it) },
isAllowedChangingState = pref.isAllowedChangingState,
intent = pref.intent,
switchState =
@@ -153,5 +154,6 @@
else -> DeviceSettingModel.Unknown(cachedDevice, settingId)
}
- private fun ToggleInfo.toModel(): ToggleModel = ToggleModel(label, icon)
+ private fun ToggleInfo.toModel(): ToggleModel =
+ ToggleModel(label, DeviceSettingIcon.BitmapIcon(icon))
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/shared/model/DeviceSettingConfigModel.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/shared/model/DeviceSettingConfigModel.kt
index 136abad..e97f76c 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/shared/model/DeviceSettingConfigModel.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/shared/model/DeviceSettingConfigModel.kt
@@ -35,7 +35,7 @@
/** A built-in item in Settings. */
data class BuiltinItem(
@DeviceSettingId override val settingId: Int,
- val preferenceKey: String
+ val preferenceKey: String?
) : DeviceSettingConfigItemModel
/** A remote item provided by other apps. */
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/shared/model/DeviceSettingModel.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/shared/model/DeviceSettingModel.kt
index db78280..2a63217 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/shared/model/DeviceSettingModel.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/shared/model/DeviceSettingModel.kt
@@ -18,6 +18,7 @@
import android.content.Intent
import android.graphics.Bitmap
+import androidx.annotation.DrawableRes
import com.android.settingslib.bluetooth.CachedBluetoothDevice
import com.android.settingslib.bluetooth.devicesettings.DeviceSettingId
@@ -32,7 +33,7 @@
@DeviceSettingId override val id: Int,
val title: String,
val summary: String? = null,
- val icon: Bitmap? = null,
+ val icon: DeviceSettingIcon? = null,
val intent: Intent? = null,
val switchState: DeviceSettingStateModel.ActionSwitchPreferenceState? = null,
val isAllowedChangingState: Boolean = true,
@@ -59,4 +60,12 @@
}
/** Models a toggle in [DeviceSettingModel.MultiTogglePreference]. */
-data class ToggleModel(val label: String, val icon: Bitmap)
+data class ToggleModel(val label: String, val icon: DeviceSettingIcon)
+
+/** Models an icon in device settings. */
+sealed interface DeviceSettingIcon {
+
+ data class BitmapIcon(val bitmap: Bitmap) : DeviceSettingIcon
+
+ data class ResourceIcon(@DrawableRes val resId: Int) : DeviceSettingIcon
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java
index 4b141e7..69ef718 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java
@@ -22,6 +22,7 @@
import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY;
import static android.service.notification.SystemZenRules.getTriggerDescriptionForScheduleEvent;
import static android.service.notification.SystemZenRules.getTriggerDescriptionForScheduleTime;
+import static android.service.notification.SystemZenRules.PACKAGE_ANDROID;
import static android.service.notification.ZenModeConfig.tryParseCountdownConditionId;
import static android.service.notification.ZenModeConfig.tryParseEventConditionId;
import static android.service.notification.ZenModeConfig.tryParseScheduleConditionId;
@@ -129,7 +130,11 @@
}
public static ZenMode manualDndMode(AutomaticZenRule manualRule, boolean isActive) {
- return new ZenMode(MANUAL_DND_MODE_ID, manualRule,
+ // Manual rule is owned by the system, so we set it here
+ AutomaticZenRule manualRuleWithPkg = new AutomaticZenRule.Builder(manualRule)
+ .setPackage(PACKAGE_ANDROID)
+ .build();
+ return new ZenMode(MANUAL_DND_MODE_ID, manualRuleWithPkg,
isActive ? Status.ENABLED_AND_ACTIVE : Status.ENABLED, true);
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
index 4551f1e..926d3cb 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
@@ -31,10 +31,13 @@
import android.bluetooth.BluetoothCsipSetCoordinator;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothLeBroadcastReceiveState;
+import android.bluetooth.BluetoothProfile;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable;
+import android.media.AudioDeviceAttributes;
+import android.media.AudioDeviceInfo;
import android.media.AudioManager;
import android.net.Uri;
import android.platform.test.flag.junit.SetFlagsRule;
@@ -70,6 +73,9 @@
@Mock private BluetoothDevice mBluetoothDevice;
@Mock private AudioManager mAudioManager;
@Mock private PackageManager mPackageManager;
+ @Mock private LeAudioProfile mA2dpProfile;
+ @Mock private LeAudioProfile mLeAudioProfile;
+ @Mock private LeAudioProfile mHearingAid;
@Mock private LocalBluetoothLeBroadcast mBroadcast;
@Mock private LocalBluetoothProfileManager mProfileManager;
@Mock private LocalBluetoothManager mLocalBluetoothManager;
@@ -100,6 +106,9 @@
when(mLocalBluetoothManager.getProfileManager()).thenReturn(mProfileManager);
when(mProfileManager.getLeAudioBroadcastProfile()).thenReturn(mBroadcast);
when(mProfileManager.getLeAudioBroadcastAssistantProfile()).thenReturn(mAssistant);
+ when(mA2dpProfile.getProfileId()).thenReturn(BluetoothProfile.A2DP);
+ when(mLeAudioProfile.getProfileId()).thenReturn(BluetoothProfile.LE_AUDIO);
+ when(mHearingAid.getProfileId()).thenReturn(BluetoothProfile.HEARING_AID);
}
@Test
@@ -756,4 +765,84 @@
mContext.getContentResolver(), mLocalBluetoothManager))
.isEqualTo(mCachedBluetoothDevice);
}
+
+ @Test
+ public void getAudioDeviceAttributesForSpatialAudio_bleHeadset() {
+ String address = "11:22:33:44:55:66";
+ when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
+ when(mCachedBluetoothDevice.getAddress()).thenReturn(address);
+ when(mCachedBluetoothDevice.getProfiles()).thenReturn(List.of(mLeAudioProfile));
+ when(mLeAudioProfile.isEnabled(mBluetoothDevice)).thenReturn(true);
+
+ AudioDeviceAttributes attr =
+ BluetoothUtils.getAudioDeviceAttributesForSpatialAudio(
+ mCachedBluetoothDevice, AudioManager.AUDIO_DEVICE_CATEGORY_HEADPHONES);
+
+ assertThat(attr)
+ .isEqualTo(
+ new AudioDeviceAttributes(
+ AudioDeviceAttributes.ROLE_OUTPUT,
+ AudioDeviceInfo.TYPE_BLE_HEADSET,
+ address));
+ }
+
+ @Test
+ public void getAudioDeviceAttributesForSpatialAudio_bleSpeaker() {
+ String address = "11:22:33:44:55:66";
+ when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
+ when(mCachedBluetoothDevice.getAddress()).thenReturn(address);
+ when(mCachedBluetoothDevice.getProfiles()).thenReturn(List.of(mLeAudioProfile));
+ when(mLeAudioProfile.isEnabled(mBluetoothDevice)).thenReturn(true);
+
+ AudioDeviceAttributes attr =
+ BluetoothUtils.getAudioDeviceAttributesForSpatialAudio(
+ mCachedBluetoothDevice, AudioManager.AUDIO_DEVICE_CATEGORY_SPEAKER);
+
+ assertThat(attr)
+ .isEqualTo(
+ new AudioDeviceAttributes(
+ AudioDeviceAttributes.ROLE_OUTPUT,
+ AudioDeviceInfo.TYPE_BLE_SPEAKER,
+ address));
+ }
+
+ @Test
+ public void getAudioDeviceAttributesForSpatialAudio_a2dp() {
+ String address = "11:22:33:44:55:66";
+ when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
+ when(mCachedBluetoothDevice.getAddress()).thenReturn(address);
+ when(mCachedBluetoothDevice.getProfiles()).thenReturn(List.of(mA2dpProfile));
+ when(mA2dpProfile.isEnabled(mBluetoothDevice)).thenReturn(true);
+
+ AudioDeviceAttributes attr =
+ BluetoothUtils.getAudioDeviceAttributesForSpatialAudio(
+ mCachedBluetoothDevice, AudioManager.AUDIO_DEVICE_CATEGORY_HEADPHONES);
+
+ assertThat(attr)
+ .isEqualTo(
+ new AudioDeviceAttributes(
+ AudioDeviceAttributes.ROLE_OUTPUT,
+ AudioDeviceInfo.TYPE_BLUETOOTH_A2DP,
+ address));
+ }
+
+ @Test
+ public void getAudioDeviceAttributesForSpatialAudio_hearingAid() {
+ String address = "11:22:33:44:55:66";
+ when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
+ when(mCachedBluetoothDevice.getAddress()).thenReturn(address);
+ when(mCachedBluetoothDevice.getProfiles()).thenReturn(List.of(mHearingAid));
+ when(mHearingAid.isEnabled(mBluetoothDevice)).thenReturn(true);
+
+ AudioDeviceAttributes attr =
+ BluetoothUtils.getAudioDeviceAttributesForSpatialAudio(
+ mCachedBluetoothDevice, AudioManager.AUDIO_DEVICE_CATEGORY_HEARING_AID);
+
+ assertThat(attr)
+ .isEqualTo(
+ new AudioDeviceAttributes(
+ AudioDeviceAttributes.ROLE_OUTPUT,
+ AudioDeviceInfo.TYPE_HEARING_AID,
+ address));
+ }
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepositoryTest.kt b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepositoryTest.kt
index fee2394..4c5ee9e 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepositoryTest.kt
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepositoryTest.kt
@@ -40,6 +40,7 @@
import com.android.settingslib.bluetooth.devicesettings.ToggleInfo
import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingConfigItemModel
import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingConfigModel
+import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingIcon
import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingModel
import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingStateModel
import com.android.settingslib.bluetooth.devicesettings.shared.model.ToggleModel
@@ -352,7 +353,7 @@
val pref = serviceResponse.preference as ActionSwitchPreference
assertThat(actual.title).isEqualTo(pref.title)
assertThat(actual.summary).isEqualTo(pref.summary)
- assertThat(actual.icon).isEqualTo(pref.icon)
+ assertThat(actual.icon).isEqualTo(DeviceSettingIcon.BitmapIcon(pref.icon!!))
assertThat(actual.isAllowedChangingState).isEqualTo(pref.isAllowedChangingState)
if (pref.hasSwitch()) {
assertThat(actual.switchState!!.checked).isEqualTo(pref.checked)
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenModeTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenModeTest.java
index d9fdcc38..f464247 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenModeTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenModeTest.java
@@ -19,6 +19,7 @@
import static android.app.NotificationManager.INTERRUPTION_FILTER_ALARMS;
import static android.app.NotificationManager.INTERRUPTION_FILTER_NONE;
import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY;
+import static android.service.notification.SystemZenRules.PACKAGE_ANDROID;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
@@ -67,6 +68,7 @@
assertThat(manualMode.canEditNameAndIcon()).isFalse();
assertThat(manualMode.canBeDeleted()).isFalse();
assertThat(manualMode.isActive()).isFalse();
+ assertThat(manualMode.getRule().getPackageName()).isEqualTo(PACKAGE_ANDROID);
}
@Test
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 6d78705..5ea75be 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -748,6 +748,7 @@
"//frameworks/libs/systemui:motion_tool_lib",
"//frameworks/libs/systemui:contextualeducationlib",
"androidx.core_core-animation-testing",
+ "androidx.lifecycle_lifecycle-runtime-testing",
"androidx.compose.ui_ui",
"flag-junit",
"ravenwood-junit",
@@ -789,6 +790,7 @@
"SystemUI-tests-base",
"androidx.test.uiautomator_uiautomator",
"androidx.core_core-animation-testing",
+ "androidx.lifecycle_lifecycle-runtime-testing",
"mockito-target-extended-minus-junit4",
"mockito-kotlin-nodeps",
"androidx.test.ext.junit",
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 5632e30..df28654 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -380,6 +380,17 @@
}
flag {
+ name: "status_bar_stop_updating_window_height"
+ namespace: "systemui"
+ description: "Don't have PhoneStatusBarView manually trigger an update of the height in "
+ "StatusBarWindowController"
+ bug: "360115167"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "compose_bouncer"
namespace: "systemui"
description: "Use the new compose bouncer in SystemUI"
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index b93b049..7472d81 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -1410,7 +1410,10 @@
R.string.accessibility_action_label_close_communal_hub
)
) {
- viewModel.changeScene(CommunalScenes.Blank)
+ viewModel.changeScene(
+ CommunalScenes.Blank,
+ "closed by accessibility"
+ )
true
},
CustomAccessibilityAction(
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeScene.kt
index eea00c4..fb7c422 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeScene.kt
@@ -29,8 +29,6 @@
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
-import androidx.compose.material3.Button
-import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
@@ -42,6 +40,7 @@
import com.android.compose.animation.scene.UserActionResult
import com.android.systemui.battery.BatteryMeterViewController
import com.android.systemui.brightness.ui.compose.BrightnessSliderContainer
+import com.android.systemui.compose.modifiers.sysuiResTag
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.ui.composable.LockscreenContent
import com.android.systemui.lifecycle.rememberViewModel
@@ -114,7 +113,7 @@
}
@Composable
-private fun ShadeBody(
+fun ShadeBody(
viewModel: QuickSettingsContainerViewModel,
) {
val isEditing by viewModel.editModeViewModel.isEditing.collectAsStateWithLifecycle()
@@ -131,6 +130,7 @@
} else {
QuickSettingsLayout(
viewModel = viewModel,
+ modifier = Modifier.sysuiResTag("quick_settings_panel")
)
}
}
@@ -158,11 +158,6 @@
Modifier.fillMaxWidth().heightIn(max = QuickSettingsShade.Dimensions.GridMaxHeight),
viewModel.editModeViewModel::startEditing,
)
- Button(
- onClick = { viewModel.editModeViewModel.startEditing() },
- ) {
- Text("Edit mode")
- }
}
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
index b329534..3487730 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
@@ -81,6 +81,7 @@
enabled: () -> Boolean,
startDragImmediately: (startedPosition: Offset) -> Boolean,
onDragStarted: (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> DragController,
+ onFirstPointerDown: () -> Unit = {},
swipeDetector: SwipeDetector = DefaultSwipeDetector,
dispatcher: NestedScrollDispatcher,
): Modifier =
@@ -90,6 +91,7 @@
enabled,
startDragImmediately,
onDragStarted,
+ onFirstPointerDown,
swipeDetector,
dispatcher,
)
@@ -101,6 +103,7 @@
private val startDragImmediately: (startedPosition: Offset) -> Boolean,
private val onDragStarted:
(startedPosition: Offset, overSlop: Float, pointersDown: Int) -> DragController,
+ private val onFirstPointerDown: () -> Unit,
private val swipeDetector: SwipeDetector,
private val dispatcher: NestedScrollDispatcher,
) : ModifierNodeElement<MultiPointerDraggableNode>() {
@@ -110,6 +113,7 @@
enabled = enabled,
startDragImmediately = startDragImmediately,
onDragStarted = onDragStarted,
+ onFirstPointerDown = onFirstPointerDown,
swipeDetector = swipeDetector,
dispatcher = dispatcher,
)
@@ -119,6 +123,7 @@
node.enabled = enabled
node.startDragImmediately = startDragImmediately
node.onDragStarted = onDragStarted
+ node.onFirstPointerDown = onFirstPointerDown
node.swipeDetector = swipeDetector
}
}
@@ -129,6 +134,7 @@
var startDragImmediately: (startedPosition: Offset) -> Boolean,
var onDragStarted:
(startedPosition: Offset, overSlop: Float, pointersDown: Int) -> DragController,
+ var onFirstPointerDown: () -> Unit,
var swipeDetector: SwipeDetector = DefaultSwipeDetector,
private val dispatcher: NestedScrollDispatcher,
) :
@@ -225,6 +231,7 @@
startedPosition = null
} else if (startedPosition == null) {
startedPosition = pointers.first().position
+ onFirstPointerDown()
}
}
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
index f062146..d1e83ba 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
@@ -67,6 +67,7 @@
enabled = ::enabled,
startDragImmediately = ::startDragImmediately,
onDragStarted = draggableHandler::onDragStarted,
+ onFirstPointerDown = ::onFirstPointerDown,
swipeDetector = swipeDetector,
dispatcher = dispatcher,
)
@@ -101,6 +102,15 @@
delegate(ScrollBehaviorOwnerNode(draggableHandler.nestedScrollKey, nestedScrollHandlerImpl))
}
+ private fun onFirstPointerDown() {
+ // When we drag our finger across the screen, the NestedScrollConnection keeps track of all
+ // the scroll events until we lift our finger. However, in some cases, the connection might
+ // not receive the "up" event. This can lead to an incorrect initial state for the gesture.
+ // To prevent this issue, we can call the reset() method when the first finger touches the
+ // screen. This ensures that the NestedScrollConnection starts from a correct state.
+ nestedScrollHandlerImpl.connection.reset()
+ }
+
override fun onDetach() {
// Make sure we reset the scroll connection when this modifier is removed from composition
nestedScrollHandlerImpl.connection.reset()
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt
index 228f7ba..16fb533b 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt
@@ -129,7 +129,11 @@
return onPriorityStop(available)
}
- /** Method to call before destroying the object or to reset the initial state. */
+ /**
+ * Method to call before destroying the object or to reset the initial state.
+ *
+ * TODO(b/303224944) This method should be removed.
+ */
fun reset() {
// Step 3c: To ensure that an onStop is always called for every onStart.
onPriorityStop(velocity = Velocity.Zero)
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/NestedScrollToSceneTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/NestedScrollToSceneTest.kt
index 9ebc426..d8a06f5 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/NestedScrollToSceneTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/NestedScrollToSceneTest.kt
@@ -23,6 +23,9 @@
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.platform.LocalViewConfiguration
@@ -266,4 +269,38 @@
val transition = assertThat(state.transitionState).isTransition()
assertThat(transition).hasProgress(0.5f)
}
+
+ @Test
+ fun resetScrollTracking_afterMissingPointerUpEvent() {
+ var canScroll = true
+ var hasScrollable by mutableStateOf(true)
+ val state = setup2ScenesAndScrollTouchSlop {
+ if (hasScrollable) {
+ Modifier.scrollable(rememberScrollableState { if (canScroll) it else 0f }, Vertical)
+ } else {
+ Modifier
+ }
+ }
+
+ // The gesture is consumed by the component in the scene.
+ scrollUp(percent = 0.2f)
+
+ // STL keeps track of the scroll consumed. The scene remains in Idle.
+ assertThat(state.transitionState).isIdle()
+
+ // The scrollable component disappears, and does not send the signal (pointer up) to reset
+ // the consumed amount.
+ hasScrollable = false
+ pointerUp()
+
+ // A new scrollable component appears and allows the scene to consume the scroll.
+ hasScrollable = true
+ canScroll = false
+ pointerDownAndScrollTouchSlop()
+ scrollUp(percent = 0.2f)
+
+ // STL can only start the transition if it has reset the amount of scroll consumed.
+ val transition = assertThat(state.transitionState).isTransition()
+ assertThat(transition).hasProgress(0.2f)
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
index 9ccf99b..70529cc 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
@@ -112,7 +112,7 @@
testScope.runTest {
val scene by collectLastValue(communalSceneInteractor.currentScene)
- communalSceneInteractor.changeScene(CommunalScenes.Communal)
+ communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
assertThat(scene).isEqualTo(CommunalScenes.Communal)
communalSceneInteractor.setEditModeState(EditModeState.STARTING)
@@ -133,7 +133,7 @@
testScope.runTest {
val scene by collectLastValue(communalSceneInteractor.currentScene)
- communalSceneInteractor.changeScene(CommunalScenes.Communal)
+ communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
assertThat(scene).isEqualTo(CommunalScenes.Communal)
communalSceneInteractor.setIsLaunchingWidget(true)
@@ -154,7 +154,7 @@
testScope.runTest {
val scene by collectLastValue(communalSceneInteractor.currentScene)
- communalSceneInteractor.changeScene(CommunalScenes.Communal)
+ communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
assertThat(scene).isEqualTo(CommunalScenes.Communal)
communalSceneInteractor.setIsLaunchingWidget(false)
@@ -174,7 +174,7 @@
with(kosmos) {
testScope.runTest {
val scene by collectLastValue(communalSceneInteractor.currentScene)
- communalSceneInteractor.changeScene(CommunalScenes.Communal)
+ communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
assertThat(scene).isEqualTo(CommunalScenes.Communal)
communalInteractor.setEditModeOpen(true)
@@ -213,7 +213,7 @@
testScope.runTest {
whenever(centralSurfaces.isLaunchingActivityOverLockscreen).thenReturn(false)
val scene by collectLastValue(communalSceneInteractor.currentScene)
- communalSceneInteractor.changeScene(CommunalScenes.Communal)
+ communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
assertThat(scene).isEqualTo(CommunalScenes.Communal)
updateDocked(true)
@@ -233,7 +233,7 @@
testScope.runTest {
whenever(centralSurfaces.isLaunchingActivityOverLockscreen).thenReturn(true)
val scene by collectLastValue(communalSceneInteractor.currentScene)
- communalSceneInteractor.changeScene(CommunalScenes.Communal)
+ communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
assertThat(scene).isEqualTo(CommunalScenes.Communal)
updateDocked(true)
@@ -270,7 +270,7 @@
with(kosmos) {
testScope.runTest {
val scene by collectLastValue(communalSceneInteractor.currentScene)
- communalSceneInteractor.changeScene(CommunalScenes.Communal)
+ communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
assertThat(scene).isEqualTo(CommunalScenes.Communal)
fakeKeyguardTransitionRepository.sendTransitionSteps(
@@ -292,7 +292,7 @@
with(kosmos) {
testScope.runTest {
val scene by collectLastValue(communalSceneInteractor.currentScene)
- communalSceneInteractor.changeScene(CommunalScenes.Communal)
+ communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
assertThat(scene).isEqualTo(CommunalScenes.Communal)
fakeKeyguardTransitionRepository.sendTransitionSteps(
@@ -320,7 +320,7 @@
fun dockingOnLockscreen_forcesCommunal() =
with(kosmos) {
testScope.runTest {
- communalSceneInteractor.changeScene(CommunalScenes.Blank)
+ communalSceneInteractor.changeScene(CommunalScenes.Blank, "test")
val scene by collectLastValue(communalSceneInteractor.currentScene)
// device is docked while on the lockscreen
@@ -342,7 +342,7 @@
fun dockingOnLockscreen_doesNotForceCommunalIfDreamStarts() =
with(kosmos) {
testScope.runTest {
- communalSceneInteractor.changeScene(CommunalScenes.Blank)
+ communalSceneInteractor.changeScene(CommunalScenes.Blank, "test")
val scene by collectLastValue(communalSceneInteractor.currentScene)
// device is docked while on the lockscreen
@@ -374,7 +374,7 @@
testScope.runTest {
// Device is dreaming and on communal.
updateDreaming(true)
- communalSceneInteractor.changeScene(CommunalScenes.Communal)
+ communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
val scene by collectLastValue(communalSceneInteractor.currentScene)
assertThat(scene).isEqualTo(CommunalScenes.Communal)
@@ -391,7 +391,7 @@
testScope.runTest {
// Device is not dreaming and on communal.
updateDreaming(false)
- communalSceneInteractor.changeScene(CommunalScenes.Communal)
+ communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
// Scene stays as Communal
advanceTimeBy(SCREEN_TIMEOUT.milliseconds)
@@ -406,7 +406,7 @@
testScope.runTest {
// Device is dreaming and on communal.
updateDreaming(true)
- communalSceneInteractor.changeScene(CommunalScenes.Communal)
+ communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
val scene by collectLastValue(communalSceneInteractor.currentScene)
assertThat(scene).isEqualTo(CommunalScenes.Communal)
@@ -429,7 +429,7 @@
testScope.runTest {
// Device is on communal, but not dreaming.
updateDreaming(false)
- communalSceneInteractor.changeScene(CommunalScenes.Communal)
+ communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
val scene by collectLastValue(communalSceneInteractor.currentScene)
assertThat(scene).isEqualTo(CommunalScenes.Communal)
@@ -450,7 +450,7 @@
with(kosmos) {
testScope.runTest {
// Device is on communal.
- communalSceneInteractor.changeScene(CommunalScenes.Communal)
+ communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
// Device stays on the hub after the timeout since we're not dreaming.
advanceTimeBy(SCREEN_TIMEOUT.milliseconds * 2)
@@ -471,7 +471,7 @@
testScope.runTest {
// Device is dreaming and on communal.
updateDreaming(true)
- communalSceneInteractor.changeScene(CommunalScenes.Communal)
+ communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
val scene by collectLastValue(communalSceneInteractor.currentScene)
assertThat(scene).isEqualTo(CommunalScenes.Communal)
@@ -500,7 +500,7 @@
// Device is dreaming and on communal.
updateDreaming(true)
- communalSceneInteractor.changeScene(CommunalScenes.Communal)
+ communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
val scene by collectLastValue(communalSceneInteractor.currentScene)
assertThat(scene).isEqualTo(CommunalScenes.Communal)
@@ -520,7 +520,7 @@
with(kosmos) {
testScope.runTest {
val scene by collectLastValue(communalSceneInteractor.currentScene)
- communalSceneInteractor.changeScene(CommunalScenes.Blank)
+ communalSceneInteractor.changeScene(CommunalScenes.Blank, "test")
assertThat(scene).isEqualTo(CommunalScenes.Blank)
fakeKeyguardTransitionRepository.sendTransitionSteps(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
index e57a4cb..864795b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
@@ -482,7 +482,7 @@
testScope.runTest {
val targetScene = CommunalScenes.Communal
- underTest.changeScene(targetScene)
+ underTest.changeScene(targetScene, "test")
val desiredScene = collectLastValue(communalRepository.currentScene)
runCurrent()
@@ -635,7 +635,7 @@
runCurrent()
assertThat(isCommunalShowing()).isEqualTo(false)
- underTest.changeScene(CommunalScenes.Communal)
+ underTest.changeScene(CommunalScenes.Communal, "test")
isCommunalShowing = collectLastValue(underTest.isCommunalShowing)
runCurrent()
@@ -659,12 +659,12 @@
assertThat(isCommunalShowing).isFalse()
// Verify scene changes (without the flag) to communal sets the value to true
- underTest.changeScene(CommunalScenes.Communal)
+ underTest.changeScene(CommunalScenes.Communal, "test")
runCurrent()
assertThat(isCommunalShowing).isTrue()
// Verify scene changes (without the flag) to blank sets the value back to false
- underTest.changeScene(CommunalScenes.Blank)
+ underTest.changeScene(CommunalScenes.Blank, "test")
runCurrent()
assertThat(isCommunalShowing).isFalse()
}
@@ -679,7 +679,7 @@
assertThat(isCommunalShowing).isFalse()
// Verify scene changes without the flag doesn't have any impact
- underTest.changeScene(CommunalScenes.Communal)
+ underTest.changeScene(CommunalScenes.Communal, "test")
runCurrent()
assertThat(isCommunalShowing).isFalse()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractorTest.kt
index 43293c7..ed7e910 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractorTest.kt
@@ -53,7 +53,7 @@
val currentScene by collectLastValue(underTest.currentScene)
assertThat(currentScene).isEqualTo(CommunalScenes.Blank)
- underTest.changeScene(CommunalScenes.Communal)
+ underTest.changeScene(CommunalScenes.Communal, "test")
assertThat(currentScene).isEqualTo(CommunalScenes.Communal)
}
@@ -63,7 +63,7 @@
val currentScene by collectLastValue(underTest.currentScene)
assertThat(currentScene).isEqualTo(CommunalScenes.Blank)
- underTest.snapToScene(CommunalScenes.Communal)
+ underTest.snapToScene(CommunalScenes.Communal, "test")
assertThat(currentScene).isEqualTo(CommunalScenes.Communal)
}
@@ -75,6 +75,7 @@
assertThat(currentScene).isEqualTo(CommunalScenes.Blank)
underTest.snapToScene(
CommunalScenes.Communal,
+ "test",
ActivityTransitionAnimator.TIMINGS.totalDuration
)
assertThat(currentScene).isEqualTo(CommunalScenes.Blank)
@@ -86,7 +87,7 @@
fun changeSceneForActivityStartOnDismissKeyguard() =
testScope.runTest {
val currentScene by collectLastValue(underTest.currentScene)
- underTest.snapToScene(CommunalScenes.Communal)
+ underTest.snapToScene(CommunalScenes.Communal, "test")
assertThat(currentScene).isEqualTo(CommunalScenes.Communal)
underTest.changeSceneForActivityStartOnDismissKeyguard()
@@ -97,7 +98,7 @@
fun changeSceneForActivityStartOnDismissKeyguard_willNotChangeScene_forEditModeActivity() =
testScope.runTest {
val currentScene by collectLastValue(underTest.currentScene)
- underTest.snapToScene(CommunalScenes.Communal)
+ underTest.snapToScene(CommunalScenes.Communal, "test")
assertThat(currentScene).isEqualTo(CommunalScenes.Communal)
underTest.setEditModeState(EditModeState.STARTING)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt
index 3a23e14..7e28e19 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt
@@ -158,7 +158,7 @@
kosmos.setCommunalAvailable(true)
communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_NOT_STARTED)
- communalInteractor.changeScene(CommunalScenes.Blank)
+ communalInteractor.changeScene(CommunalScenes.Blank, "test")
assertThat(tutorialSettingState).isEqualTo(HUB_MODE_TUTORIAL_NOT_STARTED)
}
@@ -171,7 +171,7 @@
goToCommunal()
communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_STARTED)
- communalInteractor.changeScene(CommunalScenes.Blank)
+ communalInteractor.changeScene(CommunalScenes.Blank, "test")
assertThat(tutorialSettingState).isEqualTo(HUB_MODE_TUTORIAL_COMPLETED)
}
@@ -184,13 +184,13 @@
goToCommunal()
communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
- communalInteractor.changeScene(CommunalScenes.Blank)
+ communalInteractor.changeScene(CommunalScenes.Blank, "test")
assertThat(tutorialSettingState).isEqualTo(HUB_MODE_TUTORIAL_COMPLETED)
}
private suspend fun goToCommunal() {
kosmos.setCommunalAvailable(true)
- communalInteractor.changeScene(CommunalScenes.Communal)
+ communalInteractor.changeScene(CommunalScenes.Communal, "test")
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalTransitionAnimatorControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalTransitionAnimatorControllerTest.kt
index e36fd75..a052b07 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalTransitionAnimatorControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalTransitionAnimatorControllerTest.kt
@@ -76,7 +76,7 @@
val launching by collectLastValue(communalSceneInteractor.isLaunchingWidget)
val scene by collectLastValue(communalSceneInteractor.currentScene)
- communalSceneInteractor.changeScene(CommunalScenes.Communal)
+ communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
Truth.assertThat(scene).isEqualTo(CommunalScenes.Communal)
communalSceneInteractor.setIsLaunchingWidget(true)
assertTrue(launching!!)
@@ -103,7 +103,7 @@
val launching by collectLastValue(communalSceneInteractor.isLaunchingWidget)
val scene by collectLastValue(communalSceneInteractor.currentScene)
- communalSceneInteractor.changeScene(CommunalScenes.Communal)
+ communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
Truth.assertThat(scene).isEqualTo(CommunalScenes.Communal)
communalSceneInteractor.setIsLaunchingWidget(true)
assertTrue(launching!!)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
index 7a86e57..da82b5f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
@@ -68,7 +68,6 @@
import com.android.systemui.testKosmos
import com.android.systemui.touch.TouchInsetManager
import com.android.systemui.util.concurrency.FakeExecutor
-import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
@@ -87,6 +86,7 @@
import org.mockito.Mockito.isNull
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.any
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.eq
import org.mockito.kotlin.spy
@@ -669,7 +669,7 @@
runCurrent()
verify(mDreamOverlayCallback).onRedirectWake(true)
client.onWakeRequested()
- verify(mCommunalInteractor).changeScene(eq(CommunalScenes.Communal), isNull())
+ verify(mCommunalInteractor).changeScene(eq(CommunalScenes.Communal), any(), isNull())
verify(mUiEventLogger).log(CommunalUiEvent.DREAM_TO_COMMUNAL_HUB_DREAM_AWAKE_START)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index 8c1e8de..9792c28 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -845,7 +845,7 @@
runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.GONE)
// WHEN the glanceable hub is shown
- communalSceneInteractor.changeScene(CommunalScenes.Communal)
+ communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
runCurrent()
assertThat(transitionRepository)
@@ -1004,7 +1004,7 @@
fun alternateBouncerToGlanceableHub() =
testScope.runTest {
// GIVEN the device is idle on the glanceable hub
- communalSceneInteractor.changeScene(CommunalScenes.Communal)
+ communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
runCurrent()
clearInvocations(transitionRepository)
@@ -1123,7 +1123,7 @@
fun primaryBouncerToGlanceableHub() =
testScope.runTest {
// GIVEN the device is idle on the glanceable hub
- communalSceneInteractor.changeScene(CommunalScenes.Communal)
+ communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
runCurrent()
// GIVEN a prior transition has run to PRIMARY_BOUNCER
@@ -1157,7 +1157,7 @@
advanceTimeBy(600L)
// GIVEN the device is idle on the glanceable hub
- communalSceneInteractor.changeScene(CommunalScenes.Communal)
+ communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
runCurrent()
// GIVEN a prior transition has run to PRIMARY_BOUNCER
@@ -1971,7 +1971,7 @@
fun glanceableHubToLockscreen_communalKtfRefactor() =
testScope.runTest {
// GIVEN a prior transition has run to GLANCEABLE_HUB
- communalSceneInteractor.changeScene(CommunalScenes.Communal)
+ communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
runCurrent()
clearInvocations(transitionRepository)
@@ -2035,7 +2035,7 @@
fun glanceableHubToDozing_communalKtfRefactor() =
testScope.runTest {
// GIVEN a prior transition has run to GLANCEABLE_HUB
- communalSceneInteractor.changeScene(CommunalScenes.Communal)
+ communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
runCurrent()
clearInvocations(transitionRepository)
@@ -2136,7 +2136,7 @@
fun glanceableHubToOccluded_communalKtfRefactor() =
testScope.runTest {
// GIVEN a prior transition has run to GLANCEABLE_HUB
- communalSceneInteractor.changeScene(CommunalScenes.Communal)
+ communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
runCurrent()
clearInvocations(transitionRepository)
@@ -2184,7 +2184,7 @@
fun glanceableHubToGone_communalKtfRefactor() =
testScope.runTest {
// GIVEN a prior transition has run to GLANCEABLE_HUB
- communalSceneInteractor.changeScene(CommunalScenes.Communal)
+ communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
runCurrent()
clearInvocations(transitionRepository)
@@ -2265,7 +2265,7 @@
advanceTimeBy(600L)
// GIVEN a prior transition has run to GLANCEABLE_HUB
- communalSceneInteractor.changeScene(CommunalScenes.Communal)
+ communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
runCurrent()
clearInvocations(transitionRepository)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt
new file mode 100644
index 0000000..5999265
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.composefragment.viewmodel
+
+import android.content.testableContext
+import android.testing.TestableLooper.RunWithLooper
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.testing.TestLifecycleOwner
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.qs.fgsManagerController
+import com.android.systemui.res.R
+import com.android.systemui.shade.largeScreenHeaderHelper
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestResult
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.resetMain
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import kotlinx.coroutines.test.setMain
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@RunWithLooper
+@OptIn(ExperimentalCoroutinesApi::class)
+class QSFragmentComposeViewModelTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+
+ private val lifecycleOwner =
+ TestLifecycleOwner(
+ initialState = Lifecycle.State.CREATED,
+ coroutineDispatcher = kosmos.testDispatcher,
+ )
+
+ private val underTest by lazy {
+ kosmos.qsFragmentComposeViewModelFactory.create(lifecycleOwner.lifecycleScope)
+ }
+
+ @Before
+ fun setUp() {
+ Dispatchers.setMain(kosmos.testDispatcher)
+ }
+
+ @After
+ fun teardown() {
+ Dispatchers.resetMain()
+ }
+
+ // For now the state changes at 0.5f expansion. This will change once we implement animation
+ // (and this test will fail)
+ @Test
+ fun qsExpansionValueChanges_correctExpansionState() =
+ with(kosmos) {
+ testScope.testWithinLifecycle {
+ val expansionState by collectLastValue(underTest.expansionState)
+
+ underTest.qsExpansionValue = 0f
+ assertThat(expansionState)
+ .isEqualTo(QSFragmentComposeViewModel.QSExpansionState.QQS)
+
+ underTest.qsExpansionValue = 0.3f
+ assertThat(expansionState)
+ .isEqualTo(QSFragmentComposeViewModel.QSExpansionState.QQS)
+
+ underTest.qsExpansionValue = 0.7f
+ assertThat(expansionState).isEqualTo(QSFragmentComposeViewModel.QSExpansionState.QS)
+
+ underTest.qsExpansionValue = 1f
+ assertThat(expansionState).isEqualTo(QSFragmentComposeViewModel.QSExpansionState.QS)
+ }
+ }
+
+ @Test
+ fun qqsHeaderHeight_largeScreenHeader_0() =
+ with(kosmos) {
+ testScope.testWithinLifecycle {
+ val qqsHeaderHeight by collectLastValue(underTest.qqsHeaderHeight)
+
+ testableContext.orCreateTestableResources.addOverride(
+ R.bool.config_use_large_screen_shade_header,
+ true
+ )
+ fakeConfigurationRepository.onConfigurationChange()
+
+ assertThat(qqsHeaderHeight).isEqualTo(0)
+ }
+ }
+
+ @Test
+ fun qqsHeaderHeight_noLargeScreenHeader_providedByHelper() =
+ with(kosmos) {
+ testScope.testWithinLifecycle {
+ val qqsHeaderHeight by collectLastValue(underTest.qqsHeaderHeight)
+
+ testableContext.orCreateTestableResources.addOverride(
+ R.bool.config_use_large_screen_shade_header,
+ false
+ )
+ fakeConfigurationRepository.onConfigurationChange()
+
+ assertThat(qqsHeaderHeight)
+ .isEqualTo(largeScreenHeaderHelper.getLargeScreenHeaderHeight())
+ }
+ }
+
+ @Test
+ fun footerActionsControllerInit() =
+ with(kosmos) {
+ testScope.testWithinLifecycle {
+ underTest
+ runCurrent()
+ assertThat(fgsManagerController.initialized).isTrue()
+ }
+ }
+
+ private inline fun TestScope.testWithinLifecycle(
+ crossinline block: suspend TestScope.() -> TestResult
+ ): TestResult {
+ return runTest {
+ lifecycleOwner.setCurrentState(Lifecycle.State.RESUMED)
+ block().also { lifecycleOwner.setCurrentState(Lifecycle.State.DESTROYED) }
+ }
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneContentViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneContentViewModelTest.kt
index 9563538..1118a61 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneContentViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneContentViewModelTest.kt
@@ -17,6 +17,7 @@
package com.android.systemui.qs.ui.viewmodel
import android.testing.TestableLooper.RunWithLooper
+import androidx.lifecycle.LifecycleOwner
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -59,7 +60,7 @@
private val footerActionsViewModel = mock<FooterActionsViewModel>()
private val footerActionsViewModelFactory =
mock<FooterActionsViewModel.Factory> {
- whenever(create(any())).thenReturn(footerActionsViewModel)
+ whenever(create(any<LifecycleOwner>())).thenReturn(footerActionsViewModel)
}
private val footerActionsController = mock<FooterActionsController>()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelTest.kt
index bcad7e7..54b7d25 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelTest.kt
@@ -23,6 +23,7 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.settingslib.notification.modes.TestModeBuilder
+import com.android.settingslib.notification.modes.ZenMode
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.testDispatcher
@@ -30,6 +31,7 @@
import com.android.systemui.statusbar.policy.data.repository.fakeZenModeRepository
import com.android.systemui.statusbar.policy.domain.interactor.zenModeInteractor
import com.android.systemui.statusbar.policy.ui.dialog.mockModesDialogDelegate
+import com.android.systemui.statusbar.policy.ui.dialog.mockModesDialogEventLogger
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -40,6 +42,7 @@
import org.junit.runner.RunWith
import org.mockito.Mockito.clearInvocations
import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.times
import org.mockito.kotlin.verify
@SmallTest
@@ -50,9 +53,16 @@
private val repository = kosmos.fakeZenModeRepository
private val interactor = kosmos.zenModeInteractor
private val mockDialogDelegate = kosmos.mockModesDialogDelegate
+ private val mockDialogEventLogger = kosmos.mockModesDialogEventLogger
private val underTest =
- ModesDialogViewModel(context, interactor, kosmos.testDispatcher, mockDialogDelegate)
+ ModesDialogViewModel(
+ context,
+ interactor,
+ kosmos.testDispatcher,
+ mockDialogDelegate,
+ mockDialogEventLogger
+ )
@Test
fun tiles_filtersOutUserDisabledModes() =
@@ -432,4 +442,84 @@
assertThat(intent.extras?.getString(Settings.EXTRA_AUTOMATIC_ZEN_RULE_ID))
.isEqualTo("B")
}
+
+ @Test
+ fun onClick_logsOnOffEvents() =
+ testScope.runTest {
+ val tiles by collectLastValue(underTest.tiles)
+
+ repository.addModes(
+ listOf(
+ TestModeBuilder.MANUAL_DND_ACTIVE,
+ TestModeBuilder()
+ .setId("id1")
+ .setName("Inactive Mode One")
+ .setActive(false)
+ .setManualInvocationAllowed(true)
+ .build(),
+ TestModeBuilder()
+ .setId("id2")
+ .setName("Active Non-Invokable Mode Two") // but can be turned off by tile
+ .setActive(true)
+ .setManualInvocationAllowed(false)
+ .build(),
+ )
+ )
+ runCurrent()
+
+ assertThat(tiles?.size).isEqualTo(3)
+
+ // Trigger onClick for each tile in sequence
+ tiles?.forEach { it.onClick.invoke() }
+ runCurrent()
+
+ val onModeCaptor = argumentCaptor<ZenMode>()
+ val offModeCaptor = argumentCaptor<ZenMode>()
+
+ // manual mode and mode 2 should have turned off
+ verify(mockDialogEventLogger, times(2)).logModeOff(offModeCaptor.capture())
+ val off0 = offModeCaptor.firstValue
+ assertThat(off0.isManualDnd).isTrue()
+
+ val off1 = offModeCaptor.secondValue
+ assertThat(off1.id).isEqualTo("id2")
+
+ // should also have logged turning mode 1 on
+ verify(mockDialogEventLogger).logModeOn(onModeCaptor.capture())
+ val on = onModeCaptor.lastValue
+ assertThat(on.id).isEqualTo("id1")
+ }
+
+ @Test
+ fun onLongClick_logsSettingsEvents() =
+ testScope.runTest {
+ val tiles by collectLastValue(underTest.tiles)
+
+ repository.addModes(
+ listOf(
+ TestModeBuilder.MANUAL_DND_ACTIVE,
+ TestModeBuilder()
+ .setId("id1")
+ .setName("Inactive Mode One")
+ .setActive(false)
+ .setManualInvocationAllowed(true)
+ .build(),
+ )
+ )
+ runCurrent()
+
+ assertThat(tiles?.size).isEqualTo(2)
+ val modeCaptor = argumentCaptor<ZenMode>()
+
+ // long click manual DND and then automatic mode
+ tiles?.forEach { it.onLongClick.invoke() }
+ runCurrent()
+
+ verify(mockDialogEventLogger, times(2)).logModeSettings(modeCaptor.capture())
+ val manualMode = modeCaptor.firstValue
+ assertThat(manualMode.isManualDnd).isTrue()
+
+ val automaticMode = modeCaptor.lastValue
+ assertThat(automaticMode.id).isEqualTo("id1")
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractorTest.kt
index 7385a47..7c55f7a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractorTest.kt
@@ -32,7 +32,6 @@
import com.android.systemui.testKosmos
import com.android.systemui.volume.data.repository.TestAudioDevicesFactory
import com.android.systemui.volume.data.repository.audioRepository
-import com.android.systemui.volume.data.repository.audioSharingRepository
import com.android.systemui.volume.domain.model.AudioOutputDevice
import com.android.systemui.volume.localMediaController
import com.android.systemui.volume.localMediaRepository
@@ -222,32 +221,4 @@
val testIcon = TestStubDrawable()
}
-
- @Test
- fun inAudioSharing_returnTrue() {
- with(kosmos) {
- testScope.runTest {
- audioSharingRepository.setInAudioSharing(true)
-
- val inAudioSharing by collectLastValue(underTest.isInAudioSharing)
- runCurrent()
-
- assertThat(inAudioSharing).isTrue()
- }
- }
- }
-
- @Test
- fun notInAudioSharing_returnFalse() {
- with(kosmos) {
- testScope.runTest {
- audioSharingRepository.setInAudioSharing(false)
-
- val inAudioSharing by collectLastValue(underTest.isInAudioSharing)
- runCurrent()
-
- assertThat(inAudioSharing).isFalse()
- }
- }
- }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractorTest.kt
index a1fcfcd..c9d147b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractorTest.kt
@@ -49,6 +49,24 @@
}
@Test
+ fun handleInAudioSharingChange() {
+ with(kosmos) {
+ testScope.runTest {
+ with(audioSharingRepository) { setInAudioSharing(true) }
+ val inAudioSharing by collectLastValue(underTest.isInAudioSharing)
+ runCurrent()
+
+ Truth.assertThat(inAudioSharing).isEqualTo(true)
+
+ with(audioSharingRepository) { setInAudioSharing(false) }
+ runCurrent()
+
+ Truth.assertThat(inAudioSharing).isEqualTo(false)
+ }
+ }
+ }
+
+ @Test
fun handlePrimaryGroupChange_nullVolume() {
with(kosmos) {
testScope.runTest {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 0da252d..60fff28 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -104,6 +104,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.compose.animation.scene.ObservableTransitionState;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.foldables.FoldGracePeriodProvider;
import com.android.internal.jank.InteractionJankMonitor;
@@ -119,6 +120,7 @@
import com.android.systemui.Dumpable;
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.biometrics.FingerprintInteractiveToAuthProvider;
+import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
@@ -140,6 +142,9 @@
import com.android.systemui.plugins.clocks.WeatherData;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.res.R;
+import com.android.systemui.scene.domain.interactor.SceneInteractor;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
+import com.android.systemui.scene.shared.model.Scenes;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.shared.system.TaskStackChangeListener;
import com.android.systemui.shared.system.TaskStackChangeListeners;
@@ -150,6 +155,7 @@
import com.android.systemui.telephony.TelephonyListenerManager;
import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
import com.android.systemui.util.Assert;
+import com.android.systemui.util.kotlin.JavaAdapter;
import dalvik.annotation.optimization.NeverCompile;
@@ -279,6 +285,9 @@
private final UserTracker mUserTracker;
private final KeyguardUpdateMonitorLogger mLogger;
private final boolean mIsSystemUser;
+ private final Provider<JavaAdapter> mJavaAdapter;
+ private final Provider<SceneInteractor> mSceneInteractor;
+ private final Provider<AlternateBouncerInteractor> mAlternateBouncerInteractor;
private final AuthController mAuthController;
private final UiEventLogger mUiEventLogger;
private final Set<String> mAllowFingerprintOnOccludingActivitiesFromPackage;
@@ -563,7 +572,7 @@
*/
private boolean shouldDismissKeyguardOnTrustGrantedWithCurrentUser(TrustGrantFlags flags) {
final boolean isBouncerShowing =
- mPrimaryBouncerIsOrWillBeShowing || mAlternateBouncerShowing;
+ isPrimaryBouncerShowingOrWillBeShowing() || isAlternateBouncerShowing();
return (flags.isInitiatedByUser() || flags.dismissKeyguardRequested())
&& (mDeviceInteractive || flags.temporaryAndRenewable())
&& (isBouncerShowing || flags.dismissKeyguardRequested());
@@ -1170,8 +1179,8 @@
Assert.isMainThread();
String reason =
mKeyguardBypassController.canBypass() ? "bypass"
- : mAlternateBouncerShowing ? "alternateBouncer"
- : mPrimaryBouncerFullyShown ? "bouncer"
+ : isAlternateBouncerShowing() ? "alternateBouncer"
+ : isPrimaryBouncerFullyShown() ? "bouncer"
: "udfpsFpDown";
requestActiveUnlock(
ActiveUnlockConfig.ActiveUnlockRequestOrigin.BIOMETRIC_FAIL,
@@ -2169,7 +2178,10 @@
Optional<FingerprintInteractiveToAuthProvider> interactiveToAuthProvider,
TaskStackChangeListeners taskStackChangeListeners,
SelectedUserInteractor selectedUserInteractor,
- IActivityTaskManager activityTaskManagerService) {
+ IActivityTaskManager activityTaskManagerService,
+ Provider<AlternateBouncerInteractor> alternateBouncerInteractor,
+ Provider<JavaAdapter> javaAdapter,
+ Provider<SceneInteractor> sceneInteractor) {
mContext = context;
mSubscriptionManager = subscriptionManager;
mUserTracker = userTracker;
@@ -2214,6 +2226,9 @@
mFingerprintInteractiveToAuthProvider = interactiveToAuthProvider.orElse(null);
mIsSystemUser = mUserManager.isSystemUser();
+ mAlternateBouncerInteractor = alternateBouncerInteractor;
+ mJavaAdapter = javaAdapter;
+ mSceneInteractor = sceneInteractor;
mHandler = new Handler(mainLooper) {
@Override
@@ -2470,6 +2485,30 @@
mContext.getContentResolver().registerContentObserver(
Settings.System.getUriFor(Settings.System.TIME_12_24),
false, mTimeFormatChangeObserver, UserHandle.USER_ALL);
+
+ if (SceneContainerFlag.isEnabled()) {
+ mJavaAdapter.get().alwaysCollectFlow(
+ mAlternateBouncerInteractor.get().isVisible(),
+ this::onAlternateBouncerVisibilityChange);
+ mJavaAdapter.get().alwaysCollectFlow(
+ mSceneInteractor.get().getTransitionState(),
+ this::onTransitionStateChanged
+ );
+ }
+ }
+
+ @VisibleForTesting
+ void onAlternateBouncerVisibilityChange(boolean isAlternateBouncerVisible) {
+ setAlternateBouncerShowing(isAlternateBouncerVisible);
+ }
+
+
+ @VisibleForTesting
+ void onTransitionStateChanged(ObservableTransitionState transitionState) {
+ int primaryBouncerFullyShown = isPrimaryBouncerFullyShown(transitionState) ? 1 : 0;
+ int primaryBouncerIsOrWillBeShowing =
+ isPrimaryBouncerShowingOrWillBeShowing(transitionState) ? 1 : 0;
+ handlePrimaryBouncerChanged(primaryBouncerIsOrWillBeShowing, primaryBouncerFullyShown);
}
private void initializeSimState() {
@@ -2717,8 +2756,8 @@
requestActiveUnlock(
requestOrigin,
extraReason, canFaceBypass
- || mAlternateBouncerShowing
- || mPrimaryBouncerFullyShown
+ || isAlternateBouncerShowing()
+ || isPrimaryBouncerFullyShown()
|| mAuthController.isUdfpsFingerDown());
}
@@ -2739,7 +2778,7 @@
*/
public void setAlternateBouncerShowing(boolean showing) {
mAlternateBouncerShowing = showing;
- if (mAlternateBouncerShowing) {
+ if (isAlternateBouncerShowing()) {
requestActiveUnlock(
ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT,
"alternateBouncer");
@@ -2747,6 +2786,45 @@
updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
}
+ private boolean isAlternateBouncerShowing() {
+ if (SceneContainerFlag.isEnabled()) {
+ return mAlternateBouncerInteractor.get().isVisibleState();
+ } else {
+ return mAlternateBouncerShowing;
+ }
+ }
+
+ private boolean isPrimaryBouncerShowingOrWillBeShowing() {
+ if (SceneContainerFlag.isEnabled()) {
+ return isPrimaryBouncerShowingOrWillBeShowing(
+ mSceneInteractor.get().getTransitionState().getValue());
+ } else {
+ return mPrimaryBouncerIsOrWillBeShowing;
+ }
+ }
+
+ private boolean isPrimaryBouncerFullyShown() {
+ if (SceneContainerFlag.isEnabled()) {
+ return isPrimaryBouncerFullyShown(
+ mSceneInteractor.get().getTransitionState().getValue());
+ } else {
+ return mPrimaryBouncerFullyShown;
+ }
+ }
+
+ private boolean isPrimaryBouncerShowingOrWillBeShowing(
+ ObservableTransitionState transitionState
+ ) {
+ SceneContainerFlag.assertInNewMode();
+ return isPrimaryBouncerFullyShown(transitionState)
+ || transitionState.isTransitioning(null, Scenes.Bouncer);
+ }
+
+ private boolean isPrimaryBouncerFullyShown(ObservableTransitionState transitionState) {
+ SceneContainerFlag.assertInNewMode();
+ return transitionState.isIdle(Scenes.Bouncer);
+ }
+
/**
* If the current state of the device allows for triggering active unlock. This does not
* include active unlock availability.
@@ -2762,7 +2840,7 @@
private boolean shouldTriggerActiveUnlock(boolean shouldLog) {
// Triggers:
final boolean triggerActiveUnlockForAssistant = shouldTriggerActiveUnlockForAssistant();
- final boolean awakeKeyguard = mPrimaryBouncerFullyShown || mAlternateBouncerShowing
+ final boolean awakeKeyguard = isPrimaryBouncerFullyShown() || isAlternateBouncerShowing()
|| (isKeyguardVisible() && !mGoingToSleep
&& mStatusBarState != StatusBarState.SHADE_LOCKED);
@@ -2830,14 +2908,14 @@
final boolean shouldListenKeyguardState =
isKeyguardVisible()
|| !mDeviceInteractive
- || (mPrimaryBouncerIsOrWillBeShowing && !mKeyguardGoingAway)
+ || (isPrimaryBouncerShowingOrWillBeShowing() && !mKeyguardGoingAway)
|| mGoingToSleep
|| shouldListenForFingerprintAssistant
|| (mKeyguardOccluded && mIsDreaming)
|| (mKeyguardOccluded && userDoesNotHaveTrust && mKeyguardShowing
&& (mOccludingAppRequestingFp
|| isUdfps
- || mAlternateBouncerShowing
+ || isAlternateBouncerShowing()
|| mAllowFingerprintOnCurrentOccludingActivity
)
);
@@ -2856,7 +2934,7 @@
&& !isUserInLockdown(user);
final boolean strongerAuthRequired = !isUnlockingWithFingerprintAllowed();
final boolean shouldListenBouncerState =
- !strongerAuthRequired || !mPrimaryBouncerIsOrWillBeShowing;
+ !strongerAuthRequired || !isPrimaryBouncerShowingOrWillBeShowing();
final boolean shouldListenUdfpsState = !isUdfps
|| (!userCanSkipBouncer
@@ -2872,10 +2950,10 @@
user,
shouldListen,
mAllowFingerprintOnCurrentOccludingActivity,
- mAlternateBouncerShowing,
+ isAlternateBouncerShowing(),
biometricEnabledForUser,
mBiometricPromptShowing,
- mPrimaryBouncerIsOrWillBeShowing,
+ isPrimaryBouncerShowingOrWillBeShowing(),
userCanSkipBouncer,
mCredentialAttempted,
mDeviceInteractive,
@@ -3614,6 +3692,7 @@
*/
public void sendPrimaryBouncerChanged(boolean primaryBouncerIsOrWillBeShowing,
boolean primaryBouncerFullyShown) {
+ SceneContainerFlag.assertInLegacyMode();
mLogger.logSendPrimaryBouncerChanged(primaryBouncerIsOrWillBeShowing,
primaryBouncerFullyShown);
Message message = mHandler.obtainMessage(MSG_KEYGUARD_BOUNCER_CHANGED);
@@ -4031,10 +4110,10 @@
if (isUdfpsSupported()) {
pw.println(" udfpsEnrolled=" + isUdfpsEnrolled());
pw.println(" shouldListenForUdfps=" + shouldListenForFingerprint(true));
- pw.println(" mPrimaryBouncerIsOrWillBeShowing="
- + mPrimaryBouncerIsOrWillBeShowing);
+ pw.println(" isPrimaryBouncerShowingOrWillBeShowing="
+ + isPrimaryBouncerShowingOrWillBeShowing());
pw.println(" mStatusBarState=" + StatusBarState.toString(mStatusBarState));
- pw.println(" mAlternateBouncerShowing=" + mAlternateBouncerShowing);
+ pw.println(" isAlternateBouncerShowing=" + isAlternateBouncerShowing());
} else if (isSfpsSupported()) {
pw.println(" sfpsEnrolled=" + isSfpsEnrolled());
pw.println(" shouldListenForSfps=" + shouldListenForFingerprint(false));
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index baf8f5a..a301155 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -49,7 +49,6 @@
import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager;
import com.android.systemui.statusbar.phone.LightBarController;
import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
-import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider;
import com.android.systemui.statusbar.phone.SystemUIDialogManager;
import com.android.systemui.statusbar.policy.BluetoothController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
@@ -140,7 +139,6 @@
@Inject Lazy<SysUiState> mSysUiStateFlagsContainer;
@Inject Lazy<CommandQueue> mCommandQueue;
@Inject Lazy<UiEventLogger> mUiEventLogger;
- @Inject Lazy<StatusBarContentInsetsProvider> mContentInsetsProviderLazy;
@Inject Lazy<FeatureFlags> mFeatureFlagsLazy;
@Inject Lazy<NotificationSectionsManager> mNotificationSectionsManagerLazy;
@Inject Lazy<ScreenOffAnimationController> mScreenOffAnimationController;
@@ -186,7 +184,6 @@
mProviders.put(CommandQueue.class, mCommandQueue::get);
mProviders.put(UiEventLogger.class, mUiEventLogger::get);
mProviders.put(FeatureFlags.class, mFeatureFlagsLazy::get);
- mProviders.put(StatusBarContentInsetsProvider.class, mContentInsetsProviderLazy::get);
mProviders.put(NotificationSectionsManager.class, mNotificationSectionsManagerLazy::get);
mProviders.put(ScreenOffAnimationController.class, mScreenOffAnimationController::get);
mProviders.put(AmbientState.class, mAmbientStateLazy::get);
diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
index b7c02ea..6e01393 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
@@ -112,7 +112,11 @@
communalSceneInteractor.editModeState.value == EditModeState.STARTING ||
communalSceneInteractor.isLaunchingWidget.value
if (!delaySceneTransition) {
- communalSceneInteractor.changeScene(nextScene, nextTransition)
+ communalSceneInteractor.changeScene(
+ newScene = nextScene,
+ loggingReason = "KTF syncing",
+ transitionKey = nextTransition,
+ )
}
}
.launchIn(applicationScope)
@@ -176,7 +180,10 @@
if (scene == CommunalScenes.Communal && isDreaming && timeoutJob == null) {
// If dreaming starts after timeout has expired, ex. if dream restarts under
// the hub, just close the hub immediately.
- communalSceneInteractor.changeScene(CommunalScenes.Blank)
+ communalSceneInteractor.changeScene(
+ CommunalScenes.Blank,
+ "dream started after timeout",
+ )
}
}
}
@@ -201,7 +208,10 @@
bgScope.launch {
delay(screenTimeout.milliseconds)
if (isDreaming) {
- communalSceneInteractor.changeScene(CommunalScenes.Blank)
+ communalSceneInteractor.changeScene(
+ newScene = CommunalScenes.Blank,
+ loggingReason = "hub timeout",
+ )
}
timeoutJob = null
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
index 99bcc12..7181b15 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
@@ -329,8 +329,11 @@
@Deprecated(
"Use com.android.systemui.communal.domain.interactor.CommunalSceneInteractor instead"
)
- fun changeScene(newScene: SceneKey, transitionKey: TransitionKey? = null) =
- communalSceneInteractor.changeScene(newScene, transitionKey)
+ fun changeScene(
+ newScene: SceneKey,
+ loggingReason: String,
+ transitionKey: TransitionKey? = null
+ ) = communalSceneInteractor.changeScene(newScene, loggingReason, transitionKey)
fun setEditModeOpen(isOpen: Boolean) {
_editModeOpen.value = isOpen
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt
index e45a695..a0b9966 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt
@@ -22,6 +22,7 @@
import com.android.compose.animation.scene.TransitionKey
import com.android.systemui.communal.data.repository.CommunalSceneRepository
import com.android.systemui.communal.domain.model.CommunalTransitionProgressModel
+import com.android.systemui.communal.shared.log.CommunalSceneLogger
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.communal.shared.model.CommunalTransitionKeys
import com.android.systemui.communal.shared.model.EditModeState
@@ -29,6 +30,7 @@
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.util.kotlin.BooleanFlowOperators.allOf
+import com.android.systemui.util.kotlin.pairwiseBy
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -42,8 +44,8 @@
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
-import kotlinx.coroutines.launch
@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
@@ -51,7 +53,8 @@
@Inject
constructor(
@Application private val applicationScope: CoroutineScope,
- private val communalSceneRepository: CommunalSceneRepository,
+ private val repository: CommunalSceneRepository,
+ private val logger: CommunalSceneLogger,
) {
private val _isLaunchingWidget = MutableStateFlow(false)
@@ -80,25 +83,39 @@
*/
fun changeScene(
newScene: SceneKey,
+ loggingReason: String,
transitionKey: TransitionKey? = null,
keyguardState: KeyguardState? = null,
) {
- applicationScope.launch {
+ applicationScope.launch("$TAG#changeScene") {
+ logger.logSceneChangeRequested(
+ from = currentScene.value,
+ to = newScene,
+ reason = loggingReason,
+ isInstant = false,
+ )
notifyListeners(newScene, keyguardState)
- communalSceneRepository.changeScene(newScene, transitionKey)
+ repository.changeScene(newScene, transitionKey)
}
}
/** Immediately snaps to the new scene. */
fun snapToScene(
newScene: SceneKey,
+ loggingReason: String,
delayMillis: Long = 0,
keyguardState: KeyguardState? = null
) {
applicationScope.launch("$TAG#snapToScene") {
delay(delayMillis)
+ logger.logSceneChangeRequested(
+ from = currentScene.value,
+ to = newScene,
+ reason = loggingReason,
+ isInstant = true,
+ )
notifyListeners(newScene, keyguardState)
- communalSceneRepository.snapToScene(newScene)
+ repository.snapToScene(newScene)
}
}
@@ -113,13 +130,30 @@
if (_editModeState.value == EditModeState.STARTING) {
return
}
- changeScene(CommunalScenes.Blank, CommunalTransitionKeys.SimpleFade)
+ changeScene(
+ CommunalScenes.Blank,
+ "activity start dismissing keyguard",
+ CommunalTransitionKeys.SimpleFade,
+ )
}
/**
* Target scene as requested by the underlying [SceneTransitionLayout] or through [changeScene].
*/
- val currentScene: Flow<SceneKey> = communalSceneRepository.currentScene
+ val currentScene: StateFlow<SceneKey> =
+ repository.currentScene
+ .pairwiseBy(initialValue = repository.currentScene.value) { from, to ->
+ logger.logSceneChangeCommitted(
+ from = from,
+ to = to,
+ )
+ to
+ }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.Eagerly,
+ initialValue = repository.currentScene.value,
+ )
private val _editModeState = MutableStateFlow<EditModeState?>(null)
/**
@@ -134,7 +168,13 @@
/** Transition state of the hub mode. */
val transitionState: StateFlow<ObservableTransitionState> =
- communalSceneRepository.transitionState
+ repository.transitionState
+ .onEach { logger.logSceneTransition(it) }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.Eagerly,
+ initialValue = repository.transitionState.value,
+ )
/**
* Updates the transition state of the hub [SceneTransitionLayout].
@@ -142,7 +182,7 @@
* Note that you must call is with `null` when the UI is done or risk a memory leak.
*/
fun setTransitionState(transitionState: Flow<ObservableTransitionState>?) {
- communalSceneRepository.setTransitionState(transitionState)
+ repository.setTransitionState(transitionState)
}
/** Returns a flow that tracks the progress of transitions to the given scene from 0-1. */
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/log/CommunalSceneLogger.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/log/CommunalSceneLogger.kt
new file mode 100644
index 0000000..aed9215
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/shared/log/CommunalSceneLogger.kt
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2024 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.communal.shared.log
+
+import com.android.compose.animation.scene.ObservableTransitionState
+import com.android.compose.animation.scene.SceneKey
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.log.dagger.CommunalLog
+import javax.inject.Inject
+
+class CommunalSceneLogger @Inject constructor(@CommunalLog private val logBuffer: LogBuffer) {
+
+ fun logSceneChangeRequested(
+ from: SceneKey,
+ to: SceneKey,
+ reason: String,
+ isInstant: Boolean,
+ ) {
+ logBuffer.log(
+ tag = TAG,
+ level = LogLevel.INFO,
+ messageInitializer = {
+ str1 = from.toString()
+ str2 = to.toString()
+ str3 = reason
+ bool1 = isInstant
+ },
+ messagePrinter = {
+ buildString {
+ append("Scene change requested: $str1 → $str2")
+ if (isInstant) {
+ append(" (instant)")
+ }
+ append(", reason: $str3")
+ }
+ },
+ )
+ }
+
+ fun logSceneChangeCommitted(
+ from: SceneKey,
+ to: SceneKey,
+ ) {
+ logBuffer.log(
+ tag = TAG,
+ level = LogLevel.INFO,
+ messageInitializer = {
+ str1 = from.toString()
+ str2 = to.toString()
+ },
+ messagePrinter = { "Scene change committed: $str1 → $str2" },
+ )
+ }
+
+ fun logSceneTransition(transitionState: ObservableTransitionState) {
+ when (transitionState) {
+ is ObservableTransitionState.Transition -> {
+ logBuffer.log(
+ tag = TAG,
+ level = LogLevel.INFO,
+ messageInitializer = {
+ str1 = transitionState.fromScene.toString()
+ str2 = transitionState.toScene.toString()
+ },
+ messagePrinter = { "Scene transition started: $str1 → $str2" },
+ )
+ }
+ is ObservableTransitionState.Idle -> {
+ logBuffer.log(
+ tag = TAG,
+ level = LogLevel.INFO,
+ messageInitializer = { str1 = transitionState.currentScene.toString() },
+ messagePrinter = { "Scene transition idle on: $str1" },
+ )
+ }
+ }
+ }
+
+ companion object {
+ private const val TAG = "CommunalSceneLogger"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
index d1a5a4b..b822133 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
@@ -106,10 +106,11 @@
*/
fun changeScene(
scene: SceneKey,
+ loggingReason: String,
transitionKey: TransitionKey? = null,
keyguardState: KeyguardState? = null
) {
- communalSceneInteractor.changeScene(scene, transitionKey, keyguardState)
+ communalSceneInteractor.changeScene(scene, loggingReason, transitionKey, keyguardState)
}
fun setEditModeState(state: EditModeState?) = communalSceneInteractor.setEditModeState(state)
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt
index bbd8596..6239373 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt
@@ -67,7 +67,10 @@
* transition.
*/
fun snapToCommunal() {
- communalSceneInteractor.snapToScene(CommunalScenes.Communal)
+ communalSceneInteractor.snapToScene(
+ newScene = CommunalScenes.Communal,
+ loggingReason = "transition view model",
+ )
}
// Show UMO on glanceable hub immediately on transition into glanceable hub
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalTransitionAnimatorController.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalTransitionAnimatorController.kt
index 0844462..e7cedc6 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalTransitionAnimatorController.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalTransitionAnimatorController.kt
@@ -42,6 +42,7 @@
// TODO(b/330672236): move this to onTransitionAnimationEnd() without the delay.
communalSceneInteractor.snapToScene(
CommunalScenes.Blank,
+ "CommunalTransitionAnimatorController",
ActivityTransitionAnimator.TIMINGS.totalDuration
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
index 668fef6..6d7cdc4 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
@@ -218,9 +218,10 @@
lifecycleScope.launch {
communalViewModel.canShowEditMode.collect {
communalViewModel.changeScene(
- CommunalScenes.Blank,
- CommunalTransitionKeys.ToEditMode,
- KeyguardState.GONE,
+ scene = CommunalScenes.Blank,
+ loggingReason = "edit mode opening",
+ transitionKey = CommunalTransitionKeys.ToEditMode,
+ keyguardState = KeyguardState.GONE,
)
// wait till transitioned to Blank scene, then animate in communal content in
// edit mode
@@ -252,8 +253,9 @@
communalViewModel.cleanupEditModeState()
communalViewModel.changeScene(
- CommunalScenes.Communal,
- CommunalTransitionKeys.FromEditMode
+ scene = CommunalScenes.Communal,
+ loggingReason = "edit mode closing",
+ transitionKey = CommunalTransitionKeys.FromEditMode
)
// Wait for the current scene to be idle on communal.
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
index 4b9e5a0..0c1fb72 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
@@ -442,7 +442,9 @@
@Override
public void onWakeRequested() {
mUiEventLogger.log(CommunalUiEvent.DREAM_TO_COMMUNAL_HUB_DREAM_AWAKE_START);
- mCommunalInteractor.changeScene(CommunalScenes.Communal, null);
+ mCommunalInteractor.changeScene(CommunalScenes.Communal,
+ "dream wake requested",
+ null);
}
private Lifecycle.State getLifecycleStateLocked() {
@@ -493,7 +495,7 @@
mSystemDialogsCloser.closeSystemDialogs();
// Hide glanceable hub (this is a nop if glanceable hub is not open).
- mCommunalInteractor.changeScene(CommunalScenes.Blank, null);
+ mCommunalInteractor.changeScene(CommunalScenes.Blank, "dream come to front", null);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt b/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt
index 4b07f78..5c0335a6 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt
@@ -20,9 +20,9 @@
import com.android.systemui.Flags.glanceableHubAllowKeyguardWhenDreaming
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
import com.android.systemui.communal.domain.interactor.CommunalInteractor
-import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dump.DumpManager
+import com.android.systemui.keyguard.domain.interactor.FromDreamingTransitionInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.Edge
import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
@@ -51,6 +51,7 @@
fromGlanceableHubTransitionInteractor: GlanceableHubToDreamingTransitionViewModel,
toGlanceableHubTransitionViewModel: DreamingToGlanceableHubTransitionViewModel,
private val toLockscreenTransitionViewModel: DreamingToLockscreenTransitionViewModel,
+ private val fromDreamingTransitionInteractor: FromDreamingTransitionInteractor,
private val communalInteractor: CommunalInteractor,
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
private val userTracker: UserTracker,
@@ -61,11 +62,9 @@
val showGlanceableHub =
communalInteractor.isCommunalEnabled.value &&
!keyguardUpdateMonitor.isEncryptedOrLockdown(userTracker.userId)
- if (showGlanceableHub && !glanceableHubAllowKeyguardWhenDreaming()) {
- communalInteractor.changeScene(CommunalScenes.Communal)
- } else {
- toLockscreenTransitionViewModel.startTransition()
- }
+ fromDreamingTransitionInteractor.startToLockscreenOrGlanceableHubTransition(
+ showGlanceableHub && !glanceableHubAllowKeyguardWhenDreaming()
+ )
}
val dreamOverlayTranslationX: Flow<Float> =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
index 0c12f8c..90aaf0d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
@@ -273,9 +273,10 @@
private suspend fun transitionToGlanceableHub() {
if (communalSceneKtfRefactor()) {
communalSceneInteractor.changeScene(
- CommunalScenes.Communal,
+ newScene = CommunalScenes.Communal,
+ loggingReason = "from dozing to hub",
// Immediately show the hub when transitioning from dozing to hub.
- CommunalTransitionKeys.Immediately,
+ transitionKey = CommunalTransitionKeys.Immediately,
)
} else {
startTransitionTo(KeyguardState.GLANCEABLE_HUB)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
index 7bf9c2f1..4666430 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
@@ -20,7 +20,9 @@
import com.android.app.animation.Interpolators
import com.android.app.tracing.coroutines.launch
import com.android.systemui.Flags.communalSceneKtfRefactor
+import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
+import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
@@ -58,6 +60,7 @@
@Main mainDispatcher: CoroutineDispatcher,
keyguardInteractor: KeyguardInteractor,
private val glanceableHubTransitions: GlanceableHubTransitions,
+ private val communalSceneInteractor: CommunalSceneInteractor,
private val communalSettingsInteractor: CommunalSettingsInteractor,
powerInteractor: PowerInteractor,
keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
@@ -126,17 +129,24 @@
}
}
- fun startToLockscreenTransition() {
+ fun startToLockscreenOrGlanceableHubTransition(openHub: Boolean) {
scope.launch {
if (
transitionInteractor.startedKeyguardState.replayCache.last() ==
KeyguardState.DREAMING
) {
if (powerInteractor.detailedWakefulness.value.isAwake()) {
- startTransitionTo(
- KeyguardState.LOCKSCREEN,
- ownerReason = "Dream has ended and device is awake"
- )
+ if (openHub) {
+ communalSceneInteractor.changeScene(
+ newScene = CommunalScenes.Communal,
+ loggingReason = "FromDreamingTransitionInteractor",
+ )
+ } else {
+ startTransitionTo(
+ KeyguardState.LOCKSCREEN,
+ ownerReason = "Dream has ended and device is awake"
+ )
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
index befcc9e..c9db26d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
@@ -159,6 +159,7 @@
if (communalSceneKtfRefactor()) {
communalSceneInteractor.changeScene(
newScene = CommunalScenes.Blank,
+ loggingReason = "hub to dozing",
transitionKey = CommunalTransitionKeys.Immediately,
keyguardState = KeyguardState.DOZING,
)
@@ -182,6 +183,7 @@
if (communalSceneKtfRefactor()) {
communalSceneInteractor.changeScene(
newScene = CommunalScenes.Blank,
+ loggingReason = "hub to occluded (KeyguardWmStateRefactor)",
transitionKey = CommunalTransitionKeys.SimpleFade,
keyguardState = state,
)
@@ -211,6 +213,7 @@
.collect { _ ->
communalSceneInteractor.changeScene(
newScene = CommunalScenes.Blank,
+ loggingReason = "hub to occluded",
transitionKey = CommunalTransitionKeys.SimpleFade,
keyguardState = KeyguardState.OCCLUDED,
)
@@ -254,6 +257,7 @@
} else {
communalSceneInteractor.changeScene(
newScene = CommunalScenes.Blank,
+ loggingReason = "hub to gone",
transitionKey = CommunalTransitionKeys.SimpleFade,
keyguardState = KeyguardState.GONE
)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
index 905ca8e..7b6949f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
@@ -146,8 +146,9 @@
if (SceneContainerFlag.isEnabled) return
if (communalSceneKtfRefactor()) {
communalSceneInteractor.changeScene(
- CommunalScenes.Communal,
- CommunalTransitionKeys.SimpleFade
+ newScene = CommunalScenes.Communal,
+ loggingReason = "occluded to hub",
+ transitionKey = CommunalTransitionKeys.SimpleFade
)
} else {
startTransitionTo(KeyguardState.GLANCEABLE_HUB)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
index 2823b93..0118f8e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
@@ -175,7 +175,10 @@
!communalSceneInteractor.isLaunchingWidget.value &&
communalSceneInteractor.editModeState.value == null
) {
- communalSceneInteractor.snapToScene(CommunalScenes.Blank)
+ communalSceneInteractor.snapToScene(
+ newScene = CommunalScenes.Blank,
+ loggingReason = "FromPrimaryBouncerTransitionInteractor",
+ )
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AccessibilityActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AccessibilityActionsViewModel.kt
index 34c1436..38f5d3e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AccessibilityActionsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AccessibilityActionsViewModel.kt
@@ -50,5 +50,9 @@
}
.distinctUntilChanged()
- fun openCommunalHub() = communalInteractor.changeScene(CommunalScenes.Communal)
+ fun openCommunalHub() =
+ communalInteractor.changeScene(
+ newScene = CommunalScenes.Communal,
+ loggingReason = "accessibility",
+ )
}
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
index b5ec7a6..10605b2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt
@@ -18,7 +18,6 @@
import com.android.app.animation.Interpolators.EMPHASIZED
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyguard.domain.interactor.FromDreamingTransitionInteractor
import com.android.systemui.keyguard.domain.interactor.FromDreamingTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION
import com.android.systemui.keyguard.shared.model.Edge
import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
@@ -39,10 +38,8 @@
class DreamingToLockscreenTransitionViewModel
@Inject
constructor(
- private val fromDreamingTransitionInteractor: FromDreamingTransitionInteractor,
animationFlow: KeyguardTransitionAnimationFlow,
) : DeviceEntryIconTransition {
- fun startTransition() = fromDreamingTransitionInteractor.startToLockscreenTransition()
private val transitionAnimation =
animationFlow.setup(
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragmentStartable.kt b/packages/SystemUI/src/com/android/systemui/qs/QSFragmentStartable.kt
index 9fa6769..bb238f2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragmentStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragmentStartable.kt
@@ -19,6 +19,7 @@
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.fragments.FragmentService
+import com.android.systemui.qs.composefragment.QSFragmentCompose
import dagger.Binds
import dagger.Module
import dagger.multibindings.ClassKey
@@ -31,13 +32,18 @@
@Inject
constructor(
private val fragmentService: FragmentService,
- private val qsFragmentLegacyProvider: Provider<QSFragmentLegacy>
+ private val qsFragmentLegacyProvider: Provider<QSFragmentLegacy>,
+ private val qsFragmentComposeProvider: Provider<QSFragmentCompose>,
) : CoreStartable {
override fun start() {
fragmentService.addFragmentInstantiationProvider(
QSFragmentLegacy::class.java,
qsFragmentLegacyProvider
)
+ fragmentService.addFragmentInstantiationProvider(
+ QSFragmentCompose::class.java,
+ qsFragmentComposeProvider
+ )
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSModesEvent.kt b/packages/SystemUI/src/com/android/systemui/qs/QSModesEvent.kt
new file mode 100644
index 0000000..1891c41
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSModesEvent.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs
+
+import com.android.internal.logging.UiEvent
+import com.android.internal.logging.UiEventLogger
+
+/** Events of user interactions with modes from the QS Modes dialog. {@see ModesDialogViewModel} */
+enum class QSModesEvent(private val _id: Int) : UiEventLogger.UiEventEnum {
+ @UiEvent(doc = "User turned manual Do Not Disturb on via modes dialog") QS_MODES_DND_ON(1870),
+ @UiEvent(doc = "User turned manual Do Not Disturb off via modes dialog") QS_MODES_DND_OFF(1871),
+ @UiEvent(doc = "User opened mode settings from the Do Not Disturb tile in the modes dialog")
+ QS_MODES_DND_SETTINGS(1872),
+ @UiEvent(doc = "User turned automatic mode on via modes dialog") QS_MODES_MODE_ON(1873),
+ @UiEvent(doc = "User turned automatic mode off via modes dialog") QS_MODES_MODE_OFF(1874),
+ @UiEvent(doc = "User opened mode settings from a mode tile in the modes dialog")
+ QS_MODES_MODE_SETTINGS(1875),
+ @UiEvent(doc = "User clicked on Settings from the modes dialog") QS_MODES_SETTINGS(1876),
+ @UiEvent(doc = "User clicked on Do Not Disturb tile, opening the time selection dialog")
+ QS_MODES_DURATION_DIALOG(1879);
+
+ override fun getId() = _id
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
new file mode 100644
index 0000000..5d81d4f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
@@ -0,0 +1,442 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.composefragment
+
+import android.annotation.SuppressLint
+import android.graphics.Rect
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.activity.OnBackPressedDispatcher
+import androidx.activity.OnBackPressedDispatcherOwner
+import androidx.activity.setViewTreeOnBackPressedDispatcherOwner
+import androidx.compose.animation.AnimatedContent
+import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.WindowInsets
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.navigationBars
+import androidx.compose.foundation.layout.windowInsetsPadding
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.layout
+import androidx.compose.ui.layout.onGloballyPositioned
+import androidx.compose.ui.layout.positionInRoot
+import androidx.compose.ui.platform.ComposeView
+import androidx.compose.ui.res.dimensionResource
+import androidx.compose.ui.unit.round
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.compose.modifiers.height
+import com.android.compose.modifiers.padding
+import com.android.compose.theme.PlatformTheme
+import com.android.systemui.compose.modifiers.sysuiResTag
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.plugins.qs.QS
+import com.android.systemui.plugins.qs.QSContainerController
+import com.android.systemui.qs.composefragment.viewmodel.QSFragmentComposeViewModel
+import com.android.systemui.qs.flags.QSComposeFragment
+import com.android.systemui.qs.footer.ui.compose.FooterActions
+import com.android.systemui.qs.panels.ui.compose.QuickQuickSettings
+import com.android.systemui.qs.ui.composable.QuickSettingsTheme
+import com.android.systemui.qs.ui.composable.ShadeBody
+import com.android.systemui.res.R
+import com.android.systemui.util.LifecycleFragment
+import java.util.function.Consumer
+import javax.inject.Inject
+import kotlinx.coroutines.awaitCancellation
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.launch
+
+@SuppressLint("ValidFragment")
+class QSFragmentCompose
+@Inject
+constructor(
+ private val qsFragmentComposeViewModelFactory: QSFragmentComposeViewModel.Factory,
+) : LifecycleFragment(), QS {
+
+ private val scrollListener = MutableStateFlow<QS.ScrollListener?>(null)
+ private val heightListener = MutableStateFlow<QS.HeightListener?>(null)
+ private val qsContainerController = MutableStateFlow<QSContainerController?>(null)
+
+ private lateinit var viewModel: QSFragmentComposeViewModel
+
+ // Starting with a non-zero value makes it so that it has a non-zero height on first expansion
+ // This is important for `QuickSettingsControllerImpl.mMinExpansionHeight` to detect a "change".
+ private val qqsHeight = MutableStateFlow(1)
+ private val qsHeight = MutableStateFlow(0)
+ private val qqsVisible = MutableStateFlow(false)
+ private val qqsPositionOnRoot = Rect()
+ private val composeViewPositionOnScreen = Rect()
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ QSComposeFragment.isUnexpectedlyInLegacyMode()
+ viewModel = qsFragmentComposeViewModelFactory.create(lifecycleScope)
+
+ setListenerCollections()
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View {
+ val context = inflater.context
+ return ComposeView(context).apply {
+ setBackPressedDispatcher()
+ setContent {
+ PlatformTheme {
+ val visible by viewModel.qsVisible.collectAsStateWithLifecycle()
+ val qsState by viewModel.expansionState.collectAsStateWithLifecycle()
+
+ AnimatedVisibility(
+ visible = visible,
+ modifier = Modifier.windowInsetsPadding(WindowInsets.navigationBars)
+ ) {
+ AnimatedContent(targetState = qsState) {
+ when (it) {
+ QSFragmentComposeViewModel.QSExpansionState.QQS -> {
+ QuickQuickSettingsElement()
+ }
+ QSFragmentComposeViewModel.QSExpansionState.QS -> {
+ QuickSettingsElement()
+ }
+ else -> {}
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ override fun setPanelView(notificationPanelView: QS.HeightListener?) {
+ heightListener.value = notificationPanelView
+ }
+
+ override fun hideImmediately() {
+ // view?.animate()?.cancel()
+ // view?.y = -qsMinExpansionHeight.toFloat()
+ }
+
+ override fun getQsMinExpansionHeight(): Int {
+ // TODO (b/353253277) implement split screen
+ return qqsHeight.value
+ }
+
+ override fun getDesiredHeight(): Int {
+ /*
+ * Looking at the code, it seems that
+ * * If customizing, then the height is that of the view post-layout, which is set by
+ * QSContainerImpl.calculateContainerHeight, which is the height the customizer takes
+ * * If not customizing, it's the measured height. So we may want to surface that.
+ */
+ return view?.height ?: 0
+ }
+
+ override fun setHeightOverride(desiredHeight: Int) {
+ viewModel.heightOverrideValue = desiredHeight
+ }
+
+ override fun setHeaderClickable(qsExpansionEnabled: Boolean) {
+ // Empty method
+ }
+
+ override fun isCustomizing(): Boolean {
+ return viewModel.containerViewModel.editModeViewModel.isEditing.value
+ }
+
+ override fun closeCustomizer() {
+ viewModel.containerViewModel.editModeViewModel.stopEditing()
+ }
+
+ override fun setOverscrolling(overscrolling: Boolean) {
+ viewModel.stackScrollerOverscrollingValue = overscrolling
+ }
+
+ override fun setExpanded(qsExpanded: Boolean) {
+ viewModel.isQSExpanded = qsExpanded
+ }
+
+ override fun setListening(listening: Boolean) {
+ // Not needed, views start listening and collection when composed
+ }
+
+ override fun setQsVisible(qsVisible: Boolean) {
+ viewModel.isQSVisible = qsVisible
+ }
+
+ override fun isShowingDetail(): Boolean {
+ return isCustomizing
+ }
+
+ override fun closeDetail() {
+ closeCustomizer()
+ }
+
+ override fun animateHeaderSlidingOut() {
+ // TODO(b/353254353)
+ }
+
+ override fun setQsExpansion(
+ qsExpansionFraction: Float,
+ panelExpansionFraction: Float,
+ headerTranslation: Float,
+ squishinessFraction: Float
+ ) {
+ viewModel.qsExpansionValue = qsExpansionFraction
+ viewModel.panelExpansionFractionValue = panelExpansionFraction
+ viewModel.squishinessFractionValue = squishinessFraction
+
+ // TODO(b/353254353) Handle header translation
+ }
+
+ override fun setHeaderListening(listening: Boolean) {
+ // Not needed, header will start listening as soon as it's composed
+ }
+
+ override fun notifyCustomizeChanged() {
+ // Not needed, only called from inside customizer
+ }
+
+ override fun setContainerController(controller: QSContainerController?) {
+ qsContainerController.value = controller
+ }
+
+ override fun setCollapseExpandAction(action: Runnable?) {
+ // Nothing to do yet. But this should be wired to a11y
+ }
+
+ override fun getHeightDiff(): Int {
+ return 0 // For now TODO(b/353254353)
+ }
+
+ override fun getHeader(): View? {
+ QSComposeFragment.isUnexpectedlyInLegacyMode()
+ return null
+ }
+
+ override fun setShouldUpdateSquishinessOnMedia(shouldUpdate: Boolean) {
+ super.setShouldUpdateSquishinessOnMedia(shouldUpdate)
+ // TODO (b/353253280)
+ }
+
+ override fun setInSplitShade(shouldTranslate: Boolean) {
+ // TODO (b/356435605)
+ }
+
+ override fun setTransitionToFullShadeProgress(
+ isTransitioningToFullShade: Boolean,
+ qsTransitionFraction: Float,
+ qsSquishinessFraction: Float
+ ) {
+ super.setTransitionToFullShadeProgress(
+ isTransitioningToFullShade,
+ qsTransitionFraction,
+ qsSquishinessFraction
+ )
+ }
+
+ override fun setFancyClipping(
+ leftInset: Int,
+ top: Int,
+ rightInset: Int,
+ bottom: Int,
+ cornerRadius: Int,
+ visible: Boolean,
+ fullWidth: Boolean
+ ) {}
+
+ override fun isFullyCollapsed(): Boolean {
+ return !viewModel.isQSVisible
+ }
+
+ override fun setCollapsedMediaVisibilityChangedListener(listener: Consumer<Boolean>?) {
+ // TODO (b/353253280)
+ }
+
+ override fun setScrollListener(scrollListener: QS.ScrollListener?) {
+ this.scrollListener.value = scrollListener
+ }
+
+ override fun setOverScrollAmount(overScrollAmount: Int) {
+ super.setOverScrollAmount(overScrollAmount)
+ }
+
+ override fun setIsNotificationPanelFullWidth(isFullWidth: Boolean) {
+ viewModel.isSmallScreenValue = isFullWidth
+ }
+
+ override fun getHeaderTop(): Int {
+ return viewModel.qqsHeaderHeight.value
+ }
+
+ override fun getHeaderBottom(): Int {
+ return headerTop + qqsHeight.value
+ }
+
+ override fun getHeaderLeft(): Int {
+ return qqsPositionOnRoot.left
+ }
+
+ override fun getHeaderBoundsOnScreen(outBounds: Rect) {
+ outBounds.set(qqsPositionOnRoot)
+ view?.getBoundsOnScreen(composeViewPositionOnScreen)
+ ?: run { composeViewPositionOnScreen.setEmpty() }
+ qqsPositionOnRoot.offset(composeViewPositionOnScreen.left, composeViewPositionOnScreen.top)
+ }
+
+ override fun isHeaderShown(): Boolean {
+ return qqsVisible.value
+ }
+
+ private fun setListenerCollections() {
+ lifecycleScope.launch {
+ lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
+ launch {
+ // TODO
+ // setListenerJob(
+ // scrollListener,
+ //
+ // )
+ }
+ launch {
+ setListenerJob(
+ heightListener,
+ viewModel.containerViewModel.editModeViewModel.isEditing
+ ) {
+ onQsHeightChanged()
+ }
+ }
+ launch {
+ setListenerJob(
+ qsContainerController,
+ viewModel.containerViewModel.editModeViewModel.isEditing
+ ) {
+ setCustomizerShowing(it)
+ }
+ }
+ }
+ }
+ }
+
+ @Composable
+ private fun QuickQuickSettingsElement() {
+ val qqsPadding by viewModel.qqsHeaderHeight.collectAsStateWithLifecycle()
+ DisposableEffect(Unit) {
+ qqsVisible.value = true
+
+ onDispose { qqsVisible.value = false }
+ }
+ Column(modifier = Modifier.sysuiResTag("quick_qs_panel")) {
+ QuickQuickSettings(
+ viewModel = viewModel.containerViewModel.quickQuickSettingsViewModel,
+ modifier =
+ Modifier.onGloballyPositioned { coordinates ->
+ val (leftFromRoot, topFromRoot) = coordinates.positionInRoot().round()
+ val (width, height) = coordinates.size
+ qqsPositionOnRoot.set(
+ leftFromRoot,
+ topFromRoot,
+ leftFromRoot + width,
+ topFromRoot + height
+ )
+ }
+ .layout { measurable, constraints ->
+ val placeable = measurable.measure(constraints)
+ qqsHeight.value = placeable.height
+
+ layout(placeable.width, placeable.height) { placeable.place(0, 0) }
+ }
+ .padding(top = { qqsPadding })
+ )
+ Spacer(modifier = Modifier.weight(1f))
+ }
+ }
+
+ @Composable
+ private fun QuickSettingsElement() {
+ val qqsPadding by viewModel.qqsHeaderHeight.collectAsStateWithLifecycle()
+ val qsExtraPadding = dimensionResource(R.dimen.qs_panel_padding_top)
+ Column {
+ Box(modifier = Modifier.fillMaxSize().weight(1f)) {
+ Column {
+ Spacer(modifier = Modifier.height { qqsPadding + qsExtraPadding.roundToPx() })
+ ShadeBody(viewModel = viewModel.containerViewModel)
+ }
+ }
+ QuickSettingsTheme {
+ FooterActions(
+ viewModel = viewModel.footerActionsViewModel,
+ qsVisibilityLifecycleOwner = this@QSFragmentCompose,
+ modifier = Modifier.sysuiResTag("qs_footer_actions")
+ )
+ }
+ }
+ }
+}
+
+private fun View.setBackPressedDispatcher() {
+ repeatWhenAttached {
+ repeatOnLifecycle(Lifecycle.State.CREATED) {
+ setViewTreeOnBackPressedDispatcherOwner(
+ object : OnBackPressedDispatcherOwner {
+ override val onBackPressedDispatcher =
+ OnBackPressedDispatcher().apply {
+ setOnBackInvokedDispatcher(it.viewRootImpl.onBackInvokedDispatcher)
+ }
+
+ override val lifecycle: Lifecycle = this@repeatWhenAttached.lifecycle
+ }
+ )
+ }
+ }
+}
+
+private suspend inline fun <Listener : Any, Data> setListenerJob(
+ listenerFlow: MutableStateFlow<Listener?>,
+ dataFlow: Flow<Data>,
+ crossinline onCollect: suspend Listener.(Data) -> Unit
+) {
+ coroutineScope {
+ try {
+ listenerFlow.collectLatest { listenerOrNull ->
+ listenerOrNull?.let { currentListener ->
+ launch {
+ // Called when editing mode changes
+ dataFlow.collect { currentListener.onCollect(it) }
+ }
+ }
+ }
+ awaitCancellation()
+ } finally {
+ listenerFlow.value = null
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt
new file mode 100644
index 0000000..9e109e4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.composefragment.viewmodel
+
+import android.content.res.Resources
+import android.graphics.Rect
+import androidx.lifecycle.LifecycleCoroutineScope
+import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.qs.FooterActionsController
+import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
+import com.android.systemui.qs.ui.viewmodel.QuickSettingsContainerViewModel
+import com.android.systemui.shade.LargeScreenHeaderHelper
+import com.android.systemui.shade.transition.LargeScreenShadeInterpolator
+import com.android.systemui.statusbar.SysuiStatusBarStateController
+import com.android.systemui.statusbar.disableflags.data.repository.DisableFlagsRepository
+import com.android.systemui.statusbar.phone.KeyguardBypassController
+import com.android.systemui.util.LargeScreenUtils
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
+
+class QSFragmentComposeViewModel
+@AssistedInject
+constructor(
+ val containerViewModel: QuickSettingsContainerViewModel,
+ @Main private val resources: Resources,
+ private val footerActionsViewModelFactory: FooterActionsViewModel.Factory,
+ private val footerActionsController: FooterActionsController,
+ private val sysuiStatusBarStateController: SysuiStatusBarStateController,
+ private val keyguardBypassController: KeyguardBypassController,
+ private val disableFlagsRepository: DisableFlagsRepository,
+ private val largeScreenShadeInterpolator: LargeScreenShadeInterpolator,
+ private val configurationInteractor: ConfigurationInteractor,
+ private val largeScreenHeaderHelper: LargeScreenHeaderHelper,
+ @Assisted private val lifecycleScope: LifecycleCoroutineScope,
+) {
+ val footerActionsViewModel =
+ footerActionsViewModelFactory.create(lifecycleScope).also {
+ lifecycleScope.launch { footerActionsController.init() }
+ }
+
+ private val _qsBounds = MutableStateFlow(Rect())
+
+ private val _qsExpanded = MutableStateFlow(false)
+ var isQSExpanded: Boolean
+ get() = _qsExpanded.value
+ set(value) {
+ _qsExpanded.value = value
+ }
+
+ private val _qsVisible = MutableStateFlow(false)
+ val qsVisible = _qsVisible.asStateFlow()
+ var isQSVisible: Boolean
+ get() = qsVisible.value
+ set(value) {
+ _qsVisible.value = value
+ }
+
+ private val _qsExpansion = MutableStateFlow(0f)
+ var qsExpansionValue: Float
+ get() = _qsExpansion.value
+ set(value) {
+ _qsExpansion.value = value
+ }
+
+ private val _panelFraction = MutableStateFlow(0f)
+ var panelExpansionFractionValue: Float
+ get() = _panelFraction.value
+ set(value) {
+ _panelFraction.value = value
+ }
+
+ private val _squishinessFraction = MutableStateFlow(0f)
+ var squishinessFractionValue: Float
+ get() = _squishinessFraction.value
+ set(value) {
+ _squishinessFraction.value = value
+ }
+
+ val qqsHeaderHeight =
+ configurationInteractor.onAnyConfigurationChange
+ .map {
+ if (LargeScreenUtils.shouldUseLargeScreenShadeHeader(resources)) {
+ 0
+ } else {
+ largeScreenHeaderHelper.getLargeScreenHeaderHeight()
+ }
+ }
+ .stateIn(lifecycleScope, SharingStarted.WhileSubscribed(), 0)
+
+ private val _headerAnimating = MutableStateFlow(false)
+
+ private val _stackScrollerOverscrolling = MutableStateFlow(false)
+ var stackScrollerOverscrollingValue: Boolean
+ get() = _stackScrollerOverscrolling.value
+ set(value) {
+ _stackScrollerOverscrolling.value = value
+ }
+
+ private val qsDisabled =
+ disableFlagsRepository.disableFlags
+ .map { !it.isQuickSettingsEnabled() }
+ .stateIn(
+ lifecycleScope,
+ SharingStarted.WhileSubscribed(),
+ !disableFlagsRepository.disableFlags.value.isQuickSettingsEnabled()
+ )
+
+ private val _showCollapsedOnKeyguard = MutableStateFlow(false)
+
+ private val _keyguardAndExpanded = MutableStateFlow(false)
+
+ private val _statusBarState = MutableStateFlow(-1)
+
+ private val _viewHeight = MutableStateFlow(0)
+
+ private val _headerTranslation = MutableStateFlow(0f)
+
+ private val _inSplitShade = MutableStateFlow(false)
+
+ private val _transitioningToFullShade = MutableStateFlow(false)
+
+ private val _lockscreenToShadeProgress = MutableStateFlow(false)
+
+ private val _overscrolling = MutableStateFlow(false)
+
+ private val _isSmallScreen = MutableStateFlow(false)
+ var isSmallScreenValue: Boolean
+ get() = _isSmallScreen.value
+ set(value) {
+ _isSmallScreen.value = value
+ }
+
+ private val _shouldUpdateMediaSquishiness = MutableStateFlow(false)
+
+ private val _heightOverride = MutableStateFlow(-1)
+ val heightOverride = _heightOverride.asStateFlow()
+ var heightOverrideValue: Int
+ get() = heightOverride.value
+ set(value) {
+ _heightOverride.value = value
+ }
+
+ val expansionState: StateFlow<QSExpansionState> =
+ combine(
+ _stackScrollerOverscrolling,
+ _qsExpanded,
+ _qsExpansion,
+ ) { args: Array<Any> ->
+ val expansion = args[2] as Float
+ if (expansion > 0.5f) {
+ QSExpansionState.QS
+ } else {
+ QSExpansionState.QQS
+ }
+ }
+ .stateIn(lifecycleScope, SharingStarted.WhileSubscribed(), QSExpansionState.QQS)
+
+ @AssistedFactory
+ interface Factory {
+ fun create(lifecycleScope: LifecycleCoroutineScope): QSFragmentComposeViewModel
+ }
+
+ sealed interface QSExpansionState {
+ data object QQS : QSExpansionState
+
+ data object QS : QSExpansionState
+
+ @JvmInline value class Expanding(val progress: Float) : QSExpansionState
+
+ @JvmInline value class Collapsing(val progress: Float) : QSExpansionState
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt
index ba45d17..6dc101a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt
@@ -21,6 +21,7 @@
import android.view.ContextThemeWrapper
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleCoroutineScope
import androidx.lifecycle.LifecycleOwner
import com.android.settingslib.Utils
import com.android.systemui.animation.Expandable
@@ -41,6 +42,7 @@
import javax.inject.Named
import javax.inject.Provider
import kotlin.math.max
+import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@@ -48,6 +50,8 @@
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.isActive
+import kotlinx.coroutines.launch
private const val TAG = "FooterActionsViewModel"
@@ -140,6 +144,30 @@
showPowerButton,
)
}
+
+ fun create(lifecycleCoroutineScope: LifecycleCoroutineScope): FooterActionsViewModel {
+ val globalActionsDialogLite = globalActionsDialogLiteProvider.get()
+ if (lifecycleCoroutineScope.isActive) {
+ lifecycleCoroutineScope.launch {
+ try {
+ awaitCancellation()
+ } finally {
+ globalActionsDialogLite.destroy()
+ }
+ }
+ } else {
+ globalActionsDialogLite.destroy()
+ }
+
+ return FooterActionsViewModel(
+ context,
+ footerActionsInteractor,
+ falsingManager,
+ globalActionsDialogLite,
+ activityStarter,
+ showPowerButton,
+ )
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt
index 2ee957e..08a56bf 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt
@@ -39,6 +39,7 @@
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.android.systemui.compose.modifiers.sysuiResTag
import com.android.systemui.qs.panels.dagger.PaginatedBaseLayoutType
import com.android.systemui.qs.panels.ui.compose.PaginatedGridLayout.Dimensions.FooterHeight
import com.android.systemui.qs.panels.ui.compose.PaginatedGridLayout.Dimensions.InterPageSpacing
@@ -77,7 +78,7 @@
Column {
HorizontalPager(
state = pagerState,
- modifier = Modifier,
+ modifier = Modifier.sysuiResTag("qs_pager"),
pageSpacing = if (pages.size > 1) InterPageSpacing else 0.dp,
beyondViewportPageCount = 1,
verticalAlignment = Alignment.Top,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt
index af3803b..a9027ff 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt
@@ -25,6 +25,7 @@
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.dimensionResource
import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.android.systemui.compose.modifiers.sysuiResTag
import com.android.systemui.qs.panels.ui.viewmodel.QuickQuickSettingsViewModel
import com.android.systemui.res.R
@@ -44,7 +45,10 @@
}
val columns by viewModel.columns.collectAsStateWithLifecycle()
- TileLazyGrid(modifier = modifier, columns = GridCells.Fixed(columns)) {
+ TileLazyGrid(
+ modifier = modifier.sysuiResTag("qqs_tile_layout"),
+ columns = GridCells.Fixed(columns)
+ ) {
items(
tiles.size,
key = { index -> sizedTiles[index].tile.spec.spec },
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt
index 7e6ccd6..9c0701e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt
@@ -22,7 +22,6 @@
import android.service.quicksettings.Tile.STATE_ACTIVE
import android.service.quicksettings.Tile.STATE_INACTIVE
import android.text.TextUtils
-import androidx.appcompat.content.res.AppCompatResources
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.fadeIn
@@ -593,15 +592,15 @@
}
@Composable
-private fun getTileIcon(icon: Supplier<QSTile.Icon>): Icon {
+private fun getTileIcon(icon: Supplier<QSTile.Icon?>): Icon {
val context = LocalContext.current
- return icon.get().let {
+ return icon.get()?.let {
if (it is QSTileImpl.ResourceIcon) {
Icon.Resource(it.resId, null)
} else {
Icon.Loaded(it.getDrawable(context), null)
}
- }
+ } ?: Icon.Resource(R.drawable.ic_error_outline, null)
}
@OptIn(ExperimentalAnimationGraphicsApi::class)
@@ -618,7 +617,7 @@
remember(icon, context) {
when (icon) {
is Icon.Loaded -> icon.drawable
- is Icon.Resource -> AppCompatResources.getDrawable(context, icon.res)
+ is Icon.Resource -> context.getDrawable(icon.res)
}
}
if (loadedDrawable !is Animatable) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileUiState.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileUiState.kt
index 4ec59c9..c83e3b2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileUiState.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileUiState.kt
@@ -25,7 +25,7 @@
val label: String,
val secondaryLabel: String,
val state: Int,
- val icon: Supplier<QSTile.Icon>,
+ val icon: Supplier<QSTile.Icon?>,
)
fun QSTile.State.toUiState(): TileUiState {
@@ -33,6 +33,6 @@
label?.toString() ?: "",
secondaryLabel?.toString() ?: "",
state,
- icon?.let { Supplier { icon } } ?: iconSupplier,
+ icon?.let { Supplier { icon } } ?: iconSupplier ?: Supplier { null },
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternalImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternalImpl.kt
index 107bf1e..d4ef42c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternalImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternalImpl.kt
@@ -613,8 +613,9 @@
super.onTransitionAnimationStart(isExpandingFullyAbove)
if (Flags.communalHub()) {
communalSceneInteractor.snapToScene(
- CommunalScenes.Blank,
- ActivityTransitionAnimator.TIMINGS.totalDuration
+ newScene = CommunalScenes.Blank,
+ loggingReason = "ActivityStarterInternalImpl",
+ delayMillis = ActivityTransitionAnimator.TIMINGS.totalDuration
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index c4fbc37..94dd9bb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -160,6 +160,8 @@
import com.android.systemui.power.domain.interactor.PowerInteractor;
import com.android.systemui.qs.QSFragmentLegacy;
import com.android.systemui.qs.QSPanelController;
+import com.android.systemui.qs.composefragment.QSFragmentCompose;
+import com.android.systemui.qs.flags.QSComposeFragment;
import com.android.systemui.res.R;
import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor;
import com.android.systemui.scene.shared.flag.SceneContainerFlag;
@@ -1432,9 +1434,15 @@
}
protected QS createDefaultQSFragment() {
+ Class<? extends QS> klass;
+ if (QSComposeFragment.isEnabled()) {
+ klass = QSFragmentCompose.class;
+ } else {
+ klass = QSFragmentLegacy.class;
+ }
return mFragmentService
.getFragmentHostManager(getNotificationShadeWindowView())
- .create(QSFragmentLegacy.class);
+ .create(klass);
}
private void setUpPresenter() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
index 04604e0..dda02db 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
@@ -32,6 +32,8 @@
import android.widget.FrameLayout;
import android.widget.LinearLayout;
+import androidx.annotation.NonNull;
+
import com.android.internal.policy.SystemBarUtils;
import com.android.systemui.Dependency;
import com.android.systemui.Flags;
@@ -47,7 +49,6 @@
public class PhoneStatusBarView extends FrameLayout {
private static final String TAG = "PhoneStatusBarView";
- private final StatusBarContentInsetsProvider mContentInsetsProvider;
private final StatusBarWindowController mStatusBarWindowController;
private int mRotationOrientation = -1;
@@ -60,6 +61,10 @@
private int mStatusBarHeight;
@Nullable
private Gefingerpoken mTouchEventHandler;
+ @Nullable
+ private HasCornerCutoutFetcher mHasCornerCutoutFetcher;
+ @Nullable
+ private InsetsFetcher mInsetsFetcher;
private int mDensity;
private float mFontScale;
@@ -70,7 +75,6 @@
public PhoneStatusBarView(Context context, AttributeSet attrs) {
super(context, attrs);
- mContentInsetsProvider = Dependency.get(StatusBarContentInsetsProvider.class);
mStatusBarWindowController = Dependency.get(StatusBarWindowController.class);
}
@@ -78,6 +82,14 @@
mTouchEventHandler = handler;
}
+ void setHasCornerCutoutFetcher(@NonNull HasCornerCutoutFetcher cornerCutoutFetcher) {
+ mHasCornerCutoutFetcher = cornerCutoutFetcher;
+ }
+
+ void setInsetsFetcher(@NonNull InsetsFetcher insetsFetcher) {
+ mInsetsFetcher = insetsFetcher;
+ }
+
void init(StatusBarUserChipViewModel viewModel) {
StatusBarUserSwitcherContainer container = findViewById(R.id.user_switcher_container);
StatusBarUserChipViewBinder.bind(container, viewModel);
@@ -270,7 +282,14 @@
return;
}
- boolean hasCornerCutout = mContentInsetsProvider.currentRotationHasCornerCutout();
+ boolean hasCornerCutout;
+ if (mHasCornerCutoutFetcher != null) {
+ hasCornerCutout = mHasCornerCutoutFetcher.fetchHasCornerCutout();
+ } else {
+ Log.e(TAG, "mHasCornerCutoutFetcher unexpectedly null");
+ hasCornerCutout = true;
+ }
+
if (mDisplayCutout == null || mDisplayCutout.isEmpty() || hasCornerCutout) {
mCutoutSpace.setVisibility(View.GONE);
return;
@@ -288,8 +307,12 @@
}
private void updateSafeInsets() {
- Insets insets = mContentInsetsProvider
- .getStatusBarContentInsetsForCurrentRotation();
+ if (mInsetsFetcher == null) {
+ Log.e(TAG, "mInsetsFetcher unexpectedly null");
+ return;
+ }
+
+ Insets insets = mInsetsFetcher.fetchInsets();
setPadding(
insets.left,
insets.top,
@@ -298,6 +321,17 @@
}
private void updateWindowHeight() {
+ if (Flags.statusBarStopUpdatingWindowHeight()) {
+ return;
+ }
mStatusBarWindowController.refreshStatusBarHeight();
}
+
+ interface HasCornerCutoutFetcher {
+ boolean fetchHasCornerCutout();
+ }
+
+ interface InsetsFetcher {
+ Insets fetchInsets();
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
index 468a3c3..456265b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
@@ -73,6 +73,7 @@
private val configurationController: ConfigurationController,
private val statusOverlayHoverListenerFactory: StatusOverlayHoverListenerFactory,
private val darkIconDispatcher: DarkIconDispatcher,
+ private val statusBarContentInsetsProvider: StatusBarContentInsetsProvider,
) : ViewController<PhoneStatusBarView>(view) {
private lateinit var battery: BatteryMeterView
@@ -155,7 +156,14 @@
}
init {
+ // These should likely be done in `onInit`, not `init`.
mView.setTouchEventHandler(PhoneStatusBarViewTouchHandler())
+ mView.setHasCornerCutoutFetcher {
+ statusBarContentInsetsProvider.currentRotationHasCornerCutout()
+ }
+ mView.setInsetsFetcher {
+ statusBarContentInsetsProvider.getStatusBarContentInsetsForCurrentRotation()
+ }
mView.init(userChipViewModel)
}
@@ -310,6 +318,7 @@
private val configurationController: ConfigurationController,
private val statusOverlayHoverListenerFactory: StatusOverlayHoverListenerFactory,
private val darkIconDispatcher: DarkIconDispatcher,
+ private val statusBarContentInsetsProvider: StatusBarContentInsetsProvider,
) {
fun create(view: PhoneStatusBarView): PhoneStatusBarViewController {
val statusBarMoveFromCenterAnimationController =
@@ -335,6 +344,7 @@
configurationController,
statusOverlayHoverListenerFactory,
darkIconDispatcher,
+ statusBarContentInsetsProvider,
)
}
}
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 5486abb..de4d14d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -1007,7 +1007,9 @@
mKeyguardMessageAreaController.setIsVisible(isShowingAlternateBouncer);
mKeyguardMessageAreaController.setMessage("");
}
- mKeyguardUpdateManager.setAlternateBouncerShowing(isShowingAlternateBouncer);
+ if (!SceneContainerFlag.isEnabled()) {
+ mKeyguardUpdateManager.setAlternateBouncerShowing(isShowingAlternateBouncer);
+ }
if (updateScrim) {
mCentralSurfaces.updateScrimController();
@@ -1449,10 +1451,13 @@
mNotificationShadeWindowController.setBouncerShowing(primaryBouncerShowing);
mCentralSurfaces.setBouncerShowing(primaryBouncerShowing);
}
- if (primaryBouncerIsOrWillBeShowing != mLastPrimaryBouncerIsOrWillBeShowing || mFirstUpdate
- || isPrimaryBouncerShowingChanged) {
- mKeyguardUpdateManager.sendPrimaryBouncerChanged(primaryBouncerIsOrWillBeShowing,
- primaryBouncerShowing);
+ if (!SceneContainerFlag.isEnabled()) {
+ if (primaryBouncerIsOrWillBeShowing != mLastPrimaryBouncerIsOrWillBeShowing
+ || mFirstUpdate
+ || isPrimaryBouncerShowingChanged) {
+ mKeyguardUpdateManager.sendPrimaryBouncerChanged(primaryBouncerIsOrWillBeShowing,
+ primaryBouncerShowing);
+ }
}
mFirstUpdate = false;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegate.kt
index 8aa989f..4f7749b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegate.kt
@@ -57,6 +57,7 @@
private val activityStarter: ActivityStarter,
// Using a provider to avoid a circular dependency.
private val viewModel: Provider<ModesDialogViewModel>,
+ private val dialogEventLogger: ModesDialogEventLogger,
@Main private val mainCoroutineContext: CoroutineContext,
) : SystemUIDialog.Delegate {
// NOTE: This should only be accessed/written from the main thread.
@@ -102,7 +103,9 @@
)
}
- private fun openSettings(dialog: SystemUIDialog) {
+ @VisibleForTesting
+ fun openSettings(dialog: SystemUIDialog) {
+ dialogEventLogger.logDialogSettings()
val animationController =
dialogTransitionAnimator.createActivityTransitionController(dialog)
if (animationController == null) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogEventLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogEventLogger.kt
new file mode 100644
index 0000000..33ed419
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogEventLogger.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2024 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.policy.ui.dialog
+
+import com.android.internal.logging.UiEventLogger
+import com.android.settingslib.notification.modes.ZenMode
+import com.android.systemui.qs.QSModesEvent
+import javax.inject.Inject
+
+class ModesDialogEventLogger
+@Inject
+constructor(
+ private val uiEventLogger: UiEventLogger,
+) {
+
+ fun logModeOn(mode: ZenMode) {
+ val id =
+ if (mode.isManualDnd) QSModesEvent.QS_MODES_DND_ON else QSModesEvent.QS_MODES_MODE_ON
+ uiEventLogger.log(id, /* uid= */ 0, mode.rule.packageName)
+ }
+
+ fun logModeOff(mode: ZenMode) {
+ val id =
+ if (mode.isManualDnd) QSModesEvent.QS_MODES_DND_OFF else QSModesEvent.QS_MODES_MODE_OFF
+ uiEventLogger.log(id, /* uid= */ 0, mode.rule.packageName)
+ }
+
+ fun logModeSettings(mode: ZenMode) {
+ val id =
+ if (mode.isManualDnd) QSModesEvent.QS_MODES_DND_SETTINGS
+ else QSModesEvent.QS_MODES_MODE_SETTINGS
+ uiEventLogger.log(id, /* uid= */ 0, mode.rule.packageName)
+ }
+
+ fun logOpenDurationDialog(mode: ZenMode) {
+ // should only occur for manual Do Not Disturb.
+ if (!mode.isManualDnd) {
+ return
+ }
+ uiEventLogger.log(QSModesEvent.QS_MODES_DURATION_DIALOG)
+ }
+
+ fun logDialogSettings() {
+ uiEventLogger.log(QSModesEvent.QS_MODES_SETTINGS)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt
index 44b692f..5772099 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt
@@ -30,6 +30,7 @@
import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.statusbar.policy.domain.interactor.ZenModeInteractor
import com.android.systemui.statusbar.policy.ui.dialog.ModesDialogDelegate
+import com.android.systemui.statusbar.policy.ui.dialog.ModesDialogEventLogger
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.Flow
@@ -49,6 +50,7 @@
zenModeInteractor: ZenModeInteractor,
@Background val bgDispatcher: CoroutineDispatcher,
private val dialogDelegate: ModesDialogDelegate,
+ private val dialogEventLogger: ModesDialogEventLogger,
) {
private val zenDialogMetricsLogger = QSZenModeDialogMetricsLogger(context)
@@ -94,14 +96,17 @@
if (!mode.rule.isEnabled) {
openSettings(mode)
} else if (mode.isActive) {
+ dialogEventLogger.logModeOff(mode)
zenModeInteractor.deactivateMode(mode)
} else {
if (mode.rule.isManualInvocationAllowed) {
if (zenModeInteractor.shouldAskForZenDuration(mode)) {
+ dialogEventLogger.logOpenDurationDialog(mode)
// NOTE: The dialog handles turning on the mode itself.
val dialog = makeZenModeDialog()
dialog.show()
} else {
+ dialogEventLogger.logModeOn(mode)
zenModeInteractor.activateMode(mode)
}
}
@@ -114,6 +119,7 @@
.flowOn(bgDispatcher)
private fun openSettings(mode: ZenMode) {
+ dialogEventLogger.logModeSettings(mode)
val intent: Intent =
Intent(ACTION_AUTOMATIC_ZEN_RULE_SETTINGS)
.putExtra(EXTRA_AUTOMATIC_ZEN_RULE_ID, mode.id)
diff --git a/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractor.kt
index 154737c..4f77cd0 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractor.kt
@@ -26,7 +26,6 @@
import com.android.settingslib.media.MediaDevice.MediaDeviceType
import com.android.settingslib.media.PhoneMediaDevice
import com.android.settingslib.volume.data.repository.AudioRepository
-import com.android.settingslib.volume.data.repository.AudioSharingRepository
import com.android.settingslib.volume.domain.interactor.AudioModeInteractor
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
@@ -37,7 +36,6 @@
import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.flatMapLatest
@@ -60,7 +58,6 @@
private val bluetoothAdapter: BluetoothAdapter?,
private val deviceIconInteractor: DeviceIconInteractor,
private val mediaOutputInteractor: MediaOutputInteractor,
- audioSharingRepository: AudioSharingRepository,
) {
val currentAudioDevice: StateFlow<AudioOutputDevice> =
@@ -80,9 +77,6 @@
.flowOn(backgroundCoroutineContext)
.stateIn(scope, SharingStarted.Eagerly, AudioOutputDevice.Unknown)
- /** Whether the device is in audio sharing */
- val isInAudioSharing: Flow<Boolean> = audioSharingRepository.inAudioSharing
-
private fun AudioDeviceInfo.toAudioOutputDevice(): AudioOutputDevice {
if (
BluetoothAdapter.checkBluetoothAddress(address) &&
diff --git a/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractor.kt
index 2170c36..9aed8ab 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractor.kt
@@ -36,11 +36,15 @@
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
interface AudioSharingInteractor {
+ /** Audio sharing state on the device. */
+ val isInAudioSharing: Flow<Boolean>
+
/** Audio sharing secondary headset volume changes. */
val volume: Flow<Int?>
@@ -76,6 +80,7 @@
private val audioVolumeInteractor: AudioVolumeInteractor,
private val audioSharingRepository: AudioSharingRepository
) : AudioSharingInteractor {
+ override val isInAudioSharing: Flow<Boolean> = audioSharingRepository.inAudioSharing
override val volume: Flow<Int?> =
combine(audioSharingRepository.secondaryGroupId, audioSharingRepository.volumeMap) {
@@ -125,13 +130,13 @@
}
private companion object {
- const val TAG = "AudioSharingInteractor"
const val DEFAULT_VOLUME = 20
}
}
@SysUISingleton
class AudioSharingInteractorEmptyImpl @Inject constructor() : AudioSharingInteractor {
+ override val isInAudioSharing: Flow<Boolean> = flowOf(false)
override val volume: Flow<Int?> = emptyFlow()
override val volumeMin: Int = EMPTY_VOLUME
override val volumeMax: Int = EMPTY_VOLUME
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputComponentInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputComponentInteractor.kt
index ed25129..a270d5f 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputComponentInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputComponentInteractor.kt
@@ -18,6 +18,7 @@
import com.android.settingslib.volume.domain.interactor.AudioModeInteractor
import com.android.systemui.volume.domain.interactor.AudioOutputInteractor
+import com.android.systemui.volume.domain.interactor.AudioSharingInteractor
import com.android.systemui.volume.domain.model.AudioOutputDevice
import com.android.systemui.volume.panel.component.mediaoutput.domain.model.MediaOutputComponentModel
import com.android.systemui.volume.panel.component.mediaoutput.shared.model.SessionWithPlaybackState
@@ -49,11 +50,12 @@
private val mediaDeviceSessionInteractor: MediaDeviceSessionInteractor,
audioOutputInteractor: AudioOutputInteractor,
audioModeInteractor: AudioModeInteractor,
- interactor: MediaOutputInteractor,
+ mediaOutputInteractor: MediaOutputInteractor,
+ audioSharingInteractor: AudioSharingInteractor,
) {
private val sessionWithPlaybackState: StateFlow<Result<SessionWithPlaybackState?>> =
- interactor.defaultActiveMediaSession
+ mediaOutputInteractor.defaultActiveMediaSession
.filterData()
.flatMapLatest { session ->
if (session == null) {
@@ -77,7 +79,7 @@
val mediaOutputModel: StateFlow<Result<MediaOutputComponentModel>> =
audioModeInteractor.isOngoingCall
.flatMapLatest { isOngoingCall ->
- audioOutputInteractor.isInAudioSharing.flatMapLatest { isInAudioSharing ->
+ audioSharingInteractor.isInAudioSharing.flatMapLatest { isInAudioSharing ->
if (isOngoingCall) {
currentAudioDevice.map {
MediaOutputComponentModel.Calling(it, isInAudioSharing)
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 075d8ae..7aa415b 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -102,6 +102,7 @@
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
+import android.platform.test.flag.junit.FlagsParameterization;
import android.service.dreams.IDreamManager;
import android.service.trust.TrustAgentService;
import android.telephony.ServiceState;
@@ -111,9 +112,9 @@
import android.testing.TestableLooper;
import android.text.TextUtils;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
+import com.android.compose.animation.scene.ObservableTransitionState;
import com.android.dx.mockito.inline.extended.ExtendedMockito;
import com.android.internal.foldables.FoldGracePeriodProvider;
import com.android.internal.jank.InteractionJankMonitor;
@@ -129,6 +130,7 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.biometrics.FingerprintInteractiveToAuthProvider;
+import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.deviceentry.data.repository.FaceWakeUpTriggersConfig;
import com.android.systemui.deviceentry.data.repository.FaceWakeUpTriggersConfigImpl;
@@ -138,9 +140,13 @@
import com.android.systemui.deviceentry.shared.model.FaceDetectionStatus;
import com.android.systemui.deviceentry.shared.model.FailedFaceAuthenticationStatus;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.SceneContainerFlagParameterizationKt;
import com.android.systemui.kosmos.KosmosJavaAdapter;
import com.android.systemui.log.SessionTracker;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.scene.domain.interactor.SceneInteractor;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
+import com.android.systemui.scene.shared.model.Scenes;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.shared.system.TaskStackChangeListener;
import com.android.systemui.shared.system.TaskStackChangeListeners;
@@ -149,6 +155,7 @@
import com.android.systemui.statusbar.policy.DevicePostureController;
import com.android.systemui.telephony.TelephonyListenerManager;
import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
+import com.android.systemui.util.kotlin.JavaAdapter;
import com.android.systemui.util.settings.GlobalSettings;
import org.junit.After;
@@ -175,8 +182,11 @@
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
+import platform.test.runner.parameterized.Parameters;
+
@SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(ParameterizedAndroidJunit4.class)
@TestableLooper.RunWithLooper
public class KeyguardUpdateMonitorTest extends SysuiTestCase {
private static final String PKG_ALLOWING_FP_LISTEN_ON_OCCLUDING_ACTIVITY =
@@ -277,6 +287,12 @@
private SelectedUserInteractor mSelectedUserInteractor;
@Mock
private DeviceEntryFaceAuthInteractor mFaceAuthInteractor;
+ @Mock
+ private AlternateBouncerInteractor mAlternateBouncerInteractor;
+ @Mock
+ private JavaAdapter mJavaAdapter;
+ @Mock
+ private SceneInteractor mSceneInteractor;
@Captor
private ArgumentCaptor<FaceAuthenticationListener> mFaceAuthenticationListener;
@@ -301,6 +317,16 @@
mFingerprintAuthenticatorsRegisteredCallback;
private final InstanceId mKeyguardInstanceId = InstanceId.fakeInstanceId(999);
+ @Parameters(name = "{0}")
+ public static List<FlagsParameterization> getParams() {
+ return SceneContainerFlagParameterizationKt.parameterizeSceneContainerFlag();
+ }
+
+ public KeyguardUpdateMonitorTest(FlagsParameterization flags) {
+ super();
+ mSetFlagsRule.setFlagsParameterization(flags);
+ }
+
@Before
public void setup() throws RemoteException {
mKosmos = new KosmosJavaAdapter(this);
@@ -993,7 +1019,7 @@
verifyFingerprintAuthenticateNeverCalled();
// WHEN alternate bouncer is shown
mKeyguardUpdateMonitor.setKeyguardShowing(true, true);
- mKeyguardUpdateMonitor.setAlternateBouncerShowing(true);
+ mKeyguardUpdateMonitor.setAlternateBouncerVisibility(true);
// THEN make sure FP listening begins
verifyFingerprintAuthenticateCall();
@@ -1489,7 +1515,7 @@
@Test
public void testShouldNotListenForUdfps_whenInLockDown() {
// GIVEN a "we should listen for udfps" state
- setKeyguardBouncerVisibility(false /* isVisible */);
+ mKeyguardUpdateMonitor.setPrimaryBouncerVisibility(false /* isVisible */);
mStatusBarStateListener.onStateChanged(StatusBarState.KEYGUARD);
when(mStrongAuthTracker.hasUserAuthenticatedSinceBoot()).thenReturn(true);
@@ -2124,7 +2150,7 @@
verifyFingerprintAuthenticateNeverCalled();
mKeyguardUpdateMonitor.setKeyguardShowing(true, true);
- mKeyguardUpdateMonitor.setAlternateBouncerShowing(true);
+ mKeyguardUpdateMonitor.setAlternateBouncerVisibility(true);
verifyFingerprintAuthenticateCall();
}
@@ -2323,12 +2349,7 @@
}
private void bouncerFullyVisible() {
- setKeyguardBouncerVisibility(true);
- }
-
- private void setKeyguardBouncerVisibility(boolean isVisible) {
- mKeyguardUpdateMonitor.sendPrimaryBouncerChanged(isVisible, isVisible);
- mTestableLooper.processAllMessages();
+ mKeyguardUpdateMonitor.setPrimaryBouncerVisibility(true);
}
private void setBroadcastReceiverPendingResult(BroadcastReceiver receiver) {
@@ -2434,7 +2455,12 @@
mPackageManager, mFingerprintManager, mBiometricManager,
mFaceWakeUpTriggersConfig, mDevicePostureController,
Optional.of(mInteractiveToAuthProvider),
- mTaskStackChangeListeners, mSelectedUserInteractor, mActivityTaskManager);
+ mTaskStackChangeListeners, mSelectedUserInteractor, mActivityTaskManager,
+ () -> mAlternateBouncerInteractor,
+ () -> mJavaAdapter,
+ () -> mSceneInteractor);
+ setAlternateBouncerVisibility(false);
+ setPrimaryBouncerVisibility(false);
setStrongAuthTracker(KeyguardUpdateMonitorTest.this.mStrongAuthTracker);
start();
}
@@ -2458,5 +2484,25 @@
protected int getBiometricLockoutDelay() {
return 0;
}
+
+ private void setPrimaryBouncerVisibility(boolean isVisible) {
+ if (SceneContainerFlag.isEnabled()) {
+ ObservableTransitionState transitionState = new ObservableTransitionState.Idle(
+ isVisible ? Scenes.Bouncer : Scenes.Lockscreen);
+ when(mSceneInteractor.getTransitionState()).thenReturn(
+ MutableStateFlow(transitionState));
+ onTransitionStateChanged(transitionState);
+ } else {
+ sendPrimaryBouncerChanged(isVisible, isVisible);
+ mTestableLooper.processAllMessages();
+ }
+ }
+
+ private void setAlternateBouncerVisibility(boolean isVisible) {
+ if (SceneContainerFlag.isEnabled()) {
+ when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(isVisible);
+ }
+ onAlternateBouncerVisibilityChange(isVisible);
+ }
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSImplTest.java
index 206bbbf..4ce2d7c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSImplTest.java
@@ -51,6 +51,7 @@
import androidx.compose.ui.platform.ComposeView;
import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.LifecycleOwner;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
@@ -611,7 +612,8 @@
when(mQSContainerImplController.getView()).thenReturn(mContainer);
when(mQSPanelController.getTileLayout()).thenReturn(mQQsTileLayout);
when(mQuickQSPanelController.getTileLayout()).thenReturn(mQsTileLayout);
- when(mFooterActionsViewModelFactory.create(any())).thenReturn(mFooterActionsViewModel);
+ when(mFooterActionsViewModelFactory.create(any(LifecycleOwner.class)))
+ .thenReturn(mFooterActionsViewModel);
}
private void setUpMedia() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
index 30e7247..70ac31d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
@@ -391,6 +391,7 @@
configurationController,
mStatusOverlayHoverListenerFactory,
fakeDarkIconDispatcher,
+ mock(StatusBarContentInsetsProvider::class.java),
)
.create(view)
.also { it.init() }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt
index ed5ec7b2..648ddf8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt
@@ -32,6 +32,7 @@
import android.view.WindowInsets
import android.widget.FrameLayout
import androidx.test.filters.SmallTest
+import com.android.systemui.Flags.FLAG_STATUS_BAR_STOP_UPDATING_WINDOW_HEIGHT
import com.android.systemui.Flags.FLAG_STATUS_BAR_SWIPE_OVER_CHIP
import com.android.systemui.Gefingerpoken
import com.android.systemui.SysuiTestCase
@@ -42,6 +43,7 @@
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
+import org.mockito.Mockito.never
import org.mockito.Mockito.spy
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
@@ -54,21 +56,14 @@
private val systemIconsContainer: View
get() = view.requireViewById(R.id.system_icons)
- private val contentInsetsProvider = mock<StatusBarContentInsetsProvider>()
private val windowController = mock<StatusBarWindowController>()
@Before
fun setUp() {
- mDependency.injectTestDependency(
- StatusBarContentInsetsProvider::class.java,
- contentInsetsProvider
- )
mDependency.injectTestDependency(StatusBarWindowController::class.java, windowController)
context.ensureTestableResources()
view = spy(createStatusBarView())
whenever(view.rootWindowInsets).thenReturn(emptyWindowInsets())
- whenever(contentInsetsProvider.getStatusBarContentInsetsForCurrentRotation())
- .thenReturn(Insets.NONE)
}
@Test
@@ -183,21 +178,40 @@
}
@Test
- fun onAttachedToWindow_updatesWindowHeight() {
+ @DisableFlags(FLAG_STATUS_BAR_STOP_UPDATING_WINDOW_HEIGHT)
+ fun onAttachedToWindow_flagOff_updatesWindowHeight() {
view.onAttachedToWindow()
verify(windowController).refreshStatusBarHeight()
}
@Test
- fun onConfigurationChanged_updatesWindowHeight() {
+ @EnableFlags(FLAG_STATUS_BAR_STOP_UPDATING_WINDOW_HEIGHT)
+ fun onAttachedToWindow_flagOn_doesNotUpdateWindowHeight() {
+ view.onAttachedToWindow()
+
+ verify(windowController, never()).refreshStatusBarHeight()
+ }
+
+ @Test
+ @DisableFlags(FLAG_STATUS_BAR_STOP_UPDATING_WINDOW_HEIGHT)
+ fun onConfigurationChanged_flagOff_updatesWindowHeight() {
view.onConfigurationChanged(Configuration())
verify(windowController).refreshStatusBarHeight()
}
@Test
- fun onConfigurationChanged_multipleCalls_updatesWindowHeightMultipleTimes() {
+ @EnableFlags(FLAG_STATUS_BAR_STOP_UPDATING_WINDOW_HEIGHT)
+ fun onConfigurationChanged_flagOn_doesNotUpdateWindowHeight() {
+ view.onConfigurationChanged(Configuration())
+
+ verify(windowController, never()).refreshStatusBarHeight()
+ }
+
+ @Test
+ @DisableFlags(FLAG_STATUS_BAR_STOP_UPDATING_WINDOW_HEIGHT)
+ fun onConfigurationChanged_multipleCalls_flagOff_updatesWindowHeightMultipleTimes() {
view.onConfigurationChanged(Configuration())
view.onConfigurationChanged(Configuration())
view.onConfigurationChanged(Configuration())
@@ -207,10 +221,20 @@
}
@Test
+ @EnableFlags(FLAG_STATUS_BAR_STOP_UPDATING_WINDOW_HEIGHT)
+ fun onConfigurationChanged_multipleCalls_flagOn_neverUpdatesWindowHeight() {
+ view.onConfigurationChanged(Configuration())
+ view.onConfigurationChanged(Configuration())
+ view.onConfigurationChanged(Configuration())
+ view.onConfigurationChanged(Configuration())
+
+ verify(windowController, never()).refreshStatusBarHeight()
+ }
+
+ @Test
fun onAttachedToWindow_updatesLeftTopRightPaddingsBasedOnInsets() {
val insets = Insets.of(/* left= */ 10, /* top= */ 20, /* right= */ 30, /* bottom= */ 40)
- whenever(contentInsetsProvider.getStatusBarContentInsetsForCurrentRotation())
- .thenReturn(insets)
+ view.setInsetsFetcher { insets }
view.onAttachedToWindow()
@@ -221,10 +245,23 @@
}
@Test
+ fun onAttachedToWindow_noInsetsFetcher_noCrash() {
+ // Don't call `PhoneStatusBarView.setInsetsFetcher`
+
+ // WHEN the view is attached
+ view.onAttachedToWindow()
+
+ // THEN there's no crash, and the padding stays as it was
+ assertThat(view.paddingLeft).isEqualTo(0)
+ assertThat(view.paddingTop).isEqualTo(0)
+ assertThat(view.paddingRight).isEqualTo(0)
+ assertThat(view.paddingBottom).isEqualTo(0)
+ }
+
+ @Test
fun onConfigurationChanged_updatesLeftTopRightPaddingsBasedOnInsets() {
val insets = Insets.of(/* left= */ 40, /* top= */ 30, /* right= */ 20, /* bottom= */ 10)
- whenever(contentInsetsProvider.getStatusBarContentInsetsForCurrentRotation())
- .thenReturn(insets)
+ view.setInsetsFetcher { insets }
view.onConfigurationChanged(Configuration())
@@ -235,17 +272,31 @@
}
@Test
+ fun onConfigurationChanged_noInsetsFetcher_noCrash() {
+ // Don't call `PhoneStatusBarView.setInsetsFetcher`
+
+ // WHEN the view is attached
+ view.onConfigurationChanged(Configuration())
+
+ // THEN there's no crash, and the padding stays as it was
+ assertThat(view.paddingLeft).isEqualTo(0)
+ assertThat(view.paddingTop).isEqualTo(0)
+ assertThat(view.paddingRight).isEqualTo(0)
+ assertThat(view.paddingBottom).isEqualTo(0)
+ }
+
+ @Test
fun onConfigurationChanged_noRelevantChange_doesNotUpdateInsets() {
val previousInsets =
Insets.of(/* left= */ 40, /* top= */ 30, /* right= */ 20, /* bottom= */ 10)
- whenever(contentInsetsProvider.getStatusBarContentInsetsForCurrentRotation())
- .thenReturn(previousInsets)
+ view.setInsetsFetcher { previousInsets }
+
context.orCreateTestableResources.overrideConfiguration(Configuration())
view.onAttachedToWindow()
val newInsets = Insets.NONE
- whenever(contentInsetsProvider.getStatusBarContentInsetsForCurrentRotation())
- .thenReturn(newInsets)
+ view.setInsetsFetcher { newInsets }
+
view.onConfigurationChanged(Configuration())
assertThat(view.paddingLeft).isEqualTo(previousInsets.left)
@@ -258,16 +309,14 @@
fun onConfigurationChanged_densityChanged_updatesInsets() {
val previousInsets =
Insets.of(/* left= */ 40, /* top= */ 30, /* right= */ 20, /* bottom= */ 10)
- whenever(contentInsetsProvider.getStatusBarContentInsetsForCurrentRotation())
- .thenReturn(previousInsets)
+ view.setInsetsFetcher { previousInsets }
val configuration = Configuration()
configuration.densityDpi = 123
context.orCreateTestableResources.overrideConfiguration(configuration)
view.onAttachedToWindow()
val newInsets = Insets.NONE
- whenever(contentInsetsProvider.getStatusBarContentInsetsForCurrentRotation())
- .thenReturn(newInsets)
+ view.setInsetsFetcher { newInsets }
configuration.densityDpi = 456
view.onConfigurationChanged(configuration)
@@ -281,16 +330,14 @@
fun onConfigurationChanged_fontScaleChanged_updatesInsets() {
val previousInsets =
Insets.of(/* left= */ 40, /* top= */ 30, /* right= */ 20, /* bottom= */ 10)
- whenever(contentInsetsProvider.getStatusBarContentInsetsForCurrentRotation())
- .thenReturn(previousInsets)
+ view.setInsetsFetcher { previousInsets }
val configuration = Configuration()
configuration.fontScale = 1f
context.orCreateTestableResources.overrideConfiguration(configuration)
view.onAttachedToWindow()
val newInsets = Insets.NONE
- whenever(contentInsetsProvider.getStatusBarContentInsetsForCurrentRotation())
- .thenReturn(newInsets)
+ view.setInsetsFetcher { newInsets }
configuration.fontScale = 2f
view.onConfigurationChanged(configuration)
@@ -316,8 +363,7 @@
@Test
fun onApplyWindowInsets_updatesLeftTopRightPaddingsBasedOnInsets() {
val insets = Insets.of(/* left= */ 90, /* top= */ 10, /* right= */ 45, /* bottom= */ 50)
- whenever(contentInsetsProvider.getStatusBarContentInsetsForCurrentRotation())
- .thenReturn(insets)
+ view.setInsetsFetcher { insets }
view.onApplyWindowInsets(WindowInsets(Rect()))
@@ -358,7 +404,7 @@
/* typeVisibilityMap = */ booleanArrayOf(),
/* isRound = */ false,
/* forceConsumingTypes = */ 0,
- /* forceConsumingCaptionBar = */ false,
+ /* forceConsumingOpaqueCaptionBar = */ false,
/* suppressScrimTypes = */ 0,
/* displayCutout = */ DisplayCutout.NO_CUTOUT,
/* roundedCorners = */ RoundedCorners.NO_ROUNDED_CORNERS,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateTest.kt
index bf0a39b..06b3b57 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateTest.kt
@@ -57,6 +57,7 @@
private val activityStarter = kosmos.activityStarter
private val mockDialogTransitionAnimator = kosmos.mockDialogTransitionAnimator
private val mockAnimationController = kosmos.mockActivityTransitionAnimatorController
+ private val mockDialogEventLogger = kosmos.mockModesDialogEventLogger
private lateinit var underTest: ModesDialogDelegate
@Before
@@ -75,6 +76,7 @@
mockDialogTransitionAnimator,
activityStarter,
{ kosmos.modesDialogViewModel },
+ mockDialogEventLogger,
kosmos.mainCoroutineContext,
)
}
@@ -121,4 +123,12 @@
assertThat(underTest.currentDialog).isNull()
}
+
+ @Test
+ fun openSettings_logsEvent() =
+ testScope.runTest {
+ val dialog: SystemUIDialog = mock()
+ underTest.openSettings(dialog)
+ verify(mockDialogEventLogger).logDialogSettings()
+ }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractorKosmos.kt
index ee48c10..2ab8221 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractorKosmos.kt
@@ -17,6 +17,7 @@
package com.android.systemui.communal.domain.interactor
import com.android.systemui.communal.data.repository.communalSceneRepository
+import com.android.systemui.communal.shared.log.communalSceneLogger
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
@@ -24,6 +25,7 @@
Kosmos.Fixture {
CommunalSceneInteractor(
applicationScope = applicationCoroutineScope,
- communalSceneRepository = communalSceneRepository,
+ repository = communalSceneRepository,
+ logger = communalSceneLogger,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/shared/log/CommunalSceneLoggerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/shared/log/CommunalSceneLoggerKosmos.kt
new file mode 100644
index 0000000..b560ee8
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/shared/log/CommunalSceneLoggerKosmos.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2024 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.communal.shared.log
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.log.logcatLogBuffer
+
+val Kosmos.communalSceneLogger: CommunalSceneLogger by
+ Kosmos.Fixture { CommunalSceneLogger(logcatLogBuffer("CommunalSceneLogger")) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorKosmos.kt
index f162594..64ae051 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorKosmos.kt
@@ -16,6 +16,7 @@
package com.android.systemui.keyguard.domain.interactor
+import com.android.systemui.communal.domain.interactor.communalSceneInteractor
import com.android.systemui.communal.domain.interactor.communalSettingsInteractor
import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
@@ -38,6 +39,7 @@
mainDispatcher = testDispatcher,
keyguardInteractor = keyguardInteractor,
glanceableHubTransitions = glanceableHubTransitions,
+ communalSceneInteractor = communalSceneInteractor,
communalSettingsInteractor = communalSettingsInteractor,
powerInteractor = powerInteractor,
keyguardOcclusionInteractor = keyguardOcclusionInteractor,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelKosmos.kt
index 450dcc2..d06bab2 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelKosmos.kt
@@ -19,13 +19,11 @@
import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
-import com.android.systemui.util.mockito.mock
import kotlinx.coroutines.ExperimentalCoroutinesApi
@ExperimentalCoroutinesApi
val Kosmos.dreamingToLockscreenTransitionViewModel by Fixture {
DreamingToLockscreenTransitionViewModel(
- fromDreamingTransitionInteractor = mock(),
animationFlow = keyguardTransitionAnimationFlow,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeFgsManagerController.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeFgsManagerController.kt
index 9ff7dd5..ffe6918 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeFgsManagerController.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeFgsManagerController.kt
@@ -27,6 +27,9 @@
numRunningPackages: Int = 0,
) : FgsManagerController {
+ var initialized = false
+ private set
+
override var numRunningPackages = numRunningPackages
set(value) {
if (value != field) {
@@ -53,7 +56,9 @@
dialogDismissedListeners.forEach { it.onDialogDismissed() }
}
- override fun init() {}
+ override fun init() {
+ initialized = true
+ }
override fun showDialog(expandable: Expandable?) {}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelKosmos.kt
new file mode 100644
index 0000000..d37d8f3
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelKosmos.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.composefragment.viewmodel
+
+import android.content.res.mainResources
+import androidx.lifecycle.LifecycleCoroutineScope
+import com.android.systemui.common.ui.domain.interactor.configurationInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.footerActionsController
+import com.android.systemui.qs.footerActionsViewModelFactory
+import com.android.systemui.qs.ui.viewmodel.quickSettingsContainerViewModel
+import com.android.systemui.shade.largeScreenHeaderHelper
+import com.android.systemui.shade.transition.largeScreenShadeInterpolator
+import com.android.systemui.statusbar.disableflags.data.repository.disableFlagsRepository
+import com.android.systemui.statusbar.phone.keyguardBypassController
+import com.android.systemui.statusbar.sysuiStatusBarStateController
+
+val Kosmos.qsFragmentComposeViewModelFactory by
+ Kosmos.Fixture {
+ object : QSFragmentComposeViewModel.Factory {
+ override fun create(
+ lifecycleScope: LifecycleCoroutineScope
+ ): QSFragmentComposeViewModel {
+ return QSFragmentComposeViewModel(
+ quickSettingsContainerViewModel,
+ mainResources,
+ footerActionsViewModelFactory,
+ footerActionsController,
+ sysuiStatusBarStateController,
+ keyguardBypassController,
+ disableFlagsRepository,
+ largeScreenShadeInterpolator,
+ configurationInteractor,
+ largeScreenHeaderHelper,
+ lifecycleScope,
+ )
+ }
+ }
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateKosmos.kt
index 99bb479..932e768 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateKosmos.kt
@@ -33,6 +33,7 @@
dialogTransitionAnimator,
activityStarter,
{ modesDialogViewModel },
+ modesDialogEventLogger,
mainCoroutineContext,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogEventLoggerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogEventLoggerKosmos.kt
new file mode 100644
index 0000000..24e7a87
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogEventLoggerKosmos.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2024 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.policy.ui.dialog
+
+import com.android.internal.logging.uiEventLogger
+import com.android.systemui.kosmos.Kosmos
+import org.mockito.kotlin.mock
+
+var Kosmos.modesDialogEventLogger by Kosmos.Fixture { ModesDialogEventLogger(uiEventLogger) }
+var Kosmos.mockModesDialogEventLogger by Kosmos.Fixture { mock<ModesDialogEventLogger>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogEventLoggerTest.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogEventLoggerTest.kt
new file mode 100644
index 0000000..5146f77
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogEventLoggerTest.kt
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2024 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.policy.ui.dialog
+
+import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.testing.UiEventLoggerFake
+import com.android.settingslib.notification.modes.TestModeBuilder
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.qs.QSModesEvent
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@TestableLooper.RunWithLooper
+class ModesDialogEventLoggerTest : SysuiTestCase() {
+
+ private val uiEventLogger = UiEventLoggerFake()
+ private val underTest = ModesDialogEventLogger(uiEventLogger)
+
+ @Test
+ fun testLogModeOn_manual() {
+ underTest.logModeOn(TestModeBuilder.MANUAL_DND_INACTIVE)
+
+ assertThat(uiEventLogger.numLogs()).isEqualTo(1)
+ uiEventLogger[0].match(QSModesEvent.QS_MODES_DND_ON, "android")
+ }
+
+ @Test
+ fun testLogModeOff_manual() {
+ underTest.logModeOff(TestModeBuilder.MANUAL_DND_ACTIVE)
+
+ assertThat(uiEventLogger.numLogs()).isEqualTo(1)
+ uiEventLogger[0].match(QSModesEvent.QS_MODES_DND_OFF, "android")
+ }
+
+ @Test
+ fun testLogModeSettings_manual() {
+ underTest.logModeSettings(TestModeBuilder.MANUAL_DND_ACTIVE)
+
+ assertThat(uiEventLogger.numLogs()).isEqualTo(1)
+ uiEventLogger[0].match(QSModesEvent.QS_MODES_DND_SETTINGS, "android")
+ }
+
+ @Test
+ fun testLogModeOn_automatic() {
+ underTest.logModeOn(TestModeBuilder().setActive(true).setPackage("pkg1").build())
+
+ assertThat(uiEventLogger.numLogs()).isEqualTo(1)
+ uiEventLogger[0].match(QSModesEvent.QS_MODES_MODE_ON, "pkg1")
+ }
+
+ @Test
+ fun testLogModeOff_automatic() {
+ underTest.logModeOff(TestModeBuilder().setActive(false).setPackage("pkg2").build())
+
+ assertThat(uiEventLogger.numLogs()).isEqualTo(1)
+ uiEventLogger[0].match(QSModesEvent.QS_MODES_MODE_OFF, "pkg2")
+ }
+
+ @Test
+ fun testLogModeSettings_automatic() {
+ underTest.logModeSettings(TestModeBuilder().setPackage("pkg3").build())
+
+ assertThat(uiEventLogger.numLogs()).isEqualTo(1)
+ uiEventLogger[0].match(QSModesEvent.QS_MODES_MODE_SETTINGS, "pkg3")
+ }
+
+ @Test
+ fun testLogOpenDurationDialog_manual() {
+ underTest.logOpenDurationDialog(TestModeBuilder.MANUAL_DND_INACTIVE)
+
+ assertThat(uiEventLogger.numLogs()).isEqualTo(1)
+ // package not logged for duration dialog as it only applies to manual mode
+ uiEventLogger[0].match(QSModesEvent.QS_MODES_DURATION_DIALOG, null)
+ }
+
+ @Test
+ fun testLogOpenDurationDialog_automatic_doesNotLog() {
+ underTest.logOpenDurationDialog(
+ TestModeBuilder().setActive(false).setPackage("mypkg").build()
+ )
+
+ // ignore calls to open dialog on something other than the manual rule (shouldn't happen)
+ assertThat(uiEventLogger.numLogs()).isEqualTo(0)
+ }
+
+ @Test
+ fun testLogDialogSettings() {
+ underTest.logDialogSettings()
+ assertThat(uiEventLogger.numLogs()).isEqualTo(1)
+ uiEventLogger[0].match(QSModesEvent.QS_MODES_SETTINGS, null)
+ }
+
+ private fun UiEventLoggerFake.FakeUiEvent.match(event: QSModesEvent, modePackage: String?) {
+ assertThat(eventId).isEqualTo(event.id)
+ assertThat(packageName).isEqualTo(modePackage)
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelKosmos.kt
index 00020f8..3571a73 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelKosmos.kt
@@ -21,6 +21,7 @@
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.statusbar.policy.domain.interactor.zenModeInteractor
import com.android.systemui.statusbar.policy.ui.dialog.modesDialogDelegate
+import com.android.systemui.statusbar.policy.ui.dialog.modesDialogEventLogger
import javax.inject.Provider
val Kosmos.modesDialogViewModel: ModesDialogViewModel by
@@ -30,5 +31,6 @@
zenModeInteractor,
testDispatcher,
Provider { modesDialogDelegate }.get(),
+ modesDialogEventLogger,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/mockito/KotlinMockitoHelpers.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/mockito/KotlinMockitoHelpers.kt
index 295e150..2e1ecfd 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/mockito/KotlinMockitoHelpers.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/mockito/KotlinMockitoHelpers.kt
@@ -219,7 +219,7 @@
*
* NOTE: this uses the KotlinArgumentCaptor to avoid the NullPointerException.
*/
-@Deprecated("Replace with mockito-kotlin", level = WARNING)
+// TODO(359670968): rewrite this to use mockito-kotlin
inline fun <reified T : Any> withArgCaptor(block: KotlinArgumentCaptor<T>.() -> Unit): T =
kotlinArgumentCaptor<T>().apply { block() }.value
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/OWNERS b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/OWNERS
new file mode 100644
index 0000000..1f07df9
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/OWNERS
@@ -0,0 +1 @@
+include /packages/SystemUI/src/com/android/systemui/volume/OWNERS
\ No newline at end of file
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractorKosmos.kt
index e2d414e..3ac565a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractorKosmos.kt
@@ -22,7 +22,6 @@
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testScope
import com.android.systemui.volume.data.repository.audioRepository
-import com.android.systemui.volume.data.repository.audioSharingRepository
import com.android.systemui.volume.mediaOutputInteractor
val Kosmos.audioOutputInteractor by
@@ -37,6 +36,5 @@
bluetoothAdapter,
deviceIconInteractor,
mediaOutputInteractor,
- audioSharingRepository,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputComponentInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputComponentInteractorKosmos.kt
index 9f11822..63a1325 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputComponentInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputComponentInteractorKosmos.kt
@@ -20,6 +20,7 @@
import com.android.systemui.kosmos.testScope
import com.android.systemui.volume.domain.interactor.audioModeInteractor
import com.android.systemui.volume.domain.interactor.audioOutputInteractor
+import com.android.systemui.volume.domain.interactor.audioSharingInteractor
import com.android.systemui.volume.mediaDeviceSessionInteractor
import com.android.systemui.volume.mediaOutputInteractor
@@ -31,5 +32,6 @@
audioOutputInteractor,
audioModeInteractor,
mediaOutputInteractor,
+ audioSharingInteractor,
)
}
diff --git a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
index 2c4bc7c..531fa45 100644
--- a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
+++ b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
@@ -78,8 +78,8 @@
import android.util.Size;
import android.view.Surface;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import androidx.camera.extensions.impl.AutoImageCaptureExtenderImpl;
import androidx.camera.extensions.impl.AutoPreviewExtenderImpl;
import androidx.camera.extensions.impl.BeautyImageCaptureExtenderImpl;
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
index b052d23..5c6f99a 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
@@ -16,8 +16,6 @@
package com.android.server.accessibility.magnification;
-import static android.view.InputDevice.SOURCE_MOUSE;
-import static android.view.InputDevice.SOURCE_STYLUS;
import static android.view.InputDevice.SOURCE_TOUCHSCREEN;
import static android.view.MotionEvent.ACTION_CANCEL;
import static android.view.MotionEvent.ACTION_DOWN;
@@ -342,8 +340,12 @@
cancelFling();
}
handleTouchEventWith(mCurrentState, event, rawEvent, policyFlags);
- } else if (Flags.enableMagnificationFollowsMouse()
- && (event.getSource() == SOURCE_MOUSE || event.getSource() == SOURCE_STYLUS)) {
+ }
+ }
+
+ @Override
+ void handleMouseOrStylusEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ if (Flags.enableMagnificationFollowsMouse()) {
if (mFullScreenMagnificationController.isActivated(mDisplayId)) {
// TODO(b/354696546): Allow mouse/stylus to activate whichever display they are
// over, rather than only interacting with the current display.
@@ -351,8 +353,6 @@
// Send through the mouse/stylus event handler.
mMouseEventHandler.onEvent(event, mDisplayId);
}
- // Dispatch to normal event handling flow.
- dispatchTransformedEvent(event, rawEvent, policyFlags);
}
}
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureHandler.java
index 08411c2..446123f 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureHandler.java
@@ -127,49 +127,41 @@
if (DEBUG_EVENT_STREAM) {
storeEventInto(mDebugInputEventHistory, event);
}
- if (shouldDispatchTransformedEvent(event)) {
- dispatchTransformedEvent(event, rawEvent, policyFlags);
- } else {
- onMotionEventInternal(event, rawEvent, policyFlags);
+ switch (event.getSource()) {
+ case SOURCE_TOUCHSCREEN: {
+ if (magnificationShortcutExists()) {
+ // Observe touchscreen events while magnification activation is detected.
+ onMotionEventInternal(event, rawEvent, policyFlags);
- final int action = event.getAction();
- if (action == MotionEvent.ACTION_DOWN) {
- mCallback.onTouchInteractionStart(mDisplayId, getMode());
- } else if (action == ACTION_UP || action == ACTION_CANCEL) {
- mCallback.onTouchInteractionEnd(mDisplayId, getMode());
+ final int action = event.getAction();
+ if (action == MotionEvent.ACTION_DOWN) {
+ mCallback.onTouchInteractionStart(mDisplayId, getMode());
+ } else if (action == ACTION_UP || action == ACTION_CANCEL) {
+ mCallback.onTouchInteractionEnd(mDisplayId, getMode());
+ }
+ // Return early: Do not dispatch event through normal eventing
+ // flow, it has been fully consumed by the magnifier.
+ return;
+ }
+ } break;
+ case SOURCE_MOUSE:
+ case SOURCE_STYLUS: {
+ if (magnificationShortcutExists() && Flags.enableMagnificationFollowsMouse()) {
+ handleMouseOrStylusEvent(event, rawEvent, policyFlags);
+ }
}
+ break;
+ default:
+ break;
}
+ // Dispatch event through normal eventing flow.
+ dispatchTransformedEvent(event, rawEvent, policyFlags);
}
- /**
- * Some touchscreen, mouse and stylus events may modify magnifier state. Checks for whether the
- * event should not be dispatched to the magnifier.
- *
- * @param event The event to check.
- * @return `true` if the event should be sent through the normal event flow or `false` if it
- * should be observed by magnifier.
- */
- private boolean shouldDispatchTransformedEvent(MotionEvent event) {
- if (event.getSource() == SOURCE_TOUCHSCREEN) {
- if (mDetectSingleFingerTripleTap
- || mDetectTwoFingerTripleTap
- || mDetectShortcutTrigger) {
- // Observe touchscreen events while magnification activation is detected.
- return false;
- }
- }
- if (Flags.enableMagnificationFollowsMouse()) {
- if (event.isFromSource(SOURCE_MOUSE) || event.isFromSource(SOURCE_STYLUS)) {
- // Note that mouse events include other mouse-like pointing devices
- // such as touchpads and pointing sticks.
- // Observe any mouse or stylus movement.
- // We observe all movement to ensure that events continue to come in order,
- // even though only some movement types actually move the viewport.
- return false;
- }
- }
- // Magnification dispatches (ignores) all other events
- return true;
+ private boolean magnificationShortcutExists() {
+ return (mDetectSingleFingerTripleTap
+ || mDetectTwoFingerTripleTap
+ || mDetectShortcutTrigger);
}
final void dispatchTransformedEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
@@ -202,6 +194,13 @@
abstract void onMotionEventInternal(MotionEvent event, MotionEvent rawEvent, int policyFlags);
/**
+ * Called when this MagnificationGestureHandler should handle a mouse or stylus motion event,
+ * but not re-dispatch it when completed.
+ */
+ abstract void handleMouseOrStylusEvent(
+ MotionEvent event, MotionEvent rawEvent, int policyFlags);
+
+ /**
* Called when the shortcut target is magnification.
*/
public void notifyShortcutTriggered() {
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java
index 1818cdd..a841404 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java
@@ -147,9 +147,13 @@
}
@Override
+ void handleMouseOrStylusEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ // Window Magnification viewport doesn't move with mouse events (yet).
+ }
+
+ @Override
void onMotionEventInternal(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
if (event.getSource() != SOURCE_TOUCHSCREEN) {
- // Window Magnification viewport doesn't move with mouse events (yet).
return;
}
// To keep InputEventConsistencyVerifiers within GestureDetectors happy.
diff --git a/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java b/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java
index 930af5e..5044e93 100644
--- a/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java
+++ b/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java
@@ -71,6 +71,7 @@
import android.util.Slog;
import android.view.autofill.AutofillId;
import android.view.autofill.AutofillManager;
+import android.view.autofill.AutofillValue;
import com.android.internal.util.FrameworkStatsLog;
@@ -244,10 +245,18 @@
Slog.e(TAG, "Failed to start new event because already have active event.");
return;
}
+ Slog.d(TAG, "Started new PresentationStatsEvent");
mEventInternal = Optional.of(new PresentationStatsEventInternal());
}
/**
+ * Test use only, returns a copy of the events object
+ */
+ Optional<PresentationStatsEventInternal> getInternalEvent() {
+ return mEventInternal;
+ }
+
+ /**
* Set request_id
*/
public void maybeSetRequestId(int requestId) {
@@ -339,10 +348,16 @@
});
}
- public void maybeSetCountShown(int datasets) {
+ /**
+ * This is called when a dataset is shown to the user. Will set the count shown,
+ * related timestamps and presentation reason.
+ */
+ public void logWhenDatasetShown(int datasets) {
mEventInternal.ifPresent(
event -> {
+ maybeSetSuggestionPresentedTimestampMs();
event.mCountShown = datasets;
+ event.mNoPresentationReason = NOT_SHOWN_REASON_ANY_SHOWN;
});
}
@@ -405,7 +420,12 @@
public void maybeSetDisplayPresentationType(@UiType int uiType) {
mEventInternal.ifPresent(event -> {
- event.mDisplayPresentationType = getDisplayPresentationType(uiType);
+ // There are cases in which another UI type will show up after selects a dataset
+ // such as with Inline after Fill Dialog. Set as the first presentation type only.
+ if (event.mDisplayPresentationType
+ == AUTOFILL_PRESENTATION_EVENT_REPORTED__DISPLAY_PRESENTATION_TYPE__UNKNOWN_AUTOFILL_DISPLAY_PRESENTATION_TYPE) {
+ event.mDisplayPresentationType = getDisplayPresentationType(uiType);
+ }
});
}
@@ -430,9 +450,12 @@
}
public void maybeSetSuggestionSentTimestampMs(int timestamp) {
- mEventInternal.ifPresent(event -> {
- event.mSuggestionSentTimestampMs = timestamp;
- });
+ mEventInternal.ifPresent(
+ event -> {
+ if (event.mSuggestionSentTimestampMs == DEFAULT_VALUE_INT) {
+ event.mSuggestionSentTimestampMs = timestamp;
+ }
+ });
}
public void maybeSetSuggestionSentTimestampMs() {
@@ -481,8 +504,6 @@
public void maybeSetInlinePresentationAndSuggestionHostUid(Context context, int userId) {
mEventInternal.ifPresent(event -> {
- event.mDisplayPresentationType =
- AUTOFILL_PRESENTATION_EVENT_REPORTED__DISPLAY_PRESENTATION_TYPE__INLINE;
String imeString = Settings.Secure.getStringForUser(context.getContentResolver(),
Settings.Secure.DEFAULT_INPUT_METHOD, userId);
if (TextUtils.isEmpty(imeString)) {
@@ -602,40 +623,56 @@
}
/**
+ * Sets the field length whenever the text changes. Will keep track of the first
+ * and last modification lengths.
+ */
+ public void updateTextFieldLength(AutofillValue value) {
+ mEventInternal.ifPresent(event -> {
+ if (value == null || !value.isText()) {
+ return;
+ }
+
+ int length = value.getTextValue().length();
+
+ if (event.mFieldFirstLength == DEFAULT_VALUE_INT) {
+ event.mFieldFirstLength = length;
+ }
+ event.mFieldLastLength = length;
+ });
+ }
+
+ /**
* Set various timestamps whenever the ViewState is modified
*
* <p>If the ViewState contains ViewState.STATE_AUTOFILLED, sets field_autofilled_timestamp_ms
* else, set field_first_modified_timestamp_ms (if unset) and field_last_modified_timestamp_ms
*/
- public void onFieldTextUpdated(ViewState state, int length) {
+ public void onFieldTextUpdated(ViewState state, AutofillValue value) {
mEventInternal.ifPresent(event -> {
- int timestamp = getElapsedTime();
- // Focused id should be set before this is called
- if (state == null || state.id == null || state.id.getViewId() != event.mFocusedId) {
- // if these don't match, the currently field different than before
- Slog.w(
- TAG,
- "Bad view state for: " + event.mFocusedId);
- return;
- }
+ int timestamp = getElapsedTime();
+ // Focused id should be set before this is called
+ if (state == null || state.id == null || state.id.getViewId() != event.mFocusedId) {
+ // if these don't match, the currently field different than before
+ Slog.w(
+ TAG,
+ "Bad view state for: " + event.mFocusedId + ", state: " + state);
+ return;
+ }
- // Text changed because filling into form, just log Autofill timestamp
- if ((state.getState() & ViewState.STATE_AUTOFILLED) != 0) {
- event.mAutofilledTimestampMs = timestamp;
- return;
- }
+ updateTextFieldLength(value);
- // Set length variables
- if (event.mFieldFirstLength == DEFAULT_VALUE_INT) {
- event.mFieldFirstLength = length;
- }
- event.mFieldLastLength = length;
+ // Text changed because filling into form, just log Autofill timestamp
+ if ((state.getState() & ViewState.STATE_AUTOFILLED) != 0) {
+ event.mAutofilledTimestampMs = timestamp;
+ return;
+ }
- // Set timestamp variables
- if (event.mFieldModifiedFirstTimestampMs == DEFAULT_VALUE_INT) {
- event.mFieldModifiedFirstTimestampMs = timestamp;
- }
- event.mFieldModifiedLastTimestampMs = timestamp;
+
+ // Set timestamp variables
+ if (event.mFieldModifiedFirstTimestampMs == DEFAULT_VALUE_INT) {
+ event.mFieldModifiedFirstTimestampMs = timestamp;
+ }
+ event.mFieldModifiedLastTimestampMs = timestamp;
});
}
@@ -796,7 +833,10 @@
});
}
- public void logAndEndEvent() {
+ /**
+ * Finish and log the event.
+ */
+ public void logAndEndEvent(String caller) {
if (!mEventInternal.isPresent()) {
Slog.w(TAG, "Shouldn't be logging AutofillPresentationEventReported again for same "
+ "event");
@@ -804,7 +844,8 @@
}
PresentationStatsEventInternal event = mEventInternal.get();
if (sVerbose) {
- Slog.v(TAG, "Log AutofillPresentationEventReported:"
+ Slog.v(TAG, "(" + caller + ") "
+ + "Log AutofillPresentationEventReported:"
+ " requestId=" + event.mRequestId
+ " sessionId=" + mSessionId
+ " mNoPresentationEventReason=" + event.mNoPresentationReason
@@ -926,7 +967,7 @@
mEventInternal = Optional.empty();
}
- private static final class PresentationStatsEventInternal {
+ static final class PresentationStatsEventInternal {
int mRequestId;
@NotShownReason int mNoPresentationReason = NOT_SHOWN_REASON_UNKNOWN;
boolean mIsDatasetAvailable;
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 6dea8b0..c75fd0b 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -28,7 +28,6 @@
import static android.service.autofill.Dataset.PICK_REASON_PROVIDER_DETECTION_PREFERRED_WITH_PCC;
import static android.service.autofill.Dataset.PICK_REASON_UNKNOWN;
import static android.service.autofill.FillEventHistory.Event.UI_TYPE_CREDMAN_BOTTOM_SHEET;
-import static android.service.autofill.FillEventHistory.Event.UI_TYPE_DIALOG;
import static android.service.autofill.FillEventHistory.Event.UI_TYPE_INLINE;
import static android.service.autofill.FillEventHistory.Event.UI_TYPE_UNKNOWN;
import static android.service.autofill.FillRequest.FLAG_MANUAL_REQUEST;
@@ -67,10 +66,10 @@
import static com.android.server.autofill.FillResponseEventLogger.RESPONSE_STATUS_SUCCESS;
import static com.android.server.autofill.FillResponseEventLogger.RESPONSE_STATUS_TIMEOUT;
import static com.android.server.autofill.FillResponseEventLogger.RESPONSE_STATUS_TRANSACTION_TOO_LARGE;
+import static com.android.server.autofill.Helper.SaveInfoStats;
import static com.android.server.autofill.Helper.containsCharsInOrder;
import static com.android.server.autofill.Helper.createSanitizers;
import static com.android.server.autofill.Helper.getNumericValue;
-import static com.android.server.autofill.Helper.SaveInfoStats;
import static com.android.server.autofill.Helper.sDebug;
import static com.android.server.autofill.Helper.sVerbose;
import static com.android.server.autofill.Helper.toArray;
@@ -78,6 +77,7 @@
import static com.android.server.autofill.PresentationStatsEventLogger.AUTHENTICATION_RESULT_SUCCESS;
import static com.android.server.autofill.PresentationStatsEventLogger.AUTHENTICATION_TYPE_DATASET_AUTHENTICATION;
import static com.android.server.autofill.PresentationStatsEventLogger.AUTHENTICATION_TYPE_FULL_AUTHENTICATION;
+import static com.android.server.autofill.PresentationStatsEventLogger.NOT_SHOWN_REASON_ANY_SHOWN;
import static com.android.server.autofill.PresentationStatsEventLogger.NOT_SHOWN_REASON_NO_FOCUS;
import static com.android.server.autofill.PresentationStatsEventLogger.NOT_SHOWN_REASON_REQUEST_FAILED;
import static com.android.server.autofill.PresentationStatsEventLogger.NOT_SHOWN_REASON_REQUEST_TIMEOUT;
@@ -1288,11 +1288,11 @@
* Clears the existing response for the partition, reads a new structure, and then requests a
* new fill response from the fill service.
*
- * <p> Also asks the IME to make an inline suggestions request if it's enabled.
+ * <p>Also asks the IME to make an inline suggestions request if it's enabled.
*/
@GuardedBy("mLock")
- private void requestNewFillResponseLocked(@NonNull ViewState viewState, int newState,
- int flags) {
+ private Optional<Integer> requestNewFillResponseLocked(
+ @NonNull ViewState viewState, int newState, int flags) {
boolean isSecondary = shouldRequestSecondaryProvider(flags);
final FillResponse existingResponse = isSecondary
? viewState.getSecondaryResponse() : viewState.getResponse();
@@ -1333,7 +1333,7 @@
mFillRequestEventLogger.maybeSetIsAugmented(true);
mFillRequestEventLogger.logAndEndEvent();
triggerAugmentedAutofillLocked(flags);
- return;
+ return Optional.empty();
}
viewState.setState(newState);
@@ -1353,11 +1353,6 @@
+ ", flags=" + flags);
}
boolean isCredmanRequested = (flags & FLAG_VIEW_REQUESTS_CREDMAN_SERVICE) != 0;
- mPresentationStatsEventLogger.maybeSetRequestId(requestId);
- mPresentationStatsEventLogger.maybeSetIsCredentialRequest(isCredmanRequested);
- mPresentationStatsEventLogger.maybeSetFieldClassificationRequestId(
- mFieldClassificationIdSnapshot);
- mPresentationStatsEventLogger.maybeSetAutofillServiceUid(getAutofillServiceUid());
mFillRequestEventLogger.maybeSetRequestId(requestId);
mFillRequestEventLogger.maybeSetAutofillServiceUid(getAutofillServiceUid());
mSaveEventLogger.maybeSetAutofillServiceUid(getAutofillServiceUid());
@@ -1417,6 +1412,8 @@
// Now request the assist structure data.
requestAssistStructureLocked(requestId, flags);
+
+ return Optional.of(requestId);
}
private boolean isRequestSupportFillDialog(int flags) {
@@ -1662,6 +1659,7 @@
final LogMaker requestLog;
synchronized (mLock) {
+ mPresentationStatsEventLogger.maybeSetRequestId(requestId);
// Start a new FillResponse logger for the success case.
mFillResponseEventLogger.startLogForNewResponse();
mFillResponseEventLogger.maybeSetRequestId(requestId);
@@ -2419,7 +2417,7 @@
NOT_SHOWN_REASON_REQUEST_FAILED);
mFillResponseEventLogger.maybeSetResponseStatus(RESPONSE_STATUS_FAILURE);
}
- mPresentationStatsEventLogger.logAndEndEvent();
+ mPresentationStatsEventLogger.logAndEndEvent("fill request failure");
mFillResponseEventLogger.maybeSetLatencyResponseProcessingMillis();
mFillResponseEventLogger.logAndEndEvent();
}
@@ -2642,6 +2640,8 @@
public void onShown(int uiType, int numDatasetsShown) {
synchronized (mLock) {
mPresentationStatsEventLogger.maybeSetDisplayPresentationType(uiType);
+ mPresentationStatsEventLogger.maybeSetNoPresentationEventReason(
+ NOT_SHOWN_REASON_ANY_SHOWN);
if (uiType == UI_TYPE_INLINE) {
// Inline Suggestions are inflated one at a time
@@ -2657,7 +2657,7 @@
}
mLoggedInlineDatasetShown = true;
} else {
- mPresentationStatsEventLogger.maybeSetCountShown(numDatasetsShown);
+ mPresentationStatsEventLogger.logWhenDatasetShown(numDatasetsShown);
// Explicitly sets maybeSetSuggestionPresentedTimestampMs
mPresentationStatsEventLogger.maybeSetSuggestionPresentedTimestampMs();
mService.logDatasetShown(this.id, mClientState, uiType);
@@ -2800,7 +2800,10 @@
if (mCurrentViewId == null) {
return;
}
+ mPresentationStatsEventLogger.logAndEndEvent("fallback from fill dialog");
+ startNewEventForPresentationStatsEventLogger();
final ViewState currentView = mViewStates.get(mCurrentViewId);
+ logPresentationStatsOnViewEnteredLocked(currentView.getResponse(), false);
currentView.maybeCallOnFillReady(mFlags);
}
}
@@ -2850,7 +2853,7 @@
if (requestId == AUGMENTED_AUTOFILL_REQUEST_ID) {
setAuthenticationResultForAugmentedAutofillLocked(data, authenticationId);
// Augmented autofill is not logged.
- mPresentationStatsEventLogger.logAndEndEvent();
+ mPresentationStatsEventLogger.logAndEndEvent("authentication - augmented");
return;
}
if (mResponses == null) {
@@ -2859,7 +2862,7 @@
Slog.w(TAG, "setAuthenticationResultLocked(" + authenticationId + "): no responses");
mPresentationStatsEventLogger.maybeSetAuthenticationResult(
AUTHENTICATION_RESULT_FAILURE);
- mPresentationStatsEventLogger.logAndEndEvent();
+ mPresentationStatsEventLogger.logAndEndEvent("authentication - no response");
removeFromService();
return;
}
@@ -2870,7 +2873,7 @@
Slog.w(TAG, "no authenticated response");
mPresentationStatsEventLogger.maybeSetAuthenticationResult(
AUTHENTICATION_RESULT_FAILURE);
- mPresentationStatsEventLogger.logAndEndEvent();
+ mPresentationStatsEventLogger.logAndEndEvent("authentication - bad response");
removeFromService();
return;
}
@@ -2885,7 +2888,7 @@
Slog.w(TAG, "no dataset with index " + datasetIdx + " on fill response");
mPresentationStatsEventLogger.maybeSetAuthenticationResult(
AUTHENTICATION_RESULT_FAILURE);
- mPresentationStatsEventLogger.logAndEndEvent();
+ mPresentationStatsEventLogger.logAndEndEvent("authentication - no datasets");
removeFromService();
return;
}
@@ -3330,7 +3333,7 @@
mPresentationStatsEventLogger.maybeSetNoPresentationEventReason(
PresentationStatsEventLogger.getNoPresentationEventReason(commitReason));
- mPresentationStatsEventLogger.logAndEndEvent();
+ mPresentationStatsEventLogger.logAndEndEvent("Context committed");
final int flags = lastResponse.getFlags();
if ((flags & FillResponse.FLAG_TRACK_CONTEXT_COMMITED) == 0) {
@@ -4299,6 +4302,7 @@
* Starts (if necessary) a new fill request upon entering a view.
*
* <p>A new request will be started in 2 scenarios:
+ *
* <ol>
* <li>If the user manually requested autofill.
* <li>If the view is part of a new partition.
@@ -4307,18 +4311,17 @@
* @param id The id of the view that is entered.
* @param viewState The view that is entered.
* @param flags The flag that was passed by the AutofillManager.
- *
* @return {@code true} if a new fill response is requested.
*/
@GuardedBy("mLock")
- private boolean requestNewFillResponseOnViewEnteredIfNecessaryLocked(@NonNull AutofillId id,
- @NonNull ViewState viewState, int flags) {
+ private Optional<Integer> requestNewFillResponseOnViewEnteredIfNecessaryLocked(
+ @NonNull AutofillId id, @NonNull ViewState viewState, int flags) {
// Force new response for manual request
if ((flags & FLAG_MANUAL_REQUEST) != 0) {
mSessionFlags.mAugmentedAutofillOnly = false;
if (sDebug) Slog.d(TAG, "Re-starting session on view " + id + " and flags " + flags);
- requestNewFillResponseLocked(viewState, ViewState.STATE_RESTARTED_SESSION, flags);
- return true;
+ return requestNewFillResponseLocked(
+ viewState, ViewState.STATE_RESTARTED_SESSION, flags);
}
// If it's not, then check if it should start a partition.
@@ -4331,15 +4334,15 @@
// Sometimes activity contain IMPORTANT_FOR_AUTOFILL_NO fields which marks session as
// augmentedOnly, but other fields are still fillable by standard autofill.
mSessionFlags.mAugmentedAutofillOnly = false;
- requestNewFillResponseLocked(viewState, ViewState.STATE_STARTED_PARTITION, flags);
- return true;
+ return requestNewFillResponseLocked(
+ viewState, ViewState.STATE_STARTED_PARTITION, flags);
}
if (sVerbose) {
Slog.v(TAG, "Not starting new partition for view " + id + ": "
+ viewState.getStateAsString());
}
- return false;
+ return Optional.empty();
}
/**
@@ -4428,31 +4431,32 @@
void updateLocked(AutofillId id, Rect virtualBounds, AutofillValue value, int action,
int flags) {
if (mDestroyed) {
- Slog.w(TAG, "Call to Session#updateLocked() rejected - session: "
- + id + " destroyed");
+ Slog.w(TAG, "updateLocked(" + id + "): rejected - session: destroyed");
return;
}
if (action == ACTION_RESPONSE_EXPIRED) {
mSessionFlags.mExpiredResponse = true;
if (sDebug) {
- Slog.d(TAG, "Set the response has expired.");
+ Slog.d(TAG, "updateLocked(" + id + "): Set the response has expired.");
}
mPresentationStatsEventLogger.maybeSetNoPresentationEventReasonIfNoReasonExists(
NOT_SHOWN_REASON_VIEW_CHANGED);
- mPresentationStatsEventLogger.logAndEndEvent();
+ mPresentationStatsEventLogger.logAndEndEvent("ACTION_RESPONSE_EXPIRED");
return;
}
id.setSessionId(this.id);
- if (sVerbose) {
- Slog.v(TAG, "updateLocked(" + this.id + "): id=" + id + ", action="
- + actionAsString(action) + ", flags=" + flags);
- }
ViewState viewState = mViewStates.get(id);
if (sVerbose) {
- Slog.v(TAG, "updateLocked(" + this.id + "): mCurrentViewId=" + mCurrentViewId
- + ", mExpiredResponse=" + mSessionFlags.mExpiredResponse
- + ", viewState=" + viewState);
+ Slog.v(
+ TAG,
+ "updateLocked(" + id + "): "
+ + "id=" + this.id
+ + ", action=" + actionAsString(action)
+ + ", flags=" + flags
+ + ", mCurrentViewId=" + mCurrentViewId
+ + ", mExpiredResponse=" + mSessionFlags.mExpiredResponse
+ + ", viewState=" + viewState);
}
if (viewState == null) {
@@ -4505,14 +4509,14 @@
mSessionFlags.mFillDialogDisabled = true;
mPreviouslyFillDialogPotentiallyStarted = false;
} else {
- // Set the default reason for now if the user doesn't trigger any focus event
- // on the autofillable view. This can be changed downstream when more
- // information is available or session is committed.
- mPresentationStatsEventLogger.maybeSetNoPresentationEventReason(
- NOT_SHOWN_REASON_NO_FOCUS);
mPreviouslyFillDialogPotentiallyStarted = true;
}
- requestNewFillResponseLocked(viewState, ViewState.STATE_STARTED_SESSION, flags);
+ Optional<Integer> maybeRequestId =
+ requestNewFillResponseLocked(
+ viewState, ViewState.STATE_STARTED_SESSION, flags);
+ if (maybeRequestId.isPresent()) {
+ mPresentationStatsEventLogger.maybeSetRequestId(maybeRequestId.get());
+ }
break;
case ACTION_VALUE_CHANGED:
if (mCompatMode && (viewState.getState() & ViewState.STATE_URL_BAR) != 0) {
@@ -4577,8 +4581,10 @@
}
boolean isCredmanRequested = (flags & FLAG_VIEW_REQUESTS_CREDMAN_SERVICE) != 0;
if (shouldRequestSecondaryProvider(flags)) {
- if (requestNewFillResponseOnViewEnteredIfNecessaryLocked(
- id, viewState, flags)) {
+ Optional<Integer> maybeRequestIdCred =
+ requestNewFillResponseOnViewEnteredIfNecessaryLocked(
+ id, viewState, flags);
+ if (maybeRequestIdCred.isPresent()) {
Slog.v(TAG, "Started a new fill request for secondary provider.");
return;
}
@@ -4622,17 +4628,7 @@
mLogViewEntered = true;
}
- // Previously, fill request will only start whenever a view is entered.
- // With Fill Dialog, request starts prior to view getting entered. So, we can't end
- // the event at this moment, otherwise we will be wrongly attributing fill dialog
- // event as concluded.
- if (!wasPreviouslyFillDialog && !isSameViewAgain) {
- // TODO(b/319872477): Re-consider this logic below
- mPresentationStatsEventLogger.maybeSetNoPresentationEventReason(
- NOT_SHOWN_REASON_VIEW_FOCUS_CHANGED);
- mPresentationStatsEventLogger.logAndEndEvent();
- }
-
+ // Trigger augmented autofill if applicable
if ((flags & FLAG_MANUAL_REQUEST) == 0) {
// Not a manual request
if (mAugmentedAutofillableIds != null && mAugmentedAutofillableIds.contains(
@@ -4658,26 +4654,25 @@
return;
}
}
- // If previous request was FillDialog request, a logger event was already started
- if (!wasPreviouslyFillDialog) {
+
+ Optional<Integer> maybeNewRequestId =
+ requestNewFillResponseOnViewEnteredIfNecessaryLocked(id, viewState, flags);
+
+ // Previously, fill request will only start whenever a view is entered.
+ // With Fill Dialog, request starts prior to view getting entered. So, we can't end
+ // the event at this moment, otherwise we will be wrongly attributing fill dialog
+ // event as concluded.
+ if (!wasPreviouslyFillDialog
+ && (!isSameViewEntered || maybeNewRequestId.isPresent())) {
startNewEventForPresentationStatsEventLogger();
- }
- if (requestNewFillResponseOnViewEnteredIfNecessaryLocked(id, viewState, flags)) {
- // If a new request was issued even if previously it was fill dialog request,
- // we should end the log event, and start a new one. However, it leaves us
- // susceptible to race condition. But since mPresentationStatsEventLogger is
- // lock guarded, we should be safe.
- if (wasPreviouslyFillDialog) {
- mPresentationStatsEventLogger.logAndEndEvent();
- startNewEventForPresentationStatsEventLogger();
+ if (maybeNewRequestId.isPresent()) {
+ mPresentationStatsEventLogger.maybeSetRequestId(maybeNewRequestId.get());
}
- return;
}
- FillResponse response = viewState.getResponse();
- if (response != null) {
- logPresentationStatsOnViewEnteredLocked(response, isCredmanRequested);
- }
+ logPresentationStatsOnViewEnteredLocked(
+ viewState.getResponse(), isCredmanRequested);
+ mPresentationStatsEventLogger.updateTextFieldLength(value);
if (isSameViewEntered) {
setFillDialogDisabledAndStartInput();
@@ -4719,13 +4714,17 @@
@GuardedBy("mLock")
private void logPresentationStatsOnViewEnteredLocked(FillResponse response,
boolean isCredmanRequested) {
- mPresentationStatsEventLogger.maybeSetRequestId(response.getRequestId());
mPresentationStatsEventLogger.maybeSetIsCredentialRequest(isCredmanRequested);
mPresentationStatsEventLogger.maybeSetFieldClassificationRequestId(
mFieldClassificationIdSnapshot);
- mPresentationStatsEventLogger.maybeSetAvailableCount(
- response.getDatasets(), mCurrentViewId);
+ mPresentationStatsEventLogger.maybeSetAutofillServiceUid(getAutofillServiceUid());
mPresentationStatsEventLogger.maybeSetFocusedId(mCurrentViewId);
+
+ if (response != null) {
+ mPresentationStatsEventLogger.maybeSetRequestId(response.getRequestId());
+ mPresentationStatsEventLogger.maybeSetAvailableCount(
+ response.getDatasets(), mCurrentViewId);
+ }
}
@GuardedBy("mLock")
@@ -4796,8 +4795,12 @@
viewState.setCurrentValue(value);
final String filterText = textValue;
-
final AutofillValue filledValue = viewState.getAutofilledValue();
+
+ if (textValue != null) {
+ mPresentationStatsEventLogger.onFieldTextUpdated(viewState, value);
+ }
+
if (filledValue != null) {
if (filledValue.equals(value)) {
// When the update is caused by autofilling the view, just update the
@@ -4821,9 +4824,6 @@
currentView.maybeCallOnFillReady(flags);
}
}
- if (textValue != null) {
- mPresentationStatsEventLogger.onFieldTextUpdated(viewState, textValue.length());
- }
if (viewState.id.equals(this.mCurrentViewId)
&& (viewState.getState() & ViewState.STATE_INLINE_SHOWN) != 0) {
@@ -4888,7 +4888,7 @@
mSaveEventLogger.logAndEndEvent();
mPresentationStatsEventLogger.maybeSetNoPresentationEventReason(
NOT_SHOWN_REASON_SESSION_COMMITTED_PREMATURELY);
- mPresentationStatsEventLogger.logAndEndEvent();
+ mPresentationStatsEventLogger.logAndEndEvent("on fill ready");
return;
}
}
@@ -4920,7 +4920,6 @@
synchronized (mLock) {
final ViewState currentView = mViewStates.get(mCurrentViewId);
currentView.setState(ViewState.STATE_FILL_DIALOG_SHOWN);
- mPresentationStatsEventLogger.maybeSetDisplayPresentationType(UI_TYPE_DIALOG);
}
// Just show fill dialog once, so disabled after shown.
// Note: Cannot disable before requestShowFillDialog() because the method
@@ -6086,6 +6085,11 @@
private void startNewEventForPresentationStatsEventLogger() {
synchronized (mLock) {
mPresentationStatsEventLogger.startNewEvent();
+ // Set the default reason for now if the user doesn't trigger any focus event
+ // on the autofillable view. This can be changed downstream when more
+ // information is available or session is committed.
+ mPresentationStatsEventLogger.maybeSetNoPresentationEventReason(
+ NOT_SHOWN_REASON_NO_FOCUS);
mPresentationStatsEventLogger.maybeSetDetectionPreference(
getDetectionPreferenceForLogging());
mPresentationStatsEventLogger.maybeSetAutofillServiceUid(getAutofillServiceUid());
@@ -6724,7 +6728,7 @@
SystemClock.elapsedRealtime() - mStartTime);
mFillRequestEventLogger.logAndEndEvent();
mFillResponseEventLogger.logAndEndEvent();
- mPresentationStatsEventLogger.logAndEndEvent();
+ mPresentationStatsEventLogger.logAndEndEvent("log all events");
mSaveEventLogger.logAndEndEvent();
mSessionCommittedEventLogger.logAndEndEvent();
}
diff --git a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
index a10039f..2446a6d 100644
--- a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
+++ b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
@@ -461,9 +461,9 @@
}
@Override
- public void onShown() {
+ public void onShown(int datasetsShown) {
if (mCallback != null) {
- mCallback.onShown(UI_TYPE_DIALOG, response.getDatasets().size());
+ mCallback.onShown(UI_TYPE_DIALOG, datasetsShown);
}
}
diff --git a/services/autofill/java/com/android/server/autofill/ui/DialogFillUi.java b/services/autofill/java/com/android/server/autofill/ui/DialogFillUi.java
index 5a71b89..c7b6be6 100644
--- a/services/autofill/java/com/android/server/autofill/ui/DialogFillUi.java
+++ b/services/autofill/java/com/android/server/autofill/ui/DialogFillUi.java
@@ -85,7 +85,7 @@
void onDatasetPicked(@NonNull Dataset dataset);
void onDismissed();
void onCanceled();
- void onShown();
+ void onShown(int datasetsShown);
void startIntentSender(IntentSender intentSender);
}
@@ -155,7 +155,8 @@
mDialog.setContentView(decor);
setDialogParamsAsBottomSheet();
mDialog.setOnCancelListener((d) -> mCallback.onCanceled());
- mDialog.setOnShowListener((d) -> mCallback.onShown());
+ int datasetsShown = (mAdapter != null) ? mAdapter.getCount() : 0;
+ mDialog.setOnShowListener((d) -> mCallback.onShown(datasetsShown));
show();
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 3ac91b3..d169b1e 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -3292,6 +3292,12 @@
return false;
}
+ // Check if this activity is the top activity of its task - this prevents any trampolines
+ // followed by enterPictureInPictureMode() calls by an activity from below in its stack.
+ if (getTask().getTopMostActivity() != this) {
+ return false;
+ }
+
// Check to see if PiP is supported for the display this container is on.
if (mDisplayContent != null && !mDisplayContent.mDwpcHelper.isEnteringPipAllowed(
getUid())) {
diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
index 02c8a49..20c5f02 100644
--- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
+++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
@@ -1506,10 +1506,13 @@
PackageManager pm = mService.mContext.getPackageManager();
ApplicationInfo applicationInfo;
+ final int sourceUserId = UserHandle.getUserId(sourceUid);
try {
- applicationInfo = pm.getApplicationInfo(packageName, 0);
+ applicationInfo = pm.getApplicationInfoAsUser(packageName, /* flags= */ 0,
+ sourceUserId);
} catch (PackageManager.NameNotFoundException e) {
- Slog.wtf(TAG, "Package name: " + packageName + " not found.");
+ Slog.wtf(TAG, "Package name: " + packageName + " not found for user "
+ + sourceUserId);
return bas.optedIn(ar);
}
diff --git a/services/core/java/com/android/server/wm/ConfigurationContainer.java b/services/core/java/com/android/server/wm/ConfigurationContainer.java
index 3ebaf03..9f1966b 100644
--- a/services/core/java/com/android/server/wm/ConfigurationContainer.java
+++ b/services/core/java/com/android/server/wm/ConfigurationContainer.java
@@ -255,8 +255,18 @@
inOutConfig.windowConfiguration.setAppBounds(
newParentConfiguration.windowConfiguration.getBounds());
outAppBounds = inOutConfig.windowConfiguration.getAppBounds();
- outAppBounds.inset(displayContent.getDisplayPolicy()
- .getDecorInsetsInfo(rotation, dw, dh).mOverrideNonDecorInsets);
+ if (inOutConfig.windowConfiguration.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) {
+ final DisplayPolicy.DecorInsets.Info decor =
+ displayContent.getDisplayPolicy().getDecorInsetsInfo(rotation, dw, dh);
+ if (outAppBounds.contains(decor.mOverrideNonDecorFrame)) {
+ outAppBounds.intersect(decor.mOverrideNonDecorFrame);
+ }
+ } else {
+ // TODO(b/358509380): Handle other windowing mode like split screen and freeform
+ // cases correctly.
+ outAppBounds.inset(displayContent.getDisplayPolicy()
+ .getDecorInsetsInfo(rotation, dw, dh).mOverrideNonDecorInsets);
+ }
}
float density = inOutConfig.densityDpi;
if (density == Configuration.DENSITY_DPI_UNDEFINED) {
diff --git a/services/core/java/com/android/server/wm/DisplayWindowSettings.java b/services/core/java/com/android/server/wm/DisplayWindowSettings.java
index 2f0ee17..f40f2617 100644
--- a/services/core/java/com/android/server/wm/DisplayWindowSettings.java
+++ b/services/core/java/com/android/server/wm/DisplayWindowSettings.java
@@ -16,6 +16,7 @@
package com.android.server.wm;
+import static android.view.Display.REMOVE_MODE_DESTROY_CONTENT;
import static android.view.WindowManager.DISPLAY_IME_POLICY_FALLBACK_DISPLAY;
import static android.view.WindowManager.DISPLAY_IME_POLICY_LOCAL;
import static android.view.WindowManager.REMOVE_CONTENT_MODE_DESTROY;
@@ -183,7 +184,7 @@
final DisplayInfo displayInfo = dc.getDisplayInfo();
final SettingsProvider.SettingsEntry settings = mSettingsProvider.getSettings(displayInfo);
if (settings.mRemoveContentMode == REMOVE_CONTENT_MODE_UNDEFINED) {
- if (dc.isPrivate()) {
+ if (dc.isPrivate() || dc.getDisplay().getRemoveMode() == REMOVE_MODE_DESTROY_CONTENT) {
// For private displays by default content is destroyed on removal.
return REMOVE_CONTENT_MODE_DESTROY;
}
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 329d11b..3a40943 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -1780,11 +1780,6 @@
if (resuming != null) {
// We do not want to trigger auto-PiP upon launch of a translucent activity.
final boolean resumingOccludesParent = resuming.occludesParent();
- // Resuming the new resume activity only if the previous activity can't go into Pip
- // since we want to give Pip activities a chance to enter Pip before resuming the
- // next activity.
- final boolean lastResumedCanPip = prev.checkEnterPictureInPictureState(
- "shouldAutoPipWhilePausing", userLeaving);
if (ActivityTaskManagerService.isPip2ExperimentEnabled()) {
// If a new task is being launched, then mark the existing top activity as
@@ -1794,6 +1789,12 @@
Task.enableEnterPipOnTaskSwitch(prev, resuming.getTask(),
resuming, resuming.getOptions());
}
+
+ // Resuming the new resume activity only if the previous activity can't go into Pip
+ // since we want to give Pip activities a chance to enter Pip before resuming the
+ // next activity.
+ final boolean lastResumedCanPip = prev.checkEnterPictureInPictureState(
+ "shouldAutoPipWhilePausing", userLeaving);
if (prev.supportsEnterPipOnTaskSwitch && userLeaving
&& resumingOccludesParent && lastResumedCanPip
&& prev.pictureInPictureArgs.isAutoEnterEnabled()) {
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationGestureHandlerTest.java
index 3931580..d80a1f0 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationGestureHandlerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationGestureHandlerTest.java
@@ -18,13 +18,20 @@
import static android.view.MotionEvent.ACTION_CANCEL;
import static android.view.MotionEvent.ACTION_DOWN;
+import static android.view.MotionEvent.ACTION_HOVER_MOVE;
import static android.view.MotionEvent.ACTION_UP;
+import static junit.framework.Assert.assertFalse;
+
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;
import static org.testng.AssertJUnit.assertTrue;
import android.annotation.NonNull;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.provider.Settings;
import android.view.InputDevice;
import android.view.MotionEvent;
@@ -32,8 +39,10 @@
import androidx.test.runner.AndroidJUnit4;
import com.android.server.accessibility.AccessibilityTraceManager;
+import com.android.server.accessibility.Flags;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -45,6 +54,9 @@
@RunWith(AndroidJUnit4.class)
public class MagnificationGestureHandlerTest {
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
private TestMagnificationGestureHandler mMgh;
private static final int DISPLAY_0 = 0;
private static final int FULLSCREEN_MODE =
@@ -81,6 +93,66 @@
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE)
+ public void onMotionEvent_isFromMouse_handleMouseOrStylusEvent() {
+ final MotionEvent mouseEvent = MotionEvent.obtain(0, 0, ACTION_HOVER_MOVE, 0, 0, 0);
+ mouseEvent.setSource(InputDevice.SOURCE_MOUSE);
+
+ mMgh.onMotionEvent(mouseEvent, mouseEvent, /* policyFlags= */ 0);
+
+ try {
+ assertTrue(mMgh.mIsHandleMouseOrStylusEventCalled);
+ } finally {
+ mouseEvent.recycle();
+ }
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE)
+ public void onMotionEvent_isFromStylus_handleMouseOrStylusEvent() {
+ final MotionEvent stylusEvent = MotionEvent.obtain(0, 0, ACTION_HOVER_MOVE, 0, 0, 0);
+ stylusEvent.setSource(InputDevice.SOURCE_STYLUS);
+
+ mMgh.onMotionEvent(stylusEvent, stylusEvent, /* policyFlags= */ 0);
+
+ try {
+ assertTrue(mMgh.mIsHandleMouseOrStylusEventCalled);
+ } finally {
+ stylusEvent.recycle();
+ }
+ }
+
+ @Test
+ @RequiresFlagsDisabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE)
+ public void onMotionEvent_isFromMouse_handleMouseOrStylusEventNotCalled() {
+ final MotionEvent mouseEvent = MotionEvent.obtain(0, 0, ACTION_HOVER_MOVE, 0, 0, 0);
+ mouseEvent.setSource(InputDevice.SOURCE_MOUSE);
+
+ mMgh.onMotionEvent(mouseEvent, mouseEvent, /* policyFlags= */ 0);
+
+ try {
+ assertFalse(mMgh.mIsHandleMouseOrStylusEventCalled);
+ } finally {
+ mouseEvent.recycle();
+ }
+ }
+
+ @Test
+ @RequiresFlagsDisabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE)
+ public void onMotionEvent_isFromStylus_handleMouseOrStylusEventNotCalled() {
+ final MotionEvent stylusEvent = MotionEvent.obtain(0, 0, ACTION_HOVER_MOVE, 0, 0, 0);
+ stylusEvent.setSource(InputDevice.SOURCE_STYLUS);
+
+ mMgh.onMotionEvent(stylusEvent, stylusEvent, /* policyFlags= */ 0);
+
+ try {
+ assertFalse(mMgh.mIsHandleMouseOrStylusEventCalled);
+ } finally {
+ stylusEvent.recycle();
+ }
+ }
+
+ @Test
public void onMotionEvent_downEvent_handleInteractionStart() {
final MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0);
downEvent.setSource(InputDevice.SOURCE_TOUCHSCREEN);
@@ -125,6 +197,7 @@
private static class TestMagnificationGestureHandler extends MagnificationGestureHandler {
boolean mIsInternalMethodCalled = false;
+ boolean mIsHandleMouseOrStylusEventCalled = false;
TestMagnificationGestureHandler(int displayId, boolean detectSingleFingerTripleTap,
boolean detectTwoFingerTripleTap,
@@ -135,6 +208,11 @@
}
@Override
+ void handleMouseOrStylusEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ mIsHandleMouseOrStylusEventCalled = true;
+ }
+
+ @Override
void onMotionEventInternal(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
mIsInternalMethodCalled = true;
}
diff --git a/services/tests/servicestests/src/com/android/server/autofill/PresentationEventLoggerTest.java b/services/tests/servicestests/src/com/android/server/autofill/PresentationEventLoggerTest.java
new file mode 100644
index 0000000..75258f0
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/autofill/PresentationEventLoggerTest.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2024 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.autofill;
+
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.view.autofill.AutofillId;
+import android.view.autofill.AutofillValue;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class PresentationEventLoggerTest {
+
+ @Test
+ public void testViewEntered() {
+ PresentationStatsEventLogger pEventLogger =
+ PresentationStatsEventLogger.createPresentationLog(1, 1, 1);
+
+ AutofillId id = new AutofillId(13);
+ AutofillValue initialValue = AutofillValue.forText("hello");
+ AutofillValue lastValue = AutofillValue.forText("hello world");
+ ViewState vState = new ViewState(id, null, 0, false);
+
+ pEventLogger.startNewEvent();
+ pEventLogger.maybeSetFocusedId(id);
+ pEventLogger.onFieldTextUpdated(vState, initialValue);
+ pEventLogger.onFieldTextUpdated(vState, lastValue);
+
+ PresentationStatsEventLogger.PresentationStatsEventInternal event =
+ pEventLogger.getInternalEvent().get();
+ assertThat(event).isNotNull();
+ assertThat(event.mFieldFirstLength).isEqualTo(initialValue.getTextValue().length());
+ assertThat(event.mFieldLastLength).isEqualTo(lastValue.getTextValue().length());
+ assertThat(event.mFieldModifiedFirstTimestampMs).isNotEqualTo(-1);
+ assertThat(event.mFieldModifiedLastTimestampMs).isNotEqualTo(-1);
+ }
+
+ @Test
+ public void testViewAutofilled() {
+ PresentationStatsEventLogger pEventLogger =
+ PresentationStatsEventLogger.createPresentationLog(1, 1, 1);
+
+ String newTextValue = "hello";
+ AutofillValue value = AutofillValue.forText(newTextValue);
+ AutofillId id = new AutofillId(13);
+ ViewState vState = new ViewState(id, null, ViewState.STATE_AUTOFILLED, false);
+
+ pEventLogger.startNewEvent();
+ pEventLogger.maybeSetFocusedId(id);
+ pEventLogger.onFieldTextUpdated(vState, value);
+
+ PresentationStatsEventLogger.PresentationStatsEventInternal event =
+ pEventLogger.getInternalEvent().get();
+ assertThat(event).isNotNull();
+ assertThat(event.mFieldFirstLength).isEqualTo(newTextValue.length());
+ assertThat(event.mFieldLastLength).isEqualTo(newTextValue.length());
+ assertThat(event.mAutofilledTimestampMs).isNotEqualTo(-1);
+ assertThat(event.mFieldModifiedFirstTimestampMs).isEqualTo(-1);
+ assertThat(event.mFieldModifiedLastTimestampMs).isEqualTo(-1);
+ }
+
+ @Test
+ public void testModifiedOnDifferentView() {
+ PresentationStatsEventLogger pEventLogger =
+ PresentationStatsEventLogger.createPresentationLog(1, 1, 1);
+
+ String newTextValue = "hello";
+ AutofillValue value = AutofillValue.forText(newTextValue);
+ AutofillId id = new AutofillId(13);
+ ViewState vState = new ViewState(id, null, ViewState.STATE_AUTOFILLED, false);
+
+ pEventLogger.startNewEvent();
+ pEventLogger.onFieldTextUpdated(vState, value);
+
+ PresentationStatsEventLogger.PresentationStatsEventInternal event =
+ pEventLogger.getInternalEvent().get();
+ assertThat(event).isNotNull();
+ assertThat(event.mFieldFirstLength).isEqualTo(-1);
+ assertThat(event.mFieldLastLength).isEqualTo(-1);
+ assertThat(event.mFieldModifiedFirstTimestampMs).isEqualTo(-1);
+ assertThat(event.mFieldModifiedLastTimestampMs).isEqualTo(-1);
+ assertThat(event.mAutofilledTimestampMs).isEqualTo(-1);
+ }
+
+ @Test
+ public void testSetCountShown() {
+ PresentationStatsEventLogger pEventLogger =
+ PresentationStatsEventLogger.createPresentationLog(1, 1, 1);
+
+ pEventLogger.startNewEvent();
+ pEventLogger.logWhenDatasetShown(7);
+
+ PresentationStatsEventLogger.PresentationStatsEventInternal event =
+ pEventLogger.getInternalEvent().get();
+ assertThat(event).isNotNull();
+ assertThat(event.mCountShown).isEqualTo(7);
+ assertThat(event.mNoPresentationReason)
+ .isEqualTo(PresentationStatsEventLogger.NOT_SHOWN_REASON_ANY_SHOWN);
+ }
+
+ @Test
+ public void testFillDialogShownThenInline() {
+ PresentationStatsEventLogger pEventLogger =
+ PresentationStatsEventLogger.createPresentationLog(1, 1, 1);
+
+ pEventLogger.startNewEvent();
+ pEventLogger.maybeSetDisplayPresentationType(3);
+ pEventLogger.maybeSetDisplayPresentationType(2);
+
+ PresentationStatsEventLogger.PresentationStatsEventInternal event =
+ pEventLogger.getInternalEvent().get();
+ assertThat(event).isNotNull();
+ assertThat(event.mDisplayPresentationType).isEqualTo(3);
+ }
+}
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
index 36bfbef..c510eee 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
@@ -334,13 +334,14 @@
entry: ZipEntry,
out: ZipOutputStream,
) {
- BufferedInputStream(inZip.getInputStream(entry)).use { bis ->
+ // TODO: It seems like copying entries this way is _very_ slow,
+ // even with out.setLevel(0). Look for other ways to do it.
+
+ inZip.getInputStream(entry).use { ins ->
// Copy unknown entries as is to the impl out. (but not to the stub out.)
val outEntry = ZipEntry(entry.name)
out.putNextEntry(outEntry)
- while (bis.available() > 0) {
- out.write(bis.read())
- }
+ ins.transferTo(out)
out.closeEntry()
}
}
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenLogger.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenLogger.kt
index ee4a06f..fcdf824 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenLogger.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenLogger.kt
@@ -121,7 +121,7 @@
return level.ordinal <= maxLogLevel.ordinal
}
- private fun println(level: LogLevel, message: String) {
+ fun println(level: LogLevel, message: String) {
printers.forEach {
if (it.logLevel.ordinal >= level.ordinal) {
it.println(level, indent, message)
@@ -129,7 +129,7 @@
}
}
- private fun println(level: LogLevel, format: String, vararg args: Any?) {
+ fun println(level: LogLevel, format: String, vararg args: Any?) {
if (isEnabled(level)) {
println(level, String.format(format, *args))
}
@@ -185,14 +185,29 @@
println(LogLevel.Debug, format, *args)
}
- inline fun <T> iTime(message: String, block: () -> T): T {
+ inline fun <T> logTime(level: LogLevel, message: String, block: () -> T): T {
val start = System.currentTimeMillis()
- val ret = block()
- val end = System.currentTimeMillis()
+ try {
+ return block()
+ } finally {
+ val end = System.currentTimeMillis()
+ if (isEnabled(level)) {
+ println(level,
+ String.format("%s: took %.1f second(s).", message, (end - start) / 1000.0))
+ }
+ }
+ }
- log.i("%s: took %.1f second(s).", message, (end - start) / 1000.0)
+ inline fun <T> iTime(message: String, block: () -> T): T {
+ return logTime(LogLevel.Info, message, block)
+ }
- return ret
+ inline fun <T> vTime(message: String, block: () -> T): T {
+ return logTime(LogLevel.Verbose, message, block)
+ }
+
+ inline fun <T> dTime(message: String, block: () -> T): T {
+ return logTime(LogLevel.Debug, message, block)
}
inline fun forVerbose(block: () -> Unit) {
diff --git a/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt b/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt
index aa53005..2285880 100644
--- a/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt
+++ b/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt
@@ -26,7 +26,6 @@
import com.github.javaparser.ast.Modifier
import com.github.javaparser.ast.NodeList
import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration
-import com.github.javaparser.ast.body.InitializerDeclaration
import com.github.javaparser.ast.expr.ArrayAccessExpr
import com.github.javaparser.ast.expr.ArrayCreationExpr
import com.github.javaparser.ast.expr.ArrayInitializerExpr
@@ -42,7 +41,10 @@
import com.github.javaparser.ast.expr.ObjectCreationExpr
import com.github.javaparser.ast.expr.SimpleName
import com.github.javaparser.ast.expr.StringLiteralExpr
+import com.github.javaparser.ast.expr.VariableDeclarationExpr
import com.github.javaparser.ast.stmt.BlockStmt
+import com.github.javaparser.ast.stmt.ReturnStmt
+import com.github.javaparser.ast.type.ClassOrInterfaceType
import java.io.File
import java.io.FileInputStream
import java.io.FileNotFoundException
@@ -195,6 +197,7 @@
groups: Map<String, LogGroup>,
protoLogGroupsClassName: String
) {
+ var needsCreateLogGroupsMap = false
classDeclaration.fields.forEach { field ->
field.getAnnotationByClass(ProtoLogToolInjected::class.java)
.ifPresent { annotationExpr ->
@@ -222,33 +225,10 @@
} ?: NullLiteralExpr())
}
ProtoLogToolInjected.Value.LOG_GROUPS.name -> {
- val initializerBlockStmt = BlockStmt()
- for (group in groups) {
- initializerBlockStmt.addStatement(
- MethodCallExpr()
- .setName("put")
- .setArguments(
- NodeList(StringLiteralExpr(group.key),
- FieldAccessExpr()
- .setScope(
- NameExpr(
- protoLogGroupsClassName
- ))
- .setName(group.value.name)))
- )
- group.key
- }
-
- val treeMapCreation = ObjectCreationExpr()
- .setType("TreeMap<String, IProtoLogGroup>")
- .setAnonymousClassBody(NodeList(
- InitializerDeclaration().setBody(
- initializerBlockStmt
- )
- ))
-
+ needsCreateLogGroupsMap = true
field.setFinal(true)
- field.variables.first().setInitializer(treeMapCreation)
+ field.variables.first().setInitializer(
+ MethodCallExpr().setName("createLogGroupsMap"))
}
ProtoLogToolInjected.Value.CACHE_UPDATER.name -> {
field.setFinal(true)
@@ -261,6 +241,45 @@
}
}
}
+
+ if (needsCreateLogGroupsMap) {
+ val body = BlockStmt()
+ body.addStatement(AssignExpr(
+ VariableDeclarationExpr(
+ ClassOrInterfaceType("TreeMap<String, IProtoLogGroup>"),
+ "result"
+ ),
+ ObjectCreationExpr().setType("TreeMap<String, IProtoLogGroup>"),
+ AssignExpr.Operator.ASSIGN
+ ))
+ for (group in groups) {
+ body.addStatement(
+ MethodCallExpr(
+ NameExpr("result"),
+ "put",
+ NodeList(
+ StringLiteralExpr(group.key),
+ FieldAccessExpr()
+ .setScope(
+ NameExpr(
+ protoLogGroupsClassName
+ ))
+ .setName(group.value.name)
+ )
+ )
+ )
+ }
+ body.addStatement(ReturnStmt(NameExpr("result")))
+
+ val method = classDeclaration.addMethod(
+ "createLogGroupsMap",
+ Modifier.Keyword.PRIVATE,
+ Modifier.Keyword.STATIC,
+ Modifier.Keyword.FINAL
+ )
+ method.setType("TreeMap<String, IProtoLogGroup>")
+ method.setBody(body)
+ }
}
private fun injectCacheClass(