Merge "Polish edit mode UI for BC25" into main
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index 0321e1df..97f6899 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -1669,6 +1669,46 @@
}
/**
+ * Gets the mapping between the doze brightness sensor values and brightness values. The doze
+ * brightness sensor is a light sensor used to determine the brightness while the device is
+ * dozing. Light sensor values are typically integers in the rage of 0-4. The returned values
+ * are between {@link PowerManager#BRIGHTNESS_MIN} and {@link PowerManager#BRIGHTNESS_MAX}, or
+ * -1 meaning that the current brightness should be kept.
+ * <p>
+ * Requires the {@link android.Manifest.permission#CONTROL_DISPLAY_BRIGHTNESS}
+ * permission.
+ * </p>
+ *
+ * @param displayId The ID of the display
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.CONTROL_DISPLAY_BRIGHTNESS)
+ @Nullable
+ public float[] getDozeBrightnessSensorValueToBrightness(int displayId) {
+ return mGlobal.getDozeBrightnessSensorValueToBrightness(displayId);
+ }
+
+ /**
+ * Gets the default doze brightness.
+ * The returned values are between {@link PowerManager#BRIGHTNESS_MIN} and
+ * {@link PowerManager#BRIGHTNESS_MAX}.
+ * <p>
+ * Requires the {@link android.Manifest.permission#CONTROL_DISPLAY_BRIGHTNESS}
+ * permission.
+ * </p>
+ *
+ * @param displayId The ID of the display
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.CONTROL_DISPLAY_BRIGHTNESS)
+ @FloatRange(from = 0f, to = 1f)
+ public float getDefaultDozeBrightness(int displayId) {
+ return mGlobal.getDefaultDozeBrightness(displayId);
+ }
+
+ /**
* Listens for changes in available display devices.
*/
public interface DisplayListener {
diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
index e9cd37a..cae33d0 100644
--- a/core/java/android/hardware/display/DisplayManagerGlobal.java
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -21,6 +21,7 @@
import static android.view.Display.HdrCapabilities.HdrType;
import android.Manifest;
+import android.annotation.FloatRange;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -1226,6 +1227,32 @@
}
}
+ /**
+ * @see DisplayManager#getDozeBrightnessSensorValueToBrightness
+ */
+ @RequiresPermission(Manifest.permission.CONTROL_DISPLAY_BRIGHTNESS)
+ @Nullable
+ public float[] getDozeBrightnessSensorValueToBrightness(int displayId) {
+ try {
+ return mDm.getDozeBrightnessSensorValueToBrightness(displayId);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @see DisplayManager#getDefaultDozeBrightness
+ */
+ @RequiresPermission(Manifest.permission.CONTROL_DISPLAY_BRIGHTNESS)
+ @FloatRange(from = 0f, to = 1f)
+ public float getDefaultDozeBrightness(int displayId) {
+ try {
+ return mDm.getDefaultDozeBrightness(displayId);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
private final class DisplayManagerCallback extends IDisplayManagerCallback.Stub {
@Override
public void onDisplayEvent(int displayId, @DisplayEvent int event) {
diff --git a/core/java/android/hardware/display/IDisplayManager.aidl b/core/java/android/hardware/display/IDisplayManager.aidl
index 77277ee..f3c21e9f 100644
--- a/core/java/android/hardware/display/IDisplayManager.aidl
+++ b/core/java/android/hardware/display/IDisplayManager.aidl
@@ -246,4 +246,12 @@
// Restricts display modes to specified modeIds.
@EnforcePermission("RESTRICT_DISPLAY_MODES")
void requestDisplayModes(in IBinder token, int displayId, in @nullable int[] modeIds);
+
+ // Get the mapping between the doze brightness sensor values and brightness values
+ @EnforcePermission("CONTROL_DISPLAY_BRIGHTNESS")
+ float[] getDozeBrightnessSensorValueToBrightness(int displayId);
+
+ // Get the default doze brightness
+ @EnforcePermission("CONTROL_DISPLAY_BRIGHTNESS")
+ float getDefaultDozeBrightness(int displayId);
}
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index 23e262c..d7952eb 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -1251,8 +1251,22 @@
* canceled. Only the pilfering window will continue to receive events for the affected pointers
* until the pointer is lifted.
*
- * This method should be used with caution as unexpected pilfering can break fundamental user
- * interactions.
+ * Furthermore, if any new pointers go down within the touchable region of the pilfering window
+ * and are part of the same gesture, those new pointers will be pilfered as well, and will not
+ * be sent to any other windows.
+ *
+ * Pilfering is designed to be used only once per gesture. Once the gesture is complete
+ * (i.e. on {@link MotionEvent#ACTION_UP}, {@link MotionEvent#ACTION_CANCEL},
+ * or {@link MotionEvent#ACTION_HOVER_EXIT}), the system will resume dispatching pointers
+ * to the appropriately touched windows.
+ *
+ * NOTE: This method should be used with caution as unexpected pilfering can break fundamental
+ * user interactions.
+ *
+ * NOTE: Since this method pilfers pointers based on gesture stream that is
+ * currently active for the window, the behavior will depend on the state of the system, and
+ * is inherently racy. For example, a pilfer request on a quick tap may not be successful if
+ * the tap is already complete by the time the pilfer request is received by the system.
*
* @see android.os.InputConfig#SPY
* @hide
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index 406a1a6..026013c 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -564,8 +564,7 @@
BRIGHTNESS_CONSTRAINT_TYPE_MINIMUM,
BRIGHTNESS_CONSTRAINT_TYPE_MAXIMUM,
BRIGHTNESS_CONSTRAINT_TYPE_DEFAULT,
- BRIGHTNESS_CONSTRAINT_TYPE_DIM,
- BRIGHTNESS_CONSTRAINT_TYPE_DOZE
+ BRIGHTNESS_CONSTRAINT_TYPE_DIM
})
@Retention(RetentionPolicy.SOURCE)
public @interface BrightnessConstraint{}
@@ -594,12 +593,6 @@
public static final int BRIGHTNESS_CONSTRAINT_TYPE_DIM = 3;
/**
- * Brightness constraint type: minimum allowed value.
- * @hide
- */
- public static final int BRIGHTNESS_CONSTRAINT_TYPE_DOZE = 4;
-
- /**
* @hide
*/
@IntDef(prefix = { "WAKE_REASON_" }, value = {
diff --git a/core/java/android/os/vibrator/flags.aconfig b/core/java/android/os/vibrator/flags.aconfig
index c73a422..ad2f59d 100644
--- a/core/java/android/os/vibrator/flags.aconfig
+++ b/core/java/android/os/vibrator/flags.aconfig
@@ -46,6 +46,7 @@
flag {
namespace: "haptics"
name: "vibration_xml_apis"
+ is_exported: true
description: "Enabled System APIs for vibration effect XML parser and serializer"
bug: "347273158"
metadata {
diff --git a/core/java/android/security/flags.aconfig b/core/java/android/security/flags.aconfig
index 3954bc2..f6f0eff 100644
--- a/core/java/android/security/flags.aconfig
+++ b/core/java/android/security/flags.aconfig
@@ -92,3 +92,10 @@
description: "Add a dump capability for attestation_verification service"
bug: "335498868"
}
+
+flag {
+ name: "should_trust_manager_listen_for_primary_auth"
+ namespace: "biometrics"
+ description: "Causes TrustManagerService to listen for credential attempts and ignore reports from upstream"
+ bug: "323086607"
+}
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index 0714285..d8a88b8 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -138,12 +138,6 @@
"settings_show_stylus_preferences";
/**
- * Flag to enable/disable biometrics enrollment v2
- * @hide
- */
- public static final String SETTINGS_BIOMETRICS2_ENROLLMENT = "settings_biometrics2_enrollment";
-
- /**
* Flag to enable/disable FingerprintSettings v2
* @hide
*/
@@ -223,7 +217,6 @@
DEFAULT_FLAGS.put(SETTINGS_ENABLE_SPA_METRICS, "true");
DEFAULT_FLAGS.put(SETTINGS_ADB_METRICS_WRITER, "false");
DEFAULT_FLAGS.put(SETTINGS_SHOW_STYLUS_PREFERENCES, "true");
- DEFAULT_FLAGS.put(SETTINGS_BIOMETRICS2_ENROLLMENT, "false");
DEFAULT_FLAGS.put(SETTINGS_PREFER_ACCESSIBILITY_MENU_IN_SYSTEM, "false");
DEFAULT_FLAGS.put(SETTINGS_AUDIO_ROUTING, "false");
DEFAULT_FLAGS.put(SETTINGS_FLASH_NOTIFICATIONS, "true");
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index cb5a885..e5be531 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -53,6 +53,7 @@
import android.view.IWindow;
import android.view.IWindowSession;
import android.view.IWindowSessionCallback;
+import android.view.KeyboardShortcutGroup;
import android.view.KeyEvent;
import android.view.InputEvent;
import android.view.InsetsState;
@@ -1095,4 +1096,11 @@
boolean transferTouchGesture(in InputTransferToken transferFromToken,
in InputTransferToken transferToToken);
+
+ /**
+ * Request the application launch keyboard shortcuts the system has defined.
+ *
+ * @param deviceId The id of the {@link InputDevice} that will handle the shortcut.
+ */
+ KeyboardShortcutGroup getApplicationLaunchKeyboardShortcuts(int deviceId);
}
diff --git a/core/java/android/view/InputMonitor.java b/core/java/android/view/InputMonitor.java
index 2302dc7..ea4abc1 100644
--- a/core/java/android/view/InputMonitor.java
+++ b/core/java/android/view/InputMonitor.java
@@ -50,13 +50,12 @@
private final SurfaceControl mSurface;
/**
- * Takes all of the current pointer events streams that are currently being sent to this
- * monitor and generates appropriate cancellations for the windows that would normally get
- * them.
+ * Pilfer pointers from this input monitor.
*
- * This method should be used with caution as unexpected pilfering can break fundamental user
- * interactions.
+ * @see android.hardware.input.InputManager#pilferPointers(IBinder)
+ * @deprecated
*/
+ @Deprecated
public void pilferPointers() {
try {
mHost.pilferPointers();
@@ -197,10 +196,10 @@
};
@DataClass.Generated(
- time = 1679692514588L,
+ time = 1720819824835L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/view/InputMonitor.java",
- inputSignatures = "private static final java.lang.String TAG\nprivate static final boolean DEBUG\nprivate final @android.annotation.NonNull android.view.InputChannel mInputChannel\nprivate final @android.annotation.NonNull android.view.IInputMonitorHost mHost\nprivate final @android.annotation.NonNull android.view.SurfaceControl mSurface\npublic void pilferPointers()\npublic void dispose()\nclass InputMonitor extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true)")
+ inputSignatures = "private static final java.lang.String TAG\nprivate static final boolean DEBUG\nprivate final @android.annotation.NonNull android.view.InputChannel mInputChannel\nprivate final @android.annotation.NonNull android.view.IInputMonitorHost mHost\nprivate final @android.annotation.NonNull android.view.SurfaceControl mSurface\npublic @java.lang.Deprecated void pilferPointers()\npublic void dispose()\nclass InputMonitor extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/view/KeyboardShortcutGroup.aidl b/core/java/android/view/KeyboardShortcutGroup.aidl
new file mode 100644
index 0000000..6f219db
--- /dev/null
+++ b/core/java/android/view/KeyboardShortcutGroup.aidl
@@ -0,0 +1,3 @@
+package android.view;
+
+@JavaOnlyStableParcelable parcelable KeyboardShortcutGroup;
diff --git a/core/java/android/view/KeyboardShortcutInfo.java b/core/java/android/view/KeyboardShortcutInfo.java
index 3f6fd64..3f49bf3 100644
--- a/core/java/android/view/KeyboardShortcutInfo.java
+++ b/core/java/android/view/KeyboardShortcutInfo.java
@@ -81,12 +81,29 @@
* {@link KeyEvent#META_SYM_ON}.
*/
public KeyboardShortcutInfo(CharSequence label, char baseCharacter, int modifiers) {
+ this(label, null, baseCharacter, modifiers);
+ }
+
+ /**
+ * @param label The label that identifies the action performed by this shortcut.
+ * @param icon An icon that identifies the action performed by this shortcut.
+ * @param baseCharacter The character that triggers the shortcut.
+ * @param modifiers The set of modifiers that, combined with the key, trigger the shortcut.
+ * These should be a combination of {@link KeyEvent#META_CTRL_ON},
+ * {@link KeyEvent#META_SHIFT_ON}, {@link KeyEvent#META_META_ON},
+ * {@link KeyEvent#META_ALT_ON}, {@link KeyEvent#META_FUNCTION_ON} and
+ * {@link KeyEvent#META_SYM_ON}.
+ *
+ * @hide
+ */
+ public KeyboardShortcutInfo(
+ CharSequence label, @Nullable Icon icon, char baseCharacter, int modifiers) {
mLabel = label;
checkArgument(baseCharacter != MIN_VALUE);
mBaseCharacter = baseCharacter;
mKeycode = KeyEvent.KEYCODE_UNKNOWN;
mModifiers = modifiers;
- mIcon = null;
+ mIcon = icon;
}
private KeyboardShortcutInfo(Parcel source) {
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 18006bb..14978ed 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -1668,6 +1668,15 @@
public void requestAppKeyboardShortcuts(final KeyboardShortcutsReceiver receiver, int deviceId);
/**
+ * Request the application launch keyboard shortcuts the system has defined.
+ *
+ * @param deviceId The id of the {@link InputDevice} that will handle the shortcut.
+ *
+ * @hide
+ */
+ KeyboardShortcutGroup getApplicationLaunchKeyboardShortcuts(int deviceId);
+
+ /**
* Request for ime's keyboard shortcuts to be retrieved asynchronously.
*
* @param receiver The callback to be triggered when the result is ready.
diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java
index b667427..330e46a 100644
--- a/core/java/android/view/WindowManagerImpl.java
+++ b/core/java/android/view/WindowManagerImpl.java
@@ -237,6 +237,16 @@
}
@Override
+ public KeyboardShortcutGroup getApplicationLaunchKeyboardShortcuts(int deviceId) {
+ try {
+ return WindowManagerGlobal.getWindowManagerService()
+ .getApplicationLaunchKeyboardShortcuts(deviceId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ @Override
public void requestImeKeyboardShortcuts(
final KeyboardShortcutsReceiver receiver, int deviceId) {
IResultReceiver resultReceiver = new IResultReceiver.Stub() {
diff --git a/core/java/android/window/ActivityWindowInfo.java b/core/java/android/window/ActivityWindowInfo.java
index 946bb82..71c500c 100644
--- a/core/java/android/window/ActivityWindowInfo.java
+++ b/core/java/android/window/ActivityWindowInfo.java
@@ -18,6 +18,8 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.Activity;
+import android.app.ActivityThread;
import android.graphics.Rect;
import android.os.Parcel;
import android.os.Parcelable;
@@ -144,4 +146,15 @@
+ ", taskFragmentBounds=" + mTaskFragmentBounds
+ "}";
}
+
+ /** Gets the {@link ActivityWindowInfo} of the given activity. */
+ @Nullable
+ public static ActivityWindowInfo getActivityWindowInfo(@NonNull Activity activity) {
+ if (activity.isFinishing()) {
+ return null;
+ }
+ final ActivityThread.ActivityClientRecord record = ActivityThread.currentActivityThread()
+ .getActivityClient(activity.getActivityToken());
+ return record != null ? record.getActivityWindowInfo() : null;
+ }
}
diff --git a/core/java/android/window/ITaskFragmentOrganizerController.aidl b/core/java/android/window/ITaskFragmentOrganizerController.aidl
index 2c64b8e..ac57c00 100644
--- a/core/java/android/window/ITaskFragmentOrganizerController.aidl
+++ b/core/java/android/window/ITaskFragmentOrganizerController.aidl
@@ -39,12 +39,6 @@
void unregisterOrganizer(in ITaskFragmentOrganizer organizer);
/**
- * Checks if an activity organized by a {@link android.window.TaskFragmentOrganizer} and
- * only occupies a portion of Task bounds.
- */
- boolean isActivityEmbedded(in IBinder activityToken);
-
- /**
* Notifies the server that the organizer has finished handling the given transaction. The
* server should apply the given {@link WindowContainerTransaction} for the necessary changes.
*/
diff --git a/core/java/android/window/TaskFragmentOrganizer.java b/core/java/android/window/TaskFragmentOrganizer.java
index 15f1258..8e429cb 100644
--- a/core/java/android/window/TaskFragmentOrganizer.java
+++ b/core/java/android/window/TaskFragmentOrganizer.java
@@ -21,6 +21,7 @@
import static android.view.WindowManager.TRANSIT_FIRST_CUSTOM;
import static android.view.WindowManager.TRANSIT_NONE;
import static android.view.WindowManager.TRANSIT_OPEN;
+import static android.window.ActivityWindowInfo.getActivityWindowInfo;
import android.annotation.CallSuper;
import android.annotation.FlaggedApi;
@@ -29,6 +30,7 @@
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.TestApi;
+import android.app.Activity;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
@@ -38,6 +40,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
import java.util.concurrent.Executor;
/**
@@ -324,16 +327,15 @@
}
/**
- * Checks if an activity organized by a {@link android.window.TaskFragmentOrganizer} and
+ * Checks if an activity is organized by a {@link android.window.TaskFragmentOrganizer} and
* only occupies a portion of Task bounds.
+ *
+ * @see ActivityWindowInfo for additional window info.
* @hide
*/
- // TODO(b/287582673): cleanup
- public boolean isActivityEmbedded(@NonNull IBinder activityToken) {
- try {
- return getController().isActivityEmbedded(activityToken);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ public static boolean isActivityEmbedded(@NonNull Activity activity) {
+ Objects.requireNonNull(activity);
+ final ActivityWindowInfo activityWindowInfo = getActivityWindowInfo(activity);
+ return activityWindowInfo != null && activityWindowInfo.isEmbedded();
}
}
diff --git a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig
index cc880e1..48fb2b3 100644
--- a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig
+++ b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig
@@ -91,6 +91,13 @@
}
flag {
+ name: "camera_compat_fullscreen_pick_same_task_activity"
+ namespace: "large_screen_experiences_app_compat"
+ description: "Limit undo of camera compat treatment to the same task that started the treatment."
+ bug: "350495350"
+}
+
+flag {
name: "app_compat_refactoring"
namespace: "large_screen_experiences_app_compat"
description: "Whether the changes about app compat refactoring are enabled./n"
diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig
index 1362f7b..3f1c06a 100644
--- a/core/java/android/window/flags/lse_desktop_experience.aconfig
+++ b/core/java/android/window/flags/lse_desktop_experience.aconfig
@@ -176,3 +176,10 @@
description: "Enables desktop windowing app handle education"
bug: "348208342"
}
+
+flag {
+ name: "enable_compat_ui_visibility_status"
+ namespace: "lse_desktop_experience"
+ description: "Enables the tracking of the status for compat ui elements."
+ bug: "350953004"
+}
diff --git a/core/java/com/android/internal/jank/Cuj.java b/core/java/com/android/internal/jank/Cuj.java
index 2b096ea..3e6f18e 100644
--- a/core/java/com/android/internal/jank/Cuj.java
+++ b/core/java/com/android/internal/jank/Cuj.java
@@ -188,9 +188,17 @@
*/
public static final int CUJ_DESKTOP_MODE_ENTER_MODE_APP_HANDLE_MENU = 112;
+ /** Track Launcher Keyboard Quick Switch View opening animation */
+ public static final int CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_OPEN = 113;
+
+ /** Track Launcher Keyboard Quick Switch View closing animation */
+ public static final int CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_CLOSE = 114;
+
+ /** Track launching an app through the Launcher Keyboard Quick Switch View */
+ public static final int CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_APP_LAUNCH = 115;
// When adding a CUJ, update this and make sure to also update CUJ_TO_STATSD_INTERACTION_TYPE.
- @VisibleForTesting static final int LAST_CUJ = CUJ_DESKTOP_MODE_ENTER_MODE_APP_HANDLE_MENU;
+ @VisibleForTesting static final int LAST_CUJ = CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_APP_LAUNCH;
/** @hide */
@IntDef({
@@ -294,7 +302,10 @@
CUJ_DESKTOP_MODE_EXIT_MODE,
CUJ_DESKTOP_MODE_MINIMIZE_WINDOW,
CUJ_DESKTOP_MODE_DRAG_WINDOW,
- CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP
+ CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP,
+ CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_OPEN,
+ CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_CLOSE,
+ CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_APP_LAUNCH
})
@Retention(RetentionPolicy.SOURCE)
public @interface CujType {}
@@ -409,6 +420,9 @@
CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DESKTOP_MODE_MINIMIZE_WINDOW] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DESKTOP_MODE_MINIMIZE_WINDOW;
CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DESKTOP_MODE_DRAG_WINDOW] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DESKTOP_MODE_DRAG_WINDOW;
CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_OPEN] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_KEYBOARD_QUICK_SWITCH_OPEN;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_CLOSE] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_KEYBOARD_QUICK_SWITCH_CLOSE;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_APP_LAUNCH] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_KEYBOARD_QUICK_SWITCH_APP_LAUNCH;
}
private Cuj() {
@@ -629,6 +643,12 @@
return "DESKTOP_MODE_DRAG_WINDOW";
case CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP:
return "STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP";
+ case CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_OPEN:
+ return "LAUNCHER_KEYBOARD_QUICK_SWITCH_OPEN";
+ case CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_CLOSE:
+ return "LAUNCHER_KEYBOARD_QUICK_SWITCH_CLOSE";
+ case CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_APP_LAUNCH:
+ return "LAUNCHER_KEYBOARD_QUICK_SWITCH_APP_LAUNCH";
}
return "UNKNOWN";
}
diff --git a/core/java/com/android/internal/os/flags.aconfig b/core/java/com/android/internal/os/flags.aconfig
index 30fa4f1..2ad6651 100644
--- a/core/java/com/android/internal/os/flags.aconfig
+++ b/core/java/com/android/internal/os/flags.aconfig
@@ -12,7 +12,7 @@
flag {
name: "use_transaction_codes_for_unknown_methods"
- namespace: "dropbox"
+ namespace: "stability"
description: "Use transaction codes when the method names is unknown"
bug: "350041302"
is_fixed_read_only: true
diff --git a/core/java/com/android/internal/pm/pkg/component/AconfigFlags.java b/core/java/com/android/internal/pm/pkg/component/AconfigFlags.java
index f306b0b..b873175 100644
--- a/core/java/com/android/internal/pm/pkg/component/AconfigFlags.java
+++ b/core/java/com/android/internal/pm/pkg/component/AconfigFlags.java
@@ -60,7 +60,13 @@
"/product/etc/aconfig_flags.pb",
"/vendor/etc/aconfig_flags.pb");
+ public enum Permission {
+ READ_WRITE,
+ READ_ONLY
+ }
+
private final ArrayMap<String, Boolean> mFlagValues = new ArrayMap<>();
+ private final ArrayMap<String, Permission> mFlagPermissions = new ArrayMap<>();
public AconfigFlags() {
if (!Flags.manifestFlagging()) {
@@ -184,6 +190,12 @@
Slog.v(LOG_TAG, "Read Aconfig default flag value "
+ flagPackageAndName + " = " + flagValue);
mFlagValues.put(flagPackageAndName, flagValue);
+
+ Permission permission = flag.permission == Aconfig.READ_ONLY
+ ? Permission.READ_ONLY
+ : Permission.READ_WRITE;
+
+ mFlagPermissions.put(flagPackageAndName, permission);
}
}
@@ -200,6 +212,17 @@
}
/**
+ * Get the flag permission, or null if the flag doesn't exist.
+ * @param flagPackageAndName Full flag name formatted as 'package.flag'
+ * @return the current permission of the given Aconfig flag, or null if there is no such flag
+ */
+ @Nullable
+ public Permission getFlagPermission(@NonNull String flagPackageAndName) {
+ Permission permission = mFlagPermissions.get(flagPackageAndName);
+ return permission;
+ }
+
+ /**
* Check if the element in {@code parser} should be skipped because of the feature flag.
* @param parser XML parser object currently parsing an element
* @return true if the element is disabled because of its feature flag
diff --git a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
index 32c4830..652cba7 100644
--- a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
@@ -211,7 +211,7 @@
*/
public int startLoggingToLogcat(String[] groups, ILogger logger) {
if (mViewerConfigReader != null) {
- mViewerConfigReader.loadViewerConfig(logger);
+ mViewerConfigReader.loadViewerConfig(groups, logger);
}
return setTextLogging(true, logger, groups);
}
@@ -224,7 +224,7 @@
*/
public int stopLoggingToLogcat(String[] groups, ILogger logger) {
if (mViewerConfigReader != null) {
- mViewerConfigReader.unloadViewerConfig();
+ mViewerConfigReader.unloadViewerConfig(groups, logger);
}
return setTextLogging(false, logger, groups);
}
@@ -268,7 +268,7 @@
}
case "enable-text" -> {
if (mViewerConfigReader != null) {
- mViewerConfigReader.loadViewerConfig(logger);
+ mViewerConfigReader.loadViewerConfig(groups, logger);
}
return setTextLogging(true, logger, groups);
}
diff --git a/core/java/com/android/internal/protolog/ProtoLogViewerConfigReader.java b/core/java/com/android/internal/protolog/ProtoLogViewerConfigReader.java
index b7b2424..bb6c8b7 100644
--- a/core/java/com/android/internal/protolog/ProtoLogViewerConfigReader.java
+++ b/core/java/com/android/internal/protolog/ProtoLogViewerConfigReader.java
@@ -1,20 +1,32 @@
package com.android.internal.protolog;
+import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.GROUPS;
+import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.Group.ID;
+import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.Group.NAME;
+
import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MESSAGES;
import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.MESSAGE;
import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.MESSAGE_ID;
+import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.GROUP_ID;
-import android.util.ArrayMap;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.Log;
+import android.util.LongSparseArray;
import android.util.proto.ProtoInputStream;
import com.android.internal.protolog.common.ILogger;
import java.io.IOException;
import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.TreeMap;
public class ProtoLogViewerConfigReader {
private final ViewerConfigInputStreamProvider mViewerConfigInputStreamProvider;
- private Map<Long, String> mLogMessageMap = null;
+ private final Map<String, Set<Long>> mGroupHashes = new TreeMap<>();
+ private final LongSparseArray<String> mLogMessageMap = new LongSparseArray<>();
public ProtoLogViewerConfigReader(
ViewerConfigInputStreamProvider viewerConfigInputStreamProvider) {
@@ -26,39 +38,62 @@
* or the viewer config is not loaded into memory.
*/
public synchronized String getViewerString(long messageHash) {
- if (mLogMessageMap != null) {
- return mLogMessageMap.get(messageHash);
- } else {
- return null;
- }
+ return mLogMessageMap.get(messageHash);
+ }
+
+ public synchronized void loadViewerConfig(String[] groups) {
+ loadViewerConfig(groups, (message) -> {});
}
/**
* Loads the viewer config into memory. No-op if already loaded in memory.
*/
- public synchronized void loadViewerConfig(ILogger logger) {
- if (mLogMessageMap != null) {
- return;
- }
+ public synchronized void loadViewerConfig(String[] groups, @NonNull ILogger logger) {
+ for (String group : groups) {
+ if (mGroupHashes.containsKey(group)) {
+ continue;
+ }
- try {
- doLoadViewerConfig();
- logger.log("Loaded " + mLogMessageMap.size() + " log definitions");
- } catch (IOException e) {
- logger.log("Unable to load log definitions: "
- + "IOException while processing viewer config" + e);
+ try {
+ Map<Long, String> mappings = loadViewerConfigMappingForGroup(group);
+ mGroupHashes.put(group, mappings.keySet());
+ for (Long key : mappings.keySet()) {
+ mLogMessageMap.put(key, mappings.get(key));
+ }
+
+ logger.log("Loaded " + mLogMessageMap.size() + " log definitions");
+ } catch (IOException e) {
+ logger.log("Unable to load log definitions: "
+ + "IOException while processing viewer config" + e);
+ }
}
}
+ public synchronized void unloadViewerConfig(String[] groups) {
+ unloadViewerConfig(groups, (message) -> {});
+ }
+
/**
* Unload the viewer config from memory.
*/
- public synchronized void unloadViewerConfig() {
- mLogMessageMap = null;
+ public synchronized void unloadViewerConfig(String[] groups, @NonNull ILogger logger) {
+ for (String group : groups) {
+ if (!mGroupHashes.containsKey(group)) {
+ continue;
+ }
+
+ final Set<Long> hashes = mGroupHashes.get(group);
+ for (Long hash : hashes) {
+ logger.log("Unloading viewer config hash " + hash);
+ mLogMessageMap.remove(hash);
+ }
+ }
}
- private void doLoadViewerConfig() throws IOException {
- mLogMessageMap = new ArrayMap<>();
+ private Map<Long, String> loadViewerConfigMappingForGroup(String group) throws IOException {
+ Long targetGroupId = loadGroupId(group);
+
+ final Map<Long, String> hashesForGroup = new TreeMap<>();
final ProtoInputStream pis = mViewerConfigInputStreamProvider.getInputStream();
while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
@@ -67,6 +102,7 @@
long messageId = 0;
String message = null;
+ int groupId = 0;
while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
switch (pis.getFieldNumber()) {
case (int) MESSAGE_ID:
@@ -75,9 +111,16 @@
case (int) MESSAGE:
message = pis.readString(MESSAGE);
break;
+ case (int) GROUP_ID:
+ groupId = pis.readInt(GROUP_ID);
+ break;
}
}
+ if (groupId == 0) {
+ throw new IOException("Failed to get group id");
+ }
+
if (messageId == 0) {
throw new IOException("Failed to get message id");
}
@@ -86,10 +129,45 @@
throw new IOException("Failed to get message string");
}
- mLogMessageMap.put(messageId, message);
+ if (groupId == targetGroupId) {
+ hashesForGroup.put(messageId, message);
+ }
pis.end(inMessageToken);
}
}
+
+ return hashesForGroup;
+ }
+
+ private Long loadGroupId(String group) throws IOException {
+ final ProtoInputStream pis = mViewerConfigInputStreamProvider.getInputStream();
+
+ while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+ if (pis.getFieldNumber() == (int) GROUPS) {
+ final long inMessageToken = pis.start(GROUPS);
+
+ long groupId = 0;
+ String groupName = null;
+ while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+ switch (pis.getFieldNumber()) {
+ case (int) ID:
+ groupId = pis.readInt(ID);
+ break;
+ case (int) NAME:
+ groupName = pis.readString(NAME);
+ break;
+ }
+ }
+
+ if (Objects.equals(groupName, group)) {
+ return groupId;
+ }
+
+ pis.end(inMessageToken);
+ }
+ }
+
+ throw new RuntimeException("Group " + group + "not found in viewer config");
}
}
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index f4ad487..19c6f51 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -22,6 +22,8 @@
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_SOMETHING;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
+import static android.security.Flags.reportPrimaryAuthAttempts;
+import static android.security.Flags.shouldTrustManagerListenForPrimaryAuth;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -414,7 +416,9 @@
return;
}
getDevicePolicyManager().reportFailedPasswordAttempt(userId);
- getTrustManager().reportUnlockAttempt(false /* authenticated */, userId);
+ if (!reportPrimaryAuthAttempts() || !shouldTrustManagerListenForPrimaryAuth()) {
+ getTrustManager().reportUnlockAttempt(/* authenticated= */ false, userId);
+ }
}
@UnsupportedAppUsage
@@ -423,7 +427,9 @@
return;
}
getDevicePolicyManager().reportSuccessfulPasswordAttempt(userId);
- getTrustManager().reportUnlockAttempt(true /* authenticated */, userId);
+ if (!reportPrimaryAuthAttempts() || !shouldTrustManagerListenForPrimaryAuth()) {
+ getTrustManager().reportUnlockAttempt(/* authenticated= */ true, userId);
+ }
}
public void reportPasswordLockout(int timeoutMs, int userId) {
diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index c07fd38..7c62615 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -27,6 +27,7 @@
#include <android_media_audiopolicy.h>
#include <android_os_Parcel.h>
#include <audiomanager/AudioManager.h>
+#include <android-base/properties.h>
#include <binder/IBinder.h>
#include <jni.h>
#include <media/AidlConversion.h>
@@ -41,8 +42,10 @@
#include <system/audio_policy.h>
#include <utils/Log.h>
+#include <thread>
#include <optional>
#include <sstream>
+#include <memory>
#include <vector>
#include "android_media_AudioAttributes.h"
@@ -261,6 +264,13 @@
jfieldID mMixerBehavior;
} gAudioMixerAttributesField;
+static struct {
+ jclass clazz;
+ jmethodID run;
+} gRunnableClassInfo;
+
+static JavaVM* gVm;
+
static Mutex gLock;
enum AudioError {
@@ -3362,6 +3372,55 @@
return enabled;
}
+class JavaSystemPropertyListener {
+ public:
+ JavaSystemPropertyListener(JNIEnv* env, jobject javaCallback, std::string sysPropName) :
+ mCallback(env->NewGlobalRef(javaCallback)),
+ mCachedProperty(android::base::CachedProperty{std::move(sysPropName)}) {
+ mListenerThread = std::thread([this]() mutable {
+ JNIEnv* threadEnv = GetOrAttachJNIEnvironment(gVm);
+ while (!mCleanupSignal.load()) {
+ using namespace std::chrono_literals;
+ // 1s timeout so this thread can read the cleanup signal to (slowly) be able to
+ // be destroyed.
+ std::string newVal = mCachedProperty.WaitForChange(1000ms) ?: "";
+ if (newVal != "" && mLastVal != newVal) {
+ threadEnv->CallVoidMethod(mCallback, gRunnableClassInfo.run);
+ mLastVal = std::move(newVal);
+ }
+ }
+ });
+ }
+
+ ~JavaSystemPropertyListener() {
+ mCleanupSignal.store(true);
+ mListenerThread.join();
+ JNIEnv* env = GetOrAttachJNIEnvironment(gVm);
+ env->DeleteGlobalRef(mCallback);
+ }
+
+ private:
+ jobject mCallback;
+ android::base::CachedProperty mCachedProperty;
+ std::thread mListenerThread;
+ std::atomic<bool> mCleanupSignal{false};
+ std::string mLastVal = "";
+};
+
+std::vector<std::unique_ptr<JavaSystemPropertyListener>> gSystemPropertyListeners;
+std::mutex gSysPropLock{};
+
+static void android_media_AudioSystem_listenForSystemPropertyChange(JNIEnv *env, jobject thiz,
+ jstring sysProp,
+ jobject javaCallback) {
+ ScopedUtfChars sysPropChars{env, sysProp};
+ auto listener = std::make_unique<JavaSystemPropertyListener>(env, javaCallback,
+ std::string{sysPropChars.c_str()});
+ std::unique_lock _l{gSysPropLock};
+ gSystemPropertyListeners.push_back(std::move(listener));
+}
+
+
// ----------------------------------------------------------------------------
#define MAKE_AUDIO_SYSTEM_METHOD(x) \
@@ -3534,7 +3593,12 @@
android_media_AudioSystem_clearPreferredMixerAttributes),
MAKE_AUDIO_SYSTEM_METHOD(supportsBluetoothVariableLatency),
MAKE_AUDIO_SYSTEM_METHOD(setBluetoothVariableLatencyEnabled),
- MAKE_AUDIO_SYSTEM_METHOD(isBluetoothVariableLatencyEnabled)};
+ MAKE_AUDIO_SYSTEM_METHOD(isBluetoothVariableLatencyEnabled),
+ MAKE_JNI_NATIVE_METHOD("listenForSystemPropertyChange",
+ "(Ljava/lang/String;Ljava/lang/Runnable;)V",
+ android_media_AudioSystem_listenForSystemPropertyChange),
+
+ };
static const JNINativeMethod gEventHandlerMethods[] =
{MAKE_JNI_NATIVE_METHOD("native_setup", "(Ljava/lang/Object;)V",
@@ -3816,6 +3880,12 @@
gAudioMixerAttributesField.mMixerBehavior =
GetFieldIDOrDie(env, audioMixerAttributesClass, "mMixerBehavior", "I");
+ jclass runnableClazz = FindClassOrDie(env, "java/lang/Runnable");
+ gRunnableClassInfo.clazz = MakeGlobalRefOrDie(env, runnableClazz);
+ gRunnableClassInfo.run = GetMethodIDOrDie(env, runnableClazz, "run", "()V");
+
+ LOG_ALWAYS_FATAL_IF(env->GetJavaVM(&gVm) != 0);
+
AudioSystem::addErrorCallback(android_media_AudioSystem_error_callback);
RegisterMethodsOrDie(env, kClassPathName, gMethods, NELEM(gMethods));
diff --git a/core/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp
index 982189e..1a1d83c 100644
--- a/core/jni/android_util_Process.cpp
+++ b/core/jni/android_util_Process.cpp
@@ -1063,8 +1063,8 @@
}
env->ReleaseStringUTFChars(file, file8);
- // Most proc files we read are small, so we only go through the
- // loop once and use the stack buffer. We allocate a buffer big
+ // Most proc files we read are small, so we go through the loop
+ // with the stack buffer firstly. We allocate a buffer big
// enough for the whole file.
char readBufferStack[kProcReadStackBufferSize];
@@ -1072,37 +1072,47 @@
char* readBuffer = &readBufferStack[0];
ssize_t readBufferSize = kProcReadStackBufferSize;
ssize_t numberBytesRead;
+ off_t offset = 0;
for (;;) {
+ ssize_t requestedBufferSize = readBufferSize - offset;
// By using pread, we can avoid an lseek to rewind the FD
// before retry, saving a system call.
- numberBytesRead = pread(fd, readBuffer, readBufferSize, 0);
- if (numberBytesRead < 0 && errno == EINTR) {
- continue;
- }
+ numberBytesRead =
+ TEMP_FAILURE_RETRY(pread(fd, readBuffer + offset, requestedBufferSize, offset));
if (numberBytesRead < 0) {
if (kDebugProc) {
- ALOGW("Unable to open process file: %s fd=%d\n", file8, fd.get());
+ ALOGW("Unable to read process file err: %s file: %s fd=%d\n",
+ strerror_r(errno, &readBufferStack[0], sizeof(readBufferStack)), file8,
+ fd.get());
}
return JNI_FALSE;
}
- if (numberBytesRead < readBufferSize) {
+ if (numberBytesRead == 0) {
+ // End of file.
+ numberBytesRead = offset;
break;
}
- if (readBufferSize > std::numeric_limits<ssize_t>::max() / 2) {
- if (kDebugProc) {
- ALOGW("Proc file too big: %s fd=%d\n", file8, fd.get());
+ if (numberBytesRead < requestedBufferSize) {
+ // Read less bytes than requested, it's not an error per pread(2).
+ offset += numberBytesRead;
+ } else {
+ // Buffer is fully used, try to grow it.
+ if (readBufferSize > std::numeric_limits<ssize_t>::max() / 2) {
+ if (kDebugProc) {
+ ALOGW("Proc file too big: %s fd=%d\n", file8, fd.get());
+ }
+ return JNI_FALSE;
}
- return JNI_FALSE;
+ readBufferSize = std::max(readBufferSize * 2, kProcReadMinHeapBufferSize);
+ readBufferHeap.reset(); // Free address space before getting more.
+ readBufferHeap = std::make_unique<char[]>(readBufferSize);
+ if (!readBufferHeap) {
+ jniThrowException(env, "java/lang/OutOfMemoryError", NULL);
+ return JNI_FALSE;
+ }
+ readBuffer = readBufferHeap.get();
+ offset = 0;
}
- readBufferSize = std::max(readBufferSize * 2,
- kProcReadMinHeapBufferSize);
- readBufferHeap.reset(); // Free address space before getting more.
- readBufferHeap = std::make_unique<char[]>(readBufferSize);
- if (!readBufferHeap) {
- jniThrowException(env, "java/lang/OutOfMemoryError", NULL);
- return JNI_FALSE;
- }
- readBuffer = readBufferHeap.get();
}
// parseProcLineArray below modifies the buffer while parsing!
diff --git a/core/jni/platform/host/HostRuntime.cpp b/core/jni/platform/host/HostRuntime.cpp
index 59d18b8..30c926c 100644
--- a/core/jni/platform/host/HostRuntime.cpp
+++ b/core/jni/platform/host/HostRuntime.cpp
@@ -104,6 +104,7 @@
extern int register_android_view_KeyEvent(JNIEnv* env);
extern int register_android_view_InputDevice(JNIEnv* env);
extern int register_android_view_MotionEvent(JNIEnv* env);
+extern int register_android_view_Surface(JNIEnv* env);
extern int register_android_view_ThreadedRenderer(JNIEnv* env);
extern int register_android_graphics_HardwareBufferRenderer(JNIEnv* env);
extern int register_android_view_VelocityTracker(JNIEnv* env);
@@ -151,6 +152,7 @@
{"android.view.KeyEvent", REG_JNI(register_android_view_KeyEvent)},
{"android.view.InputDevice", REG_JNI(register_android_view_InputDevice)},
{"android.view.MotionEvent", REG_JNI(register_android_view_MotionEvent)},
+ {"android.view.Surface", REG_JNI(register_android_view_Surface)},
{"android.view.VelocityTracker", REG_JNI(register_android_view_VelocityTracker)},
{"com.android.internal.util.VirtualRefBasePtr",
REG_JNI(register_com_android_internal_util_VirtualRefBasePtr)},
diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml
index cdd8557..61c7a8c 100644
--- a/core/res/res/values/config_telephony.xml
+++ b/core/res/res/values/config_telephony.xml
@@ -393,12 +393,6 @@
<bool name="config_wait_for_device_alignment_in_demo_datagram">false</bool>
<java-symbol type="bool" name="config_wait_for_device_alignment_in_demo_datagram" />
- <!-- Boolean indicating whether to enable MMS to be attempted on IWLAN if possible, even if
- existing cellular networks already supports IWLAN.
- -->
- <bool name="force_iwlan_mms_feature_enabled">false</bool>
- <java-symbol type="bool" name="force_iwlan_mms_feature_enabled" />
-
<!-- The time duration in millis after which Telephony will abort the last message datagram
sending requests. Telephony starts a timer when receiving a last message datagram sending
request in either OFF, IDLE, or NOT_CONNECTED state. In NOT_CONNECTED, the duration of the
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 6b71f97..46b15416 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -6534,4 +6534,23 @@
<string name="bg_user_sound_notification_button_mute">Mute</string>
<!-- Notification text to mute the sound from the background user [CHAR LIMIT=NOTIF_BODY]-->
<string name="bg_user_sound_notification_message">Tap to mute sound</string>
+
+ <!-- User visible title for the keyboard shortcut that takes the user to the browser app. [CHAR LIMIT=70] -->
+ <string name="keyboard_shortcut_group_applications_browser">Browser</string>
+ <!-- User visible title for the keyboard shortcut that takes the user to the contacts app. [CHAR LIMIT=70] -->
+ <string name="keyboard_shortcut_group_applications_contacts">Contacts</string>
+ <!-- User visible title for the keyboard shortcut that takes the user to the email app. [CHAR LIMIT=70] -->
+ <string name="keyboard_shortcut_group_applications_email">Email</string>
+ <!-- User visible title for the keyboard shortcut that takes the user to the SMS messaging app. [CHAR LIMIT=70] -->
+ <string name="keyboard_shortcut_group_applications_sms">SMS</string>
+ <!-- User visible title for the keyboard shortcut that takes the user to the music app. [CHAR LIMIT=70] -->
+ <string name="keyboard_shortcut_group_applications_music">Music</string>
+ <!-- User visible title for the keyboard shortcut that takes the user to the calendar app. [CHAR LIMIT=70] -->
+ <string name="keyboard_shortcut_group_applications_calendar">Calendar</string>
+ <!-- User visible title for the keyboard shortcut that takes the user to the calculator app. [CHAR LIMIT=70] -->
+ <string name="keyboard_shortcut_group_applications_calculator">Calculator</string>
+ <!-- User visible title for the keyboard shortcut that takes the user to the maps app. [CHAR LIMIT=70] -->
+ <string name="keyboard_shortcut_group_applications_maps">Maps</string>
+ <!-- User visible title for the keyboard shortcut group containing system-wide application launch shortcuts. [CHAR-LIMIT=70] -->
+ <string name="keyboard_shortcut_group_applications">Applications</string>
</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index d25f59d..c50b961 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -5561,4 +5561,15 @@
<java-symbol type="string" name="bg_user_sound_notification_button_switch_user" />
<java-symbol type="string" name="bg_user_sound_notification_button_mute" />
<java-symbol type="string" name="bg_user_sound_notification_message" />
+
+ <!-- Keyboard Shortcut default category names. -->
+ <java-symbol type="string" name="keyboard_shortcut_group_applications_browser" />
+ <java-symbol type="string" name="keyboard_shortcut_group_applications_calculator" />
+ <java-symbol type="string" name="keyboard_shortcut_group_applications_calendar" />
+ <java-symbol type="string" name="keyboard_shortcut_group_applications_contacts" />
+ <java-symbol type="string" name="keyboard_shortcut_group_applications_email" />
+ <java-symbol type="string" name="keyboard_shortcut_group_applications_maps" />
+ <java-symbol type="string" name="keyboard_shortcut_group_applications_music" />
+ <java-symbol type="string" name="keyboard_shortcut_group_applications_sms" />
+ <java-symbol type="string" name="keyboard_shortcut_group_applications" />
</resources>
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index b120723..8e1fde0 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -22,6 +22,7 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.TRANSIT_CLOSE;
+import static android.window.ActivityWindowInfo.getActivityWindowInfo;
import static android.window.TaskFragmentOperation.OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT;
import static android.window.TaskFragmentOperation.OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT;
import static android.window.TaskFragmentOrganizer.KEY_ERROR_CALLBACK_OP_TYPE;
@@ -80,6 +81,7 @@
import android.window.TaskFragmentAnimationParams;
import android.window.TaskFragmentInfo;
import android.window.TaskFragmentOperation;
+import android.window.TaskFragmentOrganizer;
import android.window.TaskFragmentParentInfo;
import android.window.TaskFragmentTransaction;
import android.window.WindowContainerTransaction;
@@ -2553,9 +2555,9 @@
return ActivityThread.currentActivityThread().getActivity(activityToken);
}
- @VisibleForTesting
@Nullable
- ActivityThread.ActivityClientRecord getActivityClientRecord(@NonNull Activity activity) {
+ private ActivityThread.ActivityClientRecord getActivityClientRecord(
+ @NonNull Activity activity) {
return ActivityThread.currentActivityThread()
.getActivityClient(activity.getActivityToken());
}
@@ -3092,10 +3094,8 @@
*/
@Override
public boolean isActivityEmbedded(@NonNull Activity activity) {
- Objects.requireNonNull(activity);
synchronized (mLock) {
- final ActivityWindowInfo activityWindowInfo = getActivityWindowInfo(activity);
- return activityWindowInfo != null && activityWindowInfo.isEmbedded();
+ return TaskFragmentOrganizer.isActivityEmbedded(activity);
}
}
@@ -3165,15 +3165,6 @@
}
}
- @Nullable
- private ActivityWindowInfo getActivityWindowInfo(@NonNull Activity activity) {
- if (activity.isFinishing()) {
- return null;
- }
- final ActivityThread.ActivityClientRecord record = getActivityClientRecord(activity);
- return record != null ? record.getActivityWindowInfo() : null;
- }
-
@NonNull
private static EmbeddedActivityWindowInfo translateActivityWindowInfo(
@NonNull Activity activity, @NonNull ActivityWindowInfo activityWindowInfo) {
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
index 99c0ee2..d852204 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
@@ -165,6 +165,7 @@
private Consumer<List<SplitInfo>> mEmbeddingCallback;
private List<SplitInfo> mSplitInfos;
private TransactionManager mTransactionManager;
+ private ActivityThread mCurrentActivityThread;
@Before
public void setUp() {
@@ -181,10 +182,12 @@
};
mSplitController.setSplitInfoCallback(mEmbeddingCallback);
mTransactionManager = mSplitController.mTransactionManager;
+ mCurrentActivityThread = ActivityThread.currentActivityThread();
spyOn(mSplitController);
spyOn(mSplitPresenter);
spyOn(mEmbeddingCallback);
spyOn(mTransactionManager);
+ spyOn(mCurrentActivityThread);
doNothing().when(mSplitPresenter).applyTransaction(any(), anyInt(), anyBoolean());
final Configuration activityConfig = new Configuration();
activityConfig.windowConfiguration.setBounds(TASK_BOUNDS);
@@ -1668,7 +1671,8 @@
final IBinder activityToken = new Binder();
doReturn(activityToken).when(activity).getActivityToken();
doReturn(activity).when(mSplitController).getActivity(activityToken);
- doReturn(activityClientRecord).when(mSplitController).getActivityClientRecord(activity);
+ doReturn(activityClientRecord).when(mCurrentActivityThread).getActivityClient(
+ activityToken);
doReturn(taskId).when(activity).getTaskId();
doReturn(new ActivityInfo()).when(activity).getActivityInfo();
doReturn(DEFAULT_DISPLAY).when(activity).getDisplayId();
diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp
index dbcad8a..a00d003 100644
--- a/libs/WindowManager/Shell/Android.bp
+++ b/libs/WindowManager/Shell/Android.bp
@@ -224,7 +224,6 @@
"//frameworks/libs/systemui:com_android_systemui_shared_flags_lib",
"//frameworks/libs/systemui:iconloader_base",
"com_android_wm_shell_flags_lib",
- "com.android.window.flags.window-aconfig-java",
"WindowManager-Shell-proto",
"WindowManager-Shell-shared",
"perfetto_trace_java_protos",
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUIRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUIRepository.kt
new file mode 100644
index 0000000..cb54d89
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUIRepository.kt
@@ -0,0 +1,39 @@
+/*
+ * 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.wm.shell.compatui.api
+
+/**
+ * Abstraction for the repository of all the available CompatUISpec
+ */
+interface CompatUIRepository {
+ /**
+ * Adds a {@link CompatUISpec} to the repository
+ * @throws IllegalStateException in case of illegal spec
+ */
+ fun addSpec(spec: CompatUISpec)
+
+ /**
+ * Iterates on the list of available {@link CompatUISpec} invoking
+ * fn for each of them.
+ */
+ fun iterateOn(fn: (CompatUISpec) -> Unit)
+
+ /**
+ * Returns the {@link CompatUISpec} for a given key
+ */
+ fun findSpec(name: String): CompatUISpec?
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUISpec.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUISpec.kt
new file mode 100644
index 0000000..24c2c8c
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUISpec.kt
@@ -0,0 +1,26 @@
+/*
+ * 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.wm.shell.compatui.api
+
+/**
+ * Describes each compat ui component to the framework.
+ */
+data class CompatUISpec(
+ // Unique name for the component. It's used for debug and for generating the
+ // unique component identifier in the system.
+ val name: String
+)
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/impl/DefaultCompatUIHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/impl/DefaultCompatUIHandler.kt
index a181eaf..8408ea6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/impl/DefaultCompatUIHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/impl/DefaultCompatUIHandler.kt
@@ -19,12 +19,15 @@
import com.android.wm.shell.compatui.api.CompatUIEvent
import com.android.wm.shell.compatui.api.CompatUIHandler
import com.android.wm.shell.compatui.api.CompatUIInfo
+import com.android.wm.shell.compatui.api.CompatUIRepository
import java.util.function.Consumer
/**
* Default implementation of {@link CompatUIHandler} to handle CompatUI components
*/
-class DefaultCompatUIHandler : CompatUIHandler {
+class DefaultCompatUIHandler(
+ private val compatUIRepository: CompatUIRepository
+) : CompatUIHandler {
private var compatUIEventSender: Consumer<CompatUIEvent>? = null
override fun onCompatInfoChanged(compatUIInfo: CompatUIInfo) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/impl/DefaultCompatUIRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/impl/DefaultCompatUIRepository.kt
new file mode 100644
index 0000000..10d9425
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/impl/DefaultCompatUIRepository.kt
@@ -0,0 +1,41 @@
+/*
+ * 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.wm.shell.compatui.impl
+
+import com.android.wm.shell.compatui.api.CompatUIRepository
+import com.android.wm.shell.compatui.api.CompatUISpec
+
+/**
+ * Default {@link CompatUIRepository} implementation
+ */
+class DefaultCompatUIRepository : CompatUIRepository {
+
+ private val allSpecs = mutableMapOf<String, CompatUISpec>()
+
+ override fun addSpec(spec: CompatUISpec) {
+ if (allSpecs[spec.name] != null) {
+ throw IllegalStateException("Spec with id:${spec.name} already present")
+ }
+ allSpecs[spec.name] = spec
+ }
+
+ override fun iterateOn(fn: (CompatUISpec) -> Unit) =
+ allSpecs.values.forEach(fn)
+
+ override fun findSpec(name: String): CompatUISpec? =
+ allSpecs[name]
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index 9bdc0b2..4b548cb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -72,7 +72,9 @@
import com.android.wm.shell.compatui.CompatUIController;
import com.android.wm.shell.compatui.CompatUIShellCommandHandler;
import com.android.wm.shell.compatui.api.CompatUIHandler;
+import com.android.wm.shell.compatui.api.CompatUIRepository;
import com.android.wm.shell.compatui.impl.DefaultCompatUIHandler;
+import com.android.wm.shell.compatui.impl.DefaultCompatUIRepository;
import com.android.wm.shell.desktopmode.DesktopMode;
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
import com.android.wm.shell.desktopmode.DesktopTasksController;
@@ -245,12 +247,13 @@
Lazy<DockStateReader> dockStateReader,
Lazy<CompatUIConfiguration> compatUIConfiguration,
Lazy<CompatUIShellCommandHandler> compatUIShellCommandHandler,
- Lazy<AccessibilityManager> accessibilityManager) {
+ Lazy<AccessibilityManager> accessibilityManager,
+ CompatUIRepository compatUIRepository) {
if (!context.getResources().getBoolean(R.bool.config_enableCompatUIController)) {
return Optional.empty();
}
if (Flags.appCompatUiFramework()) {
- return Optional.of(new DefaultCompatUIHandler());
+ return Optional.of(new DefaultCompatUIHandler(compatUIRepository));
}
return Optional.of(
new CompatUIController(
@@ -271,6 +274,12 @@
@WMSingleton
@Provides
+ static CompatUIRepository provideCompatUIRepository() {
+ return new DefaultCompatUIRepository();
+ }
+
+ @WMSingleton
+ @Provides
static SyncTransactionQueue provideSyncTransactionQueue(TransactionPool pool,
@ShellMainThread ShellExecutor mainExecutor) {
return new SyncTransactionQueue(pool, mainExecutor);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java
index 037fbb2..1a9c304 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java
@@ -206,13 +206,12 @@
@WMSingleton
@Provides
static PipMotionHelper providePipMotionHelper(Context context,
- @ShellMainThread ShellExecutor mainExecutor,
PipBoundsState pipBoundsState, PipTaskOrganizer pipTaskOrganizer,
PhonePipMenuController menuController, PipSnapAlgorithm pipSnapAlgorithm,
PipTransitionController pipTransitionController,
FloatingContentCoordinator floatingContentCoordinator,
Optional<PipPerfHintController> pipPerfHintControllerOptional) {
- return new PipMotionHelper(context, mainExecutor, pipBoundsState, pipTaskOrganizer,
+ return new PipMotionHelper(context, pipBoundsState, pipTaskOrganizer,
menuController, pipSnapAlgorithm, pipTransitionController,
floatingContentCoordinator, pipPerfHintControllerOptional);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index 6315e69..d46b2d6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -1045,14 +1045,12 @@
/** Handle task closing by removing wallpaper activity if it's the last active task */
private fun handleTaskClosing(task: RunningTaskInfo): WindowContainerTransaction? {
- val wct = if (
- desktopModeTaskRepository.isOnlyVisibleNonClosingTask(task.taskId) &&
- desktopModeTaskRepository.wallpaperActivityToken != null
- ) {
+ ProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: handleTaskClosing")
+ val wct = WindowContainerTransaction()
+ if (desktopModeTaskRepository.isOnlyVisibleNonClosingTask(task.taskId)
+ && desktopModeTaskRepository.wallpaperActivityToken != null) {
// Remove wallpaper activity when the last active task is removed
- WindowContainerTransaction().also { wct -> removeWallpaperActivity(wct) }
- } else {
- null
+ removeWallpaperActivity(wct)
}
if (!desktopModeTaskRepository.addClosingTask(task.displayId, task.taskId)) {
// Could happen if the task hasn't been removed from closing list after it disappeared
@@ -1062,7 +1060,12 @@
task.taskId
)
}
- return wct
+ // If a CLOSE or TO_BACK is triggered on a desktop task, remove the task.
+ if (Flags.enableDesktopWindowingBackNavigation() &&
+ desktopModeTaskRepository.isVisibleTask(task.taskId)) {
+ wct.removeTask(task.token)
+ }
+ return if (wct.isEmpty) null else wct
}
private fun addMoveToDesktopChanges(
@@ -1107,6 +1110,10 @@
if (useDesktopOverrideDensity()) {
wct.setDensityDpi(taskInfo.token, getDefaultDensityDpi())
}
+ if (desktopModeTaskRepository.isOnlyVisibleNonClosingTask(taskInfo.taskId)) {
+ // Remove wallpaper activity when leaving desktop mode
+ removeWallpaperActivity(wct)
+ }
}
/**
@@ -1122,6 +1129,10 @@
// The task's density may have been overridden in freeform; revert it here as we don't
// want it overridden in multi-window.
wct.setDensityDpi(taskInfo.token, getDefaultDensityDpi())
+ if (desktopModeTaskRepository.isOnlyVisibleNonClosingTask(taskInfo.taskId)) {
+ // Remove wallpaper activity when leaving desktop mode
+ removeWallpaperActivity(wct)
+ }
}
/** Returns the ID of the Task that will be minimized, or null if no task will be minimized. */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
index b27c428..a749019 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
@@ -16,12 +16,10 @@
package com.android.wm.shell.pip;
-import android.annotation.NonNull;
import android.graphics.Rect;
import com.android.wm.shell.shared.annotations.ExternalThread;
-import java.util.concurrent.Executor;
import java.util.function.Consumer;
/**
@@ -71,10 +69,9 @@
default void removePipExclusionBoundsChangeListener(Consumer<Rect> listener) { }
/**
- * Register {@link PipTransitionController.PipTransitionCallback} to listen on PiP transition
- * started / finished callbacks.
+ * @return {@link PipTransitionController} instance.
*/
- default void registerPipTransitionCallback(
- @NonNull PipTransitionController.PipTransitionCallback callback,
- @NonNull Executor executor) { }
+ default PipTransitionController getPipTransitionController() {
+ return null;
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index 6e1e44b..ff40d4c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -423,8 +423,7 @@
});
mPipTransitionController.setPipOrganizer(this);
displayController.addDisplayWindowListener(this);
- pipTransitionController.registerPipTransitionCallback(
- mPipTransitionCallback, mMainExecutor);
+ pipTransitionController.registerPipTransitionCallback(mPipTransitionCallback);
}
}
@@ -496,9 +495,7 @@
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"startSwipePipToHome: %s, state=%s", componentName, mPipTransitionState);
mPipTransitionState.setInSwipePipToHomeTransition(true);
- if (!ENABLE_SHELL_TRANSITIONS) {
- sendOnPipTransitionStarted(TRANSITION_DIRECTION_TO_PIP);
- }
+ sendOnPipTransitionStarted(TRANSITION_DIRECTION_TO_PIP);
setBoundsStateForEntry(componentName, pictureInPictureParams, activityInfo);
return mPipBoundsAlgorithm.getEntryDestinationBounds();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
index 87692ac..e5633de 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
@@ -1174,7 +1174,6 @@
}
final Rect sourceBounds = pipTaskInfo.configuration.windowConfiguration.getBounds();
- sendOnPipTransitionStarted(TRANSITION_DIRECTION_TO_PIP);
final PipAnimationController.PipTransitionAnimator animator =
mPipAnimationController.getAnimator(pipTaskInfo, leash, sourceBounds, sourceBounds,
destinationBounds, sourceHintRect, TRANSITION_DIRECTION_TO_PIP,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
index 8d36db9..b1dd4f1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
@@ -53,9 +53,8 @@
import com.android.wm.shell.transition.Transitions;
import java.io.PrintWriter;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.concurrent.Executor;
+import java.util.ArrayList;
+import java.util.List;
/**
* Responsible supplying PiP Transitions.
@@ -67,7 +66,7 @@
protected final ShellTaskOrganizer mShellTaskOrganizer;
protected final PipMenuController mPipMenuController;
protected final Transitions mTransitions;
- private final Map<PipTransitionCallback, Executor> mPipTransitionCallbacks = new HashMap<>();
+ private final List<PipTransitionCallback> mPipTransitionCallbacks = new ArrayList<>();
protected PipTaskOrganizer mPipOrganizer;
protected DefaultMixedHandler mMixedHandler;
@@ -184,18 +183,16 @@
/**
* Registers {@link PipTransitionCallback} to receive transition callbacks.
*/
- public void registerPipTransitionCallback(
- @NonNull PipTransitionCallback callback, @NonNull Executor executor) {
- mPipTransitionCallbacks.put(callback, executor);
+ public void registerPipTransitionCallback(PipTransitionCallback callback) {
+ mPipTransitionCallbacks.add(callback);
}
protected void sendOnPipTransitionStarted(
@PipAnimationController.TransitionDirection int direction) {
final Rect pipBounds = mPipBoundsState.getBounds();
- for (Map.Entry<PipTransitionCallback, Executor> entry
- : mPipTransitionCallbacks.entrySet()) {
- entry.getValue().execute(
- () -> entry.getKey().onPipTransitionStarted(direction, pipBounds));
+ for (int i = mPipTransitionCallbacks.size() - 1; i >= 0; i--) {
+ final PipTransitionCallback callback = mPipTransitionCallbacks.get(i);
+ callback.onPipTransitionStarted(direction, pipBounds);
}
if (isInPipDirection(direction) && Flags.enablePipUiStateCallbackOnEntering()) {
try {
@@ -212,10 +209,9 @@
protected void sendOnPipTransitionFinished(
@PipAnimationController.TransitionDirection int direction) {
- for (Map.Entry<PipTransitionCallback, Executor> entry
- : mPipTransitionCallbacks.entrySet()) {
- entry.getValue().execute(
- () -> entry.getKey().onPipTransitionFinished(direction));
+ for (int i = mPipTransitionCallbacks.size() - 1; i >= 0; i--) {
+ final PipTransitionCallback callback = mPipTransitionCallbacks.get(i);
+ callback.onPipTransitionFinished(direction);
}
if (isInPipDirection(direction) && Flags.enablePipUiStateCallbackOnEntering()) {
try {
@@ -232,10 +228,9 @@
protected void sendOnPipTransitionCancelled(
@PipAnimationController.TransitionDirection int direction) {
- for (Map.Entry<PipTransitionCallback, Executor> entry
- : mPipTransitionCallbacks.entrySet()) {
- entry.getValue().execute(
- () -> entry.getKey().onPipTransitionCanceled(direction));
+ for (int i = mPipTransitionCallbacks.size() - 1; i >= 0; i--) {
+ final PipTransitionCallback callback = mPipTransitionCallbacks.get(i);
+ callback.onPipTransitionCanceled(direction);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index 0cb7e17..26b7e58 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -106,7 +106,6 @@
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
-import java.util.concurrent.Executor;
import java.util.function.Consumer;
/**
@@ -479,7 +478,7 @@
mShellCommandHandler.addDumpCallback(this::dump, this);
mPipInputConsumer = new PipInputConsumer(WindowManagerGlobal.getWindowManagerService(),
INPUT_CONSUMER_PIP, mMainExecutor);
- mPipTransitionController.registerPipTransitionCallback(this, mMainExecutor);
+ mPipTransitionController.registerPipTransitionCallback(this);
mPipTaskOrganizer.registerOnDisplayIdChangeCallback((int displayId) -> {
mPipDisplayLayoutState.setDisplayId(displayId);
onDisplayChanged(mDisplayController.getDisplayLayout(displayId),
@@ -1221,11 +1220,8 @@
}
@Override
- public void registerPipTransitionCallback(
- PipTransitionController.PipTransitionCallback callback,
- Executor executor) {
- mMainExecutor.execute(() -> mPipTransitionController.registerPipTransitionCallback(
- callback, executor));
+ public PipTransitionController getPipTransitionController() {
+ return mPipTransitionController;
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
index df3803d..e8d6576 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
@@ -38,7 +38,6 @@
import com.android.wm.shell.R;
import com.android.wm.shell.animation.FloatProperties;
import com.android.wm.shell.common.FloatingContentCoordinator;
-import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
import com.android.wm.shell.common.pip.PipAppOpsListener;
import com.android.wm.shell.common.pip.PipBoundsState;
@@ -48,7 +47,6 @@
import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.shared.animation.PhysicsAnimator;
-import com.android.wm.shell.shared.annotations.ShellMainThread;
import kotlin.Unit;
import kotlin.jvm.functions.Function0;
@@ -173,9 +171,7 @@
public void onPipTransitionCanceled(int direction) {}
};
- public PipMotionHelper(Context context,
- @ShellMainThread ShellExecutor mainExecutor,
- @NonNull PipBoundsState pipBoundsState,
+ public PipMotionHelper(Context context, @NonNull PipBoundsState pipBoundsState,
PipTaskOrganizer pipTaskOrganizer, PhonePipMenuController menuController,
PipSnapAlgorithm snapAlgorithm, PipTransitionController pipTransitionController,
FloatingContentCoordinator floatingContentCoordinator,
@@ -187,7 +183,7 @@
mSnapAlgorithm = snapAlgorithm;
mFloatingContentCoordinator = floatingContentCoordinator;
mPipPerfHintController = pipPerfHintControllerOptional.orElse(null);
- pipTransitionController.registerPipTransitionCallback(mPipTransitionCallback, mainExecutor);
+ pipTransitionController.registerPipTransitionCallback(mPipTransitionCallback);
mResizePipUpdateListener = (target, values) -> {
if (mPipBoundsState.getMotionBoundsState().isInMotion()) {
mPipTaskOrganizer.scheduleUserResizePip(getBounds(),
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
index 0ed5079..62c0944 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
@@ -257,7 +257,7 @@
}
private void onInit() {
- mPipTransitionController.registerPipTransitionCallback(this, mMainExecutor);
+ mPipTransitionController.registerPipTransitionCallback(this);
reloadResources();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
index c12219c..b939b16 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
@@ -64,7 +64,6 @@
import com.android.wm.shell.sysui.ShellInit;
import java.io.PrintWriter;
-import java.util.concurrent.Executor;
import java.util.function.Consumer;
/**
@@ -413,11 +412,6 @@
@Override
public void showPictureInPictureMenu() {}
-
- @Override
- public void registerPipTransitionCallback(
- PipTransitionController.PipTransitionCallback callback,
- Executor executor) {}
}
/**
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/impl/DefaultCompatUIRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/impl/DefaultCompatUIRepositoryTest.kt
new file mode 100644
index 0000000..1a86cfd
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/impl/DefaultCompatUIRepositoryTest.kt
@@ -0,0 +1,89 @@
+/*
+ * 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.wm.shell.compatui.impl
+
+import android.platform.test.flag.junit.DeviceFlagsValueProvider
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.wm.shell.compatui.api.CompatUIRepository
+import com.android.wm.shell.compatui.api.CompatUISpec
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNull
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Tests for {@link DefaultCompatUIRepository}.
+ *
+ * Build/Install/Run:
+ * atest WMShellUnitTests:DefaultCompatUIRepositoryTest
+ */
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class DefaultCompatUIRepositoryTest {
+
+ lateinit var repository: CompatUIRepository
+
+ @get:Rule
+ val mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
+
+ @Before
+ fun setUp() {
+ repository = DefaultCompatUIRepository()
+ }
+
+ @Test(expected = IllegalStateException::class)
+ fun `addSpec throws exception with specs with duplicate id`() {
+ repository.addSpec(CompatUISpec("one"))
+ repository.addSpec(CompatUISpec("one"))
+ }
+
+ @Test
+ fun `iterateOn invokes the consumer`() {
+ with(repository) {
+ addSpec(CompatUISpec("one"))
+ addSpec(CompatUISpec("two"))
+ addSpec(CompatUISpec("three"))
+ val consumer = object : (CompatUISpec) -> Unit {
+ var acc = ""
+ override fun invoke(spec: CompatUISpec) {
+ acc += spec.name
+ }
+ }
+ iterateOn(consumer)
+ assertEquals("onetwothree", consumer.acc)
+ }
+ }
+
+ @Test
+ fun `findSpec returns existing specs`() {
+ with(repository) {
+ val one = CompatUISpec("one")
+ val two = CompatUISpec("two")
+ val three = CompatUISpec("three")
+ addSpec(one)
+ addSpec(two)
+ addSpec(three)
+ assertEquals(findSpec("one"), one)
+ assertEquals(findSpec("two"), two)
+ assertEquals(findSpec("three"), three)
+ assertNull(findSpec("abc"))
+ }
+ }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/impl/FakeCompatUIRepository.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/impl/FakeCompatUIRepository.kt
new file mode 100644
index 0000000..cdc524a
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/impl/FakeCompatUIRepository.kt
@@ -0,0 +1,39 @@
+/*
+ * 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.wm.shell.compatui.impl
+
+import com.android.wm.shell.compatui.api.CompatUIRepository
+import com.android.wm.shell.compatui.api.CompatUISpec
+
+/**
+ * Fake implementation for {@link CompatUIRepository}
+ */
+class FakeCompatUIRepository : CompatUIRepository {
+ val allSpecs = mutableMapOf<String, CompatUISpec>()
+ override fun addSpec(spec: CompatUISpec) {
+ if (findSpec(spec.name) != null) {
+ throw IllegalStateException("Spec with name:${spec.name} already present")
+ }
+ allSpecs[spec.name] = spec
+ }
+
+ override fun iterateOn(fn: (CompatUISpec) -> Unit) =
+ allSpecs.values.forEach(fn)
+
+ override fun findSpec(name: String): CompatUISpec? =
+ allSpecs[name]
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index f670434..c56671a 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -20,7 +20,6 @@
import android.app.ActivityManager.RunningTaskInfo
import android.app.KeyguardManager
import android.app.WindowConfiguration.ACTIVITY_TYPE_HOME
-import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE
import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
@@ -68,6 +67,7 @@
import com.android.dx.mockito.inline.extended.ExtendedMockito.never
import com.android.dx.mockito.inline.extended.StaticMockitoSession
import com.android.window.flags.Flags
+import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE
import com.android.wm.shell.MockToken
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.ShellTaskOrganizer
@@ -125,11 +125,11 @@
import org.mockito.Mockito.mock
import org.mockito.Mockito.spy
import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
import org.mockito.kotlin.anyOrNull
import org.mockito.kotlin.atLeastOnce
import org.mockito.kotlin.capture
import org.mockito.quality.Strictness
+import org.mockito.Mockito.`when` as whenever
/**
* Test class for {@link DesktopTasksController}
@@ -935,6 +935,24 @@
}
@Test
+ fun moveToFullscreen_tdaFullscreen_windowingModeUndefined_removesWallpaperActivity() {
+ val task = setUpFreeformTask()
+ val wallpaperToken = MockToken().token()
+
+ desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken
+ assertNotNull(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY))
+ .configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN
+
+ controller.moveToFullscreen(task.taskId, transitionSource = UNKNOWN)
+
+ val wct = getLatestExitDesktopWct()
+ val taskChange = assertNotNull(wct.changes[task.token.asBinder()])
+ assertThat(taskChange.windowingMode).isEqualTo(WINDOWING_MODE_UNDEFINED)
+ // Removes wallpaper activity when leaving desktop
+ wct.assertRemoveAt(index = 0, wallpaperToken)
+ }
+
+ @Test
fun moveToFullscreen_tdaFreeform_windowingModeSetToFullscreen() {
val task = setUpFreeformTask()
val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
@@ -946,6 +964,44 @@
}
@Test
+ fun moveToFullscreen_tdaFreeform_windowingModeFullscreen_removesWallpaperActivity() {
+ val task = setUpFreeformTask()
+ val wallpaperToken = MockToken().token()
+
+ desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken
+ assertNotNull(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY))
+ .configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM
+
+ controller.moveToFullscreen(task.taskId, transitionSource = UNKNOWN)
+
+ val wct = getLatestExitDesktopWct()
+ val taskChange = assertNotNull(wct.changes[task.token.asBinder()])
+ assertThat(taskChange.windowingMode).isEqualTo(WINDOWING_MODE_FULLSCREEN)
+ // Removes wallpaper activity when leaving desktop
+ wct.assertRemoveAt(index = 0, wallpaperToken)
+ }
+
+ @Test
+ fun moveToFullscreen_multipleVisibleNonMinimizedTasks_doesNotRemoveWallpaperActivity() {
+ val task1 = setUpFreeformTask()
+ // Setup task2
+ setUpFreeformTask()
+ val wallpaperToken = MockToken().token()
+
+ desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken
+ assertNotNull(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY))
+ .configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN
+
+ controller.moveToFullscreen(task1.taskId, transitionSource = UNKNOWN)
+
+ val wct = getLatestExitDesktopWct()
+ val task1Change = assertNotNull(wct.changes[task1.token.asBinder()])
+ assertThat(task1Change.windowingMode).isEqualTo(WINDOWING_MODE_UNDEFINED)
+ // Does not remove wallpaper activity, as desktop still has a visible desktop task
+ assertThat(wct.hierarchyOps).isEmpty()
+ }
+
+ @Test
fun moveToFullscreen_nonExistentTask_doesNothing() {
controller.moveToFullscreen(999, transitionSource = UNKNOWN)
verifyExitDesktopWCTNotExecuted()
@@ -1395,7 +1451,7 @@
.setActivityType(ACTIVITY_TYPE_STANDARD)
.setWindowingMode(WINDOWING_MODE_FULLSCREEN)
.build()
- val transition = createTransition(task = task, type = WindowManager.TRANSIT_CLOSE)
+ val transition = createTransition(task = task, type = TRANSIT_CLOSE)
val result = controller.handleRequest(Binder(), transition)
assertThat(result).isNull()
}
@@ -1489,8 +1545,11 @@
}
@Test
- @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun handleRequest_backTransition_singleActiveTaskNoTokenFlagDisabled_doesNotHandle() {
+ @DisableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION,
+ )
+ fun handleRequest_backTransition_singleActiveTaskNoToken_wallpaperDisabled_backNavDisabled_doesNotHandle() {
val task = setUpFreeformTask()
val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_TO_BACK))
@@ -1499,8 +1558,22 @@
}
@Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
+ )
+ fun handleRequest_backTransition_singleActiveTaskNoToken_wallpaperEnabled_backNavEnabled_removesTask() {
+ val task = setUpFreeformTask()
+
+ val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_TO_BACK))
+
+ assertNotNull(result, "Should handle request").assertRemoveAt(0, task.token)
+ }
+
+ @Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun handleRequest_backTransition_singleActiveTaskNoTokenFlagEnabled_doesNotHandle() {
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
+ fun handleRequest_backTransition_singleActiveTaskNoToken_backNavigationDisabled_doesNotHandle() {
val task = setUpFreeformTask()
val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_TO_BACK))
@@ -1509,8 +1582,11 @@
}
@Test
- @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun handleRequest_backTransition_singleActiveTaskWithTokenFlagDisabled_doesNotHandle() {
+ @DisableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
+ )
+ fun handleRequest_backTransition_singleActiveTaskWithToken_wallpaperDisabled_backNavDisabled_doesNotHandle() {
val task = setUpFreeformTask()
desktopModeTaskRepository.wallpaperActivityToken = MockToken().token()
@@ -1520,22 +1596,42 @@
}
@Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun handleRequest_backTransition_singleActiveTaskWithTokenFlagEnabled_handlesRequest() {
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
+ )
+ fun handleRequest_backTransition_singleActiveTaskWithToken_wallpaperEnabled_backNavEnabled_removesWallpaperAndTask() {
val task = setUpFreeformTask()
val wallpaperToken = MockToken().token()
desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken
val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_TO_BACK))
- assertNotNull(result, "Should handle request")
- // Should create remove wallpaper transaction
- .assertRemoveAt(index = 0, wallpaperToken)
+ // Should create remove wallpaper transaction
+ assertNotNull(result, "Should handle request").assertRemoveAt(index = 0, wallpaperToken)
+ result.assertRemoveAt(index = 1, task.token)
}
@Test
- @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun handleRequest_backTransition_multipleActiveTasksFlagDisabled_doesNotHandle() {
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
+ fun handleRequest_backTransition_singleActiveTaskWithToken_backNavigationDisabled_removesWallpaper() {
+ val task = setUpFreeformTask()
+ val wallpaperToken = MockToken().token()
+
+ desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken
+ val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_TO_BACK))
+
+ // Should create remove wallpaper transaction
+ assertNotNull(result, "Should handle request").assertRemoveAt(index = 0, wallpaperToken)
+ }
+
+ @Test
+ @DisableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
+ )
+ fun handleRequest_backTransition_multipleTasks_wallpaperDisabled_backNavDisabled_doesNotHandle() {
val task1 = setUpFreeformTask()
setUpFreeformTask()
@@ -1546,8 +1642,24 @@
}
@Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
+ )
+ fun handleRequest_backTransition_multipleTasks_wallpaperEnabled_backNavEnabled_removesTask() {
+ val task1 = setUpFreeformTask()
+ setUpFreeformTask()
+
+ desktopModeTaskRepository.wallpaperActivityToken = MockToken().token()
+ val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_TO_BACK))
+
+ assertNotNull(result, "Should handle request").assertRemoveAt(index = 0, task1.token)
+ }
+
+ @Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun handleRequest_backTransition_multipleActiveTasksFlagEnabled_doesNotHandle() {
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
+ fun handleRequest_backTransition_multipleTasks_backNavigationDisabled_doesNotHandle() {
val task1 = setUpFreeformTask()
setUpFreeformTask()
@@ -1558,8 +1670,11 @@
}
@Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun handleRequest_backTransition_multipleActiveTasksSingleNonClosing_handlesRequest() {
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
+ )
+ fun handleRequest_backTransition_multipleTasksSingleNonClosing_wallpaperEnabled_backNavEnabled_removesWallpaperAndTask() {
val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
val wallpaperToken = MockToken().token()
@@ -1568,14 +1683,33 @@
desktopModeTaskRepository.addClosingTask(displayId = DEFAULT_DISPLAY, taskId = task2.taskId)
val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_TO_BACK))
- assertNotNull(result, "Should handle request")
- // Should create remove wallpaper transaction
- .assertRemoveAt(index = 0, wallpaperToken)
+ // Should create remove wallpaper transaction
+ assertNotNull(result, "Should handle request").assertRemoveAt(index = 0, wallpaperToken)
+ result.assertRemoveAt(index = 1, task1.token)
}
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun handleRequest_backTransition_multipleActiveTasksSingleNonMinimized_handlesRequest() {
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
+ fun handleRequest_backTransition_multipleTasksSingleNonClosing_backNavigationDisabled_removesWallpaper() {
+ val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ val wallpaperToken = MockToken().token()
+
+ desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken
+ desktopModeTaskRepository.addClosingTask(displayId = DEFAULT_DISPLAY, taskId = task2.taskId)
+ val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_TO_BACK))
+
+ // Should create remove wallpaper transaction
+ assertNotNull(result, "Should handle request").assertRemoveAt(index = 0, wallpaperToken)
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
+ )
+ fun handleRequest_backTransition_multipleTasksSingleNonMinimized_wallpaperEnabled_backNavEnabled_removesWallpaperAndTask() {
val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
val wallpaperToken = MockToken().token()
@@ -1584,24 +1718,53 @@
desktopModeTaskRepository.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = task2.taskId)
val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_TO_BACK))
- assertNotNull(result, "Should handle request")
- // Should create remove wallpaper transaction
- .assertRemoveAt(index = 0, wallpaperToken)
- }
-
- @Test
- @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun handleRequest_closeTransition_singleActiveTaskNoTokenFlagDisabled_doesNotHandle() {
- val task = setUpFreeformTask()
-
- val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_CLOSE))
-
- assertNull(result, "Should not handle request")
+ // Should create remove wallpaper transaction
+ assertNotNull(result, "Should handle request").assertRemoveAt(index = 0, wallpaperToken)
+ result.assertRemoveAt(index = 1, task1.token)
}
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun handleRequest_closeTransition_singleActiveTaskNoTokenFlagEnabled_doesNotHandle() {
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
+ fun handleRequest_backTransition_multipleTasksSingleNonMinimized_backNavigationDisabled_removesWallpaper() {
+ val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ val wallpaperToken = MockToken().token()
+
+ desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken
+ desktopModeTaskRepository.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = task2.taskId)
+ val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_TO_BACK))
+
+ // Should create remove wallpaper transaction
+ assertNotNull(result, "Should handle request").assertRemoveAt(index = 0, wallpaperToken)
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
+ )
+ fun handleRequest_backTransition_nonMinimizadTask_wallpaperEnabled_backNavEnabled_removesWallpaper() {
+ val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ val wallpaperToken = MockToken().token()
+
+ desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken
+ desktopModeTaskRepository.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = task2.taskId)
+ // Task is being minimized so mark it as not visible.
+ desktopModeTaskRepository
+ .updateVisibleFreeformTasks(displayId = DEFAULT_DISPLAY, task2.taskId, false)
+ val result = controller.handleRequest(Binder(), createTransition(task2, type = TRANSIT_TO_BACK))
+
+ assertNull(result, "Should not handle request")
+ }
+
+ @Test
+ @DisableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
+ )
+ fun handleRequest_closeTransition_singleActiveTaskNoToken_wallpaperDisabled_backNavDisabled_doesNotHandle() {
val task = setUpFreeformTask()
val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_CLOSE))
@@ -1610,8 +1773,35 @@
}
@Test
- @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun handleRequest_closeTransition_singleActiveTaskWithTokenFlagDisabled_doesNotHandle() {
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
+ )
+ fun handleRequest_closeTransition_singleActiveTaskNoToken_wallpaperEnabled_backNavEnabled_removesTask() {
+ val task = setUpFreeformTask()
+
+ val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_CLOSE))
+
+ assertNotNull(result, "Should handle request").assertRemoveAt(index = 0, task.token)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
+ fun handleRequest_closeTransition_singleActiveTaskNoToken_backNavigationDisabled_doesNotHandle() {
+ val task = setUpFreeformTask()
+
+ val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_CLOSE))
+
+ assertNull(result, "Should not handle request")
+ }
+
+ @Test
+ @DisableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
+ )
+ fun handleRequest_closeTransition_singleActiveTaskWithToken_wallpaperDisabled_backNavDisabled_doesNotHandle() {
val task = setUpFreeformTask()
desktopModeTaskRepository.wallpaperActivityToken = MockToken().token()
@@ -1621,22 +1811,71 @@
}
@Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun handleRequest_closeTransition_singleActiveTaskWithTokenFlagEnabled_handlesRequest() {
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
+ )
+ fun handleRequest_closeTransition_singleActiveTaskWithToken_wallpaperEnabled_backNavEnabled_removesWallpaperAndTask() {
val task = setUpFreeformTask()
val wallpaperToken = MockToken().token()
desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken
val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_CLOSE))
+ // Should create remove wallpaper transaction
+ assertNotNull(result, "Should handle request").assertRemoveAt(index = 0, wallpaperToken)
+ result.assertRemoveAt(index = 1, task.token)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
+ fun handleRequest_closeTransition_singleActiveTaskWithToken_backNavigationDisabled_removesWallpaper() {
+ val task = setUpFreeformTask()
+ val wallpaperToken = MockToken().token()
+
+ desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken
+ val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_CLOSE))
+
+ // Should create remove wallpaper transaction
+ assertNotNull(result, "Should handle request").assertRemoveAt(index = 0, wallpaperToken)
+ }
+
+ @Test
+ @DisableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
+ )
+ fun handleRequest_closeTransition_multipleTasks_wallpaperDisabled_backNavDisabled_doesNotHandle() {
+ val task1 = setUpFreeformTask()
+ setUpFreeformTask()
+
+ desktopModeTaskRepository.wallpaperActivityToken = MockToken().token()
+ val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_CLOSE))
+
+ assertNull(result, "Should not handle request")
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
+ )
+ fun handleRequest_closeTransition_multipleTasks_wallpaperEnabled_backNavEnabled_removesTask() {
+ val task1 = setUpFreeformTask()
+ setUpFreeformTask()
+
+ desktopModeTaskRepository.wallpaperActivityToken = MockToken().token()
+ val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_CLOSE))
+
assertNotNull(result, "Should handle request")
- // Should create remove wallpaper transaction
- .assertRemoveAt(index = 0, wallpaperToken)
+ result.assertRemoveAt(index = 0, task1.token)
}
@Test
- @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun handleRequest_closeTransition_multipleActiveTasksFlagDisabled_doesNotHandle() {
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
+ fun handleRequest_closeTransition_multipleTasksFlagEnabled_backNavigationDisabled_doesNotHandle() {
val task1 = setUpFreeformTask()
setUpFreeformTask()
@@ -1647,20 +1886,11 @@
}
@Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun handleRequest_closeTransition_multipleActiveTasksFlagEnabled_doesNotHandle() {
- val task1 = setUpFreeformTask()
- setUpFreeformTask()
-
- desktopModeTaskRepository.wallpaperActivityToken = MockToken().token()
- val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_CLOSE))
-
- assertNull(result, "Should not handle request")
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun handleRequest_closeTransition_multipleActiveTasksSingleNonClosing_handlesRequest() {
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
+ )
+ fun handleRequest_closeTransition_multipleTasksSingleNonClosing_wallpaperEnabled_backNavEnabled_removesWallpaperAndTask() {
val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
val wallpaperToken = MockToken().token()
@@ -1669,14 +1899,33 @@
desktopModeTaskRepository.addClosingTask(displayId = DEFAULT_DISPLAY, taskId = task2.taskId)
val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_CLOSE))
- assertNotNull(result, "Should handle request")
- // Should create remove wallpaper transaction
- .assertRemoveAt(index = 0, wallpaperToken)
+ // Should create remove wallpaper transaction
+ assertNotNull(result, "Should handle request").assertRemoveAt(index = 0, wallpaperToken)
+ result.assertRemoveAt(index = 1, task1.token)
}
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun handleRequest_closeTransition_multipleActiveTasksSingleNonMinimized_handlesRequest() {
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
+ fun handleRequest_closeTransition_multipleTasksSingleNonClosing_backNavigationDisabled_removesWallpaper() {
+ val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ val wallpaperToken = MockToken().token()
+
+ desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken
+ desktopModeTaskRepository.addClosingTask(displayId = DEFAULT_DISPLAY, taskId = task2.taskId)
+ val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_CLOSE))
+
+ // Should create remove wallpaper transaction
+ assertNotNull(result, "Should handle request").assertRemoveAt(index = 0, wallpaperToken)
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
+ )
+ fun handleRequest_closeTransition_multipleTasksOneNonMinimized_wallpaperEnabled_backNavEnabled_removesWallpaperAndTask() {
val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
val wallpaperToken = MockToken().token()
@@ -1685,9 +1934,45 @@
desktopModeTaskRepository.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = task2.taskId)
val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_CLOSE))
- assertNotNull(result, "Should handle request")
- // Should create remove wallpaper transaction
- .assertRemoveAt(index = 0, wallpaperToken)
+ // Should create remove wallpaper transaction
+ assertNotNull(result, "Should handle request").assertRemoveAt(index = 0, wallpaperToken)
+ result.assertRemoveAt(index = 1, task1.token)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
+ fun handleRequest_closeTransition_multipleTasksSingleNonMinimized_backNavigationDisabled_removesWallpaper() {
+ val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ val wallpaperToken = MockToken().token()
+
+ desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken
+ desktopModeTaskRepository.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = task2.taskId)
+ val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_CLOSE))
+
+ // Should create remove wallpaper transaction
+ assertNotNull(result, "Should handle request").assertRemoveAt(index = 0, wallpaperToken)
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
+ )
+ fun handleRequest_closeTransition_minimizadTask_wallpaperEnabled_backNavEnabled_removesWallpaper() {
+ val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ val wallpaperToken = MockToken().token()
+
+ desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken
+ desktopModeTaskRepository.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = task2.taskId)
+ // Task is being minimized so mark it as not visible.
+ desktopModeTaskRepository
+ .updateVisibleFreeformTasks(displayId = DEFAULT_DISPLAY, task2.taskId, false)
+ val result = controller.handleRequest(Binder(), createTransition(task2, type = TRANSIT_TO_BACK))
+
+ assertNull(result, "Should not handle request")
}
@Test
@@ -1769,6 +2054,49 @@
}
@Test
+ fun moveFocusedTaskToFullscreen_onlyVisibleNonMinimizedTask_removesWallpaperActivity() {
+ val task1 = setUpFreeformTask()
+ val task2 = setUpFreeformTask()
+ val task3 = setUpFreeformTask()
+ val wallpaperToken = MockToken().token()
+
+ task1.isFocused = false
+ task2.isFocused = true
+ task3.isFocused = false
+ desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken
+ desktopModeTaskRepository.minimizeTask(DEFAULT_DISPLAY, task1.taskId)
+ desktopModeTaskRepository.updateVisibleFreeformTasks(DEFAULT_DISPLAY, task3.taskId,
+ visible = false)
+
+ controller.enterFullscreen(DEFAULT_DISPLAY, transitionSource = UNKNOWN)
+
+ val wct = getLatestExitDesktopWct()
+ val taskChange = assertNotNull(wct.changes[task2.token.asBinder()])
+ assertThat(taskChange.windowingMode).isEqualTo(WINDOWING_MODE_UNDEFINED) // inherited FULLSCREEN
+ wct.assertRemoveAt(index = 0, wallpaperToken)
+ }
+
+ @Test
+ fun moveFocusedTaskToFullscreen_multipleVisibleTasks_doesNotRemoveWallpaperActivity() {
+ val task1 = setUpFreeformTask()
+ val task2 = setUpFreeformTask()
+ val task3 = setUpFreeformTask()
+ val wallpaperToken = MockToken().token()
+
+ task1.isFocused = false
+ task2.isFocused = true
+ task3.isFocused = false
+ desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken
+ controller.enterFullscreen(DEFAULT_DISPLAY, transitionSource = UNKNOWN)
+
+ val wct = getLatestExitDesktopWct()
+ val taskChange = assertNotNull(wct.changes[task2.token.asBinder()])
+ assertThat(taskChange.windowingMode).isEqualTo(WINDOWING_MODE_UNDEFINED) // inherited FULLSCREEN
+ // Does not remove wallpaper activity, as desktop still has visible desktop tasks
+ assertThat(wct.hierarchyOps).isEmpty()
+ }
+
+ @Test
@EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
fun dragToDesktop_landscapeDevice_resizable_undefinedOrientation_defaultLandscapeBounds() {
val spyController = spy(controller)
@@ -1977,6 +2305,7 @@
eq(null))
}
+ @Test
fun enterSplit_freeformTaskIsMovedToSplit() {
val task1 = setUpFreeformTask()
val task2 = setUpFreeformTask()
@@ -1986,14 +2315,67 @@
task2.isFocused = true
task3.isFocused = false
- controller.enterSplit(DEFAULT_DISPLAY, false)
+ controller.enterSplit(DEFAULT_DISPLAY, leftOrTop = false)
verify(splitScreenController)
.requestEnterSplitSelect(
- task2,
+ eq(task2),
any(),
- SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT,
- task2.configuration.windowConfiguration.bounds)
+ eq(SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT),
+ eq(task2.configuration.windowConfiguration.bounds))
+ }
+
+ @Test
+ fun enterSplit_onlyVisibleNonMinimizedTask_removesWallpaperActivity() {
+ val task1 = setUpFreeformTask()
+ val task2 = setUpFreeformTask()
+ val task3 = setUpFreeformTask()
+ val wallpaperToken = MockToken().token()
+
+ task1.isFocused = false
+ task2.isFocused = true
+ task3.isFocused = false
+ desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken
+ desktopModeTaskRepository.minimizeTask(DEFAULT_DISPLAY, task1.taskId)
+ desktopModeTaskRepository.updateVisibleFreeformTasks(DEFAULT_DISPLAY, task3.taskId,
+ visible = false)
+
+ controller.enterSplit(DEFAULT_DISPLAY, leftOrTop = false)
+
+ val wctArgument = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+ verify(splitScreenController)
+ .requestEnterSplitSelect(
+ eq(task2),
+ wctArgument.capture(),
+ eq(SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT),
+ eq(task2.configuration.windowConfiguration.bounds))
+ // Removes wallpaper activity when leaving desktop
+ wctArgument.value.assertRemoveAt(index = 0, wallpaperToken)
+ }
+
+ @Test
+ fun enterSplit_multipleVisibleNonMinimizedTasks_removesWallpaperActivity() {
+ val task1 = setUpFreeformTask()
+ val task2 = setUpFreeformTask()
+ val task3 = setUpFreeformTask()
+ val wallpaperToken = MockToken().token()
+
+ task1.isFocused = false
+ task2.isFocused = true
+ task3.isFocused = false
+ desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken
+
+ controller.enterSplit(DEFAULT_DISPLAY, leftOrTop = false)
+
+ val wctArgument = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+ verify(splitScreenController)
+ .requestEnterSplitSelect(
+ eq(task2),
+ wctArgument.capture(),
+ eq(SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT),
+ eq(task2.configuration.windowConfiguration.bounds))
+ // Does not remove wallpaper activity, as desktop still has visible desktop tasks
+ assertThat(wctArgument.value.hierarchyOps).isEmpty()
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
index 75d2145..6888de5 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
@@ -182,7 +182,7 @@
@Test
public void instantiatePipController_registersPipTransitionCallback() {
- verify(mMockPipTransitionController).registerPipTransitionCallback(any(), any());
+ verify(mMockPipTransitionController).registerPipTransitionCallback(any());
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java
index 66f8c0b..ace09a8 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java
@@ -114,8 +114,8 @@
final PipBoundsAlgorithm pipBoundsAlgorithm = new PipBoundsAlgorithm(mContext,
mPipBoundsState, pipSnapAlgorithm, pipKeepClearAlgorithm, mPipDisplayLayoutState,
mSizeSpecSource);
- final PipMotionHelper motionHelper = new PipMotionHelper(mContext, mMainExecutor,
- mPipBoundsState, mPipTaskOrganizer, mPhonePipMenuController, pipSnapAlgorithm,
+ final PipMotionHelper motionHelper = new PipMotionHelper(mContext, mPipBoundsState,
+ mPipTaskOrganizer, mPhonePipMenuController, pipSnapAlgorithm,
mMockPipTransitionController, mFloatingContentCoordinator,
Optional.empty() /* pipPerfHintControllerOptional */);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
index 6d18e36..92762fa 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
@@ -116,8 +116,8 @@
mPipSnapAlgorithm = new PipSnapAlgorithm();
mPipBoundsAlgorithm = new PipBoundsAlgorithm(mContext, mPipBoundsState, mPipSnapAlgorithm,
new PipKeepClearAlgorithmInterface() {}, mPipDisplayLayoutState, mSizeSpecSource);
- PipMotionHelper pipMotionHelper = new PipMotionHelper(mContext, mMainExecutor,
- mPipBoundsState, mPipTaskOrganizer, mPhonePipMenuController, mPipSnapAlgorithm,
+ PipMotionHelper pipMotionHelper = new PipMotionHelper(mContext, mPipBoundsState,
+ mPipTaskOrganizer, mPhonePipMenuController, mPipSnapAlgorithm,
mMockPipTransitionController, mFloatingContentCoordinator,
Optional.empty() /* pipPerfHintControllerOptional */);
mPipTouchHandler = new PipTouchHandler(mContext, mShellInit, mPhonePipMenuController,
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index d148afd..52b5ff7 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -2649,4 +2649,11 @@
* @hide
*/
public static native boolean isBluetoothVariableLatencyEnabled();
+
+ /**
+ * Register a native listener for system property sysprop
+ * @param callback the listener which fires when the property changes
+ * @hide
+ */
+ public static native void listenForSystemPropertyChange(String sysprop, Runnable callback);
}
diff --git a/media/java/android/media/projection/MediaProjection.java b/media/java/android/media/projection/MediaProjection.java
index 4059291..999f40e5 100644
--- a/media/java/android/media/projection/MediaProjection.java
+++ b/media/java/android/media/projection/MediaProjection.java
@@ -312,6 +312,10 @@
* <p>Once a MediaProjection has been stopped, it's up to the application to release any
* resources it may be holding (e.g. releasing the {@link VirtualDisplay} and
* {@link Surface}).
+ *
+ * <p>After this callback any call to
+ * {@link MediaProjection#createVirtualDisplay} will fail, even if no such
+ * {@link VirtualDisplay} was ever created for this MediaProjection session.
*/
public void onStop() { }
diff --git a/media/java/android/media/projection/MediaProjectionManager.java b/media/java/android/media/projection/MediaProjectionManager.java
index 7ed67dc..4013d84 100644
--- a/media/java/android/media/projection/MediaProjectionManager.java
+++ b/media/java/android/media/projection/MediaProjectionManager.java
@@ -43,25 +43,31 @@
/**
* Manages the retrieval of certain types of {@link MediaProjection} tokens.
*
- * <p><ol>An example flow of starting a media projection will be:
- * <li>Declare a foreground service with the type {@code mediaProjection} in
- * the {@code AndroidManifest.xml}.
- * </li>
- * <li>Create an intent by calling {@link MediaProjectionManager#createScreenCaptureIntent()}
- * and pass this intent to {@link Activity#startActivityForResult(Intent, int)}.
- * </li>
- * <li>On getting {@link Activity#onActivityResult(int, int, Intent)},
- * start the foreground service with the type
- * {@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION}.
- * </li>
- * <li>Retrieve the media projection token by calling
- * {@link MediaProjectionManager#getMediaProjection(int, Intent)} with the result code and
- * intent from the {@link Activity#onActivityResult(int, int, Intent)} above.
- * </li>
- * <li>Start the screen capture session for media projection by calling
- * {@link MediaProjection#createVirtualDisplay(String, int, int, int, int, Surface,
- * android.hardware.display.VirtualDisplay.Callback, Handler)}.
- * </li>
+ * <p>
+ *
+ * <ol>
+ * An example flow of starting a media projection will be:
+ * <li>Declare a foreground service with the type {@code mediaProjection} in the {@code
+ * AndroidManifest.xml}.
+ * <li>Create an intent by calling {@link MediaProjectionManager#createScreenCaptureIntent()} and
+ * pass this intent to {@link Activity#startActivityForResult(Intent, int)}.
+ * <li>On getting {@link Activity#onActivityResult(int, int, Intent)}, start the foreground
+ * service with the type {@link
+ * android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION}.
+ * <li>Retrieve the media projection token by calling {@link
+ * MediaProjectionManager#getMediaProjection(int, Intent)} with the result code and intent
+ * from the {@link Activity#onActivityResult(int, int, Intent)} above.
+ * <li>Register a {@link MediaProjection.Callback} by calling {@link
+ * MediaProjection#registerCallback(MediaProjection.Callback, Handler)}. This is required to
+ * receive notifications about when the {@link MediaProjection} or captured content changes
+ * state. When receiving an `onStop()` callback, the client must clean up any resources it is
+ * holding, e.g. the {@link VirtualDisplay} and {@link Surface}. The MediaProjection may
+ * further no longer create any new {@link VirtualDisplay}s via {@link
+ * MediaProjection#createVirtualDisplay(String, int, int, int, int, Surface,
+ * VirtualDisplay.Callback, Handler)}.
+ * <li>Start the screen capture session for media projection by calling {@link
+ * MediaProjection#createVirtualDisplay(String, int, int, int, int, Surface,
+ * android.hardware.display.VirtualDisplay.Callback, Handler)}.
* </ol>
*/
@SystemService(Context.MEDIA_PROJECTION_SERVICE)
diff --git a/media/tests/mediatestutils/java/com/android/media/mediatestutils/TestUtils.java b/media/tests/mediatestutils/java/com/android/media/mediatestutils/TestUtils.java
index 7065e3a..46c7273 100644
--- a/media/tests/mediatestutils/java/com/android/media/mediatestutils/TestUtils.java
+++ b/media/tests/mediatestutils/java/com/android/media/mediatestutils/TestUtils.java
@@ -27,6 +27,7 @@
import com.google.common.util.concurrent.MoreExecutors;
import java.lang.ref.WeakReference;
+import java.util.Collection;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.Function;
@@ -35,12 +36,13 @@
/** Utils for audio tests. */
public class TestUtils {
/**
- * Return a future for an intent delivered by a broadcast receiver which matches an
- * action and predicate.
+ * Return a future for an intent delivered by a broadcast receiver which matches an action and
+ * predicate.
+ *
* @param context - Context to register the receiver with
* @param action - String representing action to register receiver for
- * @param pred - Predicate which sets the future if evaluates to true, otherwise, leaves
- * the future unset. If the predicate throws, the future is set exceptionally
+ * @param pred - Predicate which sets the future if evaluates to true, otherwise, leaves the
+ * future unset. If the predicate throws, the future is set exceptionally
* @return - The future representing intent delivery matching predicate.
*/
public static ListenableFuture<Intent> getFutureForIntent(
@@ -76,20 +78,77 @@
}
/**
- * Same as previous, but with no predicate.
+ * Return a future for an intent delivered by a broadcast receiver which matches one of a set of
+ * actions and predicate.
+ *
+ * @param context - Context to register the receiver with
+ * @param actionsCollection - Collection of actions which to listen for, completing on any
+ * @param pred - Predicate which sets the future if evaluates to true, otherwise, leaves the
+ * future unset. If the predicate throws, the future is set exceptionally
+ * @return - The future representing intent delivery matching predicate.
*/
+ public static ListenableFuture<Intent> getFutureForIntent(
+ Context context, Collection<String> actionsCollection, Predicate<Intent> pred) {
+ // These are evaluated async
+ Objects.requireNonNull(actionsCollection);
+ Objects.requireNonNull(pred);
+ if (actionsCollection.isEmpty()) {
+ throw new IllegalArgumentException("actionsCollection must not be empty");
+ }
+ return getFutureForListener(
+ (recv) ->
+ context.registerReceiver(
+ recv,
+ actionsCollection.stream()
+ .reduce(
+ new IntentFilter(),
+ (IntentFilter filter, String x) -> {
+ filter.addAction(x);
+ return filter;
+ },
+ (x, y) -> {
+ throw new IllegalStateException(
+ "No parallel support");
+ }),
+ Context.RECEIVER_EXPORTED),
+ (recv) -> {
+ try {
+ context.unregisterReceiver(recv);
+ } catch (IllegalArgumentException e) {
+ // Thrown when receiver is already unregistered, nothing to do
+ }
+ },
+ (completer) ->
+ new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ try {
+ if (actionsCollection.contains(intent.getAction())
+ && pred.test(intent)) {
+ completer.set(intent);
+ }
+ } catch (Exception e) {
+ completer.setException(e);
+ }
+ }
+ },
+ "Intent receiver future for actions: " + actionsCollection);
+ }
+
+ /** Same as previous, but with no predicate. */
public static ListenableFuture<Intent> getFutureForIntent(Context context, String action) {
return getFutureForIntent(context, action, i -> true);
}
/**
* Return a future for a callback registered to a listener interface.
+ *
* @param registerFunc - Function which consumes the callback object for registration
- * @param unregisterFunc - Function which consumes the callback object for unregistration
- * This function is called when the future is completed or cancelled
+ * @param unregisterFunc - Function which consumes the callback object for unregistration This
+ * function is called when the future is completed or cancelled
* @param instantiateCallback - Factory function for the callback object, provided a completer
- * object (see {@code CallbackToFutureAdapter.Completer<T>}), which is a logical reference
- * to the future returned by this function
+ * object (see {@code CallbackToFutureAdapter.Completer<T>}), which is a logical reference
+ * to the future returned by this function
* @param debug - Debug string contained in future {@code toString} representation.
*/
public static <T, V> ListenableFuture<T> getFutureForListener(
diff --git a/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java b/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java
index bbf0315..4387b6f 100644
--- a/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java
+++ b/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java
@@ -16,6 +16,8 @@
package com.android.settingslib.widget;
+import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_YES;
+
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
@@ -122,6 +124,8 @@
public void onBindViewHolder(PreferenceViewHolder holder) {
super.onBindViewHolder(holder);
+ final FrameLayout illustrationFrame = (FrameLayout) holder.findViewById(
+ R.id.illustration_frame);
final ImageView backgroundView =
(ImageView) holder.findViewById(R.id.background_view);
final FrameLayout middleGroundLayout =
@@ -130,15 +134,15 @@
(LottieAnimationView) holder.findViewById(R.id.lottie_view);
if (illustrationView != null && !TextUtils.isEmpty(mContentDescription)) {
illustrationView.setContentDescription(mContentDescription);
- illustrationView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
+ illustrationView.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
+ final View illustrationContainer = (View) illustrationFrame.getParent();
+ illustrationContainer.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
}
// To solve the problem of non-compliant illustrations, we set the frame height
// to 300dp and set the length of the short side of the screen to
// the width of the frame.
final int screenWidth = getContext().getResources().getDisplayMetrics().widthPixels;
final int screenHeight = getContext().getResources().getDisplayMetrics().heightPixels;
- final FrameLayout illustrationFrame = (FrameLayout) holder.findViewById(
- R.id.illustration_frame);
final LayoutParams lp = (LayoutParams) illustrationFrame.getLayoutParams();
lp.width = screenWidth < screenHeight ? screenWidth : screenHeight;
illustrationFrame.setLayoutParams(lp);
diff --git a/packages/SettingsLib/Spa/gallery/AndroidManifest.xml b/packages/SettingsLib/Spa/gallery/AndroidManifest.xml
index df5644b..2645360 100644
--- a/packages/SettingsLib/Spa/gallery/AndroidManifest.xml
+++ b/packages/SettingsLib/Spa/gallery/AndroidManifest.xml
@@ -46,20 +46,6 @@
</intent-filter>
</provider>
- <provider android:name="com.android.settingslib.spa.slice.SpaSliceProvider"
- android:authorities="com.android.spa.gallery.slice.provider"
- android:exported="true" >
- <intent-filter>
- <action android:name="android.intent.action.VIEW" />
- <category android:name="android.app.slice.category.SLICE" />
- </intent-filter>
- </provider>
-
- <receiver
- android:name="com.android.settingslib.spa.slice.SpaSliceBroadcastReceiver"
- android:exported="false">
- </receiver>
-
<activity
android:name="com.android.settingslib.spa.debug.BlankActivity"
android:exported="true">
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
index 91bd791..ffd2879 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
@@ -55,7 +55,6 @@
import com.android.settingslib.spa.gallery.ui.CopyablePageProvider
import com.android.settingslib.spa.gallery.scaffold.ScrollablePagerPageProvider
import com.android.settingslib.spa.gallery.ui.SpinnerPageProvider
-import com.android.settingslib.spa.slice.SpaSliceBroadcastReceiver
/**
* Enum to define all SPP name here.
@@ -120,9 +119,7 @@
override val logger = DebugLogger()
override val browseActivityClass = GalleryMainActivity::class.java
- override val sliceBroadcastReceiverClass = SpaSliceBroadcastReceiver::class.java
// For debugging
override val searchProviderAuthorities = "com.android.spa.gallery.search.provider"
- override val sliceProviderAuthorities = "com.android.spa.gallery.slice.provider"
}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePageProvider.kt
index 96de1a7..6d1d346 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePageProvider.kt
@@ -27,7 +27,6 @@
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import com.android.settingslib.spa.framework.common.EntrySearchData
-import com.android.settingslib.spa.framework.common.EntrySliceData
import com.android.settingslib.spa.framework.common.EntryStatusData
import com.android.settingslib.spa.framework.common.SettingsEntry
import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
@@ -35,10 +34,8 @@
import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
import com.android.settingslib.spa.framework.common.createSettingsPage
import com.android.settingslib.spa.framework.theme.SettingsTheme
-import com.android.settingslib.spa.framework.util.createIntent
import com.android.settingslib.spa.gallery.R
import com.android.settingslib.spa.gallery.SettingsPageProviderEnum
-import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.ASYNC_PREFERENCE_SUMMARY
import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.ASYNC_PREFERENCE_TITLE
import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.AUTO_UPDATE_PREFERENCE_TITLE
import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.DISABLE_PREFERENCE_SUMMARY
@@ -48,15 +45,10 @@
import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.SIMPLE_PREFERENCE_KEYWORDS
import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.SIMPLE_PREFERENCE_SUMMARY
import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.SIMPLE_PREFERENCE_TITLE
-import com.android.settingslib.spa.slice.createBrowsePendingIntent
-import com.android.settingslib.spa.slice.provider.createDemoActionSlice
-import com.android.settingslib.spa.slice.provider.createDemoBrowseSlice
-import com.android.settingslib.spa.slice.provider.createDemoSlice
import com.android.settingslib.spa.widget.preference.Preference
import com.android.settingslib.spa.widget.preference.PreferenceModel
import com.android.settingslib.spa.widget.preference.SimplePreferenceMacro
import com.android.settingslib.spa.widget.ui.SettingsIcon
-import kotlinx.coroutines.delay
private const val TAG = "PreferencePage"
@@ -139,26 +131,6 @@
override val enabled = { model.asyncEnable.value }
}
)
- }
- .setSliceDataFn { sliceUri, _ ->
- val createSliceImpl = { s: String ->
- createDemoBrowseSlice(
- sliceUri = sliceUri,
- title = ASYNC_PREFERENCE_TITLE,
- summary = s,
- )
- }
- return@setSliceDataFn object : EntrySliceData() {
- init {
- postValue(createSliceImpl("(loading)"))
- }
-
- override suspend fun asyncRunner() {
- spaLogger.message(TAG, "Async entry loading")
- delay(2000L)
- postValue(createSliceImpl(ASYNC_PREFERENCE_SUMMARY))
- }
- }
}.build()
)
entryList.add(
@@ -176,28 +148,6 @@
}
}
)
- }
- .setSliceDataFn { sliceUri, args ->
- val createSliceImpl = { v: Int ->
- createDemoActionSlice(
- sliceUri = sliceUri,
- title = MANUAL_UPDATE_PREFERENCE_TITLE,
- summary = "manual update value $v",
- )
- }
-
- return@setSliceDataFn object : EntrySliceData() {
- private var tick = args?.getString("init")?.toInt() ?: 0
-
- init {
- postValue(createSliceImpl(tick))
- }
-
- override suspend fun asyncAction() {
- tick++
- postValue(createSliceImpl(tick))
- }
- }
}.build()
)
entryList.add(
@@ -216,33 +166,6 @@
}
}
)
- }
- .setSliceDataFn { sliceUri, args ->
- val createSliceImpl = { v: Int ->
- createDemoBrowseSlice(
- sliceUri = sliceUri,
- title = AUTO_UPDATE_PREFERENCE_TITLE,
- summary = "auto update value $v",
- )
- }
-
- return@setSliceDataFn object : EntrySliceData() {
- private var tick = args?.getString("init")?.toInt() ?: 0
-
- init {
- postValue(createSliceImpl(tick))
- }
-
- override suspend fun asyncRunner() {
- spaLogger.message(TAG, "autoUpdater.active")
- while (true) {
- delay(1000L)
- tick++
- spaLogger.message(TAG, "autoUpdater.value $tick")
- postValue(createSliceImpl(tick))
- }
- }
- }
}.build()
)
@@ -272,22 +195,6 @@
clickRoute = SettingsPageProviderEnum.PREFERENCE.name
)
}
- .setSliceDataFn { sliceUri, _ ->
- val intent = owner.createIntent()?.createBrowsePendingIntent()
- ?: return@setSliceDataFn null
- return@setSliceDataFn object : EntrySliceData() {
- init {
- postValue(
- createDemoSlice(
- sliceUri = sliceUri,
- title = PAGE_TITLE,
- summary = "Injected Entry",
- intent = intent,
- )
- )
- }
- }
- }
}
override fun getTitle(arguments: Bundle?): String {
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt
index 2d956d5..6e5132b 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt
@@ -17,12 +17,10 @@
package com.android.settingslib.spa.framework.common
import android.app.Activity
-import android.content.BroadcastReceiver
import android.content.Context
import android.util.Log
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext
-import com.android.settingslib.spa.slice.SettingsSliceDataRepository
private const val TAG = "SpaEnvironment"
@@ -69,8 +67,6 @@
val entryRepository = lazy { SettingsEntryRepository(pageProviderRepository.value) }
- val sliceDataRepository = lazy { SettingsSliceDataRepository(entryRepository.value) }
-
// The application context. Use local context as fallback when applicationContext is not
// available (e.g. in Robolectric test).
val appContext: Context = context.applicationContext ?: context
@@ -81,11 +77,9 @@
// Specify class name of browse activity and slice broadcast receiver, which is used to
// generate the necessary intents.
open val browseActivityClass: Class<out Activity>? = null
- open val sliceBroadcastReceiverClass: Class<out BroadcastReceiver>? = null
// Specify provider authorities for debugging purpose.
open val searchProviderAuthorities: String? = null
- open val sliceProviderAuthorities: String? = null
// TODO: add other environment setup here.
companion object {
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SettingsSliceDataRepository.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SettingsSliceDataRepository.kt
deleted file mode 100644
index 7a4750d..0000000
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SettingsSliceDataRepository.kt
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settingslib.spa.slice
-
-import android.net.Uri
-import android.util.Log
-import com.android.settingslib.spa.framework.common.EntrySliceData
-import com.android.settingslib.spa.framework.common.SettingsEntryRepository
-import com.android.settingslib.spa.framework.util.getEntryId
-
-private const val TAG = "SliceDataRepository"
-
-class SettingsSliceDataRepository(private val entryRepository: SettingsEntryRepository) {
- // The map of slice uri to its EntrySliceData, a.k.a. LiveData<Slice?>
- private val sliceDataMap: MutableMap<String, EntrySliceData> = mutableMapOf()
-
- // Note: mark this function synchronized, so that we can get the same livedata during the
- // whole lifecycle of a Slice.
- @Synchronized
- fun getOrBuildSliceData(sliceUri: Uri): EntrySliceData? {
- val sliceString = sliceUri.getSliceId() ?: return null
- return sliceDataMap[sliceString] ?: buildLiveDataImpl(sliceUri)?.let {
- sliceDataMap[sliceString] = it
- it
- }
- }
-
- fun getActiveSliceData(sliceUri: Uri): EntrySliceData? {
- val sliceString = sliceUri.getSliceId() ?: return null
- val sliceData = sliceDataMap[sliceString] ?: return null
- return if (sliceData.isActive()) sliceData else null
- }
-
- private fun buildLiveDataImpl(sliceUri: Uri): EntrySliceData? {
- Log.d(TAG, "buildLiveData: $sliceUri")
-
- val entryId = sliceUri.getEntryId() ?: return null
- val entry = entryRepository.getEntry(entryId) ?: return null
- if (!entry.hasSliceSupport) return null
- val arguments = sliceUri.getRuntimeArguments()
- return entry.getSliceData(runtimeArguments = arguments, sliceUri = sliceUri)
- }
-}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SliceUtil.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SliceUtil.kt
index f362890..ec89c7c 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SliceUtil.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SliceUtil.kt
@@ -16,23 +16,10 @@
package com.android.settingslib.spa.slice
-import android.app.Activity
-import android.app.PendingIntent
-import android.content.BroadcastReceiver
-import android.content.ComponentName
-import android.content.Context
-import android.content.Intent
import android.net.Uri
import android.os.Bundle
-import com.android.settingslib.spa.framework.common.SettingsEntry
-import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
import com.android.settingslib.spa.framework.util.KEY_DESTINATION
import com.android.settingslib.spa.framework.util.KEY_HIGHLIGHT_ENTRY
-import com.android.settingslib.spa.framework.util.SESSION_SLICE
-import com.android.settingslib.spa.framework.util.SPA_INTENT_RESERVED_KEYS
-import com.android.settingslib.spa.framework.util.appendSpaParams
-import com.android.settingslib.spa.framework.util.getDestination
-import com.android.settingslib.spa.framework.util.getEntryId
// Defines SliceUri, which contains special query parameters:
// -- KEY_DESTINATION: The route that this slice is navigated to.
@@ -45,25 +32,6 @@
return getQueryParameter(KEY_HIGHLIGHT_ENTRY)
}
-fun SliceUri.getDestination(): String? {
- return getQueryParameter(KEY_DESTINATION)
-}
-
-fun SliceUri.getRuntimeArguments(): Bundle {
- val params = Bundle()
- for (queryName in queryParameterNames) {
- if (SPA_INTENT_RESERVED_KEYS.contains(queryName)) continue
- params.putString(queryName, getQueryParameter(queryName))
- }
- return params
-}
-
-fun SliceUri.getSliceId(): String? {
- val entryId = getEntryId() ?: return null
- val params = getRuntimeArguments()
- return "${entryId}_$params"
-}
-
fun Uri.Builder.appendSpaParams(
destination: String? = null,
entryId: String? = null,
@@ -79,72 +47,3 @@
return this
}
-fun Uri.Builder.fromEntry(
- entry: SettingsEntry,
- authority: String?,
- runtimeArguments: Bundle? = null
-): Uri.Builder {
- if (authority == null) return this
- val sp = entry.containerPage()
- return scheme("content").authority(authority).appendSpaParams(
- destination = sp.buildRoute(),
- entryId = entry.id,
- runtimeArguments = runtimeArguments
- )
-}
-
-fun SliceUri.createBroadcastPendingIntent(): PendingIntent? {
- val context = SpaEnvironmentFactory.instance.appContext
- val sliceBroadcastClass =
- SpaEnvironmentFactory.instance.sliceBroadcastReceiverClass ?: return null
- val entryId = getEntryId() ?: return null
- return createBroadcastPendingIntent(context, sliceBroadcastClass, entryId)
-}
-
-fun SliceUri.createBrowsePendingIntent(): PendingIntent? {
- val context = SpaEnvironmentFactory.instance.appContext
- val browseActivityClass = SpaEnvironmentFactory.instance.browseActivityClass ?: return null
- val destination = getDestination() ?: return null
- val entryId = getEntryId()
- return createBrowsePendingIntent(context, browseActivityClass, destination, entryId)
-}
-
-fun Intent.createBrowsePendingIntent(): PendingIntent? {
- val context = SpaEnvironmentFactory.instance.appContext
- val browseActivityClass = SpaEnvironmentFactory.instance.browseActivityClass ?: return null
- val destination = getDestination() ?: return null
- val entryId = getEntryId()
- return createBrowsePendingIntent(context, browseActivityClass, destination, entryId)
-}
-
-private fun createBrowsePendingIntent(
- context: Context,
- browseActivityClass: Class<out Activity>,
- destination: String,
- entryId: String?
-): PendingIntent {
- val intent = Intent().setComponent(ComponentName(context, browseActivityClass))
- .appendSpaParams(destination, entryId, SESSION_SLICE)
- .apply {
- // Set both extra and data (which is a Uri) in Slice Intent:
- // 1) extra is used in SPA navigation framework
- // 2) data is used in Slice framework
- data = Uri.Builder().appendSpaParams(destination, entryId).build()
- flags = Intent.FLAG_ACTIVITY_NEW_TASK
- }
-
- return PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE)
-}
-
-private fun createBroadcastPendingIntent(
- context: Context,
- sliceBroadcastClass: Class<out BroadcastReceiver>,
- entryId: String
-): PendingIntent {
- val intent = Intent().setComponent(ComponentName(context, sliceBroadcastClass))
- .apply { data = Uri.Builder().appendSpaParams(entryId = entryId).build() }
- return PendingIntent.getBroadcast(
- context, 0 /* requestCode */, intent,
- PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_MUTABLE
- )
-}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SpaSliceBroadcastReceiver.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SpaSliceBroadcastReceiver.kt
deleted file mode 100644
index 39cb431..0000000
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SpaSliceBroadcastReceiver.kt
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settingslib.spa.slice
-
-import android.content.BroadcastReceiver
-import android.content.Context
-import android.content.Intent
-import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
-
-class SpaSliceBroadcastReceiver : BroadcastReceiver() {
- override fun onReceive(context: Context?, intent: Intent?) {
- val sliceRepository by SpaEnvironmentFactory.instance.sliceDataRepository
- val sliceUri = intent?.data ?: return
- val sliceData = sliceRepository.getActiveSliceData(sliceUri) ?: return
- sliceData.doAction()
- }
-}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SpaSliceProvider.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SpaSliceProvider.kt
deleted file mode 100644
index 3496f02..0000000
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SpaSliceProvider.kt
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settingslib.spa.slice
-
-import android.net.Uri
-import android.util.Log
-import androidx.lifecycle.Observer
-import androidx.slice.Slice
-import androidx.slice.SliceProvider
-import com.android.settingslib.spa.framework.common.EntrySliceData
-import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.runBlocking
-import kotlinx.coroutines.withContext
-
-private const val TAG = "SpaSliceProvider"
-
-class SpaSliceProvider : SliceProvider(), Observer<Slice?> {
- private fun getOrPutSliceData(sliceUri: Uri): EntrySliceData? {
- if (!SpaEnvironmentFactory.isReady()) return null
- val sliceRepository by SpaEnvironmentFactory.instance.sliceDataRepository
- return sliceRepository.getOrBuildSliceData(sliceUri)
- }
-
- override fun onBindSlice(sliceUri: Uri): Slice? {
- if (context == null) return null
- Log.d(TAG, "onBindSlice: $sliceUri")
- return getOrPutSliceData(sliceUri)?.value
- }
-
- override fun onSlicePinned(sliceUri: Uri) {
- Log.d(TAG, "onSlicePinned: $sliceUri")
- super.onSlicePinned(sliceUri)
- val sliceLiveData = getOrPutSliceData(sliceUri) ?: return
- runBlocking {
- withContext(Dispatchers.Main) {
- sliceLiveData.observeForever(this@SpaSliceProvider)
- }
- }
- }
-
- override fun onSliceUnpinned(sliceUri: Uri) {
- Log.d(TAG, "onSliceUnpinned: $sliceUri")
- super.onSliceUnpinned(sliceUri)
- val sliceLiveData = getOrPutSliceData(sliceUri) ?: return
- runBlocking {
- withContext(Dispatchers.Main) {
- sliceLiveData.removeObserver(this@SpaSliceProvider)
- }
- }
- }
-
- override fun onChanged(value: Slice?) {
- val uri = value?.uri ?: return
- Log.d(TAG, "onChanged: $uri")
- context?.contentResolver?.notifyChange(uri, null)
- }
-
- override fun onCreateSliceProvider(): Boolean {
- Log.d(TAG, "onCreateSliceProvider")
- return true
- }
-}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/presenter/Demo.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/presenter/Demo.kt
deleted file mode 100644
index 007f47b..0000000
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/presenter/Demo.kt
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settingslib.spa.slice.presenter
-
-import android.net.Uri
-import androidx.compose.material3.HorizontalDivider
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.remember
-import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.viewinterop.AndroidView
-import androidx.lifecycle.compose.LocalLifecycleOwner
-import androidx.slice.widget.SliceLiveData
-import androidx.slice.widget.SliceView
-
-@Composable
-fun SliceDemo(sliceUri: Uri) {
- val context = LocalContext.current
- val lifecycleOwner = LocalLifecycleOwner.current
- val sliceData = remember {
- SliceLiveData.fromUri(context, sliceUri)
- }
-
- HorizontalDivider()
- AndroidView(
- factory = { localContext ->
- val view = SliceView(localContext)
- view.setShowTitleItems(true)
- view.isScrollable = false
- view
- },
- update = { view -> sliceData.observe(lifecycleOwner, view) }
- )
-}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/provider/Demo.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/provider/Demo.kt
deleted file mode 100644
index e4a7386..0000000
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/provider/Demo.kt
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settingslib.spa.slice.provider
-
-import android.app.PendingIntent
-import android.content.Context
-import android.net.Uri
-import androidx.core.R
-import androidx.core.graphics.drawable.IconCompat
-import androidx.slice.Slice
-import androidx.slice.SliceManager
-import androidx.slice.builders.ListBuilder
-import androidx.slice.builders.SliceAction
-import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
-import com.android.settingslib.spa.slice.createBroadcastPendingIntent
-import com.android.settingslib.spa.slice.createBrowsePendingIntent
-
-fun createDemoBrowseSlice(sliceUri: Uri, title: String, summary: String): Slice? {
- val intent = sliceUri.createBrowsePendingIntent() ?: return null
- return createDemoSlice(sliceUri, title, summary, intent)
-}
-
-fun createDemoActionSlice(sliceUri: Uri, title: String, summary: String): Slice? {
- val intent = sliceUri.createBroadcastPendingIntent() ?: return null
- return createDemoSlice(sliceUri, title, summary, intent)
-}
-
-fun createDemoSlice(sliceUri: Uri, title: String, summary: String, intent: PendingIntent): Slice? {
- val context = SpaEnvironmentFactory.instance.appContext
- if (!SliceManager.getInstance(context).pinnedSlices.contains(sliceUri)) return null
- return ListBuilder(context, sliceUri, ListBuilder.INFINITY)
- .addRow(ListBuilder.RowBuilder().apply {
- setPrimaryAction(createSliceAction(context, intent))
- setTitle(title)
- setSubtitle(summary)
- }).build()
-}
-
-private fun createSliceAction(context: Context, intent: PendingIntent): SliceAction {
- return SliceAction.create(
- intent,
- IconCompat.createWithResource(context, R.drawable.notification_action_background),
- ListBuilder.ICON_IMAGE,
- "Enter app"
- )
-}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SettingsSliceDataRepositoryTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SettingsSliceDataRepositoryTest.kt
deleted file mode 100644
index 341a4a5..0000000
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SettingsSliceDataRepositoryTest.kt
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settingslib.spa.slice
-
-import android.content.Context
-import android.net.Uri
-import androidx.arch.core.executor.testing.InstantTaskExecutorRule
-import androidx.lifecycle.Observer
-import androidx.slice.Slice
-import androidx.test.core.app.ApplicationProvider
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
-import com.android.settingslib.spa.framework.common.createSettingsPage
-import com.android.settingslib.spa.framework.util.genEntryId
-import com.android.settingslib.spa.tests.testutils.SpaEnvironmentForTest
-import com.android.settingslib.spa.tests.testutils.SppHome
-import com.android.settingslib.spa.tests.testutils.SppLayer2
-import com.google.common.truth.Truth.assertThat
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@RunWith(AndroidJUnit4::class)
-class SettingsSliceDataRepositoryTest {
- @get:Rule val instantTaskExecutorRule = InstantTaskExecutorRule()
-
- private val context: Context = ApplicationProvider.getApplicationContext()
- private val spaEnvironment =
- SpaEnvironmentForTest(context, listOf(SppHome.createSettingsPage()))
- private val sliceDataRepository by spaEnvironment.sliceDataRepository
-
- @Test
- fun getOrBuildSliceDataTest() {
- SpaEnvironmentFactory.reset(spaEnvironment)
-
- // Slice empty
- assertThat(sliceDataRepository.getOrBuildSliceData(Uri.EMPTY)).isNull()
-
- // Slice supported
- val page = SppLayer2.createSettingsPage()
- val entryId = genEntryId("Layer2Entry1", page)
- val sliceUri = Uri.Builder().appendSpaParams(page.buildRoute(), entryId).build()
- assertThat(sliceUri.getDestination()).isEqualTo("SppLayer2")
- assertThat(sliceUri.getSliceId()).isEqualTo("${entryId}_Bundle[{}]")
- val sliceData = sliceDataRepository.getOrBuildSliceData(sliceUri)
- assertThat(sliceData).isNotNull()
- assertThat(sliceDataRepository.getOrBuildSliceData(sliceUri)).isSameInstanceAs(sliceData)
-
- // Slice unsupported
- val entryId2 = genEntryId("Layer2Entry2", page)
- val sliceUri2 = Uri.Builder().appendSpaParams(page.buildRoute(), entryId2).build()
- assertThat(sliceUri2.getDestination()).isEqualTo("SppLayer2")
- assertThat(sliceUri2.getSliceId()).isEqualTo("${entryId2}_Bundle[{}]")
- assertThat(sliceDataRepository.getOrBuildSliceData(sliceUri2)).isNull()
- }
-
- @Test
- fun getActiveSliceDataTest() {
- SpaEnvironmentFactory.reset(spaEnvironment)
-
- val page = SppLayer2.createSettingsPage()
- val entryId = genEntryId("Layer2Entry1", page)
- val sliceUri = Uri.Builder().appendSpaParams(page.buildRoute(), entryId).build()
-
- // build slice data first
- val sliceData = sliceDataRepository.getOrBuildSliceData(sliceUri)
-
- // slice data is inactive
- assertThat(sliceData!!.isActive()).isFalse()
- assertThat(sliceDataRepository.getActiveSliceData(sliceUri)).isNull()
-
- // slice data is active
- val observer = Observer<Slice?> { }
- sliceData.observeForever(observer)
- assertThat(sliceData.isActive()).isTrue()
- assertThat(sliceDataRepository.getActiveSliceData(sliceUri)).isSameInstanceAs(sliceData)
-
- // slice data is inactive again
- sliceData.removeObserver(observer)
- assertThat(sliceData.isActive()).isFalse()
- assertThat(sliceDataRepository.getActiveSliceData(sliceUri)).isNull()
- }
-}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SliceUtilTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SliceUtilTest.kt
index d1c4e51..b489afd 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SliceUtilTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SliceUtilTest.kt
@@ -16,91 +16,27 @@
package com.android.settingslib.spa.slice
-import android.content.Context
-import android.content.Intent
import android.net.Uri
import androidx.core.os.bundleOf
-import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
-import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
-import com.android.settingslib.spa.tests.testutils.SpaEnvironmentForTest
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class SliceUtilTest {
- private val context: Context = ApplicationProvider.getApplicationContext()
- private val spaEnvironment = SpaEnvironmentForTest(context)
-
@Test
fun sliceUriTest() {
assertThat(Uri.EMPTY.getEntryId()).isNull()
- assertThat(Uri.EMPTY.getDestination()).isNull()
- assertThat(Uri.EMPTY.getRuntimeArguments().size()).isEqualTo(0)
- assertThat(Uri.EMPTY.getSliceId()).isNull()
// valid slice uri
val dest = "myRoute"
val entryId = "myEntry"
val sliceUriWithoutParams = Uri.Builder().appendSpaParams(dest, entryId).build()
assertThat(sliceUriWithoutParams.getEntryId()).isEqualTo(entryId)
- assertThat(sliceUriWithoutParams.getDestination()).isEqualTo(dest)
- assertThat(sliceUriWithoutParams.getRuntimeArguments().size()).isEqualTo(0)
- assertThat(sliceUriWithoutParams.getSliceId()).isEqualTo("${entryId}_Bundle[{}]")
val sliceUriWithParams =
Uri.Builder().appendSpaParams(dest, entryId, bundleOf("p1" to "v1")).build()
assertThat(sliceUriWithParams.getEntryId()).isEqualTo(entryId)
- assertThat(sliceUriWithParams.getDestination()).isEqualTo(dest)
- assertThat(sliceUriWithParams.getRuntimeArguments().size()).isEqualTo(1)
- assertThat(sliceUriWithParams.getSliceId()).isEqualTo("${entryId}_Bundle[{p1=v1}]")
- }
-
- @Test
- fun createBroadcastPendingIntentTest() {
- SpaEnvironmentFactory.reset(spaEnvironment)
-
- // Empty Slice Uri
- assertThat(Uri.EMPTY.createBroadcastPendingIntent()).isNull()
-
- // Valid Slice Uri
- val dest = "myRoute"
- val entryId = "myEntry"
- val sliceUriWithoutParams = Uri.Builder().appendSpaParams(dest, entryId).build()
- val pendingIntent = sliceUriWithoutParams.createBroadcastPendingIntent()
- assertThat(pendingIntent).isNotNull()
- assertThat(pendingIntent!!.isBroadcast).isTrue()
- assertThat(pendingIntent.isImmutable).isFalse()
- }
-
- @Test
- fun createBrowsePendingIntentTest() {
- SpaEnvironmentFactory.reset(spaEnvironment)
-
- // Empty Slice Uri
- assertThat(Uri.EMPTY.createBrowsePendingIntent()).isNull()
-
- // Empty Intent
- assertThat(Intent().createBrowsePendingIntent()).isNull()
-
- // Valid Slice Uri
- val dest = "myRoute"
- val entryId = "myEntry"
- val sliceUri = Uri.Builder().appendSpaParams(dest, entryId).build()
- val pendingIntent = sliceUri.createBrowsePendingIntent()
- assertThat(pendingIntent).isNotNull()
- assertThat(pendingIntent!!.isActivity).isTrue()
- assertThat(pendingIntent.isImmutable).isTrue()
-
- // Valid Intent
- val intent = Intent().apply {
- putExtra("spaActivityDestination", dest)
- putExtra("highlightEntry", entryId)
- }
- val pendingIntent2 = intent.createBrowsePendingIntent()
- assertThat(pendingIntent2).isNotNull()
- assertThat(pendingIntent2!!.isActivity).isTrue()
- assertThat(pendingIntent2.isImmutable).isTrue()
}
}
\ No newline at end of file
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/SpaEnvironmentForTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/SpaEnvironmentForTest.kt
index 22a5ca3..4f8fd79 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/SpaEnvironmentForTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/SpaEnvironmentForTest.kt
@@ -216,8 +216,6 @@
context: Context,
rootPages: List<SettingsPage> = emptyList(),
override val browseActivityClass: Class<out Activity>? = BlankActivity::class.java,
- override val sliceBroadcastReceiverClass: Class<out BroadcastReceiver>? =
- BlankSliceBroadcastReceiver::class.java,
override val logger: SpaLogger = object : SpaLogger {}
) : SpaEnvironment(context) {
diff --git a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/TileUtils.java b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/TileUtils.java
index b949cd5..ac0b9b4 100644
--- a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/TileUtils.java
+++ b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/TileUtils.java
@@ -15,6 +15,7 @@
*/
package com.android.settingslib.drawer;
+import android.annotation.TargetApi;
import android.app.ActivityManager;
import android.content.ContentResolver;
import android.content.Context;
@@ -25,7 +26,9 @@
import android.content.pm.PackageManager;
import android.content.pm.ProviderInfo;
import android.content.pm.ResolveInfo;
+import android.graphics.drawable.Icon;
import android.net.Uri;
+import android.os.Build;
import android.os.Bundle;
import android.os.RemoteException;
import android.os.UserHandle;
@@ -36,6 +39,8 @@
import android.util.Log;
import android.util.Pair;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import java.util.ArrayList;
@@ -102,6 +107,9 @@
/** The key used to get the package name of the icon resource for the preference. */
static final String EXTRA_PREFERENCE_ICON_PACKAGE = "com.android.settings.icon_package";
+ /** The key used for the raw byte data of the icon for the preference. */
+ static final String EXTRA_PREFERENCE_ICON_RAW = "com.android.settings.icon_raw";
+
/**
* Name of the meta-data item that should be set in the AndroidManifest.xml
* to specify the key that should be used for the preference.
@@ -518,6 +526,24 @@
}
/**
+ * Retrieves an icon stored in the Bundle as a Parcel with key EXTRA_PREFERENCE_ICON_RAW
+ */
+ @TargetApi(Build.VERSION_CODES.TIRAMISU)
+ @Nullable
+ public static Icon getRawIconFromUri(@NonNull Context context, @Nullable Uri uri,
+ @NonNull Map<String, IContentProvider> providerMap) {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
+ return null;
+ }
+ final Bundle bundle = getBundleFromUri(context, uri, providerMap, null /* bundle */);
+ if (bundle == null) {
+ return null;
+ }
+
+ return bundle.getParcelable(EXTRA_PREFERENCE_ICON_RAW, Icon.class);
+ }
+
+ /**
* Gets text associated with the input key from the content provider.
*
* @param context context
@@ -564,8 +590,9 @@
return getBundleFromUri(context, uri, providerMap, bundle);
}
- private static Bundle getBundleFromUri(Context context, Uri uri,
- Map<String, IContentProvider> providerMap, Bundle bundle) {
+ @Nullable
+ private static Bundle getBundleFromUri(@NonNull Context context, @Nullable Uri uri,
+ @NonNull Map<String, IContentProvider> providerMap, @Nullable Bundle bundle) {
final Pair<String, String> args = getMethodAndKey(uri);
if (args == null) {
return null;
@@ -593,8 +620,9 @@
}
}
- private static IContentProvider getProviderFromUri(Context context, Uri uri,
- Map<String, IContentProvider> providerMap) {
+ @Nullable
+ private static IContentProvider getProviderFromUri(@NonNull Context context, @Nullable Uri uri,
+ @NonNull Map<String, IContentProvider> providerMap) {
if (uri == null) {
return null;
}
@@ -609,7 +637,8 @@
}
/** Returns method and key of the complete uri. */
- private static Pair<String, String> getMethodAndKey(Uri uri) {
+ @Nullable
+ private static Pair<String, String> getMethodAndKey(@Nullable Uri uri) {
if (uri == null) {
return null;
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/FakeZenModeRepository.kt b/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/FakeZenModeRepository.kt
index 7f6a8ed..7886e85 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/FakeZenModeRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/FakeZenModeRepository.kt
@@ -18,6 +18,9 @@
import android.app.NotificationManager
import android.provider.Settings
+import com.android.settingslib.notification.modes.TestModeBuilder
+import com.android.settingslib.notification.modes.ZenMode
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
@@ -32,6 +35,11 @@
override val globalZenMode: StateFlow<Int>
get() = mutableZenMode.asStateFlow()
+ private val mutableModesFlow: MutableStateFlow<List<ZenMode>> =
+ MutableStateFlow(listOf(TestModeBuilder.EXAMPLE))
+ override val modes: Flow<List<ZenMode>>
+ get() = mutableModesFlow.asStateFlow()
+
init {
updateNotificationPolicy()
}
@@ -43,6 +51,20 @@
fun updateZenMode(zenMode: Int) {
mutableZenMode.value = zenMode
}
+
+ fun addMode(id: String, active: Boolean = false) {
+ mutableModesFlow.value += newMode(id, active)
+ }
+
+ fun removeMode(id: String) {
+ mutableModesFlow.value = mutableModesFlow.value.filter { it.id != id }
+ }
+
+ fun deactivateMode(id: String) {
+ val oldMode = mutableModesFlow.value.find { it.id == id } ?: return
+ removeMode(id)
+ mutableModesFlow.value += TestModeBuilder(oldMode).setActive(false).build()
+ }
}
fun FakeZenModeRepository.updateNotificationPolicy(
@@ -61,5 +83,8 @@
suppressedVisualEffects,
state,
priorityConversationSenders,
- )
- )
+ ))
+
+private fun newMode(id: String, active: Boolean = false): ZenMode {
+ return TestModeBuilder().setId(id).setName("Mode $id").setActive(active).build()
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/ZenModeRepository.kt b/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/ZenModeRepository.kt
index 72c3c17..26b5741 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/ZenModeRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/ZenModeRepository.kt
@@ -17,19 +17,28 @@
package com.android.settingslib.notification.data.repository
import android.app.NotificationManager
+import android.app.NotificationManager.EXTRA_NOTIFICATION_POLICY
import android.content.BroadcastReceiver
+import android.content.ContentResolver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
+import android.database.ContentObserver
import android.os.Handler
+import android.provider.Settings
import com.android.settingslib.flags.Flags
+import com.android.settingslib.notification.modes.ZenMode
+import com.android.settingslib.notification.modes.ZenModesBackend
import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
@@ -44,11 +53,16 @@
/** @see NotificationManager.getZenMode */
val globalZenMode: StateFlow<Int?>
+
+ /** A list of all existing priority modes. */
+ val modes: Flow<List<ZenMode>>
}
class ZenModeRepositoryImpl(
private val context: Context,
private val notificationManager: NotificationManager,
+ private val backend: ZenModesBackend,
+ private val contentResolver: ContentResolver,
val scope: CoroutineScope,
val backgroundCoroutineContext: CoroutineContext,
// This is nullable just to simplify testing, since SettingsLib doesn't have a good way
@@ -61,7 +75,7 @@
val receiver =
object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
- intent?.action?.let { action -> launch { send(action) } }
+ intent?.let { launch { send(it) } }
}
}
@@ -87,7 +101,6 @@
.let {
if (Flags.volumePanelBroadcastFix()) {
it.flowOn(backgroundCoroutineContext)
- .stateIn(scope, SharingStarted.WhileSubscribed(), null)
} else {
it.shareIn(
started = SharingStarted.WhileSubscribed(),
@@ -100,7 +113,9 @@
override val consolidatedNotificationPolicy: StateFlow<NotificationManager.Policy?> by lazy {
if (Flags.volumePanelBroadcastFix() && android.app.Flags.modesApi())
flowFromBroadcast(NotificationManager.ACTION_CONSOLIDATED_NOTIFICATION_POLICY_CHANGED) {
- notificationManager.consolidatedNotificationPolicy
+ // If available, get the value from extras to avoid a potential binder call.
+ it?.extras?.getParcelable(EXTRA_NOTIFICATION_POLICY)
+ ?: notificationManager.consolidatedNotificationPolicy
}
else
flowFromBroadcast(NotificationManager.ACTION_NOTIFICATION_POLICY_CHANGED) {
@@ -114,11 +129,52 @@
}
}
- private fun <T> flowFromBroadcast(intentAction: String, mapper: () -> T) =
+ private fun <T> flowFromBroadcast(intentAction: String, mapper: (Intent?) -> T) =
notificationBroadcasts
- .filter { intentAction == it }
- .map { mapper() }
- .onStart { emit(mapper()) }
+ .filter { intentAction == it.action }
+ .map { mapper(it) }
+ .onStart { emit(mapper(null)) }
.flowOn(backgroundCoroutineContext)
.stateIn(scope, SharingStarted.WhileSubscribed(), null)
+
+ private val zenConfigChanged by lazy {
+ if (android.app.Flags.modesUi()) {
+ callbackFlow {
+ // emit an initial value
+ trySend(Unit)
+
+ val observer =
+ object : ContentObserver(backgroundHandler) {
+ override fun onChange(selfChange: Boolean) {
+ trySend(Unit)
+ }
+ }
+
+ contentResolver.registerContentObserver(
+ Settings.Global.getUriFor(Settings.Global.ZEN_MODE),
+ /* notifyForDescendants= */ false,
+ observer)
+ contentResolver.registerContentObserver(
+ Settings.Global.getUriFor(Settings.Global.ZEN_MODE_CONFIG_ETAG),
+ /* notifyForDescendants= */ false,
+ observer)
+
+ awaitClose { contentResolver.unregisterContentObserver(observer) }
+ }
+ .flowOn(backgroundCoroutineContext)
+ } else {
+ flowOf(Unit)
+ }
+ }
+
+ override val modes: Flow<List<ZenMode>> by lazy {
+ if (android.app.Flags.modesUi()) {
+ zenConfigChanged
+ .map { backend.modes }
+ .distinctUntilChanged()
+ .flowOn(backgroundCoroutineContext)
+ } else {
+ flowOf(emptyList())
+ }
+ }
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/modes/TestModeBuilder.java b/packages/SettingsLib/src/com/android/settingslib/notification/modes/TestModeBuilder.java
new file mode 100644
index 0000000..7b994d5
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/modes/TestModeBuilder.java
@@ -0,0 +1,176 @@
+/*
+ * 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.settingslib.notification.modes;
+
+import android.app.AutomaticZenRule;
+import android.app.NotificationManager;
+import android.content.ComponentName;
+import android.net.Uri;
+import android.service.notification.Condition;
+import android.service.notification.ZenDeviceEffects;
+import android.service.notification.ZenModeConfig;
+import android.service.notification.ZenPolicy;
+
+import androidx.annotation.DrawableRes;
+import androidx.annotation.Nullable;
+
+import java.util.Random;
+
+public class TestModeBuilder {
+
+ private String mId;
+ private AutomaticZenRule mRule;
+ private ZenModeConfig.ZenRule mConfigZenRule;
+
+ public static final ZenMode EXAMPLE = new TestModeBuilder().build();
+
+ public TestModeBuilder() {
+ // Reasonable defaults
+ int id = new Random().nextInt(1000);
+ mId = "rule_" + id;
+ mRule = new AutomaticZenRule.Builder("Test Rule #" + id, Uri.parse("rule://" + id))
+ .setPackage("some_package")
+ .setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_PRIORITY)
+ .setZenPolicy(new ZenPolicy.Builder().disallowAllSounds().build())
+ .build();
+ mConfigZenRule = new ZenModeConfig.ZenRule();
+ mConfigZenRule.enabled = true;
+ mConfigZenRule.pkg = "some_package";
+ }
+
+ public TestModeBuilder(ZenMode previous) {
+ mId = previous.getId();
+ mRule = previous.getRule();
+
+ mConfigZenRule = new ZenModeConfig.ZenRule();
+ mConfigZenRule.enabled = previous.getRule().isEnabled();
+ mConfigZenRule.pkg = previous.getRule().getPackageName();
+ setActive(previous.isActive());
+ }
+
+ public TestModeBuilder setId(String id) {
+ mId = id;
+ return this;
+ }
+
+ public TestModeBuilder setAzr(AutomaticZenRule rule) {
+ mRule = rule;
+ mConfigZenRule.pkg = rule.getPackageName();
+ mConfigZenRule.conditionId = rule.getConditionId();
+ mConfigZenRule.enabled = rule.isEnabled();
+ return this;
+ }
+
+ public TestModeBuilder setConfigZenRule(ZenModeConfig.ZenRule configZenRule) {
+ mConfigZenRule = configZenRule;
+ return this;
+ }
+
+ public TestModeBuilder setName(String name) {
+ mRule.setName(name);
+ mConfigZenRule.name = name;
+ return this;
+ }
+
+ public TestModeBuilder setPackage(String pkg) {
+ mRule.setPackageName(pkg);
+ mConfigZenRule.pkg = pkg;
+ return this;
+ }
+
+ public TestModeBuilder setOwner(ComponentName owner) {
+ mRule.setOwner(owner);
+ mConfigZenRule.component = owner;
+ return this;
+ }
+
+ public TestModeBuilder setConfigurationActivity(ComponentName configActivity) {
+ mRule.setConfigurationActivity(configActivity);
+ mConfigZenRule.configurationActivity = configActivity;
+ return this;
+ }
+
+ public TestModeBuilder setConditionId(Uri conditionId) {
+ mRule.setConditionId(conditionId);
+ mConfigZenRule.conditionId = conditionId;
+ return this;
+ }
+
+ public TestModeBuilder setType(@AutomaticZenRule.Type int type) {
+ mRule.setType(type);
+ mConfigZenRule.type = type;
+ return this;
+ }
+
+ public TestModeBuilder setInterruptionFilter(
+ @NotificationManager.InterruptionFilter int interruptionFilter) {
+ mRule.setInterruptionFilter(interruptionFilter);
+ mConfigZenRule.zenMode = NotificationManager.zenModeFromInterruptionFilter(
+ interruptionFilter, NotificationManager.INTERRUPTION_FILTER_PRIORITY);
+ return this;
+ }
+
+ public TestModeBuilder setZenPolicy(@Nullable ZenPolicy policy) {
+ mRule.setZenPolicy(policy);
+ mConfigZenRule.zenPolicy = policy;
+ return this;
+ }
+
+ public TestModeBuilder setDeviceEffects(@Nullable ZenDeviceEffects deviceEffects) {
+ mRule.setDeviceEffects(deviceEffects);
+ mConfigZenRule.zenDeviceEffects = deviceEffects;
+ return this;
+ }
+
+ public TestModeBuilder setEnabled(boolean enabled) {
+ mRule.setEnabled(enabled);
+ mConfigZenRule.enabled = enabled;
+ return this;
+ }
+
+ public TestModeBuilder setManualInvocationAllowed(boolean allowed) {
+ mRule.setManualInvocationAllowed(allowed);
+ mConfigZenRule.allowManualInvocation = allowed;
+ return this;
+ }
+
+ public TestModeBuilder setTriggerDescription(@Nullable String triggerDescription) {
+ mRule.setTriggerDescription(triggerDescription);
+ mConfigZenRule.triggerDescription = triggerDescription;
+ return this;
+ }
+
+ public TestModeBuilder setIconResId(@DrawableRes int iconResId) {
+ mRule.setIconResId(iconResId);
+ return this;
+ }
+
+ public TestModeBuilder setActive(boolean active) {
+ if (active) {
+ mConfigZenRule.enabled = true;
+ mConfigZenRule.condition = new Condition(mRule.getConditionId(), "...",
+ Condition.STATE_TRUE);
+ } else {
+ mConfigZenRule.condition = null;
+ }
+ return this;
+ }
+
+ public ZenMode build() {
+ return new ZenMode(mId, mRule, mConfigZenRule);
+ }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/data/repository/ZenModeRepositoryTest.kt b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/data/repository/ZenModeRepositoryTest.kt
index 06333b61..4bd5cc4 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/data/repository/ZenModeRepositoryTest.kt
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/data/repository/ZenModeRepositoryTest.kt
@@ -18,13 +18,19 @@
import android.app.NotificationManager
import android.content.BroadcastReceiver
+import android.content.ContentResolver
import android.content.Context
import android.content.Intent
+import android.database.ContentObserver
+import android.os.Parcelable
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.provider.Settings.Global
import androidx.test.filters.SmallTest
import com.android.settingslib.flags.Flags
+import com.android.settingslib.notification.modes.TestModeBuilder
+import com.android.settingslib.notification.modes.ZenMode
+import com.android.settingslib.notification.modes.ZenModesBackend
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.launchIn
@@ -36,6 +42,7 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.eq
import org.mockito.Captor
import org.mockito.Mock
import org.mockito.Mockito.any
@@ -52,8 +59,16 @@
@Mock private lateinit var notificationManager: NotificationManager
+ @Mock private lateinit var zenModesBackend: ZenModesBackend
+
+ @Mock private lateinit var contentResolver: ContentResolver
+
@Captor private lateinit var receiverCaptor: ArgumentCaptor<BroadcastReceiver>
+ @Captor private lateinit var zenModeObserverCaptor: ArgumentCaptor<ContentObserver>
+
+ @Captor private lateinit var zenConfigObserverCaptor: ArgumentCaptor<ContentObserver>
+
private lateinit var underTest: ZenModeRepository
private val testScope: TestScope = TestScope()
@@ -66,6 +81,8 @@
ZenModeRepositoryImpl(
context,
notificationManager,
+ zenModesBackend,
+ contentResolver,
testScope.backgroundScope,
testScope.testScheduler,
backgroundHandler = null,
@@ -110,6 +127,26 @@
}
}
+ @EnableFlags(android.app.Flags.FLAG_MODES_API, Flags.FLAG_VOLUME_PANEL_BROADCAST_FIX)
+ @Test
+ fun consolidatedPolicyChanges_repositoryEmitsFromExtras() {
+ testScope.runTest {
+ val values = mutableListOf<NotificationManager.Policy?>()
+ `when`(notificationManager.consolidatedNotificationPolicy).thenReturn(testPolicy1)
+ underTest.consolidatedNotificationPolicy
+ .onEach { values.add(it) }
+ .launchIn(backgroundScope)
+ runCurrent()
+
+ triggerIntent(
+ NotificationManager.ACTION_CONSOLIDATED_NOTIFICATION_POLICY_CHANGED,
+ extras = mapOf(NotificationManager.EXTRA_NOTIFICATION_POLICY to testPolicy2))
+ runCurrent()
+
+ assertThat(values).containsExactly(null, testPolicy1, testPolicy2).inOrder()
+ }
+ }
+
@Test
fun zenModeChanges_repositoryEmits() {
testScope.runTest {
@@ -128,9 +165,63 @@
}
}
- private fun triggerIntent(action: String) {
+ @EnableFlags(android.app.Flags.FLAG_MODES_UI)
+ @Test
+ fun modesListEmitsOnSettingsChange() {
+ testScope.runTest {
+ val values = mutableListOf<List<ZenMode>>()
+ val modes1 = listOf(TestModeBuilder().setId("One").build())
+ `when`(zenModesBackend.modes).thenReturn(modes1)
+ underTest.modes.onEach { values.add(it) }.launchIn(backgroundScope)
+ runCurrent()
+
+ // zen mode change triggers update
+ val modes2 = listOf(TestModeBuilder().setId("Two").build())
+ `when`(zenModesBackend.modes).thenReturn(modes2)
+ triggerZenModeSettingUpdate()
+ runCurrent()
+
+ // zen config change also triggers update
+ val modes3 = listOf(TestModeBuilder().setId("Three").build())
+ `when`(zenModesBackend.modes).thenReturn(modes3)
+ triggerZenConfigSettingUpdate()
+ runCurrent()
+
+ // setting update with no list change doesn't trigger update
+ triggerZenModeSettingUpdate()
+ runCurrent()
+
+ assertThat(values).containsExactly(modes1, modes2, modes3).inOrder()
+ }
+ }
+
+ private fun triggerIntent(action: String, extras: Map<String, Parcelable>? = null) {
verify(context).registerReceiver(receiverCaptor.capture(), any(), any(), any())
- receiverCaptor.value.onReceive(context, Intent(action))
+ val intent = Intent(action)
+ if (extras?.isNotEmpty() == true) {
+ extras.forEach { (key, value) -> intent.putExtra(key, value) }
+ }
+ receiverCaptor.value.onReceive(context, intent)
+ }
+
+ private fun triggerZenModeSettingUpdate() {
+ verify(contentResolver)
+ .registerContentObserver(
+ eq(Global.getUriFor(Global.ZEN_MODE)),
+ eq(false),
+ zenModeObserverCaptor.capture(),
+ )
+ zenModeObserverCaptor.value.onChange(false)
+ }
+
+ private fun triggerZenConfigSettingUpdate() {
+ verify(contentResolver)
+ .registerContentObserver(
+ eq(Global.getUriFor(Global.ZEN_MODE_CONFIG_ETAG)),
+ eq(false),
+ zenConfigObserverCaptor.capture(),
+ )
+ zenConfigObserverCaptor.value.onChange(false)
}
private companion object {
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java b/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java
index 2227943..8b0772b 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java
@@ -42,6 +42,7 @@
import android.provider.UpdatableDeviceConfigServiceReadiness;
import android.util.Slog;
+import com.android.internal.pm.pkg.component.AconfigFlags;
import com.android.internal.util.FastPrintWriter;
import java.io.File;
@@ -416,7 +417,13 @@
DeviceConfig.setProperty(namespace, key, value, makeDefault);
break;
case OVERRIDE:
- DeviceConfig.setLocalOverride(namespace, key, value);
+ AconfigFlags.Permission permission =
+ (new AconfigFlags()).getFlagPermission(key);
+ if (permission == AconfigFlags.Permission.READ_ONLY) {
+ pout.println("cannot override read-only flag " + key);
+ } else {
+ DeviceConfig.setLocalOverride(namespace, key, value);
+ }
break;
case CLEAR_OVERRIDE:
DeviceConfig.clearLocalOverride(namespace, key);
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index db71d72..1871873 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -581,7 +581,6 @@
"androidx.activity_activity-compose",
"androidx.compose.animation_animation-graphics",
"androidx.lifecycle_lifecycle-viewmodel-compose",
- "device_policy_aconfig_flags_lib",
],
libs: [
"keepanno-annotations",
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 6e6e4b2..71f5511 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -1171,3 +1171,19 @@
bug: "345227709"
}
+flag {
+ namespace: "systemui"
+ name: "register_content_observers_async"
+ description: "Use new Async API to register content observers"
+ bug: "316922634"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
+ name: "msdl_feedback"
+ namespace: "systemui"
+ description: "Enables MSDL feedback in SysUI surfaces."
+ bug: "352600066"
+}
\ No newline at end of file
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 f6535ec0..bd4710b 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
@@ -40,6 +40,7 @@
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.focusable
+import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope
@@ -64,6 +65,7 @@
import androidx.compose.foundation.lazy.grid.LazyGridState
import androidx.compose.foundation.lazy.grid.LazyHorizontalGrid
import androidx.compose.foundation.lazy.grid.rememberLazyGridState
+import androidx.compose.foundation.selection.selectable
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
@@ -94,6 +96,7 @@
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
+import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
@@ -171,7 +174,11 @@
var removeButtonCoordinates: LayoutCoordinates? by remember { mutableStateOf(null) }
var toolbarSize: IntSize? by remember { mutableStateOf(null) }
var gridCoordinates: LayoutCoordinates? by remember { mutableStateOf(null) }
- val gridState = rememberLazyGridState()
+
+ val gridState =
+ rememberLazyGridState(viewModel.savedFirstScrollIndex, viewModel.savedFirstScrollOffset)
+ viewModel.clearPersistedScrollPosition()
+
val contentListState = rememberContentListState(widgetConfigurator, communalContent, viewModel)
val reorderingWidgets by viewModel.reorderingWidgets.collectAsStateWithLifecycle()
val selectedKey = viewModel.selectedKey.collectAsStateWithLifecycle()
@@ -187,6 +194,8 @@
val contentPadding = gridContentPadding(viewModel.isEditMode, toolbarSize)
val contentOffset = beforeContentPadding(contentPadding).toOffset()
+ ObserveScrollEffect(gridState, viewModel)
+
if (!viewModel.isEditMode) {
ScrollOnUpdatedLiveContentEffect(communalContent, gridState)
}
@@ -420,6 +429,20 @@
}
}
+@Composable
+private fun ObserveScrollEffect(
+ gridState: LazyGridState,
+ communalViewModel: BaseCommunalViewModel
+) {
+
+ LaunchedEffect(gridState) {
+ snapshotFlow {
+ Pair(gridState.firstVisibleItemIndex, gridState.firstVisibleItemScrollOffset)
+ }
+ .collect { communalViewModel.onScrollPositionUpdated(it.first, it.second) }
+ }
+}
+
/**
* Observes communal content and scrolls to any added or updated live content, e.g. a new media
* session is started, or a paused timer is resumed.
@@ -942,9 +965,25 @@
val selectedKey by viewModel.selectedKey.collectAsStateWithLifecycle()
val selectedIndex =
selectedKey?.let { key -> contentListState.list.indexOfFirst { it.key == key } }
+
+ val isSelected = selectedKey == model.key
+
+ val selectableModifier =
+ if (viewModel.isEditMode) {
+ Modifier.selectable(
+ selected = isSelected,
+ onClick = { viewModel.setSelectedKey(model.key) },
+ interactionSource = remember { MutableInteractionSource() },
+ indication = null,
+ )
+ } else {
+ Modifier
+ }
+
Box(
modifier =
modifier
+ .then(selectableModifier)
.thenIf(!viewModel.isEditMode && model.inQuietMode) {
Modifier.pointerInput(Unit) {
// consume tap to prevent the child view from triggering interactions with
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/section/CommunalPopupSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/section/CommunalPopupSection.kt
index 1ea73e1..620892a 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/section/CommunalPopupSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/section/CommunalPopupSection.kt
@@ -23,6 +23,8 @@
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.foundation.background
+import androidx.compose.foundation.focusable
+import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
@@ -39,11 +41,16 @@
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.focus.FocusRequester
+import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.graphics.TransformOrigin
import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp
@@ -54,6 +61,7 @@
import com.android.systemui.communal.ui.viewmodel.PopupType
import com.android.systemui.res.R
import javax.inject.Inject
+import kotlinx.coroutines.delay
class CommunalPopupSection
@Inject
@@ -91,6 +99,17 @@
onClick: () -> Unit,
onDismissRequest: () -> Unit,
) {
+ val interactionSource = remember { MutableInteractionSource() }
+ val focusRequester = remember { FocusRequester() }
+
+ val context = LocalContext.current
+
+ LaunchedEffect(Unit) {
+ // Adding a delay to ensure the animation completes before requesting focus
+ delay(250)
+ focusRequester.requestFocus()
+ }
+
Popup(
alignment = Alignment.TopCenter,
offset = IntOffset(0, 40),
@@ -100,6 +119,8 @@
Button(
modifier =
Modifier.height(56.dp)
+ .focusRequester(focusRequester)
+ .focusable(interactionSource = interactionSource)
.graphicsLayer { transformOrigin = TransformOrigin(0f, 0f) }
.animateEnterExit(
enter =
@@ -142,8 +163,7 @@
) {
Icon(
imageVector = Icons.Outlined.Widgets,
- contentDescription =
- stringResource(R.string.button_to_configure_widgets_text),
+ contentDescription = null,
tint = colors.onSecondary,
modifier = Modifier.size(20.dp)
)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
index a1f2042..859c036 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
@@ -90,10 +90,16 @@
*/
@Composable
fun SceneScope.Notifications(burnInParams: BurnInParameters?, modifier: Modifier = Modifier) {
+ val areNotificationsVisible by
+ lockscreenContentViewModel
+ .areNotificationsVisible(sceneKey)
+ .collectAsStateWithLifecycle(initialValue = false)
+ if (!areNotificationsVisible) {
+ return
+ }
+
val isShadeLayoutWide by
lockscreenContentViewModel.isShadeLayoutWide.collectAsStateWithLifecycle()
- val areNotificationsVisible by
- lockscreenContentViewModel.areNotificationsVisible.collectAsStateWithLifecycle()
val splitShadeTopMargin: Dp =
if (Flags.centralizedStatusBarHeightFix()) {
LargeScreenHeaderHelper.getLargeScreenHeaderHeight(LocalContext.current).dp
@@ -101,10 +107,6 @@
dimensionResource(id = R.dimen.large_screen_shade_header_height)
}
- if (!areNotificationsVisible) {
- return
- }
-
ConstrainedNotificationStack(
stackScrollView = stackScrollView.get(),
viewModel = viewModel,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
index 12ca997..a184cf3 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
@@ -337,7 +337,7 @@
// expanded, reset scrim offset.
LaunchedEffect(stackHeight, scrimOffset) {
snapshotFlow { stackHeight.intValue < minVisibleScrimHeight() && scrimOffset.value < 0f }
- .collect { shouldCollapse -> if (shouldCollapse) scrimOffset.snapTo(0f) }
+ .collect { shouldCollapse -> if (shouldCollapse) scrimOffset.animateTo(0f, tween()) }
}
// if we receive scroll delta from NSSL, offset the scrim and placeholder accordingly.
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
index d95b388..20b1303 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
@@ -184,31 +184,33 @@
): Swipes {
val fromSource =
startedPosition?.let { position ->
- layoutImpl.swipeSourceDetector.source(
- fromScene.targetSize,
- position.round(),
- layoutImpl.density,
- orientation,
- )
+ layoutImpl.swipeSourceDetector
+ .source(
+ fromScene.targetSize,
+ position.round(),
+ layoutImpl.density,
+ orientation,
+ )
+ ?.resolve(layoutImpl.layoutDirection)
}
val upOrLeft =
- Swipe(
+ Swipe.Resolved(
direction =
when (orientation) {
- Orientation.Horizontal -> SwipeDirection.Left
- Orientation.Vertical -> SwipeDirection.Up
+ Orientation.Horizontal -> SwipeDirection.Resolved.Left
+ Orientation.Vertical -> SwipeDirection.Resolved.Up
},
pointerCount = pointersDown,
fromSource = fromSource,
)
val downOrRight =
- Swipe(
+ Swipe.Resolved(
direction =
when (orientation) {
- Orientation.Horizontal -> SwipeDirection.Right
- Orientation.Vertical -> SwipeDirection.Down
+ Orientation.Horizontal -> SwipeDirection.Resolved.Right
+ Orientation.Vertical -> SwipeDirection.Resolved.Down
},
pointerCount = pointersDown,
fromSource = fromSource,
@@ -833,10 +835,10 @@
/** The [Swipe] associated to a given fromScene, startedPosition and pointersDown. */
private class Swipes(
- val upOrLeft: Swipe?,
- val downOrRight: Swipe?,
- val upOrLeftNoSource: Swipe?,
- val downOrRightNoSource: Swipe?,
+ val upOrLeft: Swipe.Resolved?,
+ val downOrRight: Swipe.Resolved?,
+ val upOrLeftNoSource: Swipe.Resolved?,
+ val downOrRightNoSource: Swipe.Resolved?,
) {
/** The [UserActionResult] associated to up and down swipes. */
var upOrLeftResult: UserActionResult? = null
@@ -844,7 +846,7 @@
fun computeSwipesResults(fromScene: Scene): Pair<UserActionResult?, UserActionResult?> {
val userActions = fromScene.userActions
- fun result(swipe: Swipe?): UserActionResult? {
+ fun result(swipe: Swipe.Resolved?): UserActionResult? {
return userActions[swipe ?: return null]
}
@@ -940,25 +942,27 @@
when {
amount < 0f -> {
val actionUpOrLeft =
- Swipe(
+ Swipe.Resolved(
direction =
when (orientation) {
- Orientation.Horizontal -> SwipeDirection.Left
- Orientation.Vertical -> SwipeDirection.Up
+ Orientation.Horizontal -> SwipeDirection.Resolved.Left
+ Orientation.Vertical -> SwipeDirection.Resolved.Up
},
pointerCount = pointersInfo().pointersDown,
+ fromSource = null,
)
fromScene.userActions[actionUpOrLeft]
}
amount > 0f -> {
val actionDownOrRight =
- Swipe(
+ Swipe.Resolved(
direction =
when (orientation) {
- Orientation.Horizontal -> SwipeDirection.Right
- Orientation.Vertical -> SwipeDirection.Down
+ Orientation.Horizontal -> SwipeDirection.Resolved.Right
+ Orientation.Vertical -> SwipeDirection.Resolved.Down
},
pointerCount = pointersInfo().pointersDown,
+ fromSource = null,
)
fromScene.userActions[actionDownOrRight]
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/EdgeDetector.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/EdgeDetector.kt
index b0dc3a1..97c0cef 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/EdgeDetector.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/EdgeDetector.kt
@@ -21,14 +21,28 @@
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
/** The edge of a [SceneTransitionLayout]. */
-enum class Edge : SwipeSource {
- Left,
- Right,
- Top,
- Bottom,
+enum class Edge(private val resolveEdge: (LayoutDirection) -> Resolved) : SwipeSource {
+ Top(resolveEdge = { Resolved.Top }),
+ Bottom(resolveEdge = { Resolved.Bottom }),
+ Left(resolveEdge = { Resolved.Left }),
+ Right(resolveEdge = { Resolved.Right }),
+ Start(resolveEdge = { if (it == LayoutDirection.Ltr) Resolved.Left else Resolved.Right }),
+ End(resolveEdge = { if (it == LayoutDirection.Ltr) Resolved.Right else Resolved.Left });
+
+ override fun resolve(layoutDirection: LayoutDirection): Resolved {
+ return resolveEdge(layoutDirection)
+ }
+
+ enum class Resolved : SwipeSource.Resolved {
+ Left,
+ Right,
+ Top,
+ Bottom,
+ }
}
val DefaultEdgeDetector = FixedSizeEdgeDetector(40.dp)
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt
index 936f4ba..a49f1af 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt
@@ -37,7 +37,7 @@
val key: SceneKey,
layoutImpl: SceneTransitionLayoutImpl,
content: @Composable SceneScope.() -> Unit,
- actions: Map<UserAction, UserActionResult>,
+ actions: Map<UserAction.Resolved, UserActionResult>,
zIndex: Float,
) {
internal val scope = SceneScopeImpl(layoutImpl, this)
@@ -54,8 +54,8 @@
}
private fun checkValid(
- userActions: Map<UserAction, UserActionResult>
- ): Map<UserAction, UserActionResult> {
+ userActions: Map<UserAction.Resolved, UserActionResult>
+ ): Map<UserAction.Resolved, UserActionResult> {
userActions.forEach { (action, result) ->
if (key == result.toScene) {
error(
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
index 45758c5..0c467b1 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
@@ -28,10 +28,13 @@
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.LayoutDirection
+import com.android.compose.animation.scene.UserAction.Resolved
/**
* [SceneTransitionLayout] is a container that automatically animates its content whenever its state
@@ -344,34 +347,71 @@
@Stable @ElementDsl interface MovableElementContentScope : BaseSceneScope, ElementBoxScope
/** An action performed by the user. */
-sealed interface UserAction {
+sealed class UserAction {
infix fun to(scene: SceneKey): Pair<UserAction, UserActionResult> {
return this to UserActionResult(toScene = scene)
}
+
+ /** Resolve this into a [Resolved] user action given [layoutDirection]. */
+ internal abstract fun resolve(layoutDirection: LayoutDirection): Resolved
+
+ /** A resolved [UserAction] that does not depend on the layout direction. */
+ internal sealed class Resolved
}
/** The user navigated back, either using a gesture or by triggering a KEYCODE_BACK event. */
-data object Back : UserAction
+data object Back : UserAction() {
+ override fun resolve(layoutDirection: LayoutDirection): Resolved = Resolved
+
+ internal object Resolved : UserAction.Resolved()
+}
/** The user swiped on the container. */
data class Swipe(
val direction: SwipeDirection,
val pointerCount: Int = 1,
val fromSource: SwipeSource? = null,
-) : UserAction {
+) : UserAction() {
companion object {
val Left = Swipe(SwipeDirection.Left)
val Up = Swipe(SwipeDirection.Up)
val Right = Swipe(SwipeDirection.Right)
val Down = Swipe(SwipeDirection.Down)
+ val Start = Swipe(SwipeDirection.Start)
+ val End = Swipe(SwipeDirection.End)
}
+
+ override fun resolve(layoutDirection: LayoutDirection): UserAction.Resolved {
+ return Resolved(
+ direction = direction.resolve(layoutDirection),
+ pointerCount = pointerCount,
+ fromSource = fromSource?.resolve(layoutDirection),
+ )
+ }
+
+ /** A resolved [Swipe] that does not depend on the layout direction. */
+ internal data class Resolved(
+ val direction: SwipeDirection.Resolved,
+ val pointerCount: Int,
+ val fromSource: SwipeSource.Resolved?,
+ ) : UserAction.Resolved()
}
-enum class SwipeDirection(val orientation: Orientation) {
- Up(Orientation.Vertical),
- Down(Orientation.Vertical),
- Left(Orientation.Horizontal),
- Right(Orientation.Horizontal),
+enum class SwipeDirection(internal val resolve: (LayoutDirection) -> Resolved) {
+ Up(resolve = { Resolved.Up }),
+ Down(resolve = { Resolved.Down }),
+ Left(resolve = { Resolved.Left }),
+ Right(resolve = { Resolved.Right }),
+ Start(resolve = { if (it == LayoutDirection.Ltr) Resolved.Left else Resolved.Right }),
+ End(resolve = { if (it == LayoutDirection.Ltr) Resolved.Right else Resolved.Left });
+
+ /** A resolved [SwipeDirection] that does not depend on the layout direction. */
+ internal enum class Resolved(val orientation: Orientation) {
+ Up(Orientation.Vertical),
+ Down(Orientation.Vertical),
+ Left(Orientation.Horizontal),
+ Right(Orientation.Horizontal),
+ }
}
/**
@@ -386,6 +426,16 @@
override fun equals(other: Any?): Boolean
override fun hashCode(): Int
+
+ /** Resolve this into a [Resolved] swipe source given [layoutDirection]. */
+ fun resolve(layoutDirection: LayoutDirection): Resolved
+
+ /** A resolved [SwipeSource] that does not depend on the layout direction. */
+ interface Resolved {
+ override fun equals(other: Any?): Boolean
+
+ override fun hashCode(): Int
+ }
}
interface SwipeSourceDetector {
@@ -460,11 +510,13 @@
scenes: SceneTransitionLayoutScope.() -> Unit,
) {
val density = LocalDensity.current
+ val layoutDirection = LocalLayoutDirection.current
val coroutineScope = rememberCoroutineScope()
val layoutImpl = remember {
SceneTransitionLayoutImpl(
state = state as BaseSceneTransitionLayoutState,
density = density,
+ layoutDirection = layoutDirection,
swipeSourceDetector = swipeSourceDetector,
transitionInterceptionThreshold = transitionInterceptionThreshold,
builder = scenes,
@@ -475,7 +527,7 @@
// TODO(b/317014852): Move this into the SideEffect {} again once STLImpl.scenes is not a
// SnapshotStateMap anymore.
- layoutImpl.updateScenes(scenes)
+ layoutImpl.updateScenes(scenes, layoutDirection)
SideEffect {
if (state != layoutImpl.state) {
@@ -486,6 +538,7 @@
}
layoutImpl.density = density
+ layoutImpl.layoutDirection = layoutDirection
layoutImpl.swipeSourceDetector = swipeSourceDetector
layoutImpl.transitionInterceptionThreshold = transitionInterceptionThreshold
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
index 6095419..3e48c42 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
@@ -33,6 +33,7 @@
import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.util.fastForEach
import androidx.compose.ui.util.fastForEachReversed
import com.android.compose.ui.util.lerp
@@ -45,6 +46,7 @@
internal class SceneTransitionLayoutImpl(
internal val state: BaseSceneTransitionLayoutState,
internal var density: Density,
+ internal var layoutDirection: LayoutDirection,
internal var swipeSourceDetector: SwipeSourceDetector,
internal var transitionInterceptionThreshold: Float,
builder: SceneTransitionLayoutScope.() -> Unit,
@@ -114,7 +116,7 @@
private set
init {
- updateScenes(builder)
+ updateScenes(builder, layoutDirection)
// DraggableHandlerImpl must wait for the scenes to be initialized, in order to access the
// current scene (required for SwipeTransition).
@@ -147,7 +149,10 @@
return scenes[key] ?: error("Scene $key is not configured")
}
- internal fun updateScenes(builder: SceneTransitionLayoutScope.() -> Unit) {
+ internal fun updateScenes(
+ builder: SceneTransitionLayoutScope.() -> Unit,
+ layoutDirection: LayoutDirection,
+ ) {
// Keep a reference of the current scenes. After processing [builder], the scenes that were
// not configured will be removed.
val scenesToRemove = scenes.keys.toMutableSet()
@@ -163,11 +168,13 @@
) {
scenesToRemove.remove(key)
+ val resolvedUserActions =
+ userActions.mapKeys { it.key.resolve(layoutDirection) }
val scene = scenes[key]
if (scene != null) {
// Update an existing scene.
scene.content = content
- scene.userActions = userActions
+ scene.userActions = resolvedUserActions
scene.zIndex = zIndex
} else {
// New scene.
@@ -176,7 +183,7 @@
key,
this@SceneTransitionLayoutImpl,
content,
- userActions,
+ resolvedUserActions,
zIndex,
)
}
@@ -213,7 +220,7 @@
@Composable
private fun BackHandler() {
val targetSceneForBack =
- scene(state.transitionState.currentScene).userActions[Back]?.toScene
+ scene(state.transitionState.currentScene).userActions[Back.Resolved]?.toScene
PredictiveBackHandler(state, coroutineScope, targetSceneForBack)
}
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 171e243..aeb6262 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
@@ -98,7 +98,9 @@
/** Whether swipe should be enabled in the given [orientation]. */
private fun Scene.shouldEnableSwipes(orientation: Orientation): Boolean {
- return userActions.keys.any { it is Swipe && it.direction.orientation == orientation }
+ return userActions.keys.any {
+ it is Swipe.Resolved && it.direction.orientation == orientation
+ }
}
private fun startDragImmediately(startedPosition: Offset): Boolean {
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt
index aa8dc38..7daefd0 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt
@@ -44,26 +44,26 @@
return value
}
- return when (edge) {
- Edge.Top ->
+ return when (edge.resolve(layoutImpl.layoutDirection)) {
+ Edge.Resolved.Top ->
if (startsOutsideLayoutBounds) {
Offset(value.x, -elementSize.height.toFloat())
} else {
Offset(value.x, 0f)
}
- Edge.Left ->
+ Edge.Resolved.Left ->
if (startsOutsideLayoutBounds) {
Offset(-elementSize.width.toFloat(), value.y)
} else {
Offset(0f, value.y)
}
- Edge.Bottom ->
+ Edge.Resolved.Bottom ->
if (startsOutsideLayoutBounds) {
Offset(value.x, sceneSize.height.toFloat())
} else {
Offset(value.x, (sceneSize.height - elementSize.height).toFloat())
}
- Edge.Right ->
+ Edge.Resolved.Right ->
if (startsOutsideLayoutBounds) {
Offset(sceneSize.width.toFloat(), value.y)
} else {
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
index ff83d4b..7a5a84e 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
@@ -25,6 +25,7 @@
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.Velocity
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.compose.animation.scene.NestedScrollBehavior.DuringTransitionBetweenScenes
@@ -61,8 +62,24 @@
canChangeScene = { canChangeScene(it) },
)
- val mutableUserActionsA = mutableMapOf(Swipe.Up to SceneB, Swipe.Down to SceneC)
- val mutableUserActionsB = mutableMapOf(Swipe.Up to SceneC, Swipe.Down to SceneA)
+ var layoutDirection = LayoutDirection.Rtl
+ set(value) {
+ field = value
+ layoutImpl.updateScenes(scenesBuilder, layoutDirection)
+ }
+
+ var mutableUserActionsA = mapOf(Swipe.Up to SceneB, Swipe.Down to SceneC)
+ set(value) {
+ field = value
+ layoutImpl.updateScenes(scenesBuilder, layoutDirection)
+ }
+
+ var mutableUserActionsB = mapOf(Swipe.Up to SceneC, Swipe.Down to SceneA)
+ set(value) {
+ field = value
+ layoutImpl.updateScenes(scenesBuilder, layoutDirection)
+ }
+
private val scenesBuilder: SceneTransitionLayoutScope.() -> Unit = {
scene(
key = SceneA,
@@ -94,6 +111,7 @@
SceneTransitionLayoutImpl(
state = layoutState,
density = Density(1f),
+ layoutDirection = LayoutDirection.Ltr,
swipeSourceDetector = DefaultEdgeDetector,
transitionInterceptionThreshold = transitionInterceptionThreshold,
builder = scenesBuilder,
@@ -466,10 +484,8 @@
dragController1.onDragStopped(velocity = -velocityThreshold)
assertTransition(currentScene = SceneB, fromScene = SceneA, toScene = SceneB)
- mutableUserActionsA.remove(Swipe.Up)
- mutableUserActionsA.remove(Swipe.Down)
- mutableUserActionsB.remove(Swipe.Up)
- mutableUserActionsB.remove(Swipe.Down)
+ mutableUserActionsA = emptyMap()
+ mutableUserActionsB = emptyMap()
// start accelaratedScroll and scroll over to B -> null
val dragController2 = onDragStartedImmediately()
@@ -495,7 +511,7 @@
val dragController1 = onDragStarted(overSlop = up(fractionOfScreen = 0.1f))
assertTransition(fromScene = SceneA, toScene = SceneB, progress = 0.1f)
- mutableUserActionsA[Swipe.Up] = UserActionResult(SceneC)
+ mutableUserActionsA += Swipe.Up to UserActionResult(SceneC)
dragController1.onDragDelta(pixels = up(fractionOfScreen = 0.1f))
// target stays B even though UserActions changed
assertTransition(fromScene = SceneA, toScene = SceneB, progress = 0.2f)
@@ -512,7 +528,7 @@
val dragController1 = onDragStarted(overSlop = up(fractionOfScreen = 0.1f))
assertTransition(fromScene = SceneA, toScene = SceneB, progress = 0.1f)
- mutableUserActionsA[Swipe.Up] = UserActionResult(SceneC)
+ mutableUserActionsA += Swipe.Up to UserActionResult(SceneC)
dragController1.onDragDelta(pixels = up(fractionOfScreen = 0.1f))
dragController1.onDragStopped(velocity = down(fractionOfScreen = 0.1f))
@@ -1149,8 +1165,7 @@
overscroll(SceneA, Orientation.Vertical) { fade(TestElements.Foo) }
}
- mutableUserActionsA.clear()
- mutableUserActionsA[Swipe.Up] = UserActionResult(SceneB)
+ mutableUserActionsA = mapOf(Swipe.Up to UserActionResult(SceneB))
val middle = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)
val dragController = onDragStarted(startedPosition = middle, overSlop = down(1f))
@@ -1178,8 +1193,7 @@
overscroll(SceneA, Orientation.Vertical) { fade(TestElements.Foo) }
}
- mutableUserActionsA.clear()
- mutableUserActionsA[Swipe.Down] = UserActionResult(SceneC)
+ mutableUserActionsA = mapOf(Swipe.Down to UserActionResult(SceneC))
val middle = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)
val dragController = onDragStarted(startedPosition = middle, overSlop = up(1f))
@@ -1220,7 +1234,8 @@
@Test
fun requireFullDistanceSwipe() = runGestureTest {
- mutableUserActionsA[Swipe.Up] = UserActionResult(SceneB, requiresFullDistanceSwipe = true)
+ mutableUserActionsA +=
+ Swipe.Up to UserActionResult(SceneB, requiresFullDistanceSwipe = true)
val controller = onDragStarted(overSlop = up(fractionOfScreen = 0.9f))
assertTransition(fromScene = SceneA, toScene = SceneB, progress = 0.9f)
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
index 25ea2ee..0766e00 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
@@ -23,11 +23,13 @@
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
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.LocalLayoutDirection
import androidx.compose.ui.platform.LocalViewConfiguration
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.test.assertPositionInRootIsEqualTo
@@ -37,10 +39,12 @@
import androidx.compose.ui.test.swipeWithVelocity
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.compose.animation.scene.TestScenes.SceneA
import com.android.compose.animation.scene.TestScenes.SceneB
+import com.android.compose.animation.scene.TestScenes.SceneC
import com.android.compose.animation.scene.subjects.assertThat
import com.google.common.truth.Truth.assertThat
import org.junit.Rule
@@ -634,4 +638,152 @@
// Foo should be translated by (20dp, 30dp).
rule.onNode(isElement(TestElements.Foo)).assertPositionInRootIsEqualTo(20.dp, 30.dp)
}
+
+ @Test
+ fun startEnd_ltrLayout() {
+ val state =
+ rule.runOnUiThread {
+ MutableSceneTransitionLayoutState(
+ initialScene = SceneA,
+ transitions =
+ transitions {
+ from(SceneA, to = SceneB) {
+ // We go to B by swiping to the start (left in LTR), so we make
+ // scene B appear from the end (right) edge.
+ translate(SceneB.rootElementKey, Edge.End)
+ }
+
+ from(SceneA, to = SceneC) {
+ // We go to C by swiping to the end (right in LTR), so we make
+ // scene C appear from the start (left) edge.
+ translate(SceneC.rootElementKey, Edge.Start)
+ }
+ },
+ )
+ }
+
+ val layoutSize = 200.dp
+ var touchSlop = 0f
+ rule.setContent {
+ touchSlop = LocalViewConfiguration.current.touchSlop
+ SceneTransitionLayout(state, Modifier.size(layoutSize)) {
+ scene(SceneA, userActions = mapOf(Swipe.Start to SceneB, Swipe.End to SceneC)) {
+ Box(Modifier.fillMaxSize())
+ }
+ scene(SceneB) { Box(Modifier.element(SceneB.rootElementKey).fillMaxSize()) }
+ scene(SceneC) { Box(Modifier.element(SceneC.rootElementKey).fillMaxSize()) }
+ }
+ }
+
+ // Swipe to the left (start).
+ rule.onRoot().performTouchInput {
+ val middle = (layoutSize / 2).toPx()
+ down(Offset(middle, middle))
+ moveBy(Offset(-touchSlop, 0f), delayMillis = 1_000)
+ }
+
+ // Scene B should come from the right (end) edge.
+ var transition = assertThat(state.transitionState).isTransition()
+ assertThat(transition).hasFromScene(SceneA)
+ assertThat(transition).hasToScene(SceneB)
+ rule
+ .onNode(isElement(SceneB.rootElementKey))
+ .assertPositionInRootIsEqualTo(layoutSize, 0.dp)
+
+ // Release to go back to A.
+ rule.onRoot().performTouchInput { up() }
+ rule.waitForIdle()
+ assertThat(state.transitionState).isIdle()
+ assertThat(state.transitionState).hasCurrentScene(SceneA)
+
+ // Swipe to the right (end).
+ rule.onRoot().performTouchInput {
+ val middle = (layoutSize / 2).toPx()
+ down(Offset(middle, middle))
+ moveBy(Offset(touchSlop, 0f), delayMillis = 1_000)
+ }
+
+ // Scene C should come from the left (start) edge.
+ transition = assertThat(state.transitionState).isTransition()
+ assertThat(transition).hasFromScene(SceneA)
+ assertThat(transition).hasToScene(SceneC)
+ rule
+ .onNode(isElement(SceneC.rootElementKey))
+ .assertPositionInRootIsEqualTo(-layoutSize, 0.dp)
+ }
+
+ @Test
+ fun startEnd_rtlLayout() {
+ val state =
+ rule.runOnUiThread {
+ MutableSceneTransitionLayoutState(
+ initialScene = SceneA,
+ transitions =
+ transitions {
+ from(SceneA, to = SceneB) {
+ // We go to B by swiping to the start (right in RTL), so we make
+ // scene B appear from the end (left) edge.
+ translate(SceneB.rootElementKey, Edge.End)
+ }
+
+ from(SceneA, to = SceneC) {
+ // We go to C by swiping to the end (left in RTL), so we make
+ // scene C appear from the start (right) edge.
+ translate(SceneC.rootElementKey, Edge.Start)
+ }
+ },
+ )
+ }
+
+ val layoutSize = 200.dp
+ var touchSlop = 0f
+ rule.setContent {
+ touchSlop = LocalViewConfiguration.current.touchSlop
+ CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) {
+ SceneTransitionLayout(state, Modifier.size(layoutSize)) {
+ scene(SceneA, userActions = mapOf(Swipe.Start to SceneB, Swipe.End to SceneC)) {
+ Box(Modifier.fillMaxSize())
+ }
+ scene(SceneB) { Box(Modifier.element(SceneB.rootElementKey).fillMaxSize()) }
+ scene(SceneC) { Box(Modifier.element(SceneC.rootElementKey).fillMaxSize()) }
+ }
+ }
+ }
+
+ // Swipe to the left (end).
+ rule.onRoot().performTouchInput {
+ val middle = (layoutSize / 2).toPx()
+ down(Offset(middle, middle))
+ moveBy(Offset(-touchSlop, 0f), delayMillis = 1_000)
+ }
+
+ // Scene C should come from the right (start) edge.
+ var transition = assertThat(state.transitionState).isTransition()
+ assertThat(transition).hasFromScene(SceneA)
+ assertThat(transition).hasToScene(SceneC)
+ rule
+ .onNode(isElement(SceneC.rootElementKey))
+ .assertPositionInRootIsEqualTo(layoutSize, 0.dp)
+
+ // Release to go back to A.
+ rule.onRoot().performTouchInput { up() }
+ rule.waitForIdle()
+ assertThat(state.transitionState).isIdle()
+ assertThat(state.transitionState).hasCurrentScene(SceneA)
+
+ // Swipe to the right (start).
+ rule.onRoot().performTouchInput {
+ val middle = (layoutSize / 2).toPx()
+ down(Offset(middle, middle))
+ moveBy(Offset(touchSlop, 0f), delayMillis = 1_000)
+ }
+
+ // Scene C should come from the left (end) edge.
+ transition = assertThat(state.transitionState).isTransition()
+ assertThat(transition).hasFromScene(SceneA)
+ assertThat(transition).hasToScene(SceneB)
+ rule
+ .onNode(isElement(SceneB.rootElementKey))
+ .assertPositionInRootIsEqualTo(-layoutSize, 0.dp)
+ }
}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
index 51c008a..9b725eb 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
@@ -17,7 +17,6 @@
import android.app.UserSwitchObserver
import android.content.Context
import android.database.ContentObserver
-import android.graphics.drawable.Drawable
import android.net.Uri
import android.os.UserHandle
import android.provider.Settings
@@ -33,6 +32,7 @@
import com.android.systemui.plugins.clocks.ClockId
import com.android.systemui.plugins.clocks.ClockMessageBuffers
import com.android.systemui.plugins.clocks.ClockMetadata
+import com.android.systemui.plugins.clocks.ClockPickerConfig
import com.android.systemui.plugins.clocks.ClockProvider
import com.android.systemui.plugins.clocks.ClockProviderPlugin
import com.android.systemui.plugins.clocks.ClockSettings
@@ -341,6 +341,7 @@
}
private var isClockChanged = AtomicBoolean(false)
+
private fun triggerOnCurrentClockChanged() {
val shouldSchedule = isClockChanged.compareAndSet(false, true)
if (!shouldSchedule) {
@@ -355,6 +356,7 @@
}
private var isClockListChanged = AtomicBoolean(false)
+
private fun triggerOnAvailableClocksChanged() {
val shouldSchedule = isClockListChanged.compareAndSet(false, true)
if (!shouldSchedule) {
@@ -458,6 +460,7 @@
}
private var isQueued = AtomicBoolean(false)
+
fun verifyLoadedProviders() {
val shouldSchedule = isQueued.compareAndSet(false, true)
if (!shouldSchedule) {
@@ -565,8 +568,8 @@
return availableClocks.map { (_, clock) -> clock.metadata }
}
- fun getClockThumbnail(clockId: ClockId): Drawable? =
- availableClocks[clockId]?.provider?.getClockThumbnail(clockId)
+ fun getClockPickerConfig(clockId: ClockId): ClockPickerConfig? =
+ availableClocks[clockId]?.provider?.getClockPickerConfig(clockId)
fun createExampleClock(clockId: ClockId): ClockController? = createClock(clockId)
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
index 9e0af97..4802e34 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
@@ -15,13 +15,13 @@
import android.content.Context
import android.content.res.Resources
-import android.graphics.drawable.Drawable
import android.view.LayoutInflater
import com.android.systemui.customization.R
import com.android.systemui.plugins.clocks.ClockController
import com.android.systemui.plugins.clocks.ClockId
import com.android.systemui.plugins.clocks.ClockMessageBuffers
import com.android.systemui.plugins.clocks.ClockMetadata
+import com.android.systemui.plugins.clocks.ClockPickerConfig
import com.android.systemui.plugins.clocks.ClockProvider
import com.android.systemui.plugins.clocks.ClockSettings
@@ -60,12 +60,17 @@
)
}
- override fun getClockThumbnail(id: ClockId): Drawable? {
+ override fun getClockPickerConfig(id: ClockId): ClockPickerConfig {
if (id != DEFAULT_CLOCK_ID) {
throw IllegalArgumentException("$id is unsupported by $TAG")
}
- // TODO(b/352049256): Update placeholder to actual resource
- return resources.getDrawable(R.drawable.clock_default_thumbnail, null)
+ return ClockPickerConfig(
+ DEFAULT_CLOCK_ID,
+ resources.getString(R.string.clock_default_name),
+ resources.getString(R.string.clock_default_description),
+ // TODO(b/352049256): Update placeholder to actual resource
+ resources.getDrawable(R.drawable.clock_default_thumbnail, null),
+ )
}
}
diff --git a/packages/SystemUI/lint-baseline.xml b/packages/SystemUI/lint-baseline.xml
index 525839d..b4c839f 100644
--- a/packages/SystemUI/lint-baseline.xml
+++ b/packages/SystemUI/lint-baseline.xml
@@ -9168,39 +9168,6 @@
<issue
id="UnclosedTrace"
- message="The `beginSection()` call is not always closed with a matching `endSection()` because the code in between may return early"
- errorLine1=" Trace.beginSection("KeyguardViewMediator#handleKeyguardDone");"
- errorLine2=" ~~~~~~~~~~~~">
- <location
- file="frameworks/base/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java"
- line="2654"
- column="15"/>
- </issue>
-
- <issue
- id="UnclosedTrace"
- message="The `beginSection()` call is not always closed with a matching `endSection()` because the code in between may return early"
- errorLine1=" Trace.beginSection("KeyguardViewMediator#handleShow");"
- errorLine2=" ~~~~~~~~~~~~">
- <location
- file="frameworks/base/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java"
- line="2780"
- column="15"/>
- </issue>
-
- <issue
- id="UnclosedTrace"
- message="The `beginSection()` call is not always closed with a matching `endSection()` because the code in between may return early"
- errorLine1=" Trace.beginSection("KeyguardViewMediator#handleStartKeyguardExitAnimation");"
- errorLine2=" ~~~~~~~~~~~~">
- <location
- file="frameworks/base/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java"
- line="3011"
- column="15"/>
- </issue>
-
- <issue
- id="UnclosedTrace"
message="The `traceBegin()` call is not always closed with a matching `traceEnd()` because the code in between may return early"
errorLine1=" Trace.traceBegin(Trace.TRACE_TAG_APP, "MediaControlPanel#bindPlayer<" + key + ">");"
errorLine2=" ~~~~~~~~~~">
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 0de0369..2cbfa7d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
@@ -16,9 +16,11 @@
package com.android.systemui.communal
+import android.platform.test.annotations.EnableFlags
import android.provider.Settings
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
import com.android.systemui.SysuiTestCase
import com.android.systemui.communal.domain.interactor.communalInteractor
import com.android.systemui.communal.domain.interactor.communalSceneInteractor
@@ -60,6 +62,7 @@
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
+@EnableFlags(FLAG_COMMUNAL_HUB)
class CommunalSceneStartableTest : SysuiTestCase() {
private val kosmos = testKosmos()
@@ -98,7 +101,7 @@
}
@Test
- fun keyguardGoesAway_forceBlankScene() =
+ fun keyguardGoesAway_whenLaunchingWidget_doNotForceBlankScene() =
with(kosmos) {
testScope.runTest {
val scene by collectLastValue(communalSceneInteractor.currentScene)
@@ -106,6 +109,27 @@
communalSceneInteractor.changeScene(CommunalScenes.Communal)
assertThat(scene).isEqualTo(CommunalScenes.Communal)
+ communalSceneInteractor.setIsLaunchingWidget(true)
+ fakeKeyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.PRIMARY_BOUNCER,
+ to = KeyguardState.GONE,
+ testScope = this
+ )
+
+ assertThat(scene).isEqualTo(CommunalScenes.Communal)
+ }
+ }
+
+ @Test
+ fun keyguardGoesAway_whenNotLaunchingWidget_forceBlankScene() =
+ with(kosmos) {
+ testScope.runTest {
+ val scene by collectLastValue(communalSceneInteractor.currentScene)
+
+ communalSceneInteractor.changeScene(CommunalScenes.Communal)
+ assertThat(scene).isEqualTo(CommunalScenes.Communal)
+
+ communalSceneInteractor.setIsLaunchingWidget(false)
fakeKeyguardTransitionRepository.sendTransitionSteps(
from = KeyguardState.PRIMARY_BOUNCER,
to = KeyguardState.GONE,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/db/DefaultWidgetPopulationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/db/DefaultWidgetPopulationTest.kt
new file mode 100644
index 0000000..ad2c42f
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/db/DefaultWidgetPopulationTest.kt
@@ -0,0 +1,157 @@
+/*
+ * 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.data.db
+
+import android.content.ComponentName
+import android.os.UserHandle
+import android.os.UserManager
+import androidx.sqlite.db.SupportSQLiteDatabase
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.data.db.DefaultWidgetPopulation.SkipReason.RESTORED_FROM_BACKUP
+import com.android.systemui.communal.widgets.CommunalWidgetHost
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.log.logcatLogBuffer
+import com.android.systemui.testKosmos
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.kotlin.any
+import org.mockito.kotlin.anyOrNull
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
+import org.mockito.kotlin.verify
+
+@SmallTest
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidJUnit4::class)
+class DefaultWidgetPopulationTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+
+ private val communalWidgetHost =
+ mock<CommunalWidgetHost> {
+ var nextId = 0
+ on { allocateIdAndBindWidget(any(), anyOrNull()) }.thenAnswer { nextId++ }
+ }
+ private val communalWidgetDao = mock<CommunalWidgetDao>()
+ private val database = mock<SupportSQLiteDatabase>()
+ private val mainUser = UserHandle(0)
+ private val userManager =
+ mock<UserManager> {
+ on { mainUser }.thenReturn(mainUser)
+ on { getUserSerialNumber(0) }.thenReturn(0)
+ }
+
+ private val defaultWidgets =
+ arrayOf(
+ "com.android.test_package_1/fake_widget_1",
+ "com.android.test_package_2/fake_widget_2",
+ "com.android.test_package_3/fake_widget_3",
+ )
+
+ private lateinit var underTest: DefaultWidgetPopulation
+
+ @Before
+ fun setUp() {
+ underTest =
+ DefaultWidgetPopulation(
+ bgScope = kosmos.applicationCoroutineScope,
+ communalWidgetHost = communalWidgetHost,
+ communalWidgetDaoProvider = { communalWidgetDao },
+ defaultWidgets = defaultWidgets,
+ logBuffer = logcatLogBuffer("DefaultWidgetPopulationTest"),
+ userManager = userManager,
+ )
+ }
+
+ @Test
+ fun testPopulateDefaultWidgetsWhenDatabaseCreated() =
+ testScope.runTest {
+ // Database created
+ underTest.onCreate(database)
+ runCurrent()
+
+ // Verify default widgets bound
+ verify(communalWidgetHost)
+ .allocateIdAndBindWidget(
+ provider = eq(ComponentName.unflattenFromString(defaultWidgets[0])!!),
+ user = eq(mainUser),
+ )
+ verify(communalWidgetHost)
+ .allocateIdAndBindWidget(
+ provider = eq(ComponentName.unflattenFromString(defaultWidgets[1])!!),
+ user = eq(mainUser),
+ )
+ verify(communalWidgetHost)
+ .allocateIdAndBindWidget(
+ provider = eq(ComponentName.unflattenFromString(defaultWidgets[2])!!),
+ user = eq(mainUser),
+ )
+
+ // Verify default widgets added in database
+ verify(communalWidgetDao)
+ .addWidget(
+ widgetId = 0,
+ componentName = defaultWidgets[0],
+ priority = 3,
+ userSerialNumber = 0,
+ )
+ verify(communalWidgetDao)
+ .addWidget(
+ widgetId = 1,
+ componentName = defaultWidgets[1],
+ priority = 2,
+ userSerialNumber = 0,
+ )
+ verify(communalWidgetDao)
+ .addWidget(
+ widgetId = 2,
+ componentName = defaultWidgets[2],
+ priority = 1,
+ userSerialNumber = 0,
+ )
+ }
+
+ @Test
+ fun testSkipDefaultWidgetsPopulation() =
+ testScope.runTest {
+ // Skip default widgets population
+ underTest.skipDefaultWidgetsPopulation(RESTORED_FROM_BACKUP)
+
+ // Database created
+ underTest.onCreate(database)
+ runCurrent()
+
+ // Verify no widget bounded or added to the database
+ verify(communalWidgetHost, never()).allocateIdAndBindWidget(any(), any())
+ verify(communalWidgetDao, never())
+ .addWidget(
+ widgetId = anyInt(),
+ componentName = any(),
+ priority = anyInt(),
+ userSerialNumber = anyInt(),
+ )
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
index 17234a90..c707ebf 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
@@ -34,6 +34,7 @@
import com.android.systemui.communal.data.db.CommunalItemRank
import com.android.systemui.communal.data.db.CommunalWidgetDao
import com.android.systemui.communal.data.db.CommunalWidgetItem
+import com.android.systemui.communal.data.db.defaultWidgetPopulation
import com.android.systemui.communal.nano.CommunalHubState
import com.android.systemui.communal.proto.toByteArray
import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
@@ -134,6 +135,7 @@
backupUtils,
packageChangeRepository,
userManager,
+ kosmos.defaultWidgetPopulation,
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/smartspace/SmartspaceInteractionHandlerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/smartspace/SmartspaceInteractionHandlerTest.kt
index 0cd3fb2..d51d356 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/smartspace/SmartspaceInteractionHandlerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/smartspace/SmartspaceInteractionHandlerTest.kt
@@ -26,14 +26,23 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.domain.interactor.communalSceneInteractor
+import com.android.systemui.communal.widgets.CommunalTransitionAnimatorController
import com.android.systemui.communal.widgets.SmartspaceAppWidgetHostView
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.testKosmos
+import kotlinx.coroutines.test.runTest
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.kotlin.any
import org.mockito.kotlin.eq
import org.mockito.kotlin.isNull
import org.mockito.kotlin.mock
-import org.mockito.kotlin.notNull
import org.mockito.kotlin.refEq
import org.mockito.kotlin.verify
@@ -41,6 +50,7 @@
@RunWith(AndroidJUnit4::class)
class SmartspaceInteractionHandlerTest : SysuiTestCase() {
private val activityStarter = mock<ActivityStarter>()
+ private val kosmos = testKosmos()
private val testIntent =
PendingIntent.getActivity(
@@ -51,29 +61,43 @@
)
private val testResponse = RemoteResponse.fromPendingIntent(testIntent)
- private val underTest: SmartspaceInteractionHandler by lazy {
- SmartspaceInteractionHandler(activityStarter)
+ private lateinit var underTest: SmartspaceInteractionHandler
+
+ @Before
+ fun setUp() {
+ with(kosmos) {
+ underTest = SmartspaceInteractionHandler(activityStarter, communalSceneInteractor)
+ }
}
@Test
fun launchAnimatorIsUsedForSmartspaceView() {
- val parent = FrameLayout(context)
- val view = SmartspaceAppWidgetHostView(context)
- parent.addView(view)
- val (fillInIntent, activityOptions) = testResponse.getLaunchOptions(view)
+ with(kosmos) {
+ testScope.runTest {
+ val launching by collectLastValue(communalSceneInteractor.isLaunchingWidget)
+ assertFalse(launching!!)
- underTest.onInteraction(view, testIntent, testResponse)
+ val parent = FrameLayout(context)
+ val view = SmartspaceAppWidgetHostView(context)
+ parent.addView(view)
+ val (fillInIntent, activityOptions) = testResponse.getLaunchOptions(view)
- // Verify that we pass in a non-null animation controller
- verify(activityStarter)
- .startPendingIntentWithoutDismissing(
- /* intent = */ eq(testIntent),
- /* dismissShade = */ eq(false),
- /* intentSentUiThreadCallback = */ isNull(),
- /* animationController = */ notNull(),
- /* fillInIntent = */ refEq(fillInIntent),
- /* extraOptions = */ refEq(activityOptions.toBundle()),
- )
+ underTest.onInteraction(view, testIntent, testResponse)
+
+ // Verify that we set the state correctly
+ assertTrue(launching!!)
+ // Verify that we pass in a non-null Communal animation controller
+ verify(activityStarter)
+ .startPendingIntentWithoutDismissing(
+ /* intent = */ eq(testIntent),
+ /* dismissShade = */ eq(false),
+ /* intentSentUiThreadCallback = */ isNull(),
+ /* animationController = */ any<CommunalTransitionAnimatorController>(),
+ /* fillInIntent = */ refEq(fillInIntent),
+ /* extraOptions = */ refEq(activityOptions.toBundle()),
+ )
+ }
+ }
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
index a2f6796..b138fb3 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
@@ -40,6 +40,7 @@
import com.android.systemui.communal.data.repository.fakeCommunalSmartspaceRepository
import com.android.systemui.communal.data.repository.fakeCommunalTutorialRepository
import com.android.systemui.communal.data.repository.fakeCommunalWidgetRepository
+import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
import com.android.systemui.communal.domain.interactor.communalInteractor
import com.android.systemui.communal.domain.interactor.communalPrefsInteractor
@@ -76,6 +77,8 @@
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.spy
@SmallTest
@RunWith(AndroidJUnit4::class)
@@ -94,6 +97,7 @@
private lateinit var smartspaceRepository: FakeCommunalSmartspaceRepository
private lateinit var mediaRepository: FakeCommunalMediaRepository
private lateinit var communalSceneInteractor: CommunalSceneInteractor
+ private lateinit var communalInteractor: CommunalInteractor
private val testableResources = context.orCreateTestableResources
@@ -108,6 +112,7 @@
smartspaceRepository = kosmos.fakeCommunalSmartspaceRepository
mediaRepository = kosmos.fakeCommunalMediaRepository
communalSceneInteractor = kosmos.communalSceneInteractor
+ communalInteractor = spy(kosmos.communalInteractor)
kosmos.fakeUserRepository.setUserInfos(listOf(MAIN_USER_INFO))
kosmos.fakeUserTracker.set(
userInfos = listOf(MAIN_USER_INFO),
@@ -119,7 +124,7 @@
underTest =
CommunalEditModeViewModel(
communalSceneInteractor,
- kosmos.communalInteractor,
+ communalInteractor,
kosmos.communalSettingsInteractor,
kosmos.keyguardTransitionInteractor,
mediaHost,
@@ -346,6 +351,16 @@
assertThat(showDisclaimer).isFalse()
}
+ @Test
+ fun scrollPosition_persistedOnEditCleanup() {
+ val index = 2
+ val offset = 30
+ underTest.onScrollPositionUpdated(index, offset)
+ underTest.cleanupEditModeState()
+
+ verify(communalInteractor).setScrollPosition(eq(index), eq(offset))
+ }
+
private companion object {
val MAIN_USER_INFO = UserInfo(0, "primary", UserInfo.FLAG_MAIN)
const val WIDGET_PICKER_PACKAGE_NAME = "widget_picker_package_name"
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
index 74a048d..c480aa8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
@@ -37,6 +37,7 @@
import com.android.systemui.communal.data.repository.fakeCommunalSmartspaceRepository
import com.android.systemui.communal.data.repository.fakeCommunalTutorialRepository
import com.android.systemui.communal.data.repository.fakeCommunalWidgetRepository
+import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.communal.domain.interactor.communalInteractor
import com.android.systemui.communal.domain.interactor.communalSceneInteractor
import com.android.systemui.communal.domain.interactor.communalSettingsInteractor
@@ -97,7 +98,9 @@
import org.mockito.Mockito
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
+import org.mockito.kotlin.spy
import org.mockito.kotlin.whenever
import platform.test.runner.parameterized.ParameterizedAndroidJunit4
import platform.test.runner.parameterized.Parameters
@@ -121,6 +124,7 @@
private lateinit var shadeTestUtil: ShadeTestUtil
private lateinit var keyguardTransitionRepository: FakeKeyguardTransitionRepository
private lateinit var communalRepository: FakeCommunalSceneRepository
+ private lateinit var communalInteractor: CommunalInteractor
private lateinit var underTest: CommunalViewModel
@@ -154,6 +158,8 @@
kosmos.powerInteractor.setAwakeForTest()
+ communalInteractor = spy(kosmos.communalInteractor)
+
underTest =
CommunalViewModel(
kosmos.testDispatcher,
@@ -164,7 +170,7 @@
kosmos.keyguardInteractor,
mock<KeyguardIndicationController>(),
kosmos.communalSceneInteractor,
- kosmos.communalInteractor,
+ communalInteractor,
kosmos.communalSettingsInteractor,
kosmos.communalTutorialInteractor,
kosmos.shadeInteractor,
@@ -779,6 +785,16 @@
.isInstanceOf(CommunalContentModel.CtaTileInViewMode::class.java)
}
+ @Test
+ fun scrollPosition_persistedOnEditEntry() {
+ val index = 2
+ val offset = 30
+ underTest.onScrollPositionUpdated(index, offset)
+ underTest.onOpenWidgetEditor(false)
+
+ verify(communalInteractor).setScrollPosition(eq(index), eq(offset))
+ }
+
private suspend fun setIsMainUser(isMainUser: Boolean) {
val user = if (isMainUser) MAIN_USER_INFO else SECONDARY_USER_INFO
with(userRepository) {
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
new file mode 100644
index 0000000..ac50db4
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalTransitionAnimatorControllerTest.kt
@@ -0,0 +1,115 @@
+/*
+ * 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.
+ */
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.ActivityTransitionAnimator
+import com.android.systemui.communal.domain.interactor.communalSceneInteractor
+import com.android.systemui.communal.shared.model.CommunalScenes
+import com.android.systemui.communal.widgets.CommunalTransitionAnimatorController
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth
+import kotlinx.coroutines.test.runTest
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.verify
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class CommunalTransitionAnimatorControllerTest : SysuiTestCase() {
+ private val controller = mock<ActivityTransitionAnimator.Controller>()
+ private val kosmos = testKosmos()
+
+ private lateinit var underTest: CommunalTransitionAnimatorController
+
+ @Before
+ fun setUp() {
+ with(kosmos) {
+ underTest = CommunalTransitionAnimatorController(controller, communalSceneInteractor)
+ }
+ }
+
+ @Test
+ fun doNotAnimate_launchingWidgetStateIsCleared() {
+ with(kosmos) {
+ testScope.runTest {
+ val launching by collectLastValue(communalSceneInteractor.isLaunchingWidget)
+
+ communalSceneInteractor.setIsLaunchingWidget(true)
+ assertTrue(launching!!)
+
+ underTest.onIntentStarted(willAnimate = false)
+ assertFalse(launching!!)
+ verify(controller).onIntentStarted(willAnimate = false)
+ }
+ }
+ }
+
+ @Test
+ fun animationCancelled_launchingWidgetStateIsClearedAndSceneIsNotChanged() {
+ with(kosmos) {
+ testScope.runTest {
+ val launching by collectLastValue(communalSceneInteractor.isLaunchingWidget)
+ val scene by collectLastValue(communalSceneInteractor.currentScene)
+
+ communalSceneInteractor.changeScene(CommunalScenes.Communal)
+ Truth.assertThat(scene).isEqualTo(CommunalScenes.Communal)
+ communalSceneInteractor.setIsLaunchingWidget(true)
+ assertTrue(launching!!)
+
+ underTest.onIntentStarted(willAnimate = true)
+ assertTrue(launching!!)
+ verify(controller).onIntentStarted(willAnimate = true)
+
+ underTest.onTransitionAnimationCancelled(newKeyguardOccludedState = true)
+ assertFalse(launching!!)
+ Truth.assertThat(scene).isEqualTo(CommunalScenes.Communal)
+ verify(controller).onTransitionAnimationCancelled(newKeyguardOccludedState = true)
+ }
+ }
+ }
+
+ @Test
+ fun animationComplete_launchingWidgetStateIsClearedAndSceneIsChanged() {
+ with(kosmos) {
+ testScope.runTest {
+ val launching by collectLastValue(communalSceneInteractor.isLaunchingWidget)
+ val scene by collectLastValue(communalSceneInteractor.currentScene)
+
+ communalSceneInteractor.changeScene(CommunalScenes.Communal)
+ Truth.assertThat(scene).isEqualTo(CommunalScenes.Communal)
+ communalSceneInteractor.setIsLaunchingWidget(true)
+ assertTrue(launching!!)
+
+ underTest.onIntentStarted(willAnimate = true)
+ assertTrue(launching!!)
+ verify(controller).onIntentStarted(willAnimate = true)
+
+ underTest.onTransitionAnimationEnd(isExpandingFullyAbove = true)
+ assertFalse(launching!!)
+ Truth.assertThat(scene).isEqualTo(CommunalScenes.Blank)
+ verify(controller).onTransitionAnimationEnd(isExpandingFullyAbove = true)
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/WidgetInteractionHandlerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/WidgetInteractionHandlerTest.kt
index 7044895..ea8b5ab 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/WidgetInteractionHandlerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/WidgetInteractionHandlerTest.kt
@@ -26,13 +26,21 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.domain.interactor.communalSceneInteractor
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.testKosmos
+import kotlinx.coroutines.test.runTest
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.kotlin.any
import org.mockito.kotlin.eq
import org.mockito.kotlin.isNull
import org.mockito.kotlin.mock
-import org.mockito.kotlin.notNull
import org.mockito.kotlin.refEq
import org.mockito.kotlin.verify
@@ -40,6 +48,7 @@
@RunWith(AndroidJUnit4::class)
class WidgetInteractionHandlerTest : SysuiTestCase() {
private val activityStarter = mock<ActivityStarter>()
+ private val kosmos = testKosmos()
private val testIntent =
PendingIntent.getActivity(
@@ -50,30 +59,44 @@
)
private val testResponse = RemoteResponse.fromPendingIntent(testIntent)
- private val underTest: WidgetInteractionHandler by lazy {
- WidgetInteractionHandler(activityStarter)
+ private lateinit var underTest: WidgetInteractionHandler
+
+ @Before
+ fun setUp() {
+ with(kosmos) {
+ underTest = WidgetInteractionHandler(activityStarter, communalSceneInteractor)
+ }
}
@Test
fun launchAnimatorIsUsedForWidgetView() {
- val parent = FrameLayout(context)
- val view = CommunalAppWidgetHostView(context)
- parent.addView(view)
- val (fillInIntent, activityOptions) = testResponse.getLaunchOptions(view)
+ with(kosmos) {
+ testScope.runTest {
+ val launching by collectLastValue(communalSceneInteractor.isLaunchingWidget)
+ assertFalse(launching!!)
- underTest.onInteraction(view, testIntent, testResponse)
+ val parent = FrameLayout(context)
+ val view = CommunalAppWidgetHostView(context)
+ parent.addView(view)
+ val (fillInIntent, activityOptions) = testResponse.getLaunchOptions(view)
- // Verify that we pass in a non-null animation controller
- verify(activityStarter)
- .startPendingIntentMaybeDismissingKeyguard(
- /* intent = */ eq(testIntent),
- /* dismissShade = */ eq(false),
- /* intentSentUiThreadCallback = */ isNull(),
- /* animationController = */ notNull(),
- /* fillInIntent = */ refEq(fillInIntent),
- /* extraOptions = */ refEq(activityOptions.toBundle()),
- /* customMessage */ isNull(),
- )
+ underTest.onInteraction(view, testIntent, testResponse)
+
+ // Verify that we set the state correctly
+ assertTrue(launching!!)
+ // Verify that we pass in a non-null Communal animation controller
+ verify(activityStarter)
+ .startPendingIntentMaybeDismissingKeyguard(
+ /* intent = */ eq(testIntent),
+ /* dismissShade = */ eq(false),
+ /* intentSentUiThreadCallback = */ isNull(),
+ /* animationController = */ any<CommunalTransitionAnimatorController>(),
+ /* fillInIntent = */ refEq(fillInIntent),
+ /* extraOptions = */ refEq(activityOptions.toBundle()),
+ /* customMessage */ isNull(),
+ )
+ }
+ }
}
@Test
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 444f63a..60c9bb0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
@@ -41,6 +41,7 @@
import com.android.internal.logging.UiEventLogger
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.KeyguardUpdateMonitorCallback
+import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
import com.android.systemui.SysuiTestCase
import com.android.systemui.ambient.touch.TouchMonitor
import com.android.systemui.ambient.touch.dagger.AmbientTouchComponent
@@ -118,11 +119,11 @@
@Mock
lateinit var mDreamComplicationComponentFactory:
- com.android.systemui.dreams.complication.dagger.ComplicationComponent.Factory
+ com.android.systemui.dreams.complication.dagger.ComplicationComponent.Factory
@Mock
lateinit var mDreamComplicationComponent:
- com.android.systemui.dreams.complication.dagger.ComplicationComponent
+ com.android.systemui.dreams.complication.dagger.ComplicationComponent
@Mock lateinit var mHideComplicationTouchHandler: HideComplicationTouchHandler
@@ -202,8 +203,12 @@
whenever(mScrimManager.getCurrentController()).thenReturn(mScrimController)
whenever(mLazyViewCapture.value).thenReturn(viewCaptureSpy)
mWindowParams = WindowManager.LayoutParams()
- mViewCaptureAwareWindowManager = ViewCaptureAwareWindowManager(mWindowManager,
- mLazyViewCapture, isViewCaptureEnabled = false)
+ mViewCaptureAwareWindowManager =
+ ViewCaptureAwareWindowManager(
+ mWindowManager,
+ mLazyViewCapture,
+ isViewCaptureEnabled = false
+ )
mService =
DreamOverlayService(
mContext,
@@ -257,7 +262,7 @@
mMainExecutor.runAllReady()
verify(mUiEventLogger).log(DreamOverlayService.DreamOverlayEvent.DREAM_OVERLAY_ENTER_START)
verify(mUiEventLogger)
- .log(DreamOverlayService.DreamOverlayEvent.DREAM_OVERLAY_COMPLETE_START)
+ .log(DreamOverlayService.DreamOverlayEvent.DREAM_OVERLAY_COMPLETE_START)
}
@Test
@@ -634,7 +639,7 @@
}
@Test
- @EnableFlags(Flags.FLAG_DREAM_WAKE_REDIRECT)
+ @EnableFlags(Flags.FLAG_DREAM_WAKE_REDIRECT, FLAG_COMMUNAL_HUB)
@kotlin.Throws(RemoteException::class)
fun testTransitionToGlanceableHub() =
testScope.runTest {
@@ -658,7 +663,7 @@
}
@Test
- @EnableFlags(Flags.FLAG_DREAM_WAKE_REDIRECT)
+ @EnableFlags(Flags.FLAG_DREAM_WAKE_REDIRECT, FLAG_COMMUNAL_HUB)
@Throws(RemoteException::class)
fun testRedirectExit() =
testScope.runTest {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryTest.kt
index 4a5342a..3a4b14b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryTest.kt
@@ -28,6 +28,8 @@
import com.android.systemui.shared.education.GestureType.BACK_GESTURE
import com.google.common.truth.Truth.assertThat
import java.io.File
+import java.time.Clock
+import java.time.Instant
import javax.inject.Provider
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.test.TestScope
@@ -48,6 +50,7 @@
private val dsScopeProvider: Provider<CoroutineScope> = Provider {
TestScope(kosmos.testDispatcher).backgroundScope
}
+ private val clock: Clock = FakeEduClock(Instant.ofEpochMilli(1000))
private val testUserId = 1111
// For deleting any test files created after the test
@@ -59,7 +62,7 @@
// needed before calling TemporaryFolder.newFolder().
val testContext = TestContext(context, tmpFolder.newFolder())
val userRepository = UserContextualEducationRepository(testContext, dsScopeProvider)
- underTest = ContextualEducationRepository(userRepository)
+ underTest = ContextualEducationRepositoryImpl(clock, userRepository)
underTest.setUser(testUserId)
}
@@ -85,6 +88,15 @@
assertThat(model?.signalCount).isEqualTo(1)
}
+ @Test
+ fun dataAddedOnUpdateShortcutTriggerTime() =
+ testScope.runTest {
+ val model by collectLastValue(underTest.readGestureEduModelFlow(BACK_GESTURE))
+ assertThat(model?.lastShortcutTriggeredTime).isNull()
+ underTest.updateShortcutTriggerTime(BACK_GESTURE)
+ assertThat(model?.lastShortcutTriggeredTime).isEqualTo(clock.instant())
+ }
+
/** Test context which allows overriding getFilesDir path */
private class TestContext(context: Context, private val folder: File) :
SysuiTestableContext(context) {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt
index ec4fd79..b885800 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt
@@ -123,7 +123,7 @@
}
@Test
- @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR, Flags.FLAG_COMMUNAL_HUB)
fun testTransitionToLockscreen_onPowerButtonPress_canDream_glanceableHubAvailable() =
testScope.runTest {
whenever(kosmos.dreamManager.canStartDreaming(anyBoolean())).thenReturn(true)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt
index 50772ee..3075c54 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt
@@ -18,6 +18,7 @@
package com.android.systemui.keyguard.ui.viewmodel
+import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.FlagsParameterization
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -37,6 +38,7 @@
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.data.repository.shadeRepository
+import com.android.systemui.shade.shared.flag.DualShade
import com.android.systemui.testKosmos
import com.android.systemui.unfold.fakeUnfoldTransitionProgressProvider
import com.android.systemui.util.mockito.whenever
@@ -124,7 +126,50 @@
fun areNotificationsVisible_splitShadeTrue_true() =
with(kosmos) {
testScope.runTest {
- val areNotificationsVisible by collectLastValue(underTest.areNotificationsVisible)
+ val areNotificationsVisible by
+ collectLastValue(underTest.areNotificationsVisible(Scenes.Lockscreen))
+ shadeRepository.setShadeLayoutWide(true)
+ fakeKeyguardClockRepository.setClockSize(ClockSize.LARGE)
+
+ assertThat(areNotificationsVisible).isTrue()
+ }
+ }
+
+ @Test
+ @EnableFlags(DualShade.FLAG_NAME)
+ fun areNotificationsVisible_dualShadeWideOnLockscreen_true() =
+ with(kosmos) {
+ testScope.runTest {
+ val areNotificationsVisible by
+ collectLastValue(underTest.areNotificationsVisible(Scenes.Lockscreen))
+ shadeRepository.setShadeLayoutWide(true)
+ fakeKeyguardClockRepository.setClockSize(ClockSize.LARGE)
+
+ assertThat(areNotificationsVisible).isTrue()
+ }
+ }
+
+ @Test
+ @EnableFlags(DualShade.FLAG_NAME)
+ fun areNotificationsVisible_dualShadeWideOnNotificationsShade_false() =
+ with(kosmos) {
+ testScope.runTest {
+ val areNotificationsVisible by
+ collectLastValue(underTest.areNotificationsVisible(Scenes.NotificationsShade))
+ shadeRepository.setShadeLayoutWide(true)
+ fakeKeyguardClockRepository.setClockSize(ClockSize.LARGE)
+
+ assertThat(areNotificationsVisible).isFalse()
+ }
+ }
+
+ @Test
+ @EnableFlags(DualShade.FLAG_NAME)
+ fun areNotificationsVisible_dualShadeWideOnQuickSettingsShade_true() =
+ with(kosmos) {
+ testScope.runTest {
+ val areNotificationsVisible by
+ collectLastValue(underTest.areNotificationsVisible(Scenes.QuickSettingsShade))
shadeRepository.setShadeLayoutWide(true)
fakeKeyguardClockRepository.setClockSize(ClockSize.LARGE)
@@ -137,7 +182,8 @@
fun areNotificationsVisible_withSmallClock_true() =
with(kosmos) {
testScope.runTest {
- val areNotificationsVisible by collectLastValue(underTest.areNotificationsVisible)
+ val areNotificationsVisible by
+ collectLastValue(underTest.areNotificationsVisible(Scenes.Lockscreen))
fakeKeyguardClockRepository.setClockSize(ClockSize.SMALL)
assertThat(areNotificationsVisible).isTrue()
}
@@ -148,7 +194,8 @@
fun areNotificationsVisible_withLargeClock_false() =
with(kosmos) {
testScope.runTest {
- val areNotificationsVisible by collectLastValue(underTest.areNotificationsVisible)
+ val areNotificationsVisible by
+ collectLastValue(underTest.areNotificationsVisible(Scenes.Lockscreen))
fakeKeyguardClockRepository.setClockSize(ClockSize.LARGE)
assertThat(areNotificationsVisible).isFalse()
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractorTest.kt
index d7b7cfe..09dca25 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractorTest.kt
@@ -20,10 +20,6 @@
import android.os.UserHandle
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
-import android.platform.test.annotations.EnabledOnRavenwood
-import android.provider.Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS
-import android.provider.Settings.Global.ZEN_MODE_NO_INTERRUPTIONS
-import android.provider.Settings.Global.ZEN_MODE_OFF
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.settingslib.notification.data.repository.FakeZenModeRepository
@@ -42,7 +38,6 @@
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
-@EnabledOnRavenwood
@RunWith(AndroidJUnit4::class)
class ModesTileDataInteractorTest : SysuiTestCase() {
private val zenModeRepository = FakeZenModeRepository()
@@ -67,24 +62,28 @@
@EnableFlags(Flags.FLAG_MODES_UI)
@Test
- fun dataMatchesTheRepository() = runTest {
+ fun isActivatedWhenModesChange() = runTest {
val dataList: List<ModesTileModel> by
collectValues(underTest.tileData(TEST_USER, flowOf(DataUpdateTrigger.InitialRequest)))
runCurrent()
+ assertThat(dataList.map { it.isActivated }).containsExactly(false).inOrder()
- // Enable zen mode
- zenModeRepository.updateZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS)
+ // Add active mode
+ zenModeRepository.addMode(id = "One", active = true)
runCurrent()
+ assertThat(dataList.map { it.isActivated }).containsExactly(false, true).inOrder()
- // Change zen mode: it's still enabled, so this shouldn't cause another emission
- zenModeRepository.updateZenMode(ZEN_MODE_NO_INTERRUPTIONS)
+ // Add another mode: state hasn't changed, so this shouldn't cause another emission
+ zenModeRepository.addMode(id = "Two", active = true)
runCurrent()
+ assertThat(dataList.map { it.isActivated }).containsExactly(false, true).inOrder()
- // Disable zen mode
- zenModeRepository.updateZenMode(ZEN_MODE_OFF)
+ // Remove a mode and disable the other
+ zenModeRepository.removeMode("One")
runCurrent()
-
- assertThat(dataList.map { it.isActivated }).containsExactly(false, true, false)
+ zenModeRepository.deactivateMode("Two")
+ runCurrent()
+ assertThat(dataList.map { it.isActivated }).containsExactly(false, true, false).inOrder()
}
private companion object {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
index b5e47d1..fd1b213 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
@@ -85,7 +85,6 @@
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -182,7 +181,6 @@
kosmos.headsUpNotificationRepository.activeHeadsUpRows.value =
buildNotificationRows(isPinned = false)
- advanceTimeBy(50L) // account for HeadsUpNotificationInteractor debounce
assertThat(isVisible).isFalse()
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractorTest.kt
index 5ef3485..8b4265f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractorTest.kt
@@ -23,7 +23,6 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.coroutines.collectValues
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
@@ -280,64 +279,6 @@
}
@Test
- fun isHeadsUpOrAnimatingAway_falseOnStart() =
- testScope.runTest {
- val isHeadsUpOrAnimatingAway by collectLastValue(underTest.isHeadsUpOrAnimatingAway)
-
- runCurrent()
-
- assertThat(isHeadsUpOrAnimatingAway).isFalse()
- }
-
- @Test
- fun isHeadsUpOrAnimatingAway_hasPinnedRows() =
- testScope.runTest {
- val isHeadsUpOrAnimatingAway by collectLastValue(underTest.isHeadsUpOrAnimatingAway)
-
- // WHEN a row is pinned
- headsUpRepository.setNotifications(fakeHeadsUpRowRepository("key 0", isPinned = true))
- runCurrent()
-
- assertThat(isHeadsUpOrAnimatingAway).isTrue()
- }
-
- @Test
- fun isHeadsUpOrAnimatingAway_headsUpAnimatingAway() =
- testScope.runTest {
- val isHeadsUpOrAnimatingAway by collectLastValue(underTest.isHeadsUpOrAnimatingAway)
-
- // WHEN the last row is animating away
- headsUpRepository.setHeadsUpAnimatingAway(true)
- runCurrent()
-
- assertThat(isHeadsUpOrAnimatingAway).isTrue()
- }
-
- @Test
- fun isHeadsUpOrAnimatingAway_headsUpAnimatingAwayDebounced() =
- testScope.runTest {
- val values by collectValues(underTest.isHeadsUpOrAnimatingAway)
-
- // GIVEN a row is pinned
- headsUpRepository.setNotifications(fakeHeadsUpRowRepository("key 0", isPinned = true))
- runCurrent()
- assertThat(values.size).isEqualTo(2)
- assertThat(values.first()).isFalse() // initial value
- assertThat(values.last()).isTrue()
-
- // WHEN the last row is removed
- headsUpRepository.setNotifications(emptyList())
- runCurrent()
- // AND starts to animate away
- headsUpRepository.setHeadsUpAnimatingAway(true)
- runCurrent()
-
- // THEN isHeadsUpOrAnimatingAway remained true
- assertThat(values.size).isEqualTo(2)
- assertThat(values.last()).isTrue()
- }
-
- @Test
fun showHeadsUpStatusBar_true() =
testScope.runTest {
val showHeadsUpStatusBar by collectLastValue(underTest.showHeadsUpStatusBar)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
index ba7ddce..f8e6337 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
@@ -633,7 +633,7 @@
@Test
@EnableFlags(NotificationsHeadsUpRefactor.FLAG_NAME)
- fun headsUpAnimationsEnabled_keyguardShowing_false() =
+ fun headsUpAnimationsEnabled_keyguardShowing_true() =
testScope.runTest {
val animationsEnabled by collectLastValue(underTest.headsUpAnimationsEnabled)
@@ -641,6 +641,6 @@
fakeKeyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
runCurrent()
- assertThat(animationsEnabled).isFalse()
+ assertThat(animationsEnabled).isTrue()
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImplTest.kt
index ccd78ee..59e8ea6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImplTest.kt
@@ -40,6 +40,7 @@
import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
import com.android.systemui.keyguard.KeyguardViewMediator
import com.android.systemui.keyguard.WakefulnessLifecycle
+import com.android.systemui.plugins.ActivityStarter.OnDismissAction
import com.android.systemui.settings.UserTracker
import com.android.systemui.shade.ShadeController
import com.android.systemui.shade.data.repository.FakeShadeRepository
@@ -136,7 +137,9 @@
communalSceneInteractor = communalSceneInteractor,
)
`when`(userTracker.userHandle).thenReturn(UserHandle.OWNER)
+ `when`(communalSceneInteractor.isCommunalVisible).thenReturn(MutableStateFlow(false))
`when`(communalSceneInteractor.isIdleOnCommunal).thenReturn(MutableStateFlow(false))
+ `when`(communalSceneInteractor.isLaunchingWidget).thenReturn(MutableStateFlow(false))
}
@Test
@@ -335,6 +338,102 @@
)
}
+ @EnableFlags(Flags.FLAG_COMMUNAL_HUB)
+ @Test
+ fun startPendingIntentDismissingKeyguard_transitionAnimator_animateCommunal() {
+ val parent = FrameLayout(context)
+ val view =
+ object : View(context), LaunchableView {
+ override fun setShouldBlockVisibilityChanges(block: Boolean) {}
+ }
+ parent.addView(view)
+ val controller = ActivityTransitionAnimator.Controller.fromView(view)
+ val pendingIntent = mock(PendingIntent::class.java)
+ `when`(pendingIntent.isActivity).thenReturn(true)
+ `when`(keyguardStateController.isShowing).thenReturn(true)
+ `when`(keyguardStateController.isOccluded).thenReturn(true)
+ `when`(communalSceneInteractor.isCommunalVisible).thenReturn(MutableStateFlow(true))
+ `when`(communalSceneInteractor.isLaunchingWidget).thenReturn(MutableStateFlow(true))
+ `when`(activityIntentHelper.wouldPendingLaunchResolverActivity(eq(pendingIntent), anyInt()))
+ .thenReturn(false)
+ `when`(activityIntentHelper.wouldPendingShowOverLockscreen(eq(pendingIntent), anyInt()))
+ .thenReturn(false)
+
+ underTest.startPendingIntentDismissingKeyguard(
+ intent = pendingIntent,
+ dismissShade = false,
+ animationController = controller,
+ showOverLockscreen = true,
+ skipLockscreenChecks = false
+ )
+ mainExecutor.runAllReady()
+
+ val actionCaptor = argumentCaptor<OnDismissAction>()
+ verify(statusBarKeyguardViewManager)
+ .dismissWithAction(actionCaptor.capture(), eq(null), anyBoolean(), eq(null))
+ actionCaptor.firstValue.onDismiss()
+ mainExecutor.runAllReady()
+
+ verify(activityTransitionAnimator)
+ .startPendingIntentWithAnimation(
+ nullable(ActivityTransitionAnimator.Controller::class.java),
+ eq(true),
+ nullable(String::class.java),
+ eq(false),
+ any(),
+ )
+ }
+
+ @DisableFlags(Flags.FLAG_COMMUNAL_HUB)
+ @Test
+ fun startPendingIntentDismissingKeyguard_transitionAnimator_doNotAnimateCommunal() {
+ val parent = FrameLayout(context)
+ val view =
+ object : View(context), LaunchableView {
+ override fun setShouldBlockVisibilityChanges(block: Boolean) {}
+ }
+ parent.addView(view)
+ val controller = ActivityTransitionAnimator.Controller.fromView(view)
+ val pendingIntent = mock(PendingIntent::class.java)
+ `when`(pendingIntent.isActivity).thenReturn(true)
+ `when`(keyguardStateController.isShowing).thenReturn(true)
+ `when`(keyguardStateController.isOccluded).thenReturn(true)
+ `when`(communalSceneInteractor.isCommunalVisible).thenReturn(MutableStateFlow(true))
+ `when`(communalSceneInteractor.isLaunchingWidget).thenReturn(MutableStateFlow(true))
+ `when`(activityIntentHelper.wouldPendingLaunchResolverActivity(eq(pendingIntent), anyInt()))
+ .thenReturn(false)
+ `when`(activityIntentHelper.wouldPendingShowOverLockscreen(eq(pendingIntent), anyInt()))
+ .thenReturn(false)
+
+ underTest.startPendingIntentDismissingKeyguard(
+ intent = pendingIntent,
+ dismissShade = false,
+ animationController = controller,
+ showOverLockscreen = true,
+ skipLockscreenChecks = false
+ )
+ mainExecutor.runAllReady()
+
+ val actionCaptor = argumentCaptor<OnDismissAction>()
+ verify(statusBarKeyguardViewManager)
+ .dismissWithAction(actionCaptor.capture(), eq(null), anyBoolean(), eq(null))
+ actionCaptor.firstValue.onDismiss()
+ mainExecutor.runAllReady()
+
+ val runnableCaptor = argumentCaptor<Runnable>()
+ verify(statusBarKeyguardViewManager).addAfterKeyguardGoneRunnable(runnableCaptor.capture())
+ runnableCaptor.firstValue.run()
+
+ verify(activityTransitionAnimator)
+ .startPendingIntentWithAnimation(
+ nullable(ActivityTransitionAnimator.Controller::class.java),
+ eq(false),
+ nullable(String::class.java),
+ eq(false),
+ any(),
+ )
+ }
+
@Test
fun startActivity_noUserHandleProvided_getUserHandle() {
val intent = mock(Intent::class.java)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.kt
index 663cf1c..d0ddbff 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.kt
@@ -22,11 +22,15 @@
import androidx.test.filters.SmallTest
import com.android.internal.logging.UiEventLogger
import com.android.systemui.dump.DumpManager
+import com.android.systemui.kosmos.testScope
import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.res.R
import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.statusbar.FakeStatusBarStateController
import com.android.systemui.statusbar.NotificationShadeWindowController
+import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager
import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun
@@ -34,13 +38,17 @@
import com.android.systemui.statusbar.phone.ConfigurationControllerImpl
import com.android.systemui.statusbar.phone.HeadsUpManagerPhone
import com.android.systemui.statusbar.phone.KeyguardBypassController
+import com.android.systemui.testKosmos
import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.systemui.util.concurrency.mockExecutorHandler
import com.android.systemui.util.kotlin.JavaAdapter
import com.android.systemui.util.settings.GlobalSettings
import com.android.systemui.util.time.SystemClock
import junit.framework.Assert
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Ignore
import org.junit.Test
@@ -51,6 +59,7 @@
import platform.test.runner.parameterized.ParameterizedAndroidJunit4
import platform.test.runner.parameterized.Parameters
+@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(ParameterizedAndroidJunit4::class)
@RunWithLooper
@@ -58,6 +67,9 @@
private val mHeadsUpManagerLogger = HeadsUpManagerLogger(logcatLogBuffer())
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+
@Mock private lateinit var mGroupManager: GroupMembershipManager
@Mock private lateinit var mVSProvider: VisualStabilityProvider
@@ -72,7 +84,7 @@
@Mock private lateinit var mUiEventLogger: UiEventLogger
- @Mock private lateinit var mJavaAdapter: JavaAdapter
+ private val mJavaAdapter: JavaAdapter = JavaAdapter(testScope.backgroundScope)
@Mock private lateinit var mShadeInteractor: ShadeInteractor
@@ -120,6 +132,11 @@
mMinimumDisplayTime = TEST_MINIMUM_DISPLAY_TIME
mAutoDismissTime = TEST_AUTO_DISMISS_TIME
}
+
+ /** Wrapper for [BaseHeadsUpManager.shouldHeadsUpBecomePinned] for testing */
+ fun shouldHeadsUpBecomePinnedWrapper(entry: NotificationEntry): Boolean {
+ return shouldHeadsUpBecomePinned(entry)
+ }
}
private fun createHeadsUpManagerPhone(): HeadsUpManagerPhone {
@@ -219,6 +236,196 @@
Assert.assertTrue(hmp.isHeadsUpEntry(entry.key))
}
+ @Test
+ fun shouldHeadsUpBecomePinned_shadeNotExpanded_true() =
+ testScope.runTest {
+ // GIVEN
+ val statusBarStateController = FakeStatusBarStateController()
+ whenever(mShadeInteractor.isAnyFullyExpanded).thenReturn(MutableStateFlow(false))
+ val hmp =
+ TestableHeadsUpManagerPhone(
+ mContext,
+ mHeadsUpManagerLogger,
+ mGroupManager,
+ mVSProvider,
+ statusBarStateController,
+ mBypassController,
+ mConfigurationController,
+ mGlobalSettings,
+ mSystemClock,
+ mExecutor,
+ mAccessibilityManagerWrapper,
+ mUiEventLogger,
+ mJavaAdapter,
+ mShadeInteractor,
+ mAvalancheController
+ )
+ val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+ statusBarStateController.setState(StatusBarState.SHADE)
+ runCurrent()
+
+ // THEN
+ Assert.assertTrue(hmp.shouldHeadsUpBecomePinnedWrapper(entry))
+ }
+
+ @Test
+ fun shouldHeadsUpBecomePinned_shadeLocked_false() =
+ testScope.runTest {
+ // GIVEN
+ val statusBarStateController = FakeStatusBarStateController()
+ val hmp =
+ TestableHeadsUpManagerPhone(
+ mContext,
+ mHeadsUpManagerLogger,
+ mGroupManager,
+ mVSProvider,
+ statusBarStateController,
+ mBypassController,
+ mConfigurationController,
+ mGlobalSettings,
+ mSystemClock,
+ mExecutor,
+ mAccessibilityManagerWrapper,
+ mUiEventLogger,
+ mJavaAdapter,
+ mShadeInteractor,
+ mAvalancheController
+ )
+ val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+ statusBarStateController.setState(StatusBarState.SHADE_LOCKED)
+ runCurrent()
+
+ // THEN
+ Assert.assertFalse(hmp.shouldHeadsUpBecomePinnedWrapper(entry))
+ }
+
+ @Test
+ fun shouldHeadsUpBecomePinned_shadeUnknown_false() =
+ testScope.runTest {
+ // GIVEN
+ val statusBarStateController = FakeStatusBarStateController()
+ val hmp =
+ TestableHeadsUpManagerPhone(
+ mContext,
+ mHeadsUpManagerLogger,
+ mGroupManager,
+ mVSProvider,
+ statusBarStateController,
+ mBypassController,
+ mConfigurationController,
+ mGlobalSettings,
+ mSystemClock,
+ mExecutor,
+ mAccessibilityManagerWrapper,
+ mUiEventLogger,
+ mJavaAdapter,
+ mShadeInteractor,
+ mAvalancheController
+ )
+ val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+ statusBarStateController.setState(1207)
+ runCurrent()
+
+ // THEN
+ Assert.assertFalse(hmp.shouldHeadsUpBecomePinnedWrapper(entry))
+ }
+
+ @Test
+ fun shouldHeadsUpBecomePinned_keyguardWithBypassOn_true() =
+ testScope.runTest {
+ // GIVEN
+ val statusBarStateController = FakeStatusBarStateController()
+ whenever(mBypassController.bypassEnabled).thenReturn(true)
+ val hmp =
+ TestableHeadsUpManagerPhone(
+ mContext,
+ mHeadsUpManagerLogger,
+ mGroupManager,
+ mVSProvider,
+ statusBarStateController,
+ mBypassController,
+ mConfigurationController,
+ mGlobalSettings,
+ mSystemClock,
+ mExecutor,
+ mAccessibilityManagerWrapper,
+ mUiEventLogger,
+ mJavaAdapter,
+ mShadeInteractor,
+ mAvalancheController
+ )
+ val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+ statusBarStateController.setState(StatusBarState.KEYGUARD)
+ runCurrent()
+
+ // THEN
+ Assert.assertTrue(hmp.shouldHeadsUpBecomePinnedWrapper(entry))
+ }
+
+ @Test
+ fun shouldHeadsUpBecomePinned_keyguardWithBypassOff_false() =
+ testScope.runTest {
+ // GIVEN
+ val statusBarStateController = FakeStatusBarStateController()
+ whenever(mBypassController.bypassEnabled).thenReturn(false)
+ val hmp =
+ TestableHeadsUpManagerPhone(
+ mContext,
+ mHeadsUpManagerLogger,
+ mGroupManager,
+ mVSProvider,
+ statusBarStateController,
+ mBypassController,
+ mConfigurationController,
+ mGlobalSettings,
+ mSystemClock,
+ mExecutor,
+ mAccessibilityManagerWrapper,
+ mUiEventLogger,
+ mJavaAdapter,
+ mShadeInteractor,
+ mAvalancheController
+ )
+ val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+ statusBarStateController.setState(StatusBarState.KEYGUARD)
+ runCurrent()
+
+ // THEN
+ Assert.assertFalse(hmp.shouldHeadsUpBecomePinnedWrapper(entry))
+ }
+
+ @Test
+ fun shouldHeadsUpBecomePinned_shadeExpanded_false() =
+ testScope.runTest {
+ // GIVEN
+ val statusBarStateController = FakeStatusBarStateController()
+ whenever(mShadeInteractor.isAnyExpanded).thenReturn(MutableStateFlow(true))
+ val hmp =
+ TestableHeadsUpManagerPhone(
+ mContext,
+ mHeadsUpManagerLogger,
+ mGroupManager,
+ mVSProvider,
+ statusBarStateController,
+ mBypassController,
+ mConfigurationController,
+ mGlobalSettings,
+ mSystemClock,
+ mExecutor,
+ mAccessibilityManagerWrapper,
+ mUiEventLogger,
+ mJavaAdapter,
+ mShadeInteractor,
+ mAvalancheController
+ )
+ val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+ statusBarStateController.setState(StatusBarState.SHADE)
+ runCurrent()
+
+ // THEN
+ Assert.assertFalse(hmp.shouldHeadsUpBecomePinnedWrapper(entry))
+ }
+
companion object {
@get:Parameters(name = "{0}")
val flags: List<FlagsParameterization>
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt
index c7998f0..4812ff0 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt
@@ -50,8 +50,8 @@
/** Initializes and returns the target clock design */
fun createClock(settings: ClockSettings): ClockController
- /** A static thumbnail for rendering in some examples */
- fun getClockThumbnail(id: ClockId): Drawable?
+ /** Settings configuration parameters for the clock */
+ fun getClockPickerConfig(id: ClockId): ClockPickerConfig
}
/** Interface for controlling an active clock */
@@ -133,6 +133,7 @@
// both small and large clock should have a container (RelativeLayout in
// SimpleClockFaceController)
override val views = listOf(view)
+
override fun applyConstraints(constraints: ConstraintSet): ConstraintSet {
if (views.size != 1) {
throw IllegalArgumentException(
@@ -267,6 +268,25 @@
val clockId: ClockId,
)
+data class ClockPickerConfig(
+ val id: String,
+
+ /** Localized name of the clock */
+ val name: String,
+
+ /** Localized accessibility description for the clock */
+ val description: String,
+
+ /* Static & lightweight thumbnail version of the clock */
+ val thumbnail: Drawable,
+
+ /** True if the clock will react to tone changes in the seed color */
+ val isReactiveToTone: Boolean = true,
+
+ /** True if the clock is capable of chagning style in reaction to touches */
+ val isReactiveToTouch: Boolean = false,
+)
+
/** Render configuration for the full clock. Modifies the way systemUI behaves with this clock. */
data class ClockConfig(
val id: String,
@@ -280,7 +300,7 @@
/** Transition to AOD should move smartspace like large clock instead of small clock */
val useAlternateSmartspaceAODTransition: Boolean = false,
- /** True if the clock will react to tone changes in the seed color. */
+ @Deprecated("TODO(b/352049256): Remove")
val isReactiveToTone: Boolean = true,
/** True if the clock is large frame clock, which will use weather in compose. */
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 7f7e634..30f23bf 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -287,7 +287,8 @@
<integer name="doze_small_icon_alpha">222</integer><!-- 87% of 0xff -->
<!-- Doze: Table that translates sensor values from the doze_brightness_sensor_type sensor
- to brightness values; -1 means keeping the current brightness. -->
+ to brightness values in the integer scale [1, 255]; -1 means keeping the current
+ brightness. -->
<integer-array name="config_doze_brightness_sensor_to_brightness">
<item>-1</item> <!-- 0: OFF -->
<item>2</item> <!-- 1: NIGHT -->
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 52e5dea..2bd97d9 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1873,7 +1873,7 @@
<string name="notification_channel_summary_low">No sound or vibration</string>
<!-- [CHAR LIMIT=150] Notification Importance title: low importance level summary -->
- <string name="notification_conversation_summary_low">No sound or vibration and appears lower in conversation section</string>
+ <string name="notification_conversation_summary_low">No sound or vibration but still appears in the conversation section</string>
<!-- [CHAR LIMIT=150] Notification Importance title: normal importance level summary -->
<string name="notification_channel_summary_default">May ring or vibrate based on device settings</string>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
index 4ef1f93..484e758 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
@@ -124,8 +124,6 @@
public static final long SYSUI_STATE_SHORTCUT_HELPER_SHOWING = 1L << 32;
// Touchpad gestures are disabled
public static final long SYSUI_STATE_TOUCHPAD_GESTURES_DISABLED = 1L << 33;
- // PiP animation is running
- public static final long SYSUI_STATE_DISABLE_GESTURE_PIP_ANIMATING = 1L << 34;
// Communal hub is showing
public static final long SYSUI_STATE_COMMUNAL_HUB_SHOWING = 1L << 35;
@@ -177,7 +175,6 @@
SYSUI_STATE_STATUS_BAR_KEYGUARD_GOING_AWAY,
SYSUI_STATE_SHORTCUT_HELPER_SHOWING,
SYSUI_STATE_TOUCHPAD_GESTURES_DISABLED,
- SYSUI_STATE_DISABLE_GESTURE_PIP_ANIMATING,
SYSUI_STATE_COMMUNAL_HUB_SHOWING,
})
public @interface SystemUiStateFlags {}
@@ -283,9 +280,6 @@
if ((flags & SYSUI_STATE_TOUCHPAD_GESTURES_DISABLED) != 0) {
str.add("touchpad_gestures_disabled");
}
- if ((flags & SYSUI_STATE_DISABLE_GESTURE_PIP_ANIMATING) != 0) {
- str.add("disable_gesture_pip_animating");
- }
if ((flags & SYSUI_STATE_COMMUNAL_HUB_SHOWING) != 0) {
str.add("communal_hub_showing");
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
index bde6f42..6b58c07 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
@@ -105,7 +105,13 @@
.mapLatest(::determineSceneAfterTransition)
.filterNotNull()
.onEach { (nextScene, nextTransition) ->
- communalSceneInteractor.changeScene(nextScene, nextTransition)
+ if (!communalSceneInteractor.isLaunchingWidget.value) {
+ // When launching a widget, we don't want to animate the scene change or the
+ // Communal Hub will reveal the wallpaper even though it shouldn't. Instead we
+ // snap to the new scene as part of the launch animation, once the activity
+ // launch is done, so we don't change scene here.
+ communalSceneInteractor.changeScene(nextScene, nextTransition)
+ }
}
.launchIn(applicationScope)
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt b/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt
index 4dcd9bf..933a25a 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt
@@ -27,7 +27,7 @@
import com.android.systemui.communal.nano.CommunalHubState
import com.android.systemui.communal.widgets.CommunalWidgetHost
import com.android.systemui.communal.widgets.CommunalWidgetModule.Companion.DEFAULT_WIDGETS
-import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.Logger
@@ -35,21 +35,19 @@
import javax.inject.Inject
import javax.inject.Named
import javax.inject.Provider
-import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.launch
-import kotlinx.coroutines.withContext
/**
* Callback that will be invoked when the Room database is created. Then the database will be
* populated with pre-configured default widgets to be rendered in the glanceable hub.
*/
+@SysUISingleton
class DefaultWidgetPopulation
@Inject
constructor(
- @Application private val applicationScope: CoroutineScope,
- @Background private val bgDispatcher: CoroutineDispatcher,
+ @Background private val bgScope: CoroutineScope,
private val communalWidgetHost: CommunalWidgetHost,
private val communalWidgetDaoProvider: Provider<CommunalWidgetDao>,
@Named(DEFAULT_WIDGETS) private val defaultWidgets: Array<String>,
@@ -62,36 +60,43 @@
private val logger = Logger(logBuffer, TAG)
+ /**
+ * Reason for skipping default widgets population. Do not skip if this value is
+ * [SkipReason.NONE].
+ */
+ private var skipReason = SkipReason.NONE
+
override fun onCreate(db: SupportSQLiteDatabase) {
super.onCreate(db)
- applicationScope.launch { addDefaultWidgets() }
- }
- // Read default widgets from config.xml and populate the database.
- private suspend fun addDefaultWidgets() =
- withContext(bgDispatcher) {
+ if (skipReason != SkipReason.NONE) {
+ logger.i("Skipped populating default widgets. Reason: $skipReason")
+ return
+ }
+
+ bgScope.launch {
// Default widgets should be associated with the main user.
- val userSerialNumber =
- userManager.mainUser?.let { mainUser ->
- userManager.getUserSerialNumber(mainUser.identifier)
- }
- if (userSerialNumber == null) {
+ val user = userManager.mainUser
+
+ if (user == null) {
logger.w(
- "Skipped populating default widgets because device does not have a main user"
+ "Skipped populating default widgets. Reason: device does not have a main user"
)
- return@withContext
+ return@launch
}
+ val userSerialNumber = userManager.getUserSerialNumber(user.identifier)
+
defaultWidgets.forEachIndexed { index, name ->
val provider = ComponentName.unflattenFromString(name)
provider?.let {
- val id = communalWidgetHost.allocateIdAndBindWidget(provider)
+ val id = communalWidgetHost.allocateIdAndBindWidget(provider, user)
id?.let {
communalWidgetDaoProvider
.get()
.addWidget(
widgetId = id,
- provider = provider,
+ componentName = name,
priority = defaultWidgets.size - index,
userSerialNumber = userSerialNumber,
)
@@ -101,6 +106,25 @@
logger.i("Populated default widgets in the database.")
}
+ }
+
+ /**
+ * Skip populating default widgets in the Glanceable Hub when the database is created. This has
+ * no effect if default widgets have been populated already.
+ *
+ * @param skipReason Reason for skipping the default widgets population.
+ */
+ fun skipDefaultWidgetsPopulation(skipReason: SkipReason) {
+ this.skipReason = skipReason
+ }
+
+ /** Reason for skipping default widgets population. */
+ enum class SkipReason {
+ /** Do not skip. */
+ NONE,
+ /** Widgets are restored from a backup. */
+ RESTORED_FROM_BACKUP,
+ }
}
@Dao
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt
index e3ef6bb..748c4fa 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt
@@ -20,6 +20,7 @@
import android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_WIDGETS_ALL
import android.content.IntentFilter
import android.content.pm.UserInfo
+import android.os.UserHandle
import android.provider.Settings
import com.android.systemui.Flags.communalHub
import com.android.systemui.broadcast.BroadcastDispatcher
@@ -102,7 +103,10 @@
.broadcastFlow(
filter =
IntentFilter(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED),
- user = user.userHandle
+ // In COPE management mode, the restriction from the managed profile may
+ // propagate to the main profile. Therefore listen to this broadcast across
+ // all users and update the state each time it changes.
+ user = UserHandle.ALL,
)
.emitOnStart()
.map { devicePolicyManager.areKeyguardWidgetsAllowed(user.id) }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
index ab4c9d2..e65e5e5 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
@@ -26,6 +26,8 @@
import com.android.systemui.communal.data.backup.CommunalBackupUtils
import com.android.systemui.communal.data.db.CommunalWidgetDao
import com.android.systemui.communal.data.db.CommunalWidgetItem
+import com.android.systemui.communal.data.db.DefaultWidgetPopulation
+import com.android.systemui.communal.data.db.DefaultWidgetPopulation.SkipReason.RESTORED_FROM_BACKUP
import com.android.systemui.communal.nano.CommunalHubState
import com.android.systemui.communal.proto.toCommunalHubState
import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
@@ -101,6 +103,7 @@
private val backupUtils: CommunalBackupUtils,
packageChangeRepository: PackageChangeRepository,
private val userManager: UserManager,
+ private val defaultWidgetPopulation: DefaultWidgetPopulation,
) : CommunalWidgetRepository {
companion object {
const val TAG = "CommunalWidgetRepository"
@@ -321,6 +324,9 @@
}
val newState = CommunalHubState().apply { widgets = newWidgets.toTypedArray() }
+ // Skip default widgets population
+ defaultWidgetPopulation.skipDefaultWidgetsPopulation(RESTORED_FROM_BACKUP)
+
// Restore database
logger.i("Restoring communal database:\n$newState")
communalWidgetDao.restoreCommunalHubState(newState)
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 597a2ce..3fffd76 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
@@ -541,4 +541,29 @@
)
}
}
+
+ /**
+ * {@link #setScrollPosition} persists the current communal grid scroll position (to volatile
+ * memory) so that the next presentation of the grid (either as glanceable hub or edit mode) can
+ * restore position.
+ */
+ fun setScrollPosition(firstVisibleItemIndex: Int, firstVisibleItemOffset: Int) {
+ _firstVisibleItemIndex = firstVisibleItemIndex
+ _firstVisibleItemOffset = firstVisibleItemOffset
+ }
+
+ fun resetScrollPosition() {
+ _firstVisibleItemIndex = 0
+ _firstVisibleItemOffset = 0
+ }
+
+ val firstVisibleItemIndex: Int
+ get() = _firstVisibleItemIndex
+
+ private var _firstVisibleItemIndex: Int = 0
+
+ val firstVisibleItemOffset: Int
+ get() = _firstVisibleItemOffset
+
+ private var _firstVisibleItemOffset: Int = 0
}
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 fd540c4..122f9647 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
@@ -48,6 +48,15 @@
@Application private val applicationScope: CoroutineScope,
private val communalSceneRepository: CommunalSceneRepository,
) {
+ val _isLaunchingWidget = MutableStateFlow(false)
+
+ /** Whether a widget launch is currently in progress. */
+ val isLaunchingWidget: StateFlow<Boolean> = _isLaunchingWidget.asStateFlow()
+
+ fun setIsLaunchingWidget(launching: Boolean) {
+ _isLaunchingWidget.value = launching
+ }
+
/**
* Asks for an asynchronous scene witch to [newScene], which will use the corresponding
* installed transition or the one specified by [transitionKey], if provided.
diff --git a/packages/SystemUI/src/com/android/systemui/communal/smartspace/SmartspaceInteractionHandler.kt b/packages/SystemUI/src/com/android/systemui/communal/smartspace/SmartspaceInteractionHandler.kt
index a88b777..4e3d3ff 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/smartspace/SmartspaceInteractionHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/smartspace/SmartspaceInteractionHandler.kt
@@ -22,21 +22,25 @@
import android.view.View
import android.widget.RemoteViews
import com.android.systemui.animation.ActivityTransitionAnimator
+import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
import com.android.systemui.communal.util.InteractionHandlerDelegate
import com.android.systemui.communal.widgets.SmartspaceAppWidgetHostView
import com.android.systemui.plugins.ActivityStarter
import javax.inject.Inject
-/**
- * Handles interactions on smartspace elements on the hub.
- */
-class SmartspaceInteractionHandler @Inject constructor(
+/** Handles interactions on smartspace elements on the hub. */
+class SmartspaceInteractionHandler
+@Inject
+constructor(
private val activityStarter: ActivityStarter,
+ communalSceneInteractor: CommunalSceneInteractor,
) : RemoteViews.InteractionHandler {
- private val delegate = InteractionHandlerDelegate(
- findViewToAnimate = { view -> view is SmartspaceAppWidgetHostView },
- intentStarter = this::startIntent,
- )
+ private val delegate =
+ InteractionHandlerDelegate(
+ communalSceneInteractor,
+ findViewToAnimate = { view -> view is SmartspaceAppWidgetHostView },
+ intentStarter = this::startIntent,
+ )
override fun onInteraction(
view: View,
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 6ec6ec1..19d7ceb 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
@@ -59,6 +59,18 @@
/** Accessibility delegate to be set on CommunalAppWidgetHostView. */
open val widgetAccessibilityDelegate: View.AccessibilityDelegate? = null
+ /**
+ * The up-to-date value of the grid scroll offset. persisted to interactor on
+ * {@link #persistScrollPosition}
+ */
+ private var currentScrollOffset = 0
+
+ /**
+ * The up-to-date value of the grid scroll index. persisted to interactor on
+ * {@link #persistScrollPosition}
+ */
+ private var currentScrollIndex = 0
+
fun signalUserInteraction() {
communalInteractor.signalUserInteraction()
}
@@ -147,6 +159,28 @@
/** Called as the user request to show the customize widget button. */
open fun onLongClick() {}
+ /** Called when the grid scroll position has been updated. */
+ open fun onScrollPositionUpdated(firstVisibleItemIndex: Int, firstVisibleItemScroll: Int) {
+ currentScrollIndex = firstVisibleItemIndex
+ currentScrollOffset = firstVisibleItemScroll
+ }
+
+ /** Stores scroll values to interactor. */
+ protected fun persistScrollPosition() {
+ communalInteractor.setScrollPosition(currentScrollIndex, currentScrollOffset)
+ }
+
+ /** Invoked after scroll values are used to initialize grid position. */
+ open fun clearPersistedScrollPosition() {
+ communalInteractor.setScrollPosition(0, 0)
+ }
+
+ val savedFirstScrollIndex: Int
+ get() = communalInteractor.firstVisibleItemIndex
+
+ val savedFirstScrollOffset: Int
+ get() = communalInteractor.firstVisibleItemOffset
+
/** Set the key of the currently selected item */
fun setSelectedKey(key: String?) {
_selectedKey.value = key
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
index 91f4c1c..7b0aadf 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
@@ -220,6 +220,9 @@
/** Called when exiting the edit mode, before transitioning back to the communal scene. */
fun cleanupEditModeState() {
communalSceneInteractor.setEditModeState(null)
+
+ // Set the scroll position of the glanceable hub to match where we are now.
+ persistScrollPosition()
}
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
index 780bf70..02ecfe1 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
@@ -233,7 +233,10 @@
override fun onOpenWidgetEditor(
shouldOpenWidgetPickerOnStart: Boolean,
- ) = communalInteractor.showWidgetEditor(selectedKey.value, shouldOpenWidgetPickerOnStart)
+ ) {
+ persistScrollPosition()
+ communalInteractor.showWidgetEditor(selectedKey.value, shouldOpenWidgetPickerOnStart)
+ }
override fun onDismissCtaTile() {
scope.launch {
diff --git a/packages/SystemUI/src/com/android/systemui/communal/util/InteractionHandlerDelegate.kt b/packages/SystemUI/src/com/android/systemui/communal/util/InteractionHandlerDelegate.kt
index 40b182d..51a5fcd 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/util/InteractionHandlerDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/util/InteractionHandlerDelegate.kt
@@ -24,17 +24,17 @@
import androidx.core.util.component1
import androidx.core.util.component2
import com.android.systemui.animation.ActivityTransitionAnimator
-
+import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
+import com.android.systemui.communal.widgets.CommunalTransitionAnimatorController
/** A delegate that can be used to launch activities from [RemoteViews] */
class InteractionHandlerDelegate(
+ private val communalSceneInteractor: CommunalSceneInteractor,
private val findViewToAnimate: (View) -> Boolean,
private val intentStarter: IntentStarter,
) : RemoteViews.InteractionHandler {
- /**
- * Responsible for starting the pending intent for launching activities.
- */
+ /** Responsible for starting the pending intent for launching activities. */
fun interface IntentStarter {
fun startPendingIntent(
intent: PendingIntent,
@@ -57,7 +57,10 @@
// activities.
val hostView = getNearestParent(view)
val animationController =
- hostView?.let(ActivityTransitionAnimator.Controller::fromView)
+ hostView?.let(ActivityTransitionAnimator.Controller::fromView)?.let {
+ communalSceneInteractor.setIsLaunchingWidget(true)
+ CommunalTransitionAnimatorController(it, communalSceneInteractor)
+ }
val (fillInIntent, activityOptions) = launchOptions
intentStarter.startPendingIntent(
pendingIntent,
@@ -66,7 +69,6 @@
animationController
)
}
-
else -> RemoteViews.startPendingIntent(view, pendingIntent, launchOptions)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalTransitionAnimatorController.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalTransitionAnimatorController.kt
new file mode 100644
index 0000000..4efaf87
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalTransitionAnimatorController.kt
@@ -0,0 +1,50 @@
+/*
+ * 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.widgets
+
+import com.android.systemui.animation.ActivityTransitionAnimator
+import com.android.systemui.animation.DelegateTransitionAnimatorController
+import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
+import com.android.systemui.communal.shared.model.CommunalScenes
+
+/**
+ * An [ActivityTransitionAnimator.Controller] that takes care of updating the state of the Communal
+ * Hub at the right time.
+ */
+class CommunalTransitionAnimatorController(
+ delegate: ActivityTransitionAnimator.Controller,
+ private val communalSceneInteractor: CommunalSceneInteractor,
+) : DelegateTransitionAnimatorController(delegate) {
+ override fun onIntentStarted(willAnimate: Boolean) {
+ if (!willAnimate) {
+ // Other callbacks won't happen, so reset the state here.
+ communalSceneInteractor.setIsLaunchingWidget(false)
+ }
+ delegate.onIntentStarted(willAnimate)
+ }
+
+ override fun onTransitionAnimationCancelled(newKeyguardOccludedState: Boolean?) {
+ communalSceneInteractor.setIsLaunchingWidget(false)
+ delegate.onTransitionAnimationCancelled(newKeyguardOccludedState)
+ }
+
+ override fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) {
+ communalSceneInteractor.snapToScene(CommunalScenes.Blank)
+ communalSceneInteractor.setIsLaunchingWidget(false)
+ delegate.onTransitionAnimationEnd(isExpandingFullyAbove)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetInteractionHandler.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetInteractionHandler.kt
index 72f9180..519903e 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetInteractionHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetInteractionHandler.kt
@@ -22,6 +22,7 @@
import android.view.View
import android.widget.RemoteViews
import com.android.systemui.animation.ActivityTransitionAnimator
+import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
import com.android.systemui.communal.util.InteractionHandlerDelegate
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.plugins.ActivityStarter
@@ -32,10 +33,12 @@
@Inject
constructor(
private val activityStarter: ActivityStarter,
+ private val communalSceneInteractor: CommunalSceneInteractor
) : RemoteViews.InteractionHandler {
private val delegate =
InteractionHandlerDelegate(
+ communalSceneInteractor,
findViewToAnimate = { view -> view is CommunalAppWidgetHostView },
intentStarter = this::startIntent,
)
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
index 7ae8409..e07b5c2 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
@@ -48,6 +48,7 @@
import com.android.internal.logging.UiEvent;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.logging.UiEventLoggerImpl;
+import com.android.systemui.Flags;
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.plugins.SensorManagerPlugin;
import com.android.systemui.statusbar.phone.DozeParameters;
@@ -426,7 +427,11 @@
}
if (!anyListening) {
- mSecureSettings.unregisterContentObserverSync(mSettingsObserver);
+ if (Flags.registerContentObserversAsync()) {
+ mSecureSettings.unregisterContentObserverAsync(mSettingsObserver);
+ } else {
+ mSecureSettings.unregisterContentObserverSync(mSettingsObserver);
+ }
} else if (!mSettingRegistered) {
for (TriggerSensor s : mTriggerSensors) {
s.registerSettingsObserver(mSettingsObserver);
@@ -750,8 +755,13 @@
public void registerSettingsObserver(ContentObserver settingsObserver) {
if (mConfigured && !TextUtils.isEmpty(mSetting)) {
- mSecureSettings.registerContentObserverForUserSync(
- mSetting, mSettingsObserver, UserHandle.USER_ALL);
+ if (Flags.registerContentObserversAsync()) {
+ mSecureSettings.registerContentObserverForUserAsync(
+ mSetting, mSettingsObserver, UserHandle.USER_ALL);
+ } else {
+ mSecureSettings.registerContentObserverForUserSync(
+ mSetting, mSettingsObserver, UserHandle.USER_ALL);
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/education/dagger/ContextualEducationModule.kt b/packages/SystemUI/src/com/android/systemui/education/dagger/ContextualEducationModule.kt
index e2bcb6b..53b9261 100644
--- a/packages/SystemUI/src/com/android/systemui/education/dagger/ContextualEducationModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/education/dagger/ContextualEducationModule.kt
@@ -17,8 +17,12 @@
package com.android.systemui.education.dagger
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.education.data.repository.ContextualEducationRepository
+import com.android.systemui.education.data.repository.ContextualEducationRepositoryImpl
+import dagger.Binds
import dagger.Module
import dagger.Provides
+import java.time.Clock
import javax.inject.Qualifier
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
@@ -26,8 +30,15 @@
@Module
interface ContextualEducationModule {
+ @Binds
+ fun bindContextualEducationRepository(
+ impl: ContextualEducationRepositoryImpl
+ ): ContextualEducationRepository
+
@Qualifier annotation class EduDataStoreScope
+ @Qualifier annotation class EduClock
+
companion object {
@EduDataStoreScope
@Provides
@@ -36,5 +47,11 @@
): CoroutineScope {
return CoroutineScope(bgDispatcher + SupervisorJob())
}
+
+ @EduClock
+ @Provides
+ fun provideEduClock(): Clock {
+ return Clock.systemUTC()
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/education/data/model/GestureEduModel.kt b/packages/SystemUI/src/com/android/systemui/education/data/model/GestureEduModel.kt
index af35e8c..9f6cb4d 100644
--- a/packages/SystemUI/src/com/android/systemui/education/data/model/GestureEduModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/education/data/model/GestureEduModel.kt
@@ -16,11 +16,14 @@
package com.android.systemui.education.data.model
+import java.time.Instant
+
/**
* Model to store education data related to each gesture (e.g. Back, Home, All Apps, Overview). Each
* gesture stores its own model separately.
*/
data class GestureEduModel(
- val signalCount: Int,
- val educationShownCount: Int,
+ val signalCount: Int = 0,
+ val educationShownCount: Int = 0,
+ val lastShortcutTriggeredTime: Instant? = null,
)
diff --git a/packages/SystemUI/src/com/android/systemui/education/data/repository/ContextualEducationRepository.kt b/packages/SystemUI/src/com/android/systemui/education/data/repository/ContextualEducationRepository.kt
index c9dd833..248b7a5 100644
--- a/packages/SystemUI/src/com/android/systemui/education/data/repository/ContextualEducationRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/education/data/repository/ContextualEducationRepository.kt
@@ -17,26 +17,50 @@
package com.android.systemui.education.data.repository
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.education.dagger.ContextualEducationModule.EduClock
+import com.android.systemui.education.data.model.GestureEduModel
import com.android.systemui.shared.education.GestureType
+import java.time.Clock
import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+
+/** Encapsulates the functions of ContextualEducationRepository. */
+interface ContextualEducationRepository {
+ fun setUser(userId: Int)
+
+ fun readGestureEduModelFlow(gestureType: GestureType): Flow<GestureEduModel>
+
+ suspend fun incrementSignalCount(gestureType: GestureType)
+
+ suspend fun updateShortcutTriggerTime(gestureType: GestureType)
+}
/**
* Provide methods to read and update on field level and allow setting datastore when user is
* changed
*/
@SysUISingleton
-class ContextualEducationRepository
+class ContextualEducationRepositoryImpl
@Inject
-constructor(private val userEduRepository: UserContextualEducationRepository) {
+constructor(
+ @EduClock private val clock: Clock,
+ private val userEduRepository: UserContextualEducationRepository
+) : ContextualEducationRepository {
/** To change data store when user is changed */
- fun setUser(userId: Int) = userEduRepository.setUser(userId)
+ override fun setUser(userId: Int) = userEduRepository.setUser(userId)
- fun readGestureEduModelFlow(gestureType: GestureType) =
+ override fun readGestureEduModelFlow(gestureType: GestureType) =
userEduRepository.readGestureEduModelFlow(gestureType)
- suspend fun incrementSignalCount(gestureType: GestureType) {
+ override suspend fun incrementSignalCount(gestureType: GestureType) {
userEduRepository.updateGestureEduModel(gestureType) {
it.copy(signalCount = it.signalCount + 1)
}
}
+
+ override suspend fun updateShortcutTriggerTime(gestureType: GestureType) {
+ userEduRepository.updateGestureEduModel(gestureType) {
+ it.copy(lastShortcutTriggeredTime = clock.instant())
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/education/data/repository/UserContextualEducationRepository.kt b/packages/SystemUI/src/com/android/systemui/education/data/repository/UserContextualEducationRepository.kt
index 229511a..b7fc773 100644
--- a/packages/SystemUI/src/com/android/systemui/education/data/repository/UserContextualEducationRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/education/data/repository/UserContextualEducationRepository.kt
@@ -18,16 +18,19 @@
import android.content.Context
import androidx.datastore.core.DataStore
+import androidx.datastore.preferences.core.MutablePreferences
import androidx.datastore.preferences.core.PreferenceDataStoreFactory
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.intPreferencesKey
+import androidx.datastore.preferences.core.longPreferencesKey
import androidx.datastore.preferences.preferencesDataStoreFile
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.education.dagger.ContextualEducationModule.EduDataStoreScope
import com.android.systemui.education.data.model.GestureEduModel
import com.android.systemui.shared.education.GestureType
+import java.time.Instant
import javax.inject.Inject
import javax.inject.Provider
import kotlinx.coroutines.CoroutineScope
@@ -55,6 +58,7 @@
companion object {
const val SIGNAL_COUNT_SUFFIX = "_SIGNAL_COUNT"
const val NUMBER_OF_EDU_SHOWN_SUFFIX = "_NUMBER_OF_EDU_SHOWN"
+ const val LAST_SHORTCUT_TRIGGERED_TIME_SUFFIX = "_LAST_SHORTCUT_TRIGGERED_TIME"
const val DATASTORE_DIR = "education/USER%s_ContextualEducation"
}
@@ -91,6 +95,10 @@
return GestureEduModel(
signalCount = preferences[getSignalCountKey(gestureType)] ?: 0,
educationShownCount = preferences[getEducationShownCountKey(gestureType)] ?: 0,
+ lastShortcutTriggeredTime =
+ preferences[getLastShortcutTriggeredTimeKey(gestureType)]?.let {
+ Instant.ofEpochMilli(it)
+ },
)
}
@@ -103,6 +111,11 @@
val updatedModel = transform(currentModel)
preferences[getSignalCountKey(gestureType)] = updatedModel.signalCount
preferences[getEducationShownCountKey(gestureType)] = updatedModel.educationShownCount
+ updateTimeByInstant(
+ preferences,
+ updatedModel.lastShortcutTriggeredTime,
+ getLastShortcutTriggeredTimeKey(gestureType)
+ )
}
}
@@ -111,4 +124,19 @@
private fun getEducationShownCountKey(gestureType: GestureType): Preferences.Key<Int> =
intPreferencesKey(gestureType.name + NUMBER_OF_EDU_SHOWN_SUFFIX)
+
+ private fun getLastShortcutTriggeredTimeKey(gestureType: GestureType): Preferences.Key<Long> =
+ longPreferencesKey(gestureType.name + LAST_SHORTCUT_TRIGGERED_TIME_SUFFIX)
+
+ private fun updateTimeByInstant(
+ preferences: MutablePreferences,
+ instant: Instant?,
+ key: Preferences.Key<Long>
+ ) {
+ if (instant != null) {
+ preferences[key] = instant.toEpochMilli()
+ } else {
+ preferences.remove(key)
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
index 1e4fb4f..493afde 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
@@ -64,7 +64,6 @@
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
-import android.service.dreams.IDreamManager;
import android.sysprop.TelephonyProperties;
import android.telecom.TelecomManager;
import android.telephony.ServiceState;
@@ -197,7 +196,6 @@
private final Context mContext;
private final GlobalActionsManager mWindowManagerFuncs;
private final AudioManager mAudioManager;
- private final IDreamManager mDreamManager;
private final DevicePolicyManager mDevicePolicyManager;
private final LockPatternUtils mLockPatternUtils;
private final SelectedUserInteractor mSelectedUserInteractor;
@@ -345,7 +343,6 @@
Context context,
GlobalActionsManager windowManagerFuncs,
AudioManager audioManager,
- IDreamManager iDreamManager,
DevicePolicyManager devicePolicyManager,
LockPatternUtils lockPatternUtils,
BroadcastDispatcher broadcastDispatcher,
@@ -382,7 +379,6 @@
mContext = context;
mWindowManagerFuncs = windowManagerFuncs;
mAudioManager = audioManager;
- mDreamManager = iDreamManager;
mDevicePolicyManager = devicePolicyManager;
mLockPatternUtils = lockPatternUtils;
mTelephonyListenerManager = telephonyListenerManager;
@@ -510,20 +506,7 @@
mHandler.sendEmptyMessage(MESSAGE_DISMISS);
}
- protected void awakenIfNecessary() {
- if (mDreamManager != null) {
- try {
- if (mDreamManager.isDreaming()) {
- mDreamManager.awaken();
- }
- } catch (RemoteException e) {
- // we tried
- }
- }
- }
-
protected void handleShow(@Nullable Expandable expandable) {
- awakenIfNecessary();
mDialog = createDialog();
prepareDialog();
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
index 9e9368d..b482862 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
@@ -120,6 +120,7 @@
ShortcutHelperSinglePane(
modifier,
shortcutsUiState.shortcutCategories,
+ shortcutsUiState.defaultSelectedCategory,
onKeyboardSettingsClicked
)
} else {
@@ -146,6 +147,7 @@
private fun ShortcutHelperSinglePane(
modifier: Modifier = Modifier,
categories: List<ShortcutCategory>,
+ defaultSelectedCategory: ShortcutCategoryType,
onKeyboardSettingsClicked: () -> Unit,
) {
Column(
@@ -159,7 +161,7 @@
Spacer(modifier = Modifier.height(6.dp))
ShortcutsSearchBar()
Spacer(modifier = Modifier.height(16.dp))
- CategoriesPanelSinglePane(categories)
+ CategoriesPanelSinglePane(categories, defaultSelectedCategory)
Spacer(modifier = Modifier.weight(1f))
KeyboardSettings(onClick = onKeyboardSettingsClicked)
}
@@ -168,8 +170,10 @@
@Composable
private fun CategoriesPanelSinglePane(
categories: List<ShortcutCategory>,
+ defaultSelectedCategory: ShortcutCategoryType,
) {
- var expandedCategory by remember { mutableStateOf<ShortcutCategory?>(null) }
+ val selectedCategory = categories.firstOrNull { it.type == defaultSelectedCategory }
+ var expandedCategory by remember { mutableStateOf(selectedCategory) }
Column(verticalArrangement = Arrangement.spacedBy(2.dp)) {
categories.fastForEachIndexed { index, category ->
val isExpanded = expandedCategory == category
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModel.kt
index ad258f4..25574ea 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModel.kt
@@ -19,6 +19,9 @@
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.keyboard.shortcut.domain.interactor.ShortcutHelperCategoriesInteractor
import com.android.systemui.keyboard.shortcut.domain.interactor.ShortcutHelperStateInteractor
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategory
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.CurrentApp
import com.android.systemui.keyboard.shortcut.ui.model.ShortcutsUiState
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
@@ -52,7 +55,7 @@
} else {
ShortcutsUiState.Active(
shortcutCategories = it,
- defaultSelectedCategory = it.first().type,
+ defaultSelectedCategory = getDefaultSelectedCategory(it),
)
}
}
@@ -62,6 +65,13 @@
initialValue = ShortcutsUiState.Inactive
)
+ private fun getDefaultSelectedCategory(
+ categories: List<ShortcutCategory>
+ ): ShortcutCategoryType {
+ val currentAppShortcuts = categories.firstOrNull { it.type is CurrentApp }
+ return currentAppShortcuts?.type ?: categories.first().type
+ }
+
fun onViewClosed() {
stateInteractor.onViewClosed()
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index fe81b20c..e46a7cb 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -78,7 +78,6 @@
import android.os.Message;
import android.os.PowerManager;
import android.os.RemoteException;
-import android.os.ServiceManager;
import android.os.SystemProperties;
import android.os.Trace;
import android.os.UserHandle;
@@ -151,6 +150,7 @@
import com.android.systemui.log.SessionTracker;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.process.ProcessWrapper;
import com.android.systemui.res.R;
import com.android.systemui.scene.shared.flag.SceneContainerFlag;
import com.android.systemui.settings.UserTracker;
@@ -360,6 +360,7 @@
private final SecureSettings mSecureSettings;
private final SystemSettings mSystemSettings;
private final SystemClock mSystemClock;
+ private final ProcessWrapper mProcessWrapper;
private final SystemPropertiesHelper mSystemPropertiesHelper;
/**
@@ -1459,10 +1460,12 @@
Lazy<ActivityTransitionAnimator> activityTransitionAnimator,
Lazy<ScrimController> scrimControllerLazy,
IActivityTaskManager activityTaskManagerService,
+ IStatusBarService statusBarService,
FeatureFlags featureFlags,
SecureSettings secureSettings,
SystemSettings systemSettings,
SystemClock systemClock,
+ ProcessWrapper processWrapper,
@Main CoroutineDispatcher mainDispatcher,
Lazy<DreamViewModel> dreamViewModel,
Lazy<CommunalTransitionViewModel> communalTransitionViewModel,
@@ -1487,9 +1490,9 @@
mSecureSettings = secureSettings;
mSystemSettings = systemSettings;
mSystemClock = systemClock;
+ mProcessWrapper = processWrapper;
mSystemPropertiesHelper = systemPropertiesHelper;
- mStatusBarService = IStatusBarService.Stub.asInterface(
- ServiceManager.getService(Context.STATUS_BAR_SERVICE));
+ mStatusBarService = statusBarService;
mKeyguardDisplayManager = keyguardDisplayManager;
mShadeController = shadeControllerLazy;
dumpManager.registerDumpable(this);
@@ -2834,6 +2837,14 @@
*/
private void handleShow(Bundle options) {
Trace.beginSection("KeyguardViewMediator#handleShow");
+ try {
+ handleShowInner(options);
+ } finally {
+ Trace.endSection();
+ }
+ }
+
+ private void handleShowInner(Bundle options) {
final boolean showUnlocked = options != null
&& options.getBoolean(OPTION_SHOW_DISMISSIBLE, false);
final int currentUser = mSelectedUserInteractor.getSelectedUserId();
@@ -2885,8 +2896,6 @@
mKeyguardDisplayManager.show();
scheduleNonStrongBiometricIdleTimeout();
-
- Trace.endSection();
}
/**
@@ -3065,6 +3074,17 @@
RemoteAnimationTarget[] apps, RemoteAnimationTarget[] wallpapers,
RemoteAnimationTarget[] nonApps, IRemoteAnimationFinishedCallback finishedCallback) {
Trace.beginSection("KeyguardViewMediator#handleStartKeyguardExitAnimation");
+ try {
+ handleStartKeyguardExitAnimationInner(startTime, fadeoutDuration, apps, wallpapers,
+ nonApps, finishedCallback);
+ } finally {
+ Trace.endSection();
+ }
+ }
+
+ private void handleStartKeyguardExitAnimationInner(long startTime, long fadeoutDuration,
+ RemoteAnimationTarget[] apps, RemoteAnimationTarget[] wallpapers,
+ RemoteAnimationTarget[] nonApps, IRemoteAnimationFinishedCallback finishedCallback) {
Log.d(TAG, "handleStartKeyguardExitAnimation startTime=" + startTime
+ " fadeoutDuration=" + fadeoutDuration);
synchronized (KeyguardViewMediator.this) {
@@ -3253,8 +3273,6 @@
onKeyguardExitFinished();
}
}
-
- Trace.endSection();
}
private void onKeyguardExitFinished() {
@@ -3496,12 +3514,20 @@
// TODO (b/155663717) After restart, status bar will not properly hide home button
// unless disable is called to show un-hide it once first
if (forceClearFlags) {
- try {
- mStatusBarService.disableForUser(flags, mStatusBarDisableToken,
- mContext.getPackageName(),
- mSelectedUserInteractor.getSelectedUserId(true));
- } catch (RemoteException e) {
- Log.d(TAG, "Failed to force clear flags", e);
+ if (UserManager.isVisibleBackgroundUsersEnabled()
+ && !mProcessWrapper.isSystemUser() && !mProcessWrapper.isForegroundUser()) {
+ // TODO: b/341604160 - Support visible background users properly.
+ if (DEBUG) {
+ Log.d(TAG, "Status bar manager is disabled for visible background users");
+ }
+ } else {
+ try {
+ mStatusBarService.disableForUser(flags, mStatusBarDisableToken,
+ mContext.getPackageName(),
+ mSelectedUserInteractor.getSelectedUserId(true));
+ } catch (RemoteException e) {
+ Log.d(TAG, "Failed to force clear flags", e);
+ }
}
}
@@ -3525,6 +3551,14 @@
}
if (!SceneContainerFlag.isEnabled()) {
+ if (UserManager.isVisibleBackgroundUsersEnabled()
+ && !mProcessWrapper.isSystemUser() && !mProcessWrapper.isForegroundUser()) {
+ // TODO: b/341604160 - Support visible background users properly.
+ if (DEBUG) {
+ Log.d(TAG, "Status bar manager is disabled for visible background users");
+ }
+ return;
+ }
try {
mStatusBarService.disableForUser(flags, mStatusBarDisableToken,
mContext.getPackageName(),
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
index 15dac09..a43bfd3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -23,6 +23,7 @@
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.logging.UiEventLogger;
+import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardDisplayManager;
import com.android.keyguard.KeyguardUpdateMonitor;
@@ -64,6 +65,7 @@
import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransitionModule;
import com.android.systemui.log.SessionTracker;
import com.android.systemui.navigationbar.NavigationModeController;
+import com.android.systemui.process.ProcessWrapper;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.shade.ShadeController;
import com.android.systemui.statusbar.NotificationShadeDepthController;
@@ -158,10 +160,12 @@
Lazy<ActivityTransitionAnimator> activityTransitionAnimator,
Lazy<ScrimController> scrimControllerLazy,
IActivityTaskManager activityTaskManagerService,
+ IStatusBarService statusBarService,
FeatureFlags featureFlags,
SecureSettings secureSettings,
SystemSettings systemSettings,
SystemClock systemClock,
+ ProcessWrapper processWrapper,
@Main CoroutineDispatcher mainDispatcher,
Lazy<DreamViewModel> dreamViewModel,
Lazy<CommunalTransitionViewModel> communalTransitionViewModel,
@@ -206,10 +210,12 @@
activityTransitionAnimator,
scrimControllerLazy,
activityTaskManagerService,
+ statusBarService,
featureFlags,
secureSettings,
systemSettings,
systemClock,
+ processWrapper,
mainDispatcher,
dreamViewModel,
communalTransitionViewModel,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt
index ab432d6..c0049d4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt
@@ -31,7 +31,6 @@
import com.android.systemui.plugins.clocks.ClockId
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.shade.domain.interactor.ShadeInteractor
-import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationInteractor
import com.android.systemui.util.kotlin.combine
@@ -104,21 +103,21 @@
val clockShouldBeCentered: Flow<Boolean> =
if (SceneContainerFlag.isEnabled) {
combine(
- shadeInteractor.shadeMode,
+ shadeInteractor.isShadeLayoutWide,
activeNotificationsInteractor.areAnyNotificationsPresent,
keyguardInteractor.isActiveDreamLockscreenHosted,
isOnAod,
headsUpNotificationInteractor.isHeadsUpOrAnimatingAway,
keyguardInteractor.isDozing,
) {
- shadeMode,
+ isShadeLayoutWide,
areAnyNotificationsPresent,
isActiveDreamLockscreenHosted,
isOnAod,
isHeadsUp,
isDozing ->
when {
- shadeMode != ShadeMode.Split -> true
+ !isShadeLayoutWide -> true
!areAnyNotificationsPresent -> true
isActiveDreamLockscreenHosted -> true
// Pulsing notification appears on the right. Move clock left to avoid overlap.
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToAodTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToAodTransitionViewModel.kt
index 754ed6c..1ee0368 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToAodTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToAodTransitionViewModel.kt
@@ -25,6 +25,7 @@
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
import javax.inject.Inject
+import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.emptyFlow
@@ -45,6 +46,13 @@
edge = Edge.create(from = DREAMING, to = AOD),
)
+ /** Lockscreen views alpha */
+ val lockscreenAlpha: Flow<Float> =
+ transitionAnimation.sharedFlow(
+ duration = 300.milliseconds,
+ onStep = { it },
+ )
+
val deviceEntryBackgroundViewAlpha: Flow<Float> =
transitionAnimation.immediatelyTransitionTo(0f)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
index 7c46807..350ceb4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
@@ -91,8 +91,9 @@
private val dozingToGoneTransitionViewModel: DozingToGoneTransitionViewModel,
private val dozingToLockscreenTransitionViewModel: DozingToLockscreenTransitionViewModel,
private val dozingToOccludedTransitionViewModel: DozingToOccludedTransitionViewModel,
- private val dreamingToLockscreenTransitionViewModel: DreamingToLockscreenTransitionViewModel,
+ private val dreamingToAodTransitionViewModel: DreamingToAodTransitionViewModel,
private val dreamingToGoneTransitionViewModel: DreamingToGoneTransitionViewModel,
+ private val dreamingToLockscreenTransitionViewModel: DreamingToLockscreenTransitionViewModel,
private val glanceableHubToLockscreenTransitionViewModel:
GlanceableHubToLockscreenTransitionViewModel,
private val goneToAodTransitionViewModel: GoneToAodTransitionViewModel,
@@ -243,6 +244,7 @@
dozingToGoneTransitionViewModel.lockscreenAlpha(viewState),
dozingToLockscreenTransitionViewModel.lockscreenAlpha,
dozingToOccludedTransitionViewModel.lockscreenAlpha(viewState),
+ dreamingToAodTransitionViewModel.lockscreenAlpha,
dreamingToGoneTransitionViewModel.lockscreenAlpha,
dreamingToLockscreenTransitionViewModel.lockscreenAlpha,
glanceableHubToLockscreenTransitionViewModel.keyguardAlpha,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
index 3b337fc..4bfefda 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
@@ -17,6 +17,7 @@
package com.android.systemui.keyguard.ui.viewmodel
import android.content.res.Resources
+import com.android.compose.animation.scene.SceneKey
import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.biometrics.AuthController
import com.android.systemui.dagger.SysUISingleton
@@ -26,14 +27,17 @@
import com.android.systemui.keyguard.shared.model.ClockSize
import com.android.systemui.res.R
import com.android.systemui.scene.domain.interactor.SceneContainerOcclusionInteractor
+import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.unfold.domain.interactor.UnfoldTransitionInteractor
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
@@ -57,19 +61,6 @@
val isShadeLayoutWide: StateFlow<Boolean> = shadeInteractor.isShadeLayoutWide
- val areNotificationsVisible: StateFlow<Boolean> =
- combine(
- clockSize,
- shadeInteractor.isShadeLayoutWide,
- ) { clockSize, isShadeLayoutWide ->
- clockSize == ClockSize.SMALL || isShadeLayoutWide
- }
- .stateIn(
- scope = applicationScope,
- started = SharingStarted.WhileSubscribed(),
- initialValue = false,
- )
-
/** Amount of horizontal translation that should be applied to elements in the scene. */
val unfoldTranslations: StateFlow<UnfoldTranslations> =
combine(
@@ -97,6 +88,25 @@
initialValue = true,
)
+ /**
+ * Returns a flow that indicates whether lockscreen notifications should be rendered in the
+ * given [sceneKey].
+ */
+ fun areNotificationsVisible(sceneKey: SceneKey): Flow<Boolean> {
+ // `Scenes.NotificationsShade` renders its own separate notifications stack, so when it's
+ // open we avoid rendering the lockscreen notifications stack.
+ if (sceneKey == Scenes.NotificationsShade) {
+ return flowOf(false)
+ }
+
+ return combine(
+ clockSize,
+ shadeInteractor.isShadeLayoutWide,
+ ) { clockSize, isShadeLayoutWide ->
+ clockSize == ClockSize.SMALL || isShadeLayoutWide
+ }
+ }
+
fun getSmartSpacePaddingTop(resources: Resources): Int {
return if (clockSize.value == ClockSize.LARGE) {
resources.getDimensionPixelSize(R.dimen.keyguard_smartspace_top_offset) +
diff --git a/packages/SystemUI/src/com/android/systemui/log/SessionTracker.java b/packages/SystemUI/src/com/android/systemui/log/SessionTracker.java
index d848b43..e8ded03 100644
--- a/packages/SystemUI/src/com/android/systemui/log/SessionTracker.java
+++ b/packages/SystemUI/src/com/android/systemui/log/SessionTracker.java
@@ -22,6 +22,7 @@
import android.annotation.Nullable;
import android.os.RemoteException;
+import android.os.UserManager;
import android.util.Log;
import androidx.annotation.NonNull;
@@ -36,6 +37,7 @@
import com.android.systemui.CoreStartable;
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.process.ProcessWrapper;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import java.io.PrintWriter;
@@ -63,6 +65,7 @@
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
private final KeyguardStateController mKeyguardStateController;
private final UiEventLogger mUiEventLogger;
+ private final ProcessWrapper mProcessWrapper;
private final Map<Integer, InstanceId> mSessionToInstanceId = new HashMap<>();
private boolean mKeyguardSessionStarted;
@@ -73,13 +76,15 @@
AuthController authController,
KeyguardUpdateMonitor keyguardUpdateMonitor,
KeyguardStateController keyguardStateController,
- UiEventLogger uiEventLogger
+ UiEventLogger uiEventLogger,
+ ProcessWrapper processWrapper
) {
mStatusBarManagerService = statusBarService;
mAuthController = authController;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mKeyguardStateController = keyguardStateController;
mUiEventLogger = uiEventLogger;
+ mProcessWrapper = processWrapper;
}
@Override
@@ -109,6 +114,16 @@
final InstanceId instanceId = mInstanceIdGenerator.newInstanceId();
mSessionToInstanceId.put(type, instanceId);
+
+ if (UserManager.isVisibleBackgroundUsersEnabled() && !mProcessWrapper.isSystemUser()
+ && !mProcessWrapper.isForegroundUser()) {
+ // TODO: b/341604160 - Support visible background users properly.
+ if (DEBUG) {
+ Log.d(TAG, "Status bar manager is disabled for visible background users");
+ }
+ return;
+ }
+
try {
if (DEBUG) {
Log.d(TAG, "Session start for [" + getString(type) + "] id=" + instanceId);
@@ -139,6 +154,14 @@
if (endSessionUiEvent != null) {
mUiEventLogger.log(endSessionUiEvent, instanceId);
}
+ if (UserManager.isVisibleBackgroundUsersEnabled() && !mProcessWrapper.isSystemUser()
+ && !mProcessWrapper.isForegroundUser()) {
+ // TODO: b/341604160 - Support visible background users properly.
+ if (DEBUG) {
+ Log.d(TAG, "Status bar manager is disabled for visible background users");
+ }
+ return;
+ }
mStatusBarManagerService.onSessionEnded(type, instanceId);
} catch (RemoteException e) {
Log.e(TAG, "Unable to send onSessionEnded for session="
diff --git a/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java b/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java
index bda0069..e7c2a45 100644
--- a/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java
+++ b/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java
@@ -17,7 +17,6 @@
package com.android.systemui.media;
import android.annotation.Nullable;
-import android.content.ContentProvider;
import android.content.ContentResolver;
import android.content.Context;
import android.content.pm.PackageManager.NameNotFoundException;
@@ -124,13 +123,9 @@
boolean looping, @Nullable VolumeShaper.Configuration volumeShaperConfig)
throws RemoteException {
if (LOGD) {
- Log.d(TAG, "play(token=" + token + ", uri=" + uri
- + ", uid=" + Binder.getCallingUid()
- + ") uriUserId=" + ContentProvider.getUserIdFromUri(uri)
- + " callingUserId=" + Binder.getCallingUserHandle().getIdentifier());
+ Log.d(TAG, "play(token=" + token + ", uri=" + uri + ", uid="
+ + Binder.getCallingUid() + ")");
}
- enforceUriUserId(uri);
-
Client client;
synchronized (mClients) {
client = mClients.get(token);
@@ -212,7 +207,6 @@
@Override
public String getTitle(Uri uri) {
- enforceUriUserId(uri);
final UserHandle user = Binder.getCallingUserHandle();
return Ringtone.getTitle(getContextForUser(user), uri,
false /*followSettingsUri*/, false /*allowRemote*/);
@@ -245,25 +239,6 @@
}
throw new SecurityException("Uri is not ringtone, alarm, or notification: " + uri);
}
-
- /**
- * Must be called from the Binder calling thread.
- * Ensures caller is from the same userId as the content they're trying to access.
- * @param uri the URI to check
- * @throws SecurityException when non-system call or userId in uri differs from the
- * caller's userId
- */
- private void enforceUriUserId(Uri uri) throws SecurityException {
- final int uriUserId = ContentProvider.getUserIdFromUri(uri);
- final int callerUserId = Binder.getCallingUserHandle().getIdentifier();
- // for a non-system call, verify the URI to play belongs to the same user as the caller
- if (UserHandle.isApp(Binder.getCallingUid()) && uriUserId != callerUserId) {
- throw new SecurityException("Illegal access to uri=" + uri
- + " content associated with user=" + uriUserId
- + ", request originates from user=" + callerUserId);
- }
- }
-
};
private Context getContextForUser(UserHandle user) {
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionManagerRepository.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionManagerRepository.kt
index 760ff7d..c90f197 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionManagerRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionManagerRepository.kt
@@ -69,7 +69,11 @@
}
override suspend fun stopProjecting() {
- withContext(backgroundDispatcher) { mediaProjectionManager.stopActiveProjection() }
+ withContext(backgroundDispatcher) {
+ // TODO(b/332662551): Convert Logcat to LogBuffer.
+ Log.d(TAG, "Requesting MediaProjectionManager#stopActiveProjection")
+ mediaProjectionManager.stopActiveProjection()
+ }
}
override val mediaProjectionState: Flow<MediaProjectionState> =
diff --git a/packages/SystemUI/src/com/android/systemui/mediarouter/MediaRouterLog.kt b/packages/SystemUI/src/com/android/systemui/mediarouter/MediaRouterLog.kt
new file mode 100644
index 0000000..16bf0ff
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediarouter/MediaRouterLog.kt
@@ -0,0 +1,25 @@
+/*
+ * 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.mediarouter
+
+import javax.inject.Qualifier
+
+/** Logs for events related to MediaRouter APIs. */
+@Qualifier
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+annotation class MediaRouterLog
diff --git a/packages/SystemUI/src/com/android/systemui/mediarouter/MediaRouterModule.kt b/packages/SystemUI/src/com/android/systemui/mediarouter/MediaRouterModule.kt
index c07e3a0..df5dae4 100644
--- a/packages/SystemUI/src/com/android/systemui/mediarouter/MediaRouterModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediarouter/MediaRouterModule.kt
@@ -16,12 +16,25 @@
package com.android.systemui.mediarouter
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogBufferFactory
import com.android.systemui.mediarouter.data.repository.MediaRouterRepository
import com.android.systemui.mediarouter.data.repository.MediaRouterRepositoryImpl
import dagger.Binds
import dagger.Module
+import dagger.Provides
@Module
interface MediaRouterModule {
@Binds fun mediaRouterRepository(impl: MediaRouterRepositoryImpl): MediaRouterRepository
+
+ companion object {
+ @Provides
+ @SysUISingleton
+ @MediaRouterLog
+ fun provideMediaRouterLogBuffer(factory: LogBufferFactory): LogBuffer {
+ return factory.create("MediaRouter", 50)
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/mediarouter/data/repository/MediaRouterRepository.kt b/packages/SystemUI/src/com/android/systemui/mediarouter/data/repository/MediaRouterRepository.kt
index 998d76c..debb667 100644
--- a/packages/SystemUI/src/com/android/systemui/mediarouter/data/repository/MediaRouterRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediarouter/data/repository/MediaRouterRepository.kt
@@ -18,6 +18,9 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.mediarouter.MediaRouterLog
import com.android.systemui.statusbar.policy.CastController
import com.android.systemui.statusbar.policy.CastDevice
import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
@@ -26,6 +29,9 @@
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
/** A repository for data coming from MediaRouter APIs. */
@@ -43,23 +49,29 @@
constructor(
@Application private val scope: CoroutineScope,
private val castController: CastController,
+ @MediaRouterLog private val logger: LogBuffer,
) : MediaRouterRepository {
override val castDevices: StateFlow<List<CastDevice>> =
conflatedCallbackFlow {
- val callback =
- CastController.Callback {
- val mediaRouterCastDevices =
- castController.castDevices.filter {
- it.origin == CastDevice.CastOrigin.MediaRouter
- }
- trySend(mediaRouterCastDevices)
- }
+ val callback = CastController.Callback { trySend(castController.castDevices) }
castController.addCallback(callback)
awaitClose { castController.removeCallback(callback) }
}
+ // The CastController.Callback is pretty noisy and sends the same values multiple times
+ // in a row, so use a distinctUntilChanged before logging.
+ .distinctUntilChanged()
+ .onEach { allDevices ->
+ val logString = allDevices.map { it.shortLogString }.toString()
+ logger.log(TAG, LogLevel.INFO, { str1 = logString }, { "All cast devices: $str1" })
+ }
+ .map { it.filter { device -> device.origin == CastDevice.CastOrigin.MediaRouter } }
.stateIn(scope, SharingStarted.WhileSubscribed(), emptyList())
override fun stopCasting(device: CastDevice) {
castController.stopCasting(device)
}
+
+ companion object {
+ private const val TAG = "MediaRouterRepo"
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/process/ProcessWrapper.java b/packages/SystemUI/src/com/android/systemui/process/ProcessWrapper.java
index b4cc196..294d0c7 100644
--- a/packages/SystemUI/src/com/android/systemui/process/ProcessWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/process/ProcessWrapper.java
@@ -16,6 +16,7 @@
package com.android.systemui.process;
+import android.app.ActivityManager;
import android.os.Process;
import android.os.UserHandle;
@@ -37,6 +38,13 @@
}
/**
+ * Returns {@code true} if the foreground user is running the current process.
+ */
+ public boolean isForegroundUser() {
+ return ActivityManager.getCurrentUser() == myUserHandle().getIdentifier();
+ }
+
+ /**
* Returns {@link UserHandle} as returned statically by {@link Process#myUserHandle()}.
*
* This should not be used to get the "current" user. This information only applies to the
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ReduceBrightColorsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/ReduceBrightColorsControllerImpl.java
index d68b22b..4d6cf78 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ReduceBrightColorsControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/ReduceBrightColorsControllerImpl.java
@@ -29,7 +29,6 @@
import androidx.annotation.NonNull;
-import com.android.server.display.feature.flags.Flags;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.settings.UserTracker;
@@ -81,10 +80,17 @@
mAvailable = true;
synchronized (mListeners) {
if (mListeners.size() > 0) {
- mSecureSettings.unregisterContentObserverSync(mContentObserver);
- mSecureSettings.registerContentObserverForUserSync(
- Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED,
- false, mContentObserver, newUser);
+ if (com.android.systemui.Flags.registerContentObserversAsync()) {
+ mSecureSettings.unregisterContentObserverAsync(mContentObserver);
+ mSecureSettings.registerContentObserverForUserAsync(
+ Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED,
+ false, mContentObserver, newUser);
+ } else {
+ mSecureSettings.unregisterContentObserverSync(mContentObserver);
+ mSecureSettings.registerContentObserverForUserSync(
+ Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED,
+ false, mContentObserver, newUser);
+ }
}
}
}
@@ -98,9 +104,15 @@
if (!mListeners.contains(listener)) {
mListeners.add(listener);
if (mListeners.size() == 1) {
- mSecureSettings.registerContentObserverForUserSync(
- Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED,
- false, mContentObserver, mUserTracker.getUserId());
+ if (com.android.systemui.Flags.registerContentObserversAsync()) {
+ mSecureSettings.registerContentObserverForUserAsync(
+ Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED,
+ false, mContentObserver, mUserTracker.getUserId());
+ } else {
+ mSecureSettings.registerContentObserverForUserSync(
+ Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED,
+ false, mContentObserver, mUserTracker.getUserId());
+ }
}
}
}
@@ -110,7 +122,11 @@
public void removeCallback(@androidx.annotation.NonNull Listener listener) {
synchronized (mListeners) {
if (mListeners.remove(listener) && mListeners.size() == 0) {
- mSecureSettings.unregisterContentObserverSync(mContentObserver);
+ if (com.android.systemui.Flags.registerContentObserversAsync()) {
+ mSecureSettings.unregisterContentObserverAsync(mContentObserver);
+ } else {
+ mSecureSettings.unregisterContentObserverSync(mContentObserver);
+ }
}
}
}
@@ -139,7 +155,8 @@
@Override
public boolean isInUpgradeMode(Resources resources) {
- return Flags.evenDimmer() && resources.getBoolean(
+ return com.android.server.display.feature.flags.Flags.evenDimmer()
+ && resources.getBoolean(
com.android.internal.R.bool.config_evenDimmerEnabled);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
index 787fd1a..6b3dfe1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
@@ -93,6 +93,7 @@
@VisibleForTesting internal const val TILE_STATE_RES_PREFIX = "tile_states_"
@VisibleForTesting internal const val LONG_PRESS_EFFECT_WIDTH_SCALE = 1.1f
@VisibleForTesting internal const val LONG_PRESS_EFFECT_HEIGHT_SCALE = 1.2f
+ internal val EMPTY_RECT = Rect()
}
private val icon: QSIconViewImpl = QSIconViewImpl(context)
@@ -916,7 +917,7 @@
}
}
- fun prepareForLaunch() {
+ private fun prepareForLaunch() {
val startingHeight = initialLongPressProperties?.height?.toInt() ?: 0
val startingWidth = initialLongPressProperties?.width?.toInt() ?: 0
val deltaH = finalLongPressProperties?.height?.minus(startingHeight)?.toInt() ?: 0
@@ -927,7 +928,12 @@
paddingForLaunch.bottom = deltaH / 2
}
- override fun getPaddingForLaunchAnimation(): Rect = paddingForLaunch
+ override fun getPaddingForLaunchAnimation(): Rect =
+ if (longPressEffect?.state == QSLongPressEffect.State.LONG_CLICKED) {
+ paddingForLaunch
+ } else {
+ EMPTY_RECT
+ }
fun updateLongPressEffectProperties(effectProgress: Float) {
if (!isLongClickable || longPressEffect == null) return
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java
index c971f54..edc49cac2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java
@@ -183,9 +183,9 @@
Context context,
InternetDialogManager internetDialogManager,
InternetDialogController internetDialogController,
- @Assisted(ABOVE_STATUS_BAR) boolean canConfigMobileData,
- @Assisted(CAN_CONFIG_MOBILE_DATA) boolean canConfigWifi,
- @Assisted(CAN_CONFIG_WIFI) boolean aboveStatusBar,
+ @Assisted(CAN_CONFIG_MOBILE_DATA) boolean canConfigMobileData,
+ @Assisted(CAN_CONFIG_WIFI) boolean canConfigWifi,
+ @Assisted(ABOVE_STATUS_BAR) boolean aboveStatusBar,
@Assisted CoroutineScope coroutineScope,
UiEventLogger uiEventLogger,
DialogTransitionAnimator dialogTransitionAnimator,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractor.kt
index da4d2f1..930109a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractor.kt
@@ -18,7 +18,6 @@
import android.app.Flags
import android.os.UserHandle
-import android.provider.Settings.Global.ZEN_MODE_OFF
import com.android.settingslib.notification.data.repository.ZenModeRepository
import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor
@@ -31,9 +30,10 @@
class ModesTileDataInteractor @Inject constructor(val zenModeRepository: ZenModeRepository) :
QSTileDataInteractor<ModesTileModel> {
- // TODO(b/346519570): This should be checking for any enabled modes.
private val zenModeActive =
- zenModeRepository.globalZenMode.map { it != ZEN_MODE_OFF }.distinctUntilChanged()
+ zenModeRepository.modes
+ .map { modes -> modes.any { mode -> mode.isActive } }
+ .distinctUntilChanged()
override fun tileData(
user: UserHandle,
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
index 3dc2070..af1b6e1 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
@@ -258,6 +258,7 @@
* Stop the recording
*/
public void stopRecording() {
+ // TODO(b/332662551): Convert Logcat to LogBuffer.
try {
if (mStopIntent != null) {
mStopIntent.send(mInteractiveBroadcastOption);
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
index 49810762..8e53949 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
@@ -137,14 +137,24 @@
public void startObserving() {
if (!mObserving) {
mObserving = true;
- mSecureSettings.registerContentObserverForUserSync(
- BRIGHTNESS_MODE_URI,
- false, this, UserHandle.USER_ALL);
+ if (Flags.registerContentObserversAsync()) {
+ mSecureSettings.registerContentObserverForUserAsync(
+ BRIGHTNESS_MODE_URI,
+ false, this, UserHandle.USER_ALL);
+ } else {
+ mSecureSettings.registerContentObserverForUserSync(
+ BRIGHTNESS_MODE_URI,
+ false, this, UserHandle.USER_ALL);
+ }
}
}
public void stopObserving() {
- mSecureSettings.unregisterContentObserverSync(this);
+ if (Flags.registerContentObserversAsync()) {
+ mSecureSettings.unregisterContentObserverAsync(this);
+ } else {
+ mSecureSettings.unregisterContentObserverSync(this);
+ }
mObserving = false;
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
index c1f8a0b..45f359e 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
@@ -55,7 +55,11 @@
/** Whether the shade can be expanded from QQS to QS. */
val isExpandToQsEnabled: Flow<Boolean>
- /** The version of the shade layout to use. */
+ /**
+ * The version of the shade layout to use.
+ *
+ * Note: Most likely, you want to read [isShadeLayoutWide] instead of this.
+ */
val shadeMode: StateFlow<ShadeMode>
/**
diff --git a/packages/SystemUI/src/com/android/systemui/shade/shared/model/ShadeMode.kt b/packages/SystemUI/src/com/android/systemui/shade/shared/model/ShadeMode.kt
index 8214a24..a8199a4 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/shared/model/ShadeMode.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/shared/model/ShadeMode.kt
@@ -29,6 +29,8 @@
/**
* The split shade where, on large screens and unfolded foldables, the QS and notification parts
* are placed side-by-side and expand/collapse as a single panel.
+ *
+ * Note: This isn't the only mode where the shade is wide.
*/
data object Split : ShadeMode
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java
index 5bb2936..c997ac5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java
@@ -19,6 +19,7 @@
import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_YES;
import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG;
+import static com.android.systemui.Flags.fetchBookmarksXmlKeyboardShortcuts;
import static com.android.systemui.Flags.validateKeyboardShortcutHelperIconUri;
import android.annotation.NonNull;
@@ -149,7 +150,7 @@
private KeyCharacterMap mBackupKeyCharacterMap;
@VisibleForTesting
- KeyboardShortcutListSearch(Context context, WindowManager windowManager) {
+ KeyboardShortcutListSearch(Context context, WindowManager windowManager, int deviceId) {
this.mContext = new ContextThemeWrapper(
context, R.style.KeyboardShortcutHelper);
this.mPackageManager = AppGlobals.getPackageManager();
@@ -159,12 +160,12 @@
this.mWindowManager = mContext.getSystemService(WindowManager.class);
}
loadResources(this.mContext);
- createHardcodedShortcuts();
+ createHardcodedShortcuts(deviceId);
}
- private static KeyboardShortcutListSearch getInstance(Context context) {
+ private static KeyboardShortcutListSearch getInstance(Context context, int deviceId) {
if (sInstance == null) {
- sInstance = new KeyboardShortcutListSearch(context, null);
+ sInstance = new KeyboardShortcutListSearch(context, null, deviceId);
}
return sInstance;
}
@@ -176,7 +177,7 @@
if (sInstance != null && !sInstance.mContext.equals(context)) {
dismiss();
}
- getInstance(context).showKeyboardShortcuts(deviceId);
+ getInstance(context, deviceId).showKeyboardShortcuts(deviceId);
}
}
@@ -367,7 +368,7 @@
KeyEvent.META_META_ON, context.getDrawable(R.drawable.ic_ksh_key_meta));
}
- private void createHardcodedShortcuts() {
+ private void createHardcodedShortcuts(int deviceId) {
// Add system shortcuts
mKeySearchResultMap.put(SHORTCUT_SYSTEM_INDEX, true);
mSystemGroup.add(getMultiMappingSystemShortcuts(mContext));
@@ -377,7 +378,7 @@
mInputGroup.add(getMultiMappingInputShortcuts(mContext));
// Add open apps shortcuts
final List<KeyboardShortcutMultiMappingGroup> appShortcuts =
- Arrays.asList(getDefaultMultiMappingApplicationShortcuts());
+ Arrays.asList(getDefaultMultiMappingApplicationShortcuts(deviceId));
if (appShortcuts != null && !appShortcuts.isEmpty()) {
mOpenAppsGroup = appShortcuts;
mKeySearchResultMap.put(SHORTCUT_OPENAPPS_INDEX, true);
@@ -739,35 +740,50 @@
shortcutMultiMappingInfoList);
}
- private KeyboardShortcutMultiMappingGroup getDefaultMultiMappingApplicationShortcuts() {
- final int userId = mContext.getUserId();
- PackageInfo assistPackageInfo = getAssistPackageInfo(mContext, mPackageManager, userId);
- CharSequence categoryTitle =
- mContext.getString(R.string.keyboard_shortcut_group_applications);
+ private KeyboardShortcutMultiMappingGroup getDefaultMultiMappingApplicationShortcuts(
+ int deviceId) {
List<ShortcutMultiMappingInfo> shortcutMultiMappingInfos = new ArrayList<>();
+ CharSequence categoryTitle;
+ if (fetchBookmarksXmlKeyboardShortcuts()) {
+ KeyboardShortcutGroup apps =
+ mWindowManager.getApplicationLaunchKeyboardShortcuts(deviceId);
+ List<KeyboardShortcutMultiMappingGroup> shortcuts =
+ reMapToKeyboardShortcutMultiMappingGroup(Arrays.asList(apps));
+ for (KeyboardShortcutMultiMappingGroup group : shortcuts) {
+ for (ShortcutMultiMappingInfo keyboardShortcutInfo : group.getItems()) {
+ shortcutMultiMappingInfos.add(keyboardShortcutInfo);
+ }
+ }
+ categoryTitle = apps.getLabel();
+ } else {
+ // Show shortcuts based on AOSP bookmarks.xml
+ categoryTitle = mContext.getString(R.string.keyboard_shortcut_group_applications);
+ final int userId = mContext.getUserId();
+ PackageInfo assistPackageInfo =
+ getAssistPackageInfo(mContext, mPackageManager, userId);
- String[] intentCategories = {
- Intent.CATEGORY_APP_BROWSER,
- Intent.CATEGORY_APP_CONTACTS,
- Intent.CATEGORY_APP_EMAIL,
- Intent.CATEGORY_APP_CALENDAR,
- Intent.CATEGORY_APP_MAPS,
- Intent.CATEGORY_APP_MUSIC,
- Intent.CATEGORY_APP_MESSAGING,
- Intent.CATEGORY_APP_CALCULATOR,
+ String[] intentCategories = {
+ Intent.CATEGORY_APP_BROWSER,
+ Intent.CATEGORY_APP_CONTACTS,
+ Intent.CATEGORY_APP_EMAIL,
+ Intent.CATEGORY_APP_CALENDAR,
+ Intent.CATEGORY_APP_MAPS,
+ Intent.CATEGORY_APP_MUSIC,
+ Intent.CATEGORY_APP_MESSAGING,
+ Intent.CATEGORY_APP_CALCULATOR,
+ };
+ String[] shortcutLabels = {
+ mContext.getString(R.string.keyboard_shortcut_group_applications_browser),
+ mContext.getString(R.string.keyboard_shortcut_group_applications_contacts),
+ mContext.getString(R.string.keyboard_shortcut_group_applications_email),
+ mContext.getString(R.string.keyboard_shortcut_group_applications_calendar),
+ mContext.getString(R.string.keyboard_shortcut_group_applications_maps),
+ mContext.getString(R.string.keyboard_shortcut_group_applications_music),
+ mContext.getString(R.string.keyboard_shortcut_group_applications_sms),
+ mContext.getString(R.string.keyboard_shortcut_group_applications_calculator)
+ };
- };
- String[] shortcutLabels = {
- mContext.getString(R.string.keyboard_shortcut_group_applications_browser),
- mContext.getString(R.string.keyboard_shortcut_group_applications_contacts),
- mContext.getString(R.string.keyboard_shortcut_group_applications_email),
- mContext.getString(R.string.keyboard_shortcut_group_applications_calendar),
- mContext.getString(R.string.keyboard_shortcut_group_applications_maps),
- mContext.getString(R.string.keyboard_shortcut_group_applications_music),
- mContext.getString(R.string.keyboard_shortcut_group_applications_sms),
- mContext.getString(R.string.keyboard_shortcut_group_applications_calculator)
- };
- int[] keyCodes = {
+ int[] keyCodes = {
KeyEvent.KEYCODE_B,
KeyEvent.KEYCODE_C,
KeyEvent.KEYCODE_E,
@@ -776,52 +792,44 @@
KeyEvent.KEYCODE_P,
KeyEvent.KEYCODE_S,
KeyEvent.KEYCODE_U,
- };
+ };
- // Assist.
- if (assistPackageInfo != null) {
+ // Assist.
if (assistPackageInfo != null) {
- final Icon assistIcon = Icon.createWithResource(
- assistPackageInfo.applicationInfo.packageName,
- assistPackageInfo.applicationInfo.icon);
- CharSequence assistLabel =
- mContext.getString(R.string.keyboard_shortcut_group_applications_assist);
- KeyboardShortcutInfo assistShortcutInfo = new KeyboardShortcutInfo(
- assistLabel,
- assistIcon,
- KeyEvent.KEYCODE_A,
- KeyEvent.META_META_ON);
- shortcutMultiMappingInfos.add(
- new ShortcutMultiMappingInfo(
- assistLabel,
- assistIcon,
- Arrays.asList(new ShortcutKeyGroup(assistShortcutInfo, null))));
+ if (assistPackageInfo != null) {
+ final Icon assistIcon = Icon.createWithResource(
+ assistPackageInfo.applicationInfo.packageName,
+ assistPackageInfo.applicationInfo.icon);
+ CharSequence assistLabel = mContext.getString(
+ R.string.keyboard_shortcut_group_applications_assist);
+ KeyboardShortcutInfo assistShortcutInfo = new KeyboardShortcutInfo(
+ assistLabel,
+ assistIcon,
+ KeyEvent.KEYCODE_A,
+ KeyEvent.META_META_ON);
+ shortcutMultiMappingInfos.add(
+ new ShortcutMultiMappingInfo(
+ assistLabel,
+ assistIcon,
+ Arrays.asList(new ShortcutKeyGroup(assistShortcutInfo, null))));
+ }
}
- }
- // Browser (Chrome as default): Meta + B
- // Contacts: Meta + C
- // Email (Gmail as default): Meta + E
- // Gmail: Meta + G
- // Calendar: Meta + K
- // Maps: Meta + M
- // Music: Meta + P
- // SMS: Meta + S
- // Calculator: Meta + U
- for (int i = 0; i < shortcutLabels.length; i++) {
- final Icon icon = getIconForIntentCategory(intentCategories[i], userId);
- if (icon != null) {
- CharSequence label =
- shortcutLabels[i];
- KeyboardShortcutInfo keyboardShortcutInfo = new KeyboardShortcutInfo(
- label,
- icon,
- keyCodes[i],
- KeyEvent.META_META_ON);
- List<ShortcutKeyGroup> shortcutKeyGroups =
- Arrays.asList(new ShortcutKeyGroup(keyboardShortcutInfo, null));
- shortcutMultiMappingInfos.add(
- new ShortcutMultiMappingInfo(label, icon, shortcutKeyGroups));
+ for (int i = 0; i < shortcutLabels.length; i++) {
+ final Icon icon = getIconForIntentCategory(intentCategories[i], userId);
+ if (icon != null) {
+ CharSequence label =
+ shortcutLabels[i];
+ KeyboardShortcutInfo keyboardShortcutInfo = new KeyboardShortcutInfo(
+ label,
+ icon,
+ keyCodes[i],
+ KeyEvent.META_META_ON);
+ List<ShortcutKeyGroup> shortcutKeyGroups =
+ Arrays.asList(new ShortcutKeyGroup(keyboardShortcutInfo, null));
+ shortcutMultiMappingInfos.add(
+ new ShortcutMultiMappingInfo(label, icon, shortcutKeyGroups));
+ }
}
}
@@ -1221,7 +1229,8 @@
String shortcutKeyString = null;
Drawable shortcutKeyDrawable = null;
if (info.getBaseCharacter() > Character.MIN_VALUE) {
- shortcutKeyString = String.valueOf(info.getBaseCharacter());
+ shortcutKeyString = String.valueOf(info.getBaseCharacter())
+ .toUpperCase(Locale.getDefault());
} else if (mSpecialCharacterNames.get(info.getKeycode()) != null) {
shortcutKeyString = mSpecialCharacterNames.get(info.getKeycode());
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java
index a49ca38..766c391 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java
@@ -20,6 +20,7 @@
import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_YES;
import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG;
+import static com.android.systemui.Flags.fetchBookmarksXmlKeyboardShortcuts;
import static com.android.systemui.Flags.validateKeyboardShortcutHelperIconUri;
import android.annotation.NonNull;
@@ -75,6 +76,7 @@
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
+import java.util.Locale;
/**
* Contains functionality for handling keyboard shortcuts.
@@ -133,6 +135,7 @@
@Nullable private List<KeyboardShortcutGroup> mReceivedAppShortcutGroups = null;
@Nullable private List<KeyboardShortcutGroup> mReceivedImeShortcutGroups = null;
+ @Nullable private KeyboardShortcutGroup mDefaultApplicationShortcuts = null;
@VisibleForTesting
KeyboardShortcuts(Context context, WindowManager windowManager) {
@@ -390,6 +393,7 @@
mReceivedAppShortcutGroups = null;
mReceivedImeShortcutGroups = null;
+ mDefaultApplicationShortcuts = getDefaultApplicationShortcuts(deviceId);
mWindowManager.requestAppKeyboardShortcuts(
result -> {
mBackgroundHandler.post(() -> {
@@ -443,10 +447,8 @@
mReceivedAppShortcutGroups = null;
mReceivedImeShortcutGroups = null;
- final KeyboardShortcutGroup defaultAppShortcuts =
- getDefaultApplicationShortcuts();
- if (defaultAppShortcuts != null) {
- shortcutGroups.add(defaultAppShortcuts);
+ if (mDefaultApplicationShortcuts != null) {
+ shortcutGroups.add(mDefaultApplicationShortcuts);
}
shortcutGroups.add(getSystemShortcuts());
showKeyboardShortcutsDialog(shortcutGroups);
@@ -499,7 +501,7 @@
return systemGroup;
}
- private KeyboardShortcutGroup getDefaultApplicationShortcuts() {
+ private KeyboardShortcutGroup getDefaultApplicationShortcuts(int deviceId) {
final int userId = mContext.getUserId();
List<KeyboardShortcutInfo> keyboardShortcutInfoAppItems = new ArrayList<>();
@@ -524,70 +526,82 @@
keyboardShortcutInfoAppItems.add(new KeyboardShortcutInfo(
mContext.getString(R.string.keyboard_shortcut_group_applications_assist),
assistIcon,
- KeyEvent.KEYCODE_UNKNOWN,
+ KeyEvent.KEYCODE_A,
KeyEvent.META_META_ON));
}
}
- // Browser.
- final Icon browserIcon = getIconForIntentCategory(Intent.CATEGORY_APP_BROWSER, userId);
- if (browserIcon != null) {
- keyboardShortcutInfoAppItems.add(new KeyboardShortcutInfo(
- mContext.getString(R.string.keyboard_shortcut_group_applications_browser),
- browserIcon,
- KeyEvent.KEYCODE_B,
- KeyEvent.META_META_ON));
- }
+ CharSequence categoryTitle;
+ if (fetchBookmarksXmlKeyboardShortcuts()) {
+ KeyboardShortcutGroup apps =
+ mWindowManager.getApplicationLaunchKeyboardShortcuts(deviceId);
+ categoryTitle = apps.getLabel();
+ keyboardShortcutInfoAppItems.addAll(apps.getItems());
+ } else {
+ categoryTitle = mContext.getString(R.string.keyboard_shortcut_group_applications);
+ // Browser.
+ final Icon browserIcon = getIconForIntentCategory(Intent.CATEGORY_APP_BROWSER, userId);
+ if (browserIcon != null) {
+ keyboardShortcutInfoAppItems.add(new KeyboardShortcutInfo(
+ mContext.getString(R.string.keyboard_shortcut_group_applications_browser),
+ browserIcon,
+ KeyEvent.KEYCODE_B,
+ KeyEvent.META_META_ON));
+ }
- // Contacts.
- final Icon contactsIcon = getIconForIntentCategory(Intent.CATEGORY_APP_CONTACTS, userId);
- if (contactsIcon != null) {
- keyboardShortcutInfoAppItems.add(new KeyboardShortcutInfo(
- mContext.getString(R.string.keyboard_shortcut_group_applications_contacts),
- contactsIcon,
- KeyEvent.KEYCODE_C,
- KeyEvent.META_META_ON));
- }
+ // Contacts.
+ final Icon contactsIcon = getIconForIntentCategory(
+ Intent.CATEGORY_APP_CONTACTS, userId);
+ if (contactsIcon != null) {
+ keyboardShortcutInfoAppItems.add(new KeyboardShortcutInfo(
+ mContext.getString(R.string.keyboard_shortcut_group_applications_contacts),
+ contactsIcon,
+ KeyEvent.KEYCODE_C,
+ KeyEvent.META_META_ON));
+ }
- // Email.
- final Icon emailIcon = getIconForIntentCategory(Intent.CATEGORY_APP_EMAIL, userId);
- if (emailIcon != null) {
- keyboardShortcutInfoAppItems.add(new KeyboardShortcutInfo(
- mContext.getString(R.string.keyboard_shortcut_group_applications_email),
- emailIcon,
- KeyEvent.KEYCODE_E,
- KeyEvent.META_META_ON));
- }
+ // Email.
+ final Icon emailIcon = getIconForIntentCategory(Intent.CATEGORY_APP_EMAIL, userId);
+ if (emailIcon != null) {
+ keyboardShortcutInfoAppItems.add(new KeyboardShortcutInfo(
+ mContext.getString(R.string.keyboard_shortcut_group_applications_email),
+ emailIcon,
+ KeyEvent.KEYCODE_E,
+ KeyEvent.META_META_ON));
+ }
- // Messaging.
- final Icon messagingIcon = getIconForIntentCategory(Intent.CATEGORY_APP_MESSAGING, userId);
- if (messagingIcon != null) {
- keyboardShortcutInfoAppItems.add(new KeyboardShortcutInfo(
- mContext.getString(R.string.keyboard_shortcut_group_applications_sms),
- messagingIcon,
- KeyEvent.KEYCODE_S,
- KeyEvent.META_META_ON));
- }
+ // Messaging.
+ final Icon messagingIcon = getIconForIntentCategory(
+ Intent.CATEGORY_APP_MESSAGING, userId);
+ if (messagingIcon != null) {
+ keyboardShortcutInfoAppItems.add(new KeyboardShortcutInfo(
+ mContext.getString(R.string.keyboard_shortcut_group_applications_sms),
+ messagingIcon,
+ KeyEvent.KEYCODE_S,
+ KeyEvent.META_META_ON));
+ }
- // Music.
- final Icon musicIcon = getIconForIntentCategory(Intent.CATEGORY_APP_MUSIC, userId);
- if (musicIcon != null) {
- keyboardShortcutInfoAppItems.add(new KeyboardShortcutInfo(
- mContext.getString(R.string.keyboard_shortcut_group_applications_music),
- musicIcon,
- KeyEvent.KEYCODE_P,
- KeyEvent.META_META_ON));
- }
+ // Music.
+ final Icon musicIcon = getIconForIntentCategory(Intent.CATEGORY_APP_MUSIC, userId);
+ if (musicIcon != null) {
+ keyboardShortcutInfoAppItems.add(new KeyboardShortcutInfo(
+ mContext.getString(R.string.keyboard_shortcut_group_applications_music),
+ musicIcon,
+ KeyEvent.KEYCODE_P,
+ KeyEvent.META_META_ON));
+ }
- // Calendar.
- final Icon calendarIcon = getIconForIntentCategory(Intent.CATEGORY_APP_CALENDAR, userId);
- if (calendarIcon != null) {
- keyboardShortcutInfoAppItems.add(new KeyboardShortcutInfo(
- mContext.getString(R.string.keyboard_shortcut_group_applications_calendar),
- calendarIcon,
- KeyEvent.KEYCODE_K,
- KeyEvent.META_META_ON));
+ // Calendar.
+ final Icon calendarIcon = getIconForIntentCategory(
+ Intent.CATEGORY_APP_CALENDAR, userId);
+ if (calendarIcon != null) {
+ keyboardShortcutInfoAppItems.add(new KeyboardShortcutInfo(
+ mContext.getString(R.string.keyboard_shortcut_group_applications_calendar),
+ calendarIcon,
+ KeyEvent.KEYCODE_K,
+ KeyEvent.META_META_ON));
+ }
}
final int itemsSize = keyboardShortcutInfoAppItems.size();
@@ -598,7 +612,7 @@
// Sorts by label, case insensitive with nulls and/or empty labels last.
Collections.sort(keyboardShortcutInfoAppItems, mApplicationItemsComparator);
return new KeyboardShortcutGroup(
- mContext.getString(R.string.keyboard_shortcut_group_applications),
+ categoryTitle,
keyboardShortcutInfoAppItems,
true);
}
@@ -777,7 +791,8 @@
String shortcutKeyString = null;
Drawable shortcutKeyDrawable = null;
if (info.getBaseCharacter() > Character.MIN_VALUE) {
- shortcutKeyString = String.valueOf(info.getBaseCharacter());
+ shortcutKeyString = String.valueOf(info.getBaseCharacter())
+ .toUpperCase(Locale.getDefault());
} else if (mSpecialCharacterNames.get(info.getKeycode()) != null) {
shortcutKeyString = mSpecialCharacterNames.get(info.getKeycode());
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt
index ed1756a..11ccdff 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt
@@ -23,8 +23,11 @@
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.res.R
+import com.android.systemui.statusbar.chips.StatusBarChipsLog
import com.android.systemui.statusbar.chips.call.domain.interactor.CallChipInteractor
import com.android.systemui.statusbar.chips.ui.model.ColorsModel
import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
@@ -48,6 +51,7 @@
interactor: CallChipInteractor,
systemClock: SystemClock,
private val activityStarter: ActivityStarter,
+ @StatusBarChipsLog private val logger: LogBuffer,
) : OngoingActivityChipViewModel {
override val chip: StateFlow<OngoingActivityChipModel> =
interactor.ongoingCallState
@@ -86,9 +90,9 @@
}
return View.OnClickListener { view ->
+ logger.log(TAG, LogLevel.INFO, {}, { "Chip clicked" })
val backgroundView =
view.requireViewById<ChipBackgroundContainer>(R.id.ongoing_activity_chip_background)
- // TODO(b/332662551): Log the click event.
// This mimics OngoingCallController#updateChipClickListener.
activityStarter.postStartActivityDismissingKeyguard(
state.intent,
@@ -108,5 +112,6 @@
R.string.ongoing_phone_call_content_description,
),
)
+ private const val TAG = "CallVM"
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/domain/interactor/MediaRouterChipInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/domain/interactor/MediaRouterChipInteractor.kt
index 6917f46..7c95f1e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/domain/interactor/MediaRouterChipInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/domain/interactor/MediaRouterChipInteractor.kt
@@ -18,7 +18,10 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
import com.android.systemui.mediarouter.data.repository.MediaRouterRepository
+import com.android.systemui.statusbar.chips.StatusBarChipsLog
import com.android.systemui.statusbar.chips.casttootherdevice.domain.model.MediaRouterCastModel
import com.android.systemui.statusbar.policy.CastDevice
import javax.inject.Inject
@@ -38,6 +41,7 @@
constructor(
@Application private val scope: CoroutineScope,
private val mediaRouterRepository: MediaRouterRepository,
+ @StatusBarChipsLog private val logger: LogBuffer,
) {
private val activeCastDevice: StateFlow<CastDevice?> =
mediaRouterRepository.castDevices
@@ -49,8 +53,10 @@
activeCastDevice
.map {
if (it != null) {
+ logger.log(TAG, LogLevel.INFO, { str1 = it.name }, { "State: Casting($str1)" })
MediaRouterCastModel.Casting(deviceName = it.name)
} else {
+ logger.log(TAG, LogLevel.INFO, {}, { "State: DoingNothing" })
MediaRouterCastModel.DoingNothing
}
}
@@ -60,4 +66,8 @@
fun stopCasting() {
activeCastDevice.value?.let { mediaRouterRepository.stopCasting(it) }
}
+
+ companion object {
+ private const val TAG = "MediaRouter"
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModel.kt
index f5e17df..afa9cce 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModel.kt
@@ -22,7 +22,10 @@
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
import com.android.systemui.res.R
+import com.android.systemui.statusbar.chips.StatusBarChipsLog
import com.android.systemui.statusbar.chips.casttootherdevice.domain.interactor.MediaRouterChipInteractor
import com.android.systemui.statusbar.chips.casttootherdevice.domain.model.MediaRouterCastModel
import com.android.systemui.statusbar.chips.casttootherdevice.ui.view.EndCastScreenToOtherDeviceDialogDelegate
@@ -58,6 +61,7 @@
private val mediaRouterChipInteractor: MediaRouterChipInteractor,
private val systemClock: SystemClock,
private val endMediaProjectionDialogHelper: EndMediaProjectionDialogHelper,
+ @StatusBarChipsLog private val logger: LogBuffer,
) : OngoingActivityChipViewModel {
/**
* The cast chip to show, based only on MediaProjection API events.
@@ -123,6 +127,16 @@
override val chip: StateFlow<OngoingActivityChipModel> =
combine(projectionChip, routerChip) { projection, router ->
+ logger.log(
+ TAG,
+ LogLevel.INFO,
+ {
+ str1 = projection.logName
+ str2 = router.logName
+ },
+ { "projectionChip=$str1 > routerChip=$str2" }
+ )
+
// A consequence of b/269975671 is that MediaRouter and MediaProjection APIs fire at
// different times when *screen* casting:
//
@@ -149,10 +163,13 @@
/** Stops the currently active projection. */
private fun stopProjecting() {
+ logger.log(TAG, LogLevel.INFO, {}, { "Stop casting requested (projection)" })
mediaProjectionChipInteractor.stopProjecting()
}
+ /** Stops the currently active media route. */
private fun stopMediaRouterCasting() {
+ logger.log(TAG, LogLevel.INFO, {}, { "Stop casting requested (router)" })
mediaRouterChipInteractor.stopCasting()
}
@@ -173,6 +190,8 @@
startTimeMs = systemClock.elapsedRealtime(),
createDialogLaunchOnClickListener(
createCastScreenToOtherDeviceDialogDelegate(state),
+ logger,
+ TAG,
),
)
}
@@ -188,6 +207,8 @@
colors = ColorsModel.Red,
createDialogLaunchOnClickListener(
createGenericCastToOtherDeviceDialogDelegate(deviceName),
+ logger,
+ TAG,
),
)
}
@@ -212,5 +233,6 @@
companion object {
@DrawableRes val CAST_TO_OTHER_DEVICE_ICON = R.drawable.ic_cast_connected
+ private const val TAG = "CastToOtherVM"
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModel.kt
index e201652..0c34981 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModel.kt
@@ -23,8 +23,11 @@
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
import com.android.systemui.res.R
import com.android.systemui.screenrecord.data.model.ScreenRecordModel.Starting.Companion.toCountdownSeconds
+import com.android.systemui.statusbar.chips.StatusBarChipsLog
import com.android.systemui.statusbar.chips.mediaprojection.ui.view.EndMediaProjectionDialogHelper
import com.android.systemui.statusbar.chips.screenrecord.domain.interactor.ScreenRecordChipInteractor
import com.android.systemui.statusbar.chips.screenrecord.domain.model.ScreenRecordChipModel
@@ -51,6 +54,7 @@
private val interactor: ScreenRecordChipInteractor,
private val systemClock: SystemClock,
private val endMediaProjectionDialogHelper: EndMediaProjectionDialogHelper,
+ @StatusBarChipsLog private val logger: LogBuffer,
) : OngoingActivityChipViewModel {
override val chip: StateFlow<OngoingActivityChipModel> =
interactor.screenRecordState
@@ -76,6 +80,8 @@
startTimeMs = systemClock.elapsedRealtime(),
createDialogLaunchOnClickListener(
createDelegate(state.recordedTask),
+ logger,
+ TAG,
),
)
}
@@ -90,12 +96,18 @@
return EndScreenRecordingDialogDelegate(
endMediaProjectionDialogHelper,
context,
- stopAction = interactor::stopRecording,
+ stopAction = this::stopRecording,
recordedTask,
)
}
+ private fun stopRecording() {
+ logger.log(TAG, LogLevel.INFO, {}, { "Stop recording requested" })
+ interactor.stopRecording()
+ }
+
companion object {
@DrawableRes val ICON = R.drawable.ic_screenrecord
+ private const val TAG = "ScreenRecordVM"
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModel.kt
index 45260e18..ddebd3a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModel.kt
@@ -22,7 +22,10 @@
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
import com.android.systemui.res.R
+import com.android.systemui.statusbar.chips.StatusBarChipsLog
import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.MediaProjectionChipInteractor
import com.android.systemui.statusbar.chips.mediaprojection.domain.model.ProjectionChipModel
import com.android.systemui.statusbar.chips.mediaprojection.ui.view.EndMediaProjectionDialogHelper
@@ -52,6 +55,7 @@
private val mediaProjectionChipInteractor: MediaProjectionChipInteractor,
private val systemClock: SystemClock,
private val endMediaProjectionDialogHelper: EndMediaProjectionDialogHelper,
+ @StatusBarChipsLog private val logger: LogBuffer,
) : OngoingActivityChipViewModel {
override val chip: StateFlow<OngoingActivityChipModel> =
mediaProjectionChipInteractor.projection
@@ -72,6 +76,7 @@
/** Stops the currently active projection. */
private fun stopProjecting() {
+ logger.log(TAG, LogLevel.INFO, {}, { "Stop sharing requested" })
mediaProjectionChipInteractor.stopProjecting()
}
@@ -87,7 +92,7 @@
colors = ColorsModel.Red,
// TODO(b/332662551): Maybe use a MediaProjection API to fetch this time.
startTimeMs = systemClock.elapsedRealtime(),
- createDialogLaunchOnClickListener(createShareToAppDialogDelegate(state)),
+ createDialogLaunchOnClickListener(createShareToAppDialogDelegate(state), logger, TAG),
)
}
@@ -101,5 +106,6 @@
companion object {
@DrawableRes val SHARE_TO_APP_ICON = R.drawable.ic_present_to_all
+ private const val TAG = "ShareToAppVM"
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModel.kt
index 65f94ac..ee010f7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModel.kt
@@ -17,6 +17,9 @@
package com.android.systemui.statusbar.chips.ui.viewmodel
import android.view.View
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.statusbar.chips.StatusBarChipsLog
import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
import com.android.systemui.statusbar.phone.SystemUIDialog
import kotlinx.coroutines.flow.StateFlow
@@ -33,8 +36,11 @@
/** Creates a chip click listener that launches a dialog created by [dialogDelegate]. */
fun createDialogLaunchOnClickListener(
dialogDelegate: SystemUIDialog.Delegate,
+ @StatusBarChipsLog logger: LogBuffer,
+ tag: String,
): View.OnClickListener {
- return View.OnClickListener { view ->
+ return View.OnClickListener { _ ->
+ logger.log(tag, LogLevel.INFO, {}, { "Chip clicked" })
val dialog = dialogDelegate.createDialog()
dialog.show()
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
index b63ee4c..ca5f49d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
@@ -25,6 +25,7 @@
import com.android.settingslib.notification.data.repository.ZenModeRepository;
import com.android.settingslib.notification.data.repository.ZenModeRepositoryImpl;
import com.android.settingslib.notification.domain.interactor.NotificationsSoundPolicyInteractor;
+import com.android.settingslib.notification.modes.ZenModesBackend;
import com.android.systemui.CoreStartable;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Application;
@@ -288,6 +289,7 @@
@Background Handler handler
) {
return new ZenModeRepositoryImpl(context, notificationManager,
+ ZenModesBackend.getInstance(context), context.getContentResolver(),
coroutineScope, coroutineContext, handler);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt
index eebbb13..bf44b9f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-@file:OptIn(ExperimentalCoroutinesApi::class, FlowPreview::class)
+@file:OptIn(ExperimentalCoroutinesApi::class)
package com.android.systemui.statusbar.notification.domain.interactor
@@ -25,17 +25,14 @@
import com.android.systemui.statusbar.notification.data.repository.HeadsUpRepository
import com.android.systemui.statusbar.notification.data.repository.HeadsUpRowRepository
import com.android.systemui.statusbar.notification.shared.HeadsUpRowKey
+import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor
import javax.inject.Inject
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.debounce
-import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.onStart
class HeadsUpNotificationInteractor
@Inject
@@ -50,48 +47,54 @@
val topHeadsUpRow: Flow<HeadsUpRowKey?> = headsUpRepository.topHeadsUpRow
/** Set of currently pinned top-level heads up rows to be displayed. */
- val pinnedHeadsUpRows: Flow<Set<HeadsUpRowKey>> =
- headsUpRepository.activeHeadsUpRows.flatMapLatest { repositories ->
- if (repositories.isNotEmpty()) {
- val toCombine: List<Flow<Pair<HeadsUpRowRepository, Boolean>>> =
- repositories.map { repo -> repo.isPinned.map { isPinned -> repo to isPinned } }
- combine(toCombine) { pairs ->
- pairs.filter { (_, isPinned) -> isPinned }.map { (repo, _) -> repo }.toSet()
+ val pinnedHeadsUpRows: Flow<Set<HeadsUpRowKey>> by lazy {
+ if (NotificationsHeadsUpRefactor.isUnexpectedlyInLegacyMode()) {
+ flowOf(emptySet())
+ } else {
+ headsUpRepository.activeHeadsUpRows.flatMapLatest { repositories ->
+ if (repositories.isNotEmpty()) {
+ val toCombine: List<Flow<Pair<HeadsUpRowRepository, Boolean>>> =
+ repositories.map { repo ->
+ repo.isPinned.map { isPinned -> repo to isPinned }
+ }
+ combine(toCombine) { pairs ->
+ pairs.filter { (_, isPinned) -> isPinned }.map { (repo, _) -> repo }.toSet()
+ }
+ } else {
+ // if the set is empty, there are no flows to combine
+ flowOf(emptySet())
}
- } else {
- // if the set is empty, there are no flows to combine
- flowOf(emptySet())
}
}
+ }
/** Are there any pinned heads up rows to display? */
- val hasPinnedRows: Flow<Boolean> =
- headsUpRepository.activeHeadsUpRows.flatMapLatest { rows ->
- if (rows.isNotEmpty()) {
- combine(rows.map { it.isPinned }) { pins -> pins.any { it } }
- } else {
- // if the set is empty, there are no flows to combine
- flowOf(false)
- }
- }
-
- val isHeadsUpOrAnimatingAway: Flow<Boolean> =
- combine(hasPinnedRows, headsUpRepository.isHeadsUpAnimatingAway) {
- hasPinnedRows,
- animatingAway ->
- hasPinnedRows || animatingAway
- }
- .debounce { isHeadsUpOrAnimatingAway ->
- if (isHeadsUpOrAnimatingAway) {
- 0
+ val hasPinnedRows: Flow<Boolean> by lazy {
+ if (NotificationsHeadsUpRefactor.isUnexpectedlyInLegacyMode()) {
+ flowOf(false)
+ } else {
+ headsUpRepository.activeHeadsUpRows.flatMapLatest { rows ->
+ if (rows.isNotEmpty()) {
+ combine(rows.map { it.isPinned }) { pins -> pins.any { it } }
} else {
- // When the last pinned entry is removed from the [HeadsUpRepository],
- // there might be a delay before the View starts animating.
- 50L
+ // if the set is empty, there are no flows to combine
+ flowOf(false)
}
}
- .onStart { emit(false) } // emit false, so we don't wait for the initial update
- .distinctUntilChanged()
+ }
+ }
+
+ val isHeadsUpOrAnimatingAway: Flow<Boolean> by lazy {
+ if (NotificationsHeadsUpRefactor.isUnexpectedlyInLegacyMode()) {
+ flowOf(false)
+ } else {
+ combine(hasPinnedRows, headsUpRepository.isHeadsUpAnimatingAway) {
+ hasPinnedRows,
+ animatingAway ->
+ hasPinnedRows || animatingAway
+ }
+ }
+ }
private val canShowHeadsUp: Flow<Boolean> =
combine(
@@ -109,10 +112,15 @@
}
}
- val showHeadsUpStatusBar: Flow<Boolean> =
- combine(hasPinnedRows, canShowHeadsUp) { hasPinnedRows, canShowHeadsUp ->
- hasPinnedRows && canShowHeadsUp
+ val showHeadsUpStatusBar: Flow<Boolean> by lazy {
+ if (NotificationsHeadsUpRefactor.isUnexpectedlyInLegacyMode()) {
+ flowOf(false)
+ } else {
+ combine(hasPinnedRows, canShowHeadsUp) { hasPinnedRows, canShowHeadsUp ->
+ hasPinnedRows && canShowHeadsUp
+ }
}
+ }
fun headsUpRow(key: HeadsUpRowKey): HeadsUpRowInteractor =
HeadsUpRowInteractor(key as HeadsUpRowRepository)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
index b54f9c4..5fba615 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
@@ -275,13 +275,7 @@
if (NotificationsHeadsUpRefactor.isUnexpectedlyInLegacyMode()) {
flowOf(false)
} else {
- combine(
- notificationStackInteractor.isShowingOnLockscreen,
- shadeInteractor.isShadeFullyCollapsed
- ) { (isKeyguardShowing, isShadeFullyCollapsed) ->
- !isKeyguardShowing && isShadeFullyCollapsed
- }
- .dumpWhileCollecting("headsUpAnimationsEnabled")
+ flowOf(true).dumpWhileCollecting("headsUpAnimationsEnabled")
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
index 6d76200..6f29f61 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -369,10 +369,12 @@
private void releaseBiometricWakeLock() {
if (mWakeLock != null) {
+ Trace.beginSection("release wake-and-unlock");
mHandler.removeCallbacks(mReleaseBiometricWakeLockRunnable);
mLogger.i("releasing biometric wakelock");
mWakeLock.release();
mWakeLock = null;
+ Trace.endSection();
}
}
@@ -398,7 +400,7 @@
}
mWakeLock = mPowerManager.newWakeLock(
PowerManager.PARTIAL_WAKE_LOCK, BIOMETRIC_WAKE_LOCK_NAME);
- Trace.beginSection("acquiring wake-and-unlock");
+ Trace.beginSection("acquire wake-and-unlock");
mWakeLock.acquire();
Trace.endSection();
mLogger.i("biometric acquired, grabbing biometric wakelock");
@@ -412,14 +414,13 @@
public void onBiometricDetected(int userId, BiometricSourceType biometricSourceType,
boolean isStrongBiometric) {
Trace.beginSection("BiometricUnlockController#onBiometricDetected");
- if (mUpdateMonitor.isGoingToSleep()) {
- Trace.endSection();
- return;
+ if (!mUpdateMonitor.isGoingToSleep()) {
+ startWakeAndUnlock(
+ MODE_SHOW_BOUNCER,
+ BiometricUnlockSource.Companion.fromBiometricSourceType(biometricSourceType)
+ );
}
- startWakeAndUnlock(
- MODE_SHOW_BOUNCER,
- BiometricUnlockSource.Companion.fromBiometricSourceType(biometricSourceType)
- );
+ Trace.endSection();
}
@Override
@@ -451,6 +452,7 @@
} else {
mLogger.d("onBiometricUnlocked aborted by bypass controller");
}
+ Trace.endSection();
}
/**
@@ -479,6 +481,7 @@
@WakeAndUnlockMode int mode,
BiometricUnlockSource biometricUnlockSource
) {
+ Trace.beginSection("BiometricUnlockController#startWakeAndUnlock");
mLogger.logStartWakeAndUnlock(mode);
boolean wasDeviceInteractive = mUpdateMonitor.isDeviceInteractive();
mMode = mode;
@@ -501,9 +504,7 @@
"android.policy:BIOMETRIC"
);
}
- Trace.beginSection("release wake-and-unlock");
releaseBiometricWakeLock();
- Trace.endSection();
};
final boolean wakeInKeyguard = mMode == MODE_WAKE_AND_UNLOCK_FROM_DREAM
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
index a11cbc3..98869be 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
@@ -37,6 +37,7 @@
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.systemui.Dumpable;
+import com.android.systemui.Flags;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
@@ -54,6 +55,7 @@
import com.android.systemui.tuner.TunerService;
import com.android.systemui.unfold.FoldAodAnimationController;
import com.android.systemui.unfold.SysUIUnfoldComponent;
+import com.android.systemui.util.settings.SecureSettings;
import java.io.PrintWriter;
import java.util.Optional;
@@ -86,6 +88,7 @@
private final FoldAodAnimationController mFoldAodAnimationController;
private final UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
private final UserTracker mUserTracker;
+ private final SecureSettings mSecureSettings;
private boolean mDozeAlwaysOn;
private boolean mControlScreenOffAnimation;
@@ -130,7 +133,8 @@
ConfigurationController configurationController,
StatusBarStateController statusBarStateController,
UserTracker userTracker,
- DozeInteractor dozeInteractor) {
+ DozeInteractor dozeInteractor,
+ SecureSettings secureSettings) {
mResources = resources;
mAmbientDisplayConfiguration = ambientDisplayConfiguration;
mAlwaysOnPolicy = alwaysOnDisplayPolicy;
@@ -144,6 +148,7 @@
mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController;
mUserTracker = userTracker;
mDozeInteractor = dozeInteractor;
+ mSecureSettings = secureSettings;
keyguardUpdateMonitor.registerCallback(mKeyguardVisibilityCallback);
tunerService.addTunable(
@@ -160,7 +165,8 @@
mFoldAodAnimationController.addCallback(this);
}
- SettingsObserver quickPickupSettingsObserver = new SettingsObserver(context, handler);
+ SettingsObserver quickPickupSettingsObserver =
+ new SettingsObserver(context, handler, mSecureSettings);
quickPickupSettingsObserver.observe();
batteryController.addCallback(new BatteryStateChangeCallback() {
@@ -479,18 +485,36 @@
Settings.Secure.getUriFor(Settings.Secure.DOZE_ALWAYS_ON);
private final Context mContext;
- SettingsObserver(Context context, Handler handler) {
+ private final Handler mHandler;
+ private final SecureSettings mSecureSettings;
+
+ SettingsObserver(Context context, Handler handler, SecureSettings secureSettings) {
super(handler);
mContext = context;
+ mHandler = handler;
+ mSecureSettings = secureSettings;
}
void observe() {
- ContentResolver resolver = mContext.getContentResolver();
- resolver.registerContentObserver(mQuickPickupGesture, false, this,
- UserHandle.USER_ALL);
- resolver.registerContentObserver(mPickupGesture, false, this, UserHandle.USER_ALL);
- resolver.registerContentObserver(mAlwaysOnEnabled, false, this, UserHandle.USER_ALL);
- update(null);
+ if (Flags.registerContentObserversAsync()) {
+ mSecureSettings.registerContentObserverForUserAsync(mQuickPickupGesture,
+ this, UserHandle.USER_ALL);
+ mSecureSettings.registerContentObserverForUserAsync(mPickupGesture,
+ this, UserHandle.USER_ALL);
+ mSecureSettings.registerContentObserverForUserAsync(mAlwaysOnEnabled,
+ this, UserHandle.USER_ALL,
+ // The register calls are called in order, so this ensures that update()
+ // is called after them all and value retrieval isn't racy.
+ () -> mHandler.post(() -> update(null)));
+ } else {
+ ContentResolver resolver = mContext.getContentResolver();
+ resolver.registerContentObserver(mQuickPickupGesture, false, this,
+ UserHandle.USER_ALL);
+ resolver.registerContentObserver(mPickupGesture, false, this, UserHandle.USER_ALL);
+ resolver.registerContentObserver(mAlwaysOnEnabled, false, this,
+ UserHandle.USER_ALL);
+ update(null);
+ }
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
index 97791ac..316e1f1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
@@ -22,6 +22,7 @@
import android.hardware.biometrics.BiometricSourceType
import android.provider.Settings
import com.android.app.tracing.ListenersTracing.forEachTraced
+import com.android.app.tracing.coroutines.launch
import com.android.systemui.Dumpable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
@@ -159,7 +160,7 @@
}
fun listenForQsExpandedChange() =
- applicationScope.launch {
+ applicationScope.launch("listenForQsExpandedChange") {
shadeInteractorLazy.get().qsExpansion.map { it > 0f }.distinctUntilChanged()
.collect { isQsExpanded ->
val changed = qsExpanded != isQsExpanded
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt
index bcb613f..de76b10d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt
@@ -276,10 +276,11 @@
statusBarController
}
+ val isCommunalDismissLaunch = isCommunalWidgetLaunch() && !actuallyShowOverLockscreen
// If we animate, don't collapse the shade and defer the keyguard dismiss (in case we
// run the animation on the keyguard). The animation will take care of (instantly)
// collapsing the shade and hiding the keyguard once it is done.
- val collapse = dismissShade && !animate
+ val collapse = (dismissShade || isCommunalDismissLaunch) && !animate
val runnable = Runnable {
try {
activityTransitionAnimator.startPendingIntentWithAnimation(
@@ -338,8 +339,9 @@
postOnUiThread(delay = 0) {
executeRunnableDismissingKeyguard(
runnable = runnable,
- afterKeyguardGone = willLaunchResolverActivity,
dismissShade = collapse,
+ afterKeyguardGone = willLaunchResolverActivity,
+ deferred = isCommunalDismissLaunch,
willAnimateOnKeyguard = animate,
customMessage = customMessage,
)
@@ -461,7 +463,9 @@
override fun onDismiss(): Boolean {
if (runnable != null) {
if (
- keyguardStateController.isShowing && keyguardStateController.isOccluded
+ keyguardStateController.isShowing &&
+ keyguardStateController.isOccluded &&
+ !isCommunalWidgetLaunch()
) {
statusBarKeyguardViewManagerLazy
.get()
@@ -473,17 +477,10 @@
if (dismissShade) {
shadeControllerLazy.get().collapseShadeForActivityStart()
}
- if (communalHub()) {
- communalSceneInteractor.changeSceneForActivityStartOnDismissKeyguard()
- }
return deferred
}
override fun willRunAnimationOnKeyguard(): Boolean {
- if (communalHub() && communalSceneInteractor.isIdleOnCommunal.value) {
- // Override to false when launching activity over the hub that requires auth
- return false
- }
return willAnimateOnKeyguard
}
}
@@ -639,7 +636,8 @@
showOverLockscreen: Boolean,
): Boolean {
// TODO(b/294418322): always support launch animations when occluded.
- val ignoreOcclusion = showOverLockscreen && mediaLockscreenLaunchAnimation()
+ val ignoreOcclusion =
+ (showOverLockscreen && mediaLockscreenLaunchAnimation()) || isCommunalWidgetLaunch()
if (keyguardStateController.isOccluded && !ignoreOcclusion) {
return false
}
@@ -659,6 +657,12 @@
return shouldAnimateLaunch(isActivityIntent, false)
}
+ private fun isCommunalWidgetLaunch(): Boolean {
+ return communalHub() &&
+ communalSceneInteractor.isCommunalVisible.value &&
+ communalSceneInteractor.isLaunchingWidget.value
+ }
+
private fun postOnUiThread(delay: Int = 0, runnable: Runnable) {
mainExecutor.executeDelayed(runnable, delay.toLong())
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java
index 45cb52a..994a0d0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java
@@ -183,6 +183,7 @@
@Override
public void stopCasting(CastDevice device) {
+ // TODO(b/332662551): Convert Logcat to LogBuffer.
final boolean isProjection = device.getTag() instanceof MediaProjectionInfo;
if (DEBUG) Log.d(TAG, "stopCasting isProjection=" + isProjection);
if (isProjection) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastDevice.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastDevice.kt
index 5fc160b..68edd75 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastDevice.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastDevice.kt
@@ -38,6 +38,9 @@
) {
val isCasting = state == CastState.Connecting || state == CastState.Connected
+ val shortLogString: String =
+ "CastDevice(id=$id name=$name description=$description state=$state origin=$origin)"
+
companion object {
/** Creates a [CastDevice] based on the provided information from MediaRouter. */
fun MediaRouter.RouteInfo.toCastDevice(context: Context): CastDevice {
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.kt b/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.kt
index 3bf5b65..848a6e6 100644
--- a/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.kt
@@ -16,6 +16,7 @@
package com.android.systemui.util.settings
import android.annotation.UserIdInt
+import android.annotation.WorkerThread
import android.content.ContentResolver
import android.database.ContentObserver
import android.net.Uri
@@ -66,6 +67,7 @@
} else userTracker.userId
}
+ @WorkerThread
override fun registerContentObserverSync(uri: Uri, settingsObserver: ContentObserver) {
registerContentObserverForUserSync(uri, settingsObserver, userId)
}
@@ -82,6 +84,7 @@
}
/** Convenience wrapper around [ContentResolver.registerContentObserver].' */
+ @WorkerThread
override fun registerContentObserverSync(
uri: Uri,
notifyForDescendants: Boolean,
@@ -119,6 +122,7 @@
*
* Implicitly calls [getUriFor] on the passed in name.
*/
+ @WorkerThread
fun registerContentObserverForUserSync(
name: String,
settingsObserver: ContentObserver,
@@ -159,6 +163,7 @@
}
/** Convenience wrapper around [ContentResolver.registerContentObserver] */
+ @WorkerThread
fun registerContentObserverForUserSync(
uri: Uri,
settingsObserver: ContentObserver,
@@ -199,10 +204,29 @@
}
/**
+ * Convenience wrapper around [ContentResolver.registerContentObserver].'
+ *
+ * API corresponding to [registerContentObserverForUser] for Java usage. After registration is
+ * complete, the callback block is called on the <b>background thread</b> to allow for update of
+ * value.
+ */
+ fun registerContentObserverForUserAsync(
+ uri: Uri,
+ settingsObserver: ContentObserver,
+ userHandle: Int,
+ @WorkerThread registered: Runnable
+ ) =
+ CoroutineScope(backgroundDispatcher).launch {
+ registerContentObserverForUserSync(uri, settingsObserver, userHandle)
+ registered.run()
+ }
+
+ /**
* Convenience wrapper around [ContentResolver.registerContentObserver]
*
* Implicitly calls [getUriFor] on the passed in name.
*/
+ @WorkerThread
fun registerContentObserverForUserSync(
name: String,
notifyForDescendants: Boolean,
@@ -262,6 +286,7 @@
}
/** Convenience wrapper around [ContentResolver.registerContentObserver] */
+ @WorkerThread
fun registerContentObserverForUserSync(
uri: Uri,
notifyForDescendants: Boolean,
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index 3f1ec85..ec9b5cf 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -20,9 +20,8 @@
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DIALOG_SHOWING;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DISABLE_GESTURE_PIP_ANIMATING;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DISABLE_GESTURE_SPLIT_INVOCATION;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DISABLE_GESTURE_SPLIT_INVOCATION;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_ONE_HANDED_ACTIVE;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
@@ -67,7 +66,6 @@
import com.android.wm.shell.onehanded.OneHandedTransitionCallback;
import com.android.wm.shell.onehanded.OneHandedUiEventLogger;
import com.android.wm.shell.pip.Pip;
-import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.recents.RecentTasks;
import com.android.wm.shell.splitscreen.SplitScreen;
import com.android.wm.shell.sysui.ShellInterface;
@@ -251,25 +249,7 @@
pip.showPictureInPictureMenu();
}
});
- pip.registerPipTransitionCallback(
- new PipTransitionController.PipTransitionCallback() {
- @Override
- public void onPipTransitionStarted(int direction, Rect pipBounds) {
- mSysUiState.setFlag(SYSUI_STATE_DISABLE_GESTURE_PIP_ANIMATING, true)
- .commitUpdate(mDisplayTracker.getDefaultDisplayId());
- }
- @Override
- public void onPipTransitionFinished(int direction) {
- mSysUiState.setFlag(SYSUI_STATE_DISABLE_GESTURE_PIP_ANIMATING, false)
- .commitUpdate(mDisplayTracker.getDefaultDisplayId());
- }
-
- @Override
- public void onPipTransitionCanceled(int direction) {
- // No op.
- }
- }, mSysUiMainExecutor);
mSysUiState.addCallback(sysUiStateFlag -> {
mIsSysUiStateValid = (sysUiStateFlag & INVALID_SYSUI_STATE_MASK) == 0;
pip.onSystemUiStateChanged(mIsSysUiStateValid, sysUiStateFlag);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/TestableWindowManager.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/TestableWindowManager.java
index b23dfdc..8595178 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/TestableWindowManager.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/TestableWindowManager.java
@@ -21,6 +21,7 @@
import android.graphics.Region;
import android.os.IBinder;
import android.view.Display;
+import android.view.KeyboardShortcutGroup;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowInsets;
@@ -55,6 +56,11 @@
}
@Override
+ public KeyboardShortcutGroup getApplicationLaunchKeyboardShortcuts(int deviceId) {
+ return mWindowManager.getApplicationLaunchKeyboardShortcuts(deviceId);
+ }
+
+ @Override
public Region getCurrentImeTouchRegion() {
return mWindowManager.getCurrentImeTouchRegion();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
index e2cca38..ae635b8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
@@ -41,7 +41,6 @@
import android.os.Handler;
import android.os.UserManager;
import android.provider.Settings;
-import android.service.dreams.IDreamManager;
import android.testing.TestableLooper;
import android.view.GestureDetector;
import android.view.IWindowManager;
@@ -106,7 +105,6 @@
@Mock private GlobalActions.GlobalActionsManager mWindowManagerFuncs;
@Mock private AudioManager mAudioManager;
- @Mock private IDreamManager mDreamManager;
@Mock private DevicePolicyManager mDevicePolicyManager;
@Mock private LockPatternUtils mLockPatternUtils;
@Mock private BroadcastDispatcher mBroadcastDispatcher;
@@ -165,7 +163,6 @@
mGlobalActionsDialogLite = new GlobalActionsDialogLite(mContext,
mWindowManagerFuncs,
mAudioManager,
- mDreamManager,
mDevicePolicyManager,
mLockPatternUtils,
mBroadcastDispatcher,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/data/source/TestShortcuts.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/data/source/TestShortcuts.kt
index 4fba7e3..c9c39b3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/data/source/TestShortcuts.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/data/source/TestShortcuts.kt
@@ -189,6 +189,15 @@
listOf(standardShortcutInfo1, standardShortcutInfo2, standardShortcutInfo3)
)
+ private val standardPackageName1 = "standard.app.group1"
+
+ private val standardAppGroup1 =
+ KeyboardShortcutGroup(
+ "Standard app group 1",
+ listOf(standardShortcutInfo1, standardShortcutInfo2, standardShortcutInfo3)
+ )
+ .apply { packageName = standardPackageName1 }
+
private val standardSubCategory1 =
ShortcutSubCategory(
standardGroup1.label!!.toString(),
@@ -230,6 +239,9 @@
)
)
+ val currentAppGroups = listOf(standardAppGroup1)
+ val currentAppPackageName = standardPackageName1
+
val systemGroups = listOf(standardGroup3, standardGroup2, standardGroup1)
val systemCategory =
ShortcutCategory(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModelTest.kt
index 07feaa1..69fc463 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModelTest.kt
@@ -23,6 +23,7 @@
import com.android.systemui.coroutines.collectValues
import com.android.systemui.keyboard.shortcut.data.source.FakeKeyboardShortcutGroupsSource
import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.CurrentApp
import com.android.systemui.keyboard.shortcut.shortcutHelperAppCategoriesShortcutsSource
import com.android.systemui.keyboard.shortcut.shortcutHelperCurrentAppShortcutsSource
import com.android.systemui.keyboard.shortcut.shortcutHelperInputShortcutsSource
@@ -52,6 +53,7 @@
private val fakeSystemSource = FakeKeyboardShortcutGroupsSource()
private val fakeMultiTaskingSource = FakeKeyboardShortcutGroupsSource()
+ private val fakeCurrentAppsSource = FakeKeyboardShortcutGroupsSource()
private val kosmos =
Kosmos().also {
@@ -61,7 +63,7 @@
it.shortcutHelperMultiTaskingShortcutsSource = fakeMultiTaskingSource
it.shortcutHelperAppCategoriesShortcutsSource = FakeKeyboardShortcutGroupsSource()
it.shortcutHelperInputShortcutsSource = FakeKeyboardShortcutGroupsSource()
- it.shortcutHelperCurrentAppShortcutsSource = FakeKeyboardShortcutGroupsSource()
+ it.shortcutHelperCurrentAppShortcutsSource = fakeCurrentAppsSource
}
private val testScope = kosmos.testScope
@@ -216,4 +218,17 @@
assertThat(activeUiState.defaultSelectedCategory)
.isEqualTo(activeUiState.shortcutCategories.first().type)
}
+
+ @Test
+ fun shortcutsUiState_featureActive_emitsActiveWithCurrentAppsCategorySelectedWhenPresent() =
+ testScope.runTest {
+ fakeCurrentAppsSource.setGroups(TestShortcuts.currentAppGroups)
+ val uiState by collectLastValue(viewModel.shortcutsUiState)
+
+ testHelper.showFromActivity()
+
+ val activeUiState = uiState as ShortcutsUiState.Active
+ assertThat(activeUiState.defaultSelectedCategory)
+ .isEqualTo(CurrentApp(TestShortcuts.currentAppPackageName))
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index 03afcb7..e68a4a5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -77,6 +77,7 @@
import com.android.internal.foldables.FoldGracePeriodProvider;
import com.android.internal.logging.InstanceId;
import com.android.internal.logging.UiEventLogger;
+import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardDisplayManager;
import com.android.keyguard.KeyguardSecurityView;
@@ -101,6 +102,7 @@
import com.android.systemui.kosmos.KosmosJavaAdapter;
import com.android.systemui.log.SessionTracker;
import com.android.systemui.navigationbar.NavigationModeController;
+import com.android.systemui.process.ProcessWrapper;
import com.android.systemui.scene.FakeWindowRootViewComponent;
import com.android.systemui.scene.ui.view.WindowRootView;
import com.android.systemui.settings.UserTracker;
@@ -188,6 +190,7 @@
private @Mock ActivityTransitionAnimator mActivityTransitionAnimator;
private @Mock ScrimController mScrimController;
private @Mock IActivityTaskManager mActivityTaskManagerService;
+ private @Mock IStatusBarService mStatusBarService;
private @Mock SysuiColorExtractor mColorExtractor;
private @Mock AuthController mAuthController;
private @Mock ShadeExpansionStateManager mShadeExpansionStateManager;
@@ -211,6 +214,7 @@
private @Mock SystemSettings mSystemSettings;
private @Mock SecureSettings mSecureSettings;
private @Mock AlarmManager mAlarmManager;
+ private @Mock ProcessWrapper mProcessWrapper;
private FakeSystemClock mSystemClock;
private final FakeWallpaperRepository mWallpaperRepository = new FakeWallpaperRepository();
@@ -247,6 +251,7 @@
.thenReturn(mock(Flow.class));
when(mSelectedUserInteractor.getSelectedUserId()).thenReturn(mDefaultUserId);
when(mSelectedUserInteractor.getSelectedUserId(anyBoolean())).thenReturn(mDefaultUserId);
+ when(mProcessWrapper.isSystemUser()).thenReturn(true);
mNotificationShadeWindowController = new NotificationShadeWindowControllerImpl(
mContext,
new FakeWindowRootViewComponent.Factory(mock(WindowRootView.class)),
@@ -1225,10 +1230,12 @@
() -> mActivityTransitionAnimator,
() -> mScrimController,
mActivityTaskManagerService,
+ mStatusBarService,
mFeatureFlags,
mSecureSettings,
mSystemSettings,
mSystemClock,
+ mProcessWrapper,
mDispatcher,
() -> mDreamViewModel,
() -> mCommunalTransitionViewModel,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/SessionTrackerTest.java b/packages/SystemUI/tests/src/com/android/systemui/log/SessionTrackerTest.java
index fbeb6d8..732bef1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/log/SessionTrackerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/log/SessionTrackerTest.java
@@ -44,6 +44,7 @@
import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.biometrics.AuthController;
+import com.android.systemui.process.ProcessWrapper;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import org.junit.Before;
@@ -68,6 +69,8 @@
private KeyguardStateController mKeyguardStateController;
@Mock
private UiEventLogger mUiEventLogger;
+ @Mock
+ private ProcessWrapper mProcessWrapper;
@Captor
ArgumentCaptor<KeyguardUpdateMonitorCallback> mKeyguardUpdateMonitorCallbackCaptor;
@@ -86,13 +89,15 @@
@Before
public void setup() throws RemoteException {
MockitoAnnotations.initMocks(this);
+ when(mProcessWrapper.isSystemUser()).thenReturn(true);
mSessionTracker = new SessionTracker(
mStatusBarService,
mAuthController,
mKeyguardUpdateMonitor,
mKeyguardStateController,
- mUiEventLogger
+ mUiEventLogger,
+ mProcessWrapper
);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt
index 415cc7c..988769f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt
@@ -471,7 +471,7 @@
}
@Test
- fun onPrepareForLaunch_paddingForLaunchAnimationIsConfigured() {
+ fun getPaddingForLaunchAnimation_onLongClickedState_paddingForLaunchAnimationIsConfigured() {
val startingWidth = 100
val startingHeight = 50
val deltaWidth = (QSTileViewImpl.LONG_PRESS_EFFECT_WIDTH_SCALE - 1f) * startingWidth
@@ -480,8 +480,8 @@
// GIVEN that long-press effect properties are initialized
tileView.initializeLongPressProperties(startingHeight, startingWidth)
- // WHEN the tile is preparing for the launch animation
- tileView.prepareForLaunch()
+ // WHEN the long-press effect has ended in the long-click state
+ kosmos.qsLongPressEffect.setState(QSLongPressEffect.State.LONG_CLICKED)
// THE animation padding corresponds to the tile's growth due to the effect
val padding = tileView.getPaddingForLaunchAnimation()
@@ -497,6 +497,22 @@
}
@Test
+ fun getPaddingForLaunchAnimation_notInLongClickState_paddingForLaunchAnimationIsEmpty() {
+ val startingWidth = 100
+ val startingHeight = 50
+
+ // GIVEN that long-press effect properties are initialized
+ tileView.initializeLongPressProperties(startingHeight, startingWidth)
+
+ // WHEN the long-press effect has ended in the click state
+ kosmos.qsLongPressEffect.setState(QSLongPressEffect.State.CLICKED)
+
+ // THE animation padding is empty
+ val padding = tileView.getPaddingForLaunchAnimation()
+ assertThat(padding.isEmpty).isTrue()
+ }
+
+ @Test
fun onActivityLaunchAnimationEnd_onFreshTile_longPressPropertiesAreReset() {
// WHEN an activity launch animation ends on a fresh tile
tileView.onActivityLaunchAnimationEnd()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
index 2f52248..150f53d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
@@ -28,6 +28,7 @@
import com.android.systemui.plugins.clocks.ClockId
import com.android.systemui.plugins.clocks.ClockMessageBuffers
import com.android.systemui.plugins.clocks.ClockMetadata
+import com.android.systemui.plugins.clocks.ClockPickerConfig
import com.android.systemui.plugins.clocks.ClockProviderPlugin
import com.android.systemui.plugins.clocks.ClockSettings
import com.android.systemui.plugins.PluginLifecycleManager
@@ -74,6 +75,7 @@
private lateinit var fakeDefaultProvider: FakeClockPlugin
private lateinit var pluginListener: PluginListener<ClockProviderPlugin>
private lateinit var registry: ClockRegistry
+ private lateinit var pickerConfig: ClockPickerConfig
private val featureFlags = FakeFeatureFlags()
companion object {
@@ -82,9 +84,9 @@
return null!!
}
- private fun failThumbnail(clockId: ClockId): Drawable? {
- fail("Unexpected call to getThumbnail: $clockId")
- return null
+ private fun failPickerConfig(clockId: ClockId): ClockPickerConfig {
+ fail("Unexpected call to getClockPickerConfig: $clockId")
+ return null!!
}
}
@@ -123,22 +125,31 @@
private class FakeClockPlugin : ClockProviderPlugin {
private val metadata = mutableListOf<ClockMetadata>()
private val createCallbacks = mutableMapOf<ClockId, (ClockId) -> ClockController>()
- private val thumbnailCallbacks = mutableMapOf<ClockId, (ClockId) -> Drawable?>()
+ private val pickerConfigs = mutableMapOf<ClockId, (ClockId) -> ClockPickerConfig>()
override fun getClocks() = metadata
- override fun createClock(settings: ClockSettings): ClockController =
- createCallbacks[settings.clockId!!]!!(settings.clockId!!)
- override fun getClockThumbnail(id: ClockId): Drawable? = thumbnailCallbacks[id]!!(id)
+
+ override fun createClock(settings: ClockSettings): ClockController {
+ val clockId = settings.clockId ?: throw IllegalArgumentException("No clockId specified")
+ return createCallbacks[clockId]?.invoke(clockId)
+ ?: throw NotImplementedError("No callback for '$clockId'")
+ }
+
+ override fun getClockPickerConfig(clockId: ClockId): ClockPickerConfig {
+ return pickerConfigs[clockId]?.invoke(clockId)
+ ?: throw NotImplementedError("No picker config for '$clockId'")
+ }
+
override fun initialize(buffers: ClockMessageBuffers?) { }
fun addClock(
id: ClockId,
create: (ClockId) -> ClockController = ::failFactory,
- getThumbnail: (ClockId) -> Drawable? = ::failThumbnail
+ getPickerConfig: (ClockId) -> ClockPickerConfig = ::failPickerConfig
): FakeClockPlugin {
metadata.add(ClockMetadata(id))
createCallbacks[id] = create
- thumbnailCallbacks[id] = getThumbnail
+ pickerConfigs[id] = getPickerConfig
return this
}
}
@@ -148,9 +159,10 @@
scheduler = TestCoroutineScheduler()
dispatcher = StandardTestDispatcher(scheduler)
scope = TestScope(dispatcher)
+ pickerConfig = ClockPickerConfig("CLOCK_ID", "NAME", "DESC", mockThumbnail)
fakeDefaultProvider = FakeClockPlugin()
- .addClock(DEFAULT_CLOCK_ID, { mockDefaultClock }, { mockThumbnail })
+ .addClock(DEFAULT_CLOCK_ID, { mockDefaultClock }, { pickerConfig })
whenever(mockContext.contentResolver).thenReturn(mockContentResolver)
val captor = argumentCaptor<PluginListener<ClockProviderPlugin>>()
@@ -215,8 +227,8 @@
@Test
fun clockIdConflict_ErrorWithoutCrash_unloadDuplicate() {
val plugin1 = FakeClockPlugin()
- .addClock("clock_1", { mockClock }, { mockThumbnail })
- .addClock("clock_2", { mockClock }, { mockThumbnail })
+ .addClock("clock_1", { mockClock }, { pickerConfig })
+ .addClock("clock_2", { mockClock }, { pickerConfig })
val lifecycle1 = spy(FakeLifecycle("1", plugin1))
val plugin2 = FakeClockPlugin()
@@ -238,8 +250,8 @@
assertEquals(registry.createExampleClock("clock_1"), mockClock)
assertEquals(registry.createExampleClock("clock_2"), mockClock)
- assertEquals(registry.getClockThumbnail("clock_1"), mockThumbnail)
- assertEquals(registry.getClockThumbnail("clock_2"), mockThumbnail)
+ assertEquals(registry.getClockPickerConfig("clock_1"), pickerConfig)
+ assertEquals(registry.getClockPickerConfig("clock_2"), pickerConfig)
verify(lifecycle1, never()).unloadPlugin()
verify(lifecycle2, times(2)).unloadPlugin()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt
index 2522ed7..bbe03f0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt
@@ -72,6 +72,10 @@
.thenReturn(mockSmallClockView)
whenever(layoutInflater.inflate(eq(R.layout.clock_default_large), any(), anyBoolean()))
.thenReturn(mockLargeClockView)
+ whenever(resources.getString(R.string.clock_default_name))
+ .thenReturn("DEFAULT_CLOCK_NAME")
+ whenever(resources.getString(R.string.clock_default_description))
+ .thenReturn("DEFAULT_CLOCK_DESC")
whenever(resources.getDrawable(R.drawable.clock_default_thumbnail, null))
.thenReturn(mockClockThumbnail)
whenever(mockSmallClockView.getLayoutParams()).thenReturn(FrameLayout.LayoutParams(10, 10))
@@ -85,7 +89,7 @@
// All providers need to provide clocks & thumbnails for exposed clocks
for (metadata in provider.getClocks()) {
assertNotNull(provider.createClock(metadata.clockId))
- assertNotNull(provider.getClockThumbnail(metadata.clockId))
+ assertNotNull(provider.getClockPickerConfig(metadata.clockId))
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutListSearchTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutListSearchTest.java
index 6985a27..63e56ee 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutListSearchTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutListSearchTest.java
@@ -66,7 +66,9 @@
@Before
public void setUp() {
- mKeyboardShortcutListSearch = new KeyboardShortcutListSearch(mContext, mWindowManager);
+ when(mWindowManager.getApplicationLaunchKeyboardShortcuts(anyInt())).thenReturn(
+ new KeyboardShortcutGroup("", Collections.emptyList()));
+ mKeyboardShortcutListSearch = new KeyboardShortcutListSearch(mContext, mWindowManager, -1);
mKeyboardShortcutListSearch.sInstance = mKeyboardShortcutListSearch;
mKeyboardShortcutListSearch.mKeyboardShortcutsBottomSheetDialog = mBottomSheetDialog;
mKeyboardShortcutListSearch.mContext = mContext;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutsTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutsTest.java
index 6ad8b8b..105cf16 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutsTest.java
@@ -54,6 +54,7 @@
import org.mockito.stubbing.Answer;
import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
@SmallTest
@@ -71,6 +72,8 @@
@Before
public void setUp() {
+ when(mWindowManager.getApplicationLaunchKeyboardShortcuts(anyInt())).thenReturn(
+ new KeyboardShortcutGroup("", Collections.emptyList()));
mKeyboardShortcuts = new KeyboardShortcuts(mContext, mWindowManager);
KeyboardShortcuts.sInstance = mKeyboardShortcuts;
mKeyboardShortcuts.mKeyboardShortcutsDialog = mDialog;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModelTest.kt
index 2e0c773..ca043f1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModelTest.kt
@@ -19,6 +19,7 @@
import android.view.View
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel.Companion.createDialogLaunchOnClickListener
import com.android.systemui.statusbar.phone.SystemUIDialog
import kotlin.test.Test
@@ -32,7 +33,12 @@
@Test
fun createDialogLaunchOnClickListener_showsDialogOnClick() {
- val clickListener = createDialogLaunchOnClickListener(dialogDelegate)
+ val clickListener =
+ createDialogLaunchOnClickListener(
+ dialogDelegate,
+ logcatLogBuffer("OngoingActivityChipViewModelTest"),
+ "tag",
+ )
// Dialogs must be created on the main thread
context.mainExecutor.execute {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java
index 7cb41f1..5052a00 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java
@@ -52,6 +52,8 @@
import com.android.systemui.tuner.TunerService;
import com.android.systemui.unfold.FoldAodAnimationController;
import com.android.systemui.unfold.SysUIUnfoldComponent;
+import com.android.systemui.util.settings.FakeSettings;
+import com.android.systemui.util.settings.SecureSettings;
import org.junit.Assert;
import org.junit.Before;
@@ -113,6 +115,7 @@
.thenReturn(mFoldAodAnimationController);
when(mUserTracker.getUserId()).thenReturn(ActivityManager.getCurrentUser());
+ SecureSettings secureSettings = new FakeSettings();
mDozeParameters = new DozeParameters(
mContext,
mHandler,
@@ -130,7 +133,8 @@
mConfigurationController,
mStatusBarStateController,
mUserTracker,
- mDozeInteractor
+ mDozeInteractor,
+ secureSettings
);
verify(mBatteryController).addCallback(mBatteryStateChangeCallback.capture());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt
index ef4e734..cc2ef53 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.ui.viewmodel
+import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.FlagsParameterization
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -32,6 +33,7 @@
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.statusbar.domain.interactor.keyguardStatusBarInteractor
import com.android.systemui.statusbar.notification.data.repository.FakeHeadsUpRowRepository
+import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor
import com.android.systemui.statusbar.notification.stack.data.repository.headsUpNotificationRepository
import com.android.systemui.statusbar.notification.stack.data.repository.setNotifications
import com.android.systemui.statusbar.notification.stack.domain.interactor.headsUpNotificationInteractor
@@ -126,6 +128,7 @@
}
@Test
+ @EnableFlags(NotificationsHeadsUpRefactor.FLAG_NAME)
fun isVisible_headsUpStatusBarShown_false() =
testScope.runTest {
val latest by collectLastValue(underTest.isVisible)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/settings/UserSettingsProxyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/settings/UserSettingsProxyTest.kt
index e3e20c8..5f7420d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/settings/UserSettingsProxyTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/settings/UserSettingsProxyTest.kt
@@ -31,9 +31,11 @@
import com.android.systemui.settings.UserTracker
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.advanceUntilIdle
import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertThrows
import org.junit.Before
@@ -65,20 +67,21 @@
}
@Test
- fun registerContentObserverForUser_inputString_success() {
- mSettings.registerContentObserverForUserSync(
- TEST_SETTING,
- mContentObserver,
- mUserTracker.userId
- )
- verify(mSettings.getContentResolver())
- .registerContentObserver(
- eq(TEST_SETTING_URI),
- eq(false),
- eq(mContentObserver),
- eq(MAIN_USER_ID)
+ fun registerContentObserverForUser_inputString_success() =
+ testScope.runTest {
+ mSettings.registerContentObserverForUserSync(
+ TEST_SETTING,
+ mContentObserver,
+ mUserTracker.userId
)
- }
+ verify(mSettings.getContentResolver())
+ .registerContentObserver(
+ eq(TEST_SETTING_URI),
+ eq(false),
+ eq(mContentObserver),
+ eq(MAIN_USER_ID)
+ )
+ }
@Test
fun registerContentObserverForUserSuspend_inputString_success() =
@@ -98,13 +101,14 @@
}
@Test
- fun registerContentObserverForUserAsync_inputString_success() {
- mSettings.registerContentObserverForUserAsync(
- TEST_SETTING,
- mContentObserver,
- mUserTracker.userId
- )
- testScope.launch {
+ fun registerContentObserverForUserAsync_inputString_success() =
+ testScope.runTest {
+ mSettings.registerContentObserverForUserAsync(
+ TEST_SETTING,
+ mContentObserver,
+ mUserTracker.userId
+ )
+ testScope.advanceUntilIdle()
verify(mSettings.getContentResolver())
.registerContentObserver(
eq(TEST_SETTING_URI),
@@ -113,24 +117,24 @@
eq(MAIN_USER_ID)
)
}
- }
@Test
- fun registerContentObserverForUser_inputString_notifyForDescendants_true() {
- mSettings.registerContentObserverForUserSync(
- TEST_SETTING,
- notifyForDescendants = true,
- mContentObserver,
- mUserTracker.userId
- )
- verify(mSettings.getContentResolver())
- .registerContentObserver(
- eq(TEST_SETTING_URI),
- eq(true),
- eq(mContentObserver),
- eq(MAIN_USER_ID)
+ fun registerContentObserverForUser_inputString_notifyForDescendants_true() =
+ testScope.runTest {
+ mSettings.registerContentObserverForUserSync(
+ TEST_SETTING,
+ notifyForDescendants = true,
+ mContentObserver,
+ mUserTracker.userId
)
- }
+ verify(mSettings.getContentResolver())
+ .registerContentObserver(
+ eq(TEST_SETTING_URI),
+ eq(true),
+ eq(mContentObserver),
+ eq(MAIN_USER_ID)
+ )
+ }
@Test
fun registerContentObserverForUserSuspend_inputString_notifyForDescendants_true() =
@@ -153,14 +157,15 @@
}
@Test
- fun registerContentObserverForUserAsync_inputString_notifyForDescendants_true() {
- mSettings.registerContentObserverForUserAsync(
- TEST_SETTING,
- notifyForDescendants = true,
- mContentObserver,
- mUserTracker.userId
- )
- testScope.launch {
+ fun registerContentObserverForUserAsync_inputString_notifyForDescendants_true() =
+ testScope.runTest {
+ mSettings.registerContentObserverForUserAsync(
+ TEST_SETTING,
+ notifyForDescendants = true,
+ mContentObserver,
+ mUserTracker.userId
+ )
+ testScope.advanceUntilIdle()
verify(mSettings.getContentResolver())
.registerContentObserver(
eq(TEST_SETTING_URI),
@@ -169,23 +174,23 @@
eq(MAIN_USER_ID)
)
}
- }
@Test
- fun registerContentObserverForUser_inputUri_success() {
- mSettings.registerContentObserverForUserSync(
- TEST_SETTING_URI,
- mContentObserver,
- mUserTracker.userId
- )
- verify(mSettings.getContentResolver())
- .registerContentObserver(
- eq(TEST_SETTING_URI),
- eq(false),
- eq(mContentObserver),
- eq(MAIN_USER_ID)
+ fun registerContentObserverForUser_inputUri_success() =
+ testScope.runTest {
+ mSettings.registerContentObserverForUserSync(
+ TEST_SETTING_URI,
+ mContentObserver,
+ mUserTracker.userId
)
- }
+ verify(mSettings.getContentResolver())
+ .registerContentObserver(
+ eq(TEST_SETTING_URI),
+ eq(false),
+ eq(mContentObserver),
+ eq(MAIN_USER_ID)
+ )
+ }
@Test
fun registerContentObserverForUserSuspend_inputUri_success() =
@@ -205,13 +210,15 @@
}
@Test
- fun registerContentObserverForUserAsync_inputUri_success() {
- mSettings.registerContentObserverForUserAsync(
- TEST_SETTING_URI,
- mContentObserver,
- mUserTracker.userId
- )
- testScope.launch {
+ fun registerContentObserverForUserAsync_inputUri_success() =
+ testScope.runTest {
+ mSettings.registerContentObserverForUserAsync(
+ TEST_SETTING_URI,
+ mContentObserver,
+ mUserTracker.userId
+ )
+ testScope.advanceUntilIdle()
+
verify(mSettings.getContentResolver())
.registerContentObserver(
eq(TEST_SETTING_URI),
@@ -220,24 +227,41 @@
eq(MAIN_USER_ID)
)
}
- }
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ @Test
+ fun registerContentObserverForUserAsync_callbackAfterRegister() =
+ testScope.runTest {
+ var callbackCalled = false
+ val runnable = { callbackCalled = true }
+
+ mSettings.registerContentObserverForUserAsync(
+ TEST_SETTING_URI,
+ mContentObserver,
+ mUserTracker.userId,
+ runnable
+ )
+ testScope.advanceUntilIdle()
+ assertThat(callbackCalled).isTrue()
+ }
@Test
- fun registerContentObserverForUser_inputUri_notifyForDescendants_true() {
- mSettings.registerContentObserverForUserSync(
- TEST_SETTING_URI,
- notifyForDescendants = true,
- mContentObserver,
- mUserTracker.userId
- )
- verify(mSettings.getContentResolver())
- .registerContentObserver(
- eq(TEST_SETTING_URI),
- eq(true),
- eq(mContentObserver),
- eq(MAIN_USER_ID)
+ fun registerContentObserverForUser_inputUri_notifyForDescendants_true() =
+ testScope.runTest {
+ mSettings.registerContentObserverForUserSync(
+ TEST_SETTING_URI,
+ notifyForDescendants = true,
+ mContentObserver,
+ mUserTracker.userId
)
- }
+ verify(mSettings.getContentResolver())
+ .registerContentObserver(
+ eq(TEST_SETTING_URI),
+ eq(true),
+ eq(mContentObserver),
+ eq(MAIN_USER_ID)
+ )
+ }
@Test
fun registerContentObserverForUserSuspend_inputUri_notifyForDescendants_true() =
@@ -260,14 +284,15 @@
}
@Test
- fun registerContentObserverForUserAsync_inputUri_notifyForDescendants_true() {
- mSettings.registerContentObserverForUserAsync(
- TEST_SETTING_URI,
- notifyForDescendants = true,
- mContentObserver,
- mUserTracker.userId
- )
- testScope.launch {
+ fun registerContentObserverForUserAsync_inputUri_notifyForDescendants_true() =
+ testScope.runTest {
+ mSettings.registerContentObserverForUserAsync(
+ TEST_SETTING_URI,
+ notifyForDescendants = true,
+ mContentObserver,
+ mUserTracker.userId
+ )
+ testScope.advanceUntilIdle()
verify(mSettings.getContentResolver())
.registerContentObserver(
eq(TEST_SETTING_URI),
@@ -276,14 +301,19 @@
eq(MAIN_USER_ID)
)
}
- }
@Test
- fun registerContentObserver_inputUri_success() {
- mSettings.registerContentObserverSync(TEST_SETTING_URI, mContentObserver)
- verify(mSettings.getContentResolver())
- .registerContentObserver(eq(TEST_SETTING_URI), eq(false), eq(mContentObserver), eq(0))
- }
+ fun registerContentObserver_inputUri_success() =
+ testScope.runTest {
+ mSettings.registerContentObserverSync(TEST_SETTING_URI, mContentObserver)
+ verify(mSettings.getContentResolver())
+ .registerContentObserver(
+ eq(TEST_SETTING_URI),
+ eq(false),
+ eq(mContentObserver),
+ eq(0)
+ )
+ }
@Test
fun registerContentObserverSuspend_inputUri_success() =
@@ -313,15 +343,21 @@
}
@Test
- fun registerContentObserver_inputUri_notifyForDescendants_true() {
- mSettings.registerContentObserverSync(
- TEST_SETTING_URI,
- notifyForDescendants = true,
- mContentObserver
- )
- verify(mSettings.getContentResolver())
- .registerContentObserver(eq(TEST_SETTING_URI), eq(true), eq(mContentObserver), eq(0))
- }
+ fun registerContentObserver_inputUri_notifyForDescendants_true() =
+ testScope.runTest {
+ mSettings.registerContentObserverSync(
+ TEST_SETTING_URI,
+ notifyForDescendants = true,
+ mContentObserver
+ )
+ verify(mSettings.getContentResolver())
+ .registerContentObserver(
+ eq(TEST_SETTING_URI),
+ eq(true),
+ eq(mContentObserver),
+ eq(0)
+ )
+ }
@Test
fun registerContentObserverSuspend_inputUri_notifyForDescendants_true() =
@@ -337,18 +373,19 @@
}
@Test
- fun registerContentObserverAsync_inputUri_notifyForDescendants_true() {
- mSettings.registerContentObserverAsync(TEST_SETTING_URI, mContentObserver)
- testScope.launch {
- verify(mSettings.getContentResolver())
- .registerContentObserver(
- eq(TEST_SETTING_URI),
- eq(false),
- eq(mContentObserver),
- eq(0)
- )
+ fun registerContentObserverAsync_inputUri_notifyForDescendants_true() =
+ testScope.runTest {
+ mSettings.registerContentObserverAsync(TEST_SETTING_URI, mContentObserver)
+ testScope.launch {
+ verify(mSettings.getContentResolver())
+ .registerContentObserver(
+ eq(TEST_SETTING_URI),
+ eq(false),
+ eq(mContentObserver),
+ eq(0)
+ )
+ }
}
- }
@Test
fun getString_keyPresent_returnValidValue() {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/db/DefaultWidgetPopulationKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/db/DefaultWidgetPopulationKosmos.kt
new file mode 100644
index 0000000..8d01fcd
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/db/DefaultWidgetPopulationKosmos.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.data.db
+
+import com.android.systemui.kosmos.Kosmos
+import org.mockito.Mockito.mock
+
+val Kosmos.defaultWidgetPopulation by
+ Kosmos.Fixture<DefaultWidgetPopulation> { mock(DefaultWidgetPopulation::class.java) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryKosmos.kt
new file mode 100644
index 0000000..f73f43d
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryKosmos.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright 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.education.data.repository
+
+import com.android.systemui.kosmos.Kosmos
+import java.time.Instant
+
+var Kosmos.contextualEducationRepository: ContextualEducationRepository by
+ Kosmos.Fixture { FakeContextualEducationRepository(FakeEduClock(Instant.MIN)) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/FakeContextualEducationRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/FakeContextualEducationRepository.kt
new file mode 100644
index 0000000..5410882
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/FakeContextualEducationRepository.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright 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.education.data.repository
+
+import com.android.systemui.education.data.model.GestureEduModel
+import com.android.systemui.shared.education.GestureType
+import java.time.Clock
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+class FakeContextualEducationRepository(private val clock: Clock) : ContextualEducationRepository {
+
+ private val userGestureMap = mutableMapOf<Int, GestureEduModel>()
+ private val _gestureEduModels = MutableStateFlow(GestureEduModel())
+ private val gestureEduModelsFlow = _gestureEduModels.asStateFlow()
+
+ override fun setUser(userId: Int) {
+ if (!userGestureMap.contains(userId)) {
+ userGestureMap[userId] = GestureEduModel()
+ }
+ _gestureEduModels.value = userGestureMap[userId]!!
+ }
+
+ override fun readGestureEduModelFlow(gestureType: GestureType): Flow<GestureEduModel> {
+ return gestureEduModelsFlow
+ }
+
+ override suspend fun incrementSignalCount(gestureType: GestureType) {
+ _gestureEduModels.value =
+ GestureEduModel(
+ signalCount = _gestureEduModels.value.signalCount + 1,
+ )
+ }
+
+ override suspend fun updateShortcutTriggerTime(gestureType: GestureType) {
+ _gestureEduModels.value = GestureEduModel(lastShortcutTriggeredTime = clock.instant())
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/FakeEduClock.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/FakeEduClock.kt
new file mode 100644
index 0000000..513c143
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/FakeEduClock.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright 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.education.data.repository
+
+import java.time.Clock
+import java.time.Instant
+import java.time.ZoneId
+
+class FakeEduClock(private val base: Instant) : Clock() {
+ private val zone: ZoneId = ZoneId.of("UTC")
+
+ override fun instant(): Instant {
+ return base
+ }
+
+ override fun withZone(zoneId: ZoneId?): Clock {
+ return FakeEduClock(base)
+ }
+
+ override fun getZone(): ZoneId {
+ return zone
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperTestHelper.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperTestHelper.kt
index 3e09b23..6ca5cd8 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperTestHelper.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperTestHelper.kt
@@ -41,6 +41,7 @@
}
private var imeShortcuts: List<KeyboardShortcutGroup> = emptyList()
+ private var currentAppsShortcuts: List<KeyboardShortcutGroup> = emptyList()
init {
whenever(windowManager.requestImeKeyboardShortcuts(any(), any())).thenAnswer {
@@ -48,6 +49,11 @@
keyboardShortcutReceiver.onKeyboardShortcutsReceived(imeShortcuts)
return@thenAnswer Unit
}
+ whenever(windowManager.requestAppKeyboardShortcuts(any(), any())).thenAnswer {
+ val keyboardShortcutReceiver = it.getArgument<KeyboardShortcutsReceiver>(0)
+ keyboardShortcutReceiver.onKeyboardShortcutsReceived(currentAppsShortcuts)
+ return@thenAnswer Unit
+ }
repo.start()
}
@@ -59,6 +65,14 @@
this.imeShortcuts = imeShortcuts
}
+ /**
+ * Use this method to set what current app shortcuts should be returned from windowManager in
+ * tests. By default [WindowManager.requestAppKeyboardShortcuts] will return emptyList.
+ */
+ fun setCurrentAppsShortcuts(currentAppShortcuts: List<KeyboardShortcutGroup>) {
+ this.currentAppsShortcuts = currentAppShortcuts
+ }
+
fun hideThroughCloseSystemDialogs() {
fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
context,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToAodTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToAodTransitionViewModelKosmos.kt
new file mode 100644
index 0000000..b5f0b89
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToAodTransitionViewModelKosmos.kt
@@ -0,0 +1,32 @@
+/*
+ * 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.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryUdfpsInteractor
+import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+var Kosmos.dreamingToAodTransitionViewModel by Fixture {
+ DreamingToAodTransitionViewModel(
+ deviceEntryUdfpsInteractor = deviceEntryUdfpsInteractor,
+ animationFlow = keyguardTransitionAnimationFlow,
+ )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
index 2567ffe..3c5baa5 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
@@ -46,6 +46,7 @@
dozingToGoneTransitionViewModel = dozingToGoneTransitionViewModel,
dozingToLockscreenTransitionViewModel = dozingToLockscreenTransitionViewModel,
dozingToOccludedTransitionViewModel = dozingToOccludedTransitionViewModel,
+ dreamingToAodTransitionViewModel = dreamingToAodTransitionViewModel,
dreamingToGoneTransitionViewModel = dreamingToGoneTransitionViewModel,
dreamingToLockscreenTransitionViewModel = dreamingToLockscreenTransitionViewModel,
glanceableHubToLockscreenTransitionViewModel = glanceableHubToLockscreenTransitionViewModel,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/mediarouter/data/repository/MediaRouterRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/mediarouter/data/repository/MediaRouterRepositoryKosmos.kt
index eec9920..e1ecc51 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/mediarouter/data/repository/MediaRouterRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/mediarouter/data/repository/MediaRouterRepositoryKosmos.kt
@@ -18,6 +18,7 @@
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.statusbar.policy.fakeCastController
val Kosmos.realMediaRouterRepository by
@@ -25,6 +26,7 @@
MediaRouterRepositoryImpl(
scope = applicationCoroutineScope,
castController = fakeCastController,
+ logger = logcatLogBuffer("MediaRouter"),
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelKosmos.kt
index ab71b5e..1e304d9 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelKosmos.kt
@@ -20,6 +20,7 @@
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.plugins.activityStarter
import com.android.systemui.statusbar.chips.call.domain.interactor.callChipInteractor
+import com.android.systemui.statusbar.chips.statusBarChipsLogger
import com.android.systemui.util.time.fakeSystemClock
val Kosmos.callChipViewModel: CallChipViewModel by
@@ -29,5 +30,6 @@
interactor = callChipInteractor,
systemClock = fakeSystemClock,
activityStarter = activityStarter,
+ logger = statusBarChipsLogger,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/casttootherdevice/domain/interactor/MediaRouterChipInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/casttootherdevice/domain/interactor/MediaRouterChipInteractorKosmos.kt
index cb18b68..1737bc4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/casttootherdevice/domain/interactor/MediaRouterChipInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/casttootherdevice/domain/interactor/MediaRouterChipInteractorKosmos.kt
@@ -19,11 +19,13 @@
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.mediarouter.data.repository.fakeMediaRouterRepository
+import com.android.systemui.statusbar.chips.statusBarChipsLogger
val Kosmos.mediaRouterChipInteractor by
Kosmos.Fixture {
MediaRouterChipInteractor(
scope = applicationCoroutineScope,
mediaRouterRepository = fakeMediaRouterRepository,
+ logger = statusBarChipsLogger,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelKosmos.kt
index 2335f21..3d85a4a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelKosmos.kt
@@ -22,6 +22,7 @@
import com.android.systemui.statusbar.chips.casttootherdevice.domain.interactor.mediaRouterChipInteractor
import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.mediaProjectionChipInteractor
import com.android.systemui.statusbar.chips.mediaprojection.ui.view.endMediaProjectionDialogHelper
+import com.android.systemui.statusbar.chips.statusBarChipsLogger
import com.android.systemui.util.time.fakeSystemClock
val Kosmos.castToOtherDeviceChipViewModel: CastToOtherDeviceChipViewModel by
@@ -33,5 +34,6 @@
mediaRouterChipInteractor = mediaRouterChipInteractor,
systemClock = fakeSystemClock,
endMediaProjectionDialogHelper = endMediaProjectionDialogHelper,
+ logger = statusBarChipsLogger,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelKosmos.kt
index 2773f82..e4bb166 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelKosmos.kt
@@ -21,6 +21,7 @@
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.statusbar.chips.mediaprojection.ui.view.endMediaProjectionDialogHelper
import com.android.systemui.statusbar.chips.screenrecord.domain.interactor.screenRecordChipInteractor
+import com.android.systemui.statusbar.chips.statusBarChipsLogger
import com.android.systemui.util.time.fakeSystemClock
val Kosmos.screenRecordChipViewModel: ScreenRecordChipViewModel by
@@ -31,5 +32,6 @@
interactor = screenRecordChipInteractor,
endMediaProjectionDialogHelper = endMediaProjectionDialogHelper,
systemClock = fakeSystemClock,
+ logger = statusBarChipsLogger,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelKosmos.kt
index 1b3108c..8ed7f96 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelKosmos.kt
@@ -21,6 +21,7 @@
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.mediaProjectionChipInteractor
import com.android.systemui.statusbar.chips.mediaprojection.ui.view.endMediaProjectionDialogHelper
+import com.android.systemui.statusbar.chips.statusBarChipsLogger
import com.android.systemui.util.time.fakeSystemClock
val Kosmos.shareToAppChipViewModel: ShareToAppChipViewModel by
@@ -31,5 +32,6 @@
mediaProjectionChipInteractor = mediaProjectionChipInteractor,
systemClock = fakeSystemClock,
endMediaProjectionDialogHelper = endMediaProjectionDialogHelper,
+ logger = statusBarChipsLogger,
)
}
diff --git a/services/Android.bp b/services/Android.bp
index cd974c5..dce6aa7 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -186,7 +186,15 @@
// merge all required services into one jar
// ============================================================
-java_library {
+soong_config_module_type {
+ name: "system_java_library",
+ module_type: "java_library",
+ config_namespace: "system_services",
+ bool_variables: ["without_vibrator"],
+ properties: ["vintf_fragments"],
+}
+
+system_java_library {
name: "services",
defaults: [
"services_java_defaults",
@@ -248,9 +256,19 @@
"service-sdksandbox.stubs.system_server",
],
- vintf_fragments: [
- "manifest_services.xml",
- ],
+ soong_config_variables: {
+ without_vibrator: {
+ vintf_fragments: [
+ "manifest_services.xml",
+ ],
+ conditions_default: {
+ vintf_fragments: [
+ "manifest_services.xml",
+ "manifest_services_android.frameworks.vibrator.xml",
+ ],
+ },
+ },
+ },
required: [
"libukey2_jni_shared",
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 1cd20ed..9d4310c 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -215,6 +215,7 @@
"power_hint_flags_lib",
"biometrics_flags_lib",
"am_flags_lib",
+ "updates_flags_lib",
"com_android_server_accessibility_flags_lib",
"//frameworks/libs/systemui:com_android_systemui_shared_flags_lib",
"com_android_wm_shell_flags_lib",
diff --git a/services/core/java/com/android/server/ExplicitHealthCheckController.java b/services/core/java/com/android/server/ExplicitHealthCheckController.java
index 3d610d3..6a6aea4 100644
--- a/services/core/java/com/android/server/ExplicitHealthCheckController.java
+++ b/services/core/java/com/android/server/ExplicitHealthCheckController.java
@@ -15,6 +15,7 @@
*/
package com.android.server;
+import static android.crashrecovery.flags.Flags.refactorCrashrecovery;
import static android.service.watchdog.ExplicitHealthCheckService.EXTRA_HEALTH_CHECK_PASSED_PACKAGE;
import static android.service.watchdog.ExplicitHealthCheckService.EXTRA_REQUESTED_PACKAGES;
import static android.service.watchdog.ExplicitHealthCheckService.EXTRA_SUPPORTED_PACKAGES;
@@ -41,7 +42,6 @@
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
-import com.android.internal.util.Preconditions;
import java.util.Collection;
import java.util.Collections;
@@ -363,22 +363,34 @@
@GuardedBy("mLock")
@Nullable
private ServiceInfo getServiceInfoLocked() {
- final String packageName =
- mContext.getPackageManager().getServicesSystemSharedLibraryPackageName();
- if (packageName == null) {
- Slog.w(TAG, "no external services package!");
- return null;
- }
+ if (refactorCrashrecovery()) {
+ final Intent intent = new Intent(ExplicitHealthCheckService.SERVICE_INTERFACE);
+ final ResolveInfo resolveInfo = mContext.getPackageManager().resolveService(intent,
+ PackageManager.GET_SERVICES | PackageManager.GET_META_DATA
+ | PackageManager.MATCH_SYSTEM_ONLY);
+ if (resolveInfo == null || resolveInfo.serviceInfo == null) {
+ Slog.w(TAG, "No valid components found.");
+ return null;
+ }
+ return resolveInfo.serviceInfo;
+ } else {
+ final String packageName =
+ mContext.getPackageManager().getServicesSystemSharedLibraryPackageName();
+ if (packageName == null) {
+ Slog.w(TAG, "no external services package!");
+ return null;
+ }
- final Intent intent = new Intent(ExplicitHealthCheckService.SERVICE_INTERFACE);
- intent.setPackage(packageName);
- final ResolveInfo resolveInfo = mContext.getPackageManager().resolveService(intent,
- PackageManager.GET_SERVICES | PackageManager.GET_META_DATA);
- if (resolveInfo == null || resolveInfo.serviceInfo == null) {
- Slog.w(TAG, "No valid components found.");
- return null;
+ final Intent intent = new Intent(ExplicitHealthCheckService.SERVICE_INTERFACE);
+ intent.setPackage(packageName);
+ final ResolveInfo resolveInfo = mContext.getPackageManager().resolveService(intent,
+ PackageManager.GET_SERVICES | PackageManager.GET_META_DATA);
+ if (resolveInfo == null || resolveInfo.serviceInfo == null) {
+ Slog.w(TAG, "No valid components found.");
+ return null;
+ }
+ return resolveInfo.serviceInfo;
}
- return resolveInfo.serviceInfo;
}
@GuardedBy("mLock")
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index 3201223..458749d 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -5052,6 +5052,9 @@
if (resolveInfo == null) {
return false;
}
+ if ("content".equals(intent.getScheme())) {
+ return false;
+ }
ActivityInfo targetActivityInfo = resolveInfo.activityInfo;
int targetUid = targetActivityInfo.applicationInfo.uid;
PackageManagerInternal pmi = LocalServices.getService(PackageManagerInternal.class);
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index 00183ac..67985ef 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -76,6 +76,7 @@
import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.Trace;
+import android.os.UidBatteryConsumer;
import android.os.UserHandle;
import android.os.WakeLockStats;
import android.os.WorkSource;
@@ -158,6 +159,7 @@
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collection;
+import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -1107,6 +1109,13 @@
FrameworkStatsLog.BATTERY_USAGE_STATS_BEFORE_RESET,
null, // use default PullAtomMetadata values
DIRECT_EXECUTOR, pullAtomCallback);
+ if (Flags.addBatteryUsageStatsSliceAtom()) {
+ statsManager.setPullAtomCallback(
+ FrameworkStatsLog.BATTERY_USAGE_STATS_PER_UID,
+ null, // use default PullAtomMetadata values
+ DIRECT_EXECUTOR,
+ pullAtomCallback);
+ }
}
/** StatsPullAtomCallback for pulling BatteryUsageStats data. */
@@ -1115,7 +1124,7 @@
public int onPullAtom(int atomTag, List<StatsEvent> data) {
final BatteryUsageStats bus;
switch (atomTag) {
- case FrameworkStatsLog.BATTERY_USAGE_STATS_SINCE_RESET:
+ case FrameworkStatsLog.BATTERY_USAGE_STATS_SINCE_RESET: {
@SuppressLint("MissingPermission")
final double minConsumedPowerThreshold =
DeviceConfig.getFloat(DEVICE_CONFIG_NAMESPACE,
@@ -1130,6 +1139,7 @@
.build();
bus = getBatteryUsageStats(List.of(querySinceReset)).get(0);
break;
+ }
case FrameworkStatsLog.BATTERY_USAGE_STATS_SINCE_RESET_USING_POWER_PROFILE_MODEL:
final BatteryUsageStatsQuery queryPowerProfile =
new BatteryUsageStatsQuery.Builder()
@@ -1141,7 +1151,7 @@
.build();
bus = getBatteryUsageStats(List.of(queryPowerProfile)).get(0);
break;
- case FrameworkStatsLog.BATTERY_USAGE_STATS_BEFORE_RESET:
+ case FrameworkStatsLog.BATTERY_USAGE_STATS_BEFORE_RESET: {
final long sessionStart =
getLastBatteryUsageStatsBeforeResetAtomPullTimestamp();
final long sessionEnd;
@@ -1158,6 +1168,31 @@
bus = getBatteryUsageStats(List.of(queryBeforeReset)).get(0);
setLastBatteryUsageStatsBeforeResetAtomPullTimestamp(sessionEnd);
break;
+ }
+ case FrameworkStatsLog.BATTERY_USAGE_STATS_PER_UID: {
+ if (!Flags.addBatteryUsageStatsSliceAtom()) {
+ return StatsManager.PULL_SKIP;
+ }
+
+ @SuppressLint("MissingPermission")
+ final double minConsumedPowerThreshold =
+ DeviceConfig.getFloat(
+ DEVICE_CONFIG_NAMESPACE,
+ MIN_CONSUMED_POWER_THRESHOLD_KEY,
+ 0);
+ final long sessionStart = 0;
+ final long sessionEnd = System.currentTimeMillis();
+ final BatteryUsageStatsQuery query =
+ new BatteryUsageStatsQuery.Builder()
+ .setMaxStatsAgeMs(0)
+ .includeProcessStateData()
+ .includeVirtualUids()
+ .aggregateSnapshots(sessionStart, sessionEnd)
+ .setMinConsumedPowerThreshold(minConsumedPowerThreshold)
+ .build();
+ bus = getBatteryUsageStats(List.of(query)).get(0);
+ return StatsPerUidLogger.logStats(bus, data);
+ }
default:
throw new UnsupportedOperationException("Unknown tagId=" + atomTag);
}
@@ -1169,6 +1204,262 @@
}
}
+ private static class StatsPerUidLogger {
+
+ private static final int STATSD_METRIC_MAX_DIMENSIONS_COUNT = 3000;
+
+ private static final int[] UID_PROCESS_STATES = {
+ BatteryConsumer.PROCESS_STATE_UNSPECIFIED,
+ BatteryConsumer.PROCESS_STATE_FOREGROUND,
+ BatteryConsumer.PROCESS_STATE_BACKGROUND,
+ BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE,
+ BatteryConsumer.PROCESS_STATE_CACHED
+ };
+
+ public record SessionInfo(
+ long startTs,
+ long endTs,
+ long duration,
+ int dischargePercentage,
+ long dischargeDuration) {}
+ ;
+
+ static int logStats(BatteryUsageStats bus, List<StatsEvent> data) {
+ final SessionInfo sessionInfo =
+ new SessionInfo(
+ bus.getStatsStartTimestamp(),
+ bus.getStatsEndTimestamp(),
+ bus.getStatsDuration(),
+ bus.getDischargePercentage(),
+ bus.getDischargeDurationMs());
+
+ if (DBG) {
+ Slog.d(TAG, "BatteryUsageStats dump = " + bus);
+ }
+ final BatteryConsumer deviceConsumer =
+ bus.getAggregateBatteryConsumer(
+ BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE);
+
+ final float totalDeviceConsumedPowerMah = (float) deviceConsumer.getConsumedPower();
+
+ for (@BatteryConsumer.PowerComponent int componentId = 0;
+ componentId < BatteryConsumer.POWER_COMPONENT_COUNT;
+ componentId++) {
+
+ for (@BatteryConsumer.ProcessState int processState : UID_PROCESS_STATES) {
+
+ if (!addStatsForPredefinedComponent(
+ data,
+ sessionInfo,
+ Process.INVALID_UID,
+ processState,
+ totalDeviceConsumedPowerMah,
+ deviceConsumer,
+ componentId)) {
+ return StatsManager.PULL_SUCCESS;
+ }
+ }
+ }
+
+ final int customPowerComponentCount = deviceConsumer.getCustomPowerComponentCount();
+ for (int componentId = BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID;
+ componentId
+ < BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID
+ + customPowerComponentCount;
+ componentId++) {
+
+ if (!addStatsForCustomComponent(
+ data,
+ sessionInfo,
+ Process.INVALID_UID,
+ BatteryConsumer.PROCESS_STATE_UNSPECIFIED,
+ 0,
+ totalDeviceConsumedPowerMah,
+ deviceConsumer,
+ componentId)) {
+ return StatsManager.PULL_SUCCESS;
+ }
+ }
+
+ final List<UidBatteryConsumer> uidConsumers = bus.getUidBatteryConsumers();
+ uidConsumers.sort(
+ Comparator.<BatteryConsumer>comparingDouble(BatteryConsumer::getConsumedPower)
+ .reversed());
+
+ // Log single atom for BatteryUsageStats per uid/process_state/component/etc.
+ for (UidBatteryConsumer uidConsumer : uidConsumers) {
+ final int uid = uidConsumer.getUid();
+ final float totalConsumedPowerMah = (float) uidConsumer.getConsumedPower();
+
+ for (@BatteryConsumer.PowerComponent int componentId = 0;
+ componentId < BatteryConsumer.POWER_COMPONENT_COUNT;
+ componentId++) {
+
+ for (@BatteryConsumer.ProcessState int processState : UID_PROCESS_STATES) {
+
+ if (!addStatsForPredefinedComponent(
+ data,
+ sessionInfo,
+ uid,
+ processState,
+ totalConsumedPowerMah,
+ uidConsumer,
+ componentId)) {
+ return StatsManager.PULL_SUCCESS;
+ }
+ }
+ }
+
+ // looping over custom components
+ for (int componentId = BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID;
+ componentId
+ < BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID
+ + customPowerComponentCount;
+ componentId++) {
+ for (@BatteryConsumer.ProcessState int processState : UID_PROCESS_STATES) {
+ final long timeInStateMillis =
+ uidConsumer.getTimeInProcessStateMs(processState);
+ if (timeInStateMillis <= 0) {
+ continue;
+ }
+
+ if (!addStatsForCustomComponent(
+ data,
+ sessionInfo,
+ uid,
+ processState,
+ timeInStateMillis,
+ totalConsumedPowerMah,
+ uidConsumer,
+ componentId)) {
+ return StatsManager.PULL_SUCCESS;
+ }
+ }
+ }
+ }
+ return StatsManager.PULL_SUCCESS;
+ }
+
+ private static boolean addStatsForPredefinedComponent(
+ List<StatsEvent> data,
+ SessionInfo sessionInfo,
+ int uid,
+ @BatteryConsumer.ProcessState int processState,
+ float totalConsumedPowerMah,
+ BatteryConsumer batteryConsumer,
+ @BatteryConsumer.PowerComponent int componentId) {
+ final BatteryConsumer.Key key = batteryConsumer.getKey(componentId, processState);
+ if (key == null) {
+ return true;
+ }
+
+ final String powerComponentName = BatteryConsumer.powerComponentIdToString(componentId);
+ final float powerMah = (float) batteryConsumer.getConsumedPower(key);
+ final long powerComponentDurationMillis = batteryConsumer.getUsageDurationMillis(key);
+
+ if (powerMah == 0 && powerComponentDurationMillis == 0) {
+ return true;
+ }
+
+ long timeInState = 0;
+ if (batteryConsumer instanceof UidBatteryConsumer) {
+ timeInState =
+ ((UidBatteryConsumer) batteryConsumer)
+ .getTimeInProcessStateMs(processState);
+ }
+
+ return addStatsAtom(
+ data,
+ sessionInfo,
+ uid,
+ processState,
+ timeInState,
+ powerComponentName,
+ totalConsumedPowerMah,
+ powerMah,
+ powerComponentDurationMillis);
+ }
+
+ private static boolean addStatsForCustomComponent(
+ List<StatsEvent> data,
+ SessionInfo sessionInfo,
+ int uid,
+ @BatteryConsumer.ProcessState int processState,
+ long timeInStateMillis,
+ float totalConsumedPowerMah,
+ BatteryConsumer batteryConsumer,
+ int componentId) {
+
+ if (componentId < BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID) {
+ throw new IllegalArgumentException("Invalid custom component id: " + componentId);
+ }
+
+ final float powerMah =
+ (float) batteryConsumer.getConsumedPowerForCustomComponent(componentId);
+ if (powerMah == 0) {
+ return true;
+ }
+
+ final String powerComponentName =
+ batteryConsumer.getCustomPowerComponentName(componentId);
+
+ final long powerComponentDurationMillis =
+ batteryConsumer.getUsageDurationForCustomComponentMillis(componentId);
+
+ return addStatsAtom(
+ data,
+ sessionInfo,
+ uid,
+ processState,
+ timeInStateMillis,
+ powerComponentName,
+ totalConsumedPowerMah,
+ powerMah,
+ powerComponentDurationMillis);
+ }
+
+ /**
+ * Returns true on success and false if reached max atoms capacity and no more atoms should
+ * be added
+ */
+ private static boolean addStatsAtom(
+ List<StatsEvent> data,
+ SessionInfo sessionInfo,
+ int uid,
+ int processState,
+ long timeInStateMillis,
+ String powerComponentName,
+ float totalConsumedPowerMah,
+ float powerComponentMah,
+ long powerComponentDurationMillis) {
+ data.add(
+ FrameworkStatsLog.buildStatsEvent(
+ FrameworkStatsLog.BATTERY_USAGE_STATS_PER_UID,
+ sessionInfo.startTs(),
+ sessionInfo.endTs(),
+ sessionInfo.duration(),
+ sessionInfo.dischargePercentage(),
+ sessionInfo.dischargeDuration(),
+ uid,
+ processState,
+ timeInStateMillis,
+ powerComponentName,
+ totalConsumedPowerMah,
+ powerComponentMah,
+ powerComponentDurationMillis));
+
+ // Early termination due to statsd dimensions guardrail
+ if (data.size() == STATSD_METRIC_MAX_DIMENSIONS_COUNT) {
+ Slog.w(
+ TAG,
+ "BATTERY_USAGE_STATS_PER_UID is complete reaching"
+ + " dimension guardrail");
+ return false;
+ }
+ return true;
+ }
+ }
+
@Override
@RequiresNoPermission
public boolean isCharging() {
@@ -2824,9 +3115,11 @@
pw.println(" --checkin: generate output for a checkin report; will write (and clear) the");
pw.println(" last old completed stats when they had been reset.");
pw.println(" -c: write the current stats in checkin format.");
- pw.println(" --proto: write the current aggregate stats (without history) in proto format.");
+ pw.println(
+ " --proto: write the current aggregate stats (without history) in proto format.");
pw.println(" --history: show only history data.");
- pw.println(" --history-start <num>: show only history data starting at given time offset.");
+ pw.println(
+ " --history-start <num>: show only history data starting at given time offset.");
pw.println(" --history-create-events <num>: create <num> of battery history events.");
pw.println(" --charged: only output data since last charged.");
pw.println(" --daily: only output full daily data.");
@@ -2850,12 +3143,15 @@
pw.println(" -h: print this help text.");
pw.println("Battery stats (batterystats) commands:");
pw.println(" enable|disable <option>");
- pw.println(" Enable or disable a running option. Option state is not saved across boots.");
+ pw.println(
+ " Enable or disable a running option. Option state is not saved across boots.");
pw.println(" Options are:");
pw.println(" full-history: include additional detailed events in battery history:");
pw.println(" wake_lock_in, alarms and proc events");
pw.println(" no-auto-reset: don't automatically reset stats when unplugged");
- pw.println(" pretend-screen-off: pretend the screen is off, even if screen state changes");
+ pw.println(
+ " pretend-screen-off: pretend the screen is off, even if screen state"
+ + " changes");
}
private void dumpSettings(PrintWriter pw) {
diff --git a/services/core/java/com/android/server/am/flags.aconfig b/services/core/java/com/android/server/am/flags.aconfig
index d214788..5315167 100644
--- a/services/core/java/com/android/server/am/flags.aconfig
+++ b/services/core/java/com/android/server/am/flags.aconfig
@@ -163,7 +163,7 @@
flag {
name: "collect_logcat_on_run_synchronously"
- namespace: "dropbox"
+ namespace: "stability"
description: "Allow logcat collection on synchronous dropbox collection"
bug: "324222683"
is_fixed_read_only: true
@@ -171,7 +171,7 @@
flag {
name: "enable_dropbox_watchdog_headers"
- namespace: "dropbox"
+ namespace: "stability"
description: "Add watchdog-specific dropbox headers"
bug: "330682397"
is_fixed_read_only: true
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 0d309eb..a84306b 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -739,6 +739,8 @@
// Broadcast receiver for device connections intent broadcasts
private final BroadcastReceiver mReceiver = new AudioServiceBroadcastReceiver();
+ private final Executor mAudioServerLifecycleExecutor;
+
private IMediaProjectionManager mProjectionService; // to validate projection token
/** Interface for UserManagerService. */
@@ -1059,7 +1061,8 @@
audioserverPermissions() ?
initializeAudioServerPermissionProvider(
context, audioPolicyFacade, audioserverLifecycleExecutor) :
- null
+ null,
+ audioserverLifecycleExecutor
);
}
@@ -1145,13 +1148,16 @@
* {@link AudioSystemThread} is created as the messaging thread instead.
* @param appOps {@link AppOpsManager} system service
* @param enforcer Used for permission enforcing
+ * @param permissionProvider Used to push permissions to audioserver
+ * @param audioserverLifecycleExecutor Used for tasks managing audioserver lifecycle
*/
@RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG)
public AudioService(Context context, AudioSystemAdapter audioSystem,
SystemServerAdapter systemServer, SettingsAdapter settings,
AudioVolumeGroupHelperBase audioVolumeGroupHelper, AudioPolicyFacade audioPolicy,
@Nullable Looper looper, AppOpsManager appOps, @NonNull PermissionEnforcer enforcer,
- /* @NonNull */ AudioServerPermissionProvider permissionProvider) {
+ /* @NonNull */ AudioServerPermissionProvider permissionProvider,
+ Executor audioserverLifecycleExecutor) {
super(enforcer);
sLifecycleLogger.enqueue(new EventLogger.StringEvent("AudioService()"));
mContext = context;
@@ -1159,6 +1165,7 @@
mAppOps = appOps;
mPermissionProvider = permissionProvider;
+ mAudioServerLifecycleExecutor = audioserverLifecycleExecutor;
mAudioSystem = audioSystem;
mSystemServer = systemServer;
@@ -1170,6 +1177,34 @@
mBroadcastHandlerThread = new HandlerThread("AudioService Broadcast");
mBroadcastHandlerThread.start();
+ // Listen to permission invalidations for the PermissionProvider
+ if (audioserverPermissions()) {
+ final Handler broadcastHandler = mBroadcastHandlerThread.getThreadHandler();
+ mAudioSystem.listenForSystemPropertyChange(PermissionManager.CACHE_KEY_PACKAGE_INFO,
+ new Runnable() {
+ // Roughly chosen to be long enough to suppress the autocork behavior
+ // of the permission cache (50ms), and longer than the task could reasonably
+ // take, even with many packages and users, while not introducing visible
+ // permission leaks - since the app needs to restart, and trigger an action
+ // which requires permissions from audioserver before this delay.
+ // For RECORD_AUDIO, we are additionally protected by appops.
+ final long UPDATE_DELAY_MS = 110;
+ final AtomicLong scheduledUpdateTimestamp = new AtomicLong(0);
+ @Override
+ public void run() {
+ var currentTime = SystemClock.uptimeMillis();
+ if (currentTime > scheduledUpdateTimestamp.get()) {
+ scheduledUpdateTimestamp.set(currentTime + UPDATE_DELAY_MS);
+ broadcastHandler.postAtTime( () ->
+ mAudioServerLifecycleExecutor.execute(mPermissionProvider
+ ::onPermissionStateChanged),
+ currentTime + UPDATE_DELAY_MS
+ );
+ }
+ }
+ });
+ }
+
mDeviceBroker = new AudioDeviceBroker(mContext, this, mAudioSystem);
mIsSingleVolume = AudioSystem.isSingleVolume(context);
@@ -11965,29 +12000,6 @@
provider.onServiceStart(audioPolicy.getPermissionController());
});
- // Set up event listeners
- // Must be kept in sync with PermissionManager
- Runnable cacheSysPropHandler = new Runnable() {
- private AtomicReference<SystemProperties.Handle> mHandle = new AtomicReference();
- private AtomicLong mNonce = new AtomicLong();
- @Override
- public void run() {
- if (mHandle.get() == null) {
- // Cache the handle
- mHandle.compareAndSet(null, SystemProperties.find(
- PermissionManager.CACHE_KEY_PACKAGE_INFO));
- }
- long nonce;
- SystemProperties.Handle ref;
- if ((ref = mHandle.get()) != null && (nonce = ref.getLong(0)) != 0 &&
- mNonce.getAndSet(nonce) != nonce) {
- audioserverExecutor.execute(() -> provider.onPermissionStateChanged());
- }
- }
- };
-
- SystemProperties.addChangeCallback(cacheSysPropHandler);
-
IntentFilter packageUpdateFilter = new IntentFilter();
packageUpdateFilter.addAction(ACTION_PACKAGE_ADDED);
packageUpdateFilter.addAction(ACTION_PACKAGE_REMOVED);
diff --git a/services/core/java/com/android/server/audio/AudioSystemAdapter.java b/services/core/java/com/android/server/audio/AudioSystemAdapter.java
index 7f4bc74..d083c68 100644
--- a/services/core/java/com/android/server/audio/AudioSystemAdapter.java
+++ b/services/core/java/com/android/server/audio/AudioSystemAdapter.java
@@ -748,6 +748,10 @@
return AudioSystem.setMasterMute(mute);
}
+ public void listenForSystemPropertyChange(String systemPropertyName, Runnable callback) {
+ AudioSystem.listenForSystemPropertyChange(systemPropertyName, callback);
+ }
+
/**
* Part of AudioService dump
* @param pw
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index f5231ae..7a055d1 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -619,6 +619,15 @@
* </idleScreenRefreshRateTimeout>
* <supportsVrr>true</supportsVrr>
*
+ * <dozeBrightnessSensorValueToBrightness>
+ * <item>-1</item> <!-- 0: OFF -->
+ * <item>0.003937008</item> <!-- 1: NIGHT -->
+ * <item>0.015748031</item> <!-- 2: LOW -->
+ * <item>0.102362205</item> <!-- 3: HIGH -->
+ * <item>0.106299213</item> <!-- 4: SUN -->
+ * </dozeBrightnessSensorValueToBrightness>
+ * <defaultDozeBrightness>0.235</defaultDozeBrightness>
+ *
* </displayConfiguration>
* }
* </pre>
@@ -638,6 +647,10 @@
public static final int DEFAULT_LOW_REFRESH_RATE = 60;
+ // Float.NaN (used as invalid for brightness) cannot be stored in config.xml
+ // so -2 is used instead
+ public static final float INVALID_BRIGHTNESS_IN_CONFIG = -2f;
+
@VisibleForTesting
static final float BRIGHTNESS_DEFAULT = 0.5f;
private static final String ETC_DIR = "etc";
@@ -656,10 +669,6 @@
private static final int INTERPOLATION_DEFAULT = 0;
private static final int INTERPOLATION_LINEAR = 1;
- // Float.NaN (used as invalid for brightness) cannot be stored in config.xml
- // so -2 is used instead
- private static final float INVALID_BRIGHTNESS_IN_CONFIG = -2f;
-
// Length of the ambient light horizon used to calculate the long term estimate of ambient
// light.
private static final int AMBIENT_LIGHT_LONG_HORIZON_MILLIS = 10000;
@@ -670,6 +679,11 @@
// Invalid value of AutoBrightness brightening and darkening light debounce
private static final int INVALID_AUTO_BRIGHTNESS_LIGHT_DEBOUNCE = -1;
+ @VisibleForTesting
+ static final float HDR_PERCENT_OF_SCREEN_REQUIRED_DEFAULT = 0.5f;
+
+ private static final int KEEP_CURRENT_BRIGHTNESS = -1;
+
private final Context mContext;
// The details of the ambient light sensor associated with this display.
@@ -877,6 +891,10 @@
private boolean mVrrSupportEnabled;
+ @Nullable
+ private float[] mDozeBrightnessSensorValueToBrightness;
+ private float mDefaultDozeBrightness;
+
private final DisplayManagerFlags mFlags;
@VisibleForTesting
@@ -1592,6 +1610,24 @@
return mVrrSupportEnabled;
}
+ /**
+ * While the device is dozing, a designated light sensor is used to determine the brightness.
+ * @return The mapping between doze brightness sensor values and brightness values. The value
+ * -1 means that the current brightness should be kept.
+ */
+ @Nullable
+ public float[] getDozeBrightnessSensorValueToBrightness() {
+ return mDozeBrightnessSensorValueToBrightness;
+ }
+
+ /**
+ * @return The default doze brightness to use while no other doze brightness is available. Can
+ * be {@link PowerManager#BRIGHTNESS_INVALID_FLOAT} if undefined.
+ */
+ public float getDefaultDozeBrightness() {
+ return mDefaultDozeBrightness;
+ }
+
@Override
public String toString() {
return "DisplayDeviceConfig{"
@@ -1689,6 +1725,9 @@
? mEvenDimmerBrightnessData.toString() : "null")
+ "\n"
+ "mVrrSupported= " + mVrrSupportEnabled + "\n"
+ + "mDozeBrightnessSensorValueToBrightness= "
+ + Arrays.toString(mDozeBrightnessSensorValueToBrightness) + "\n"
+ + "mDefaultDozeBrightness= " + mDefaultDozeBrightness + "\n"
+ "}";
}
@@ -1783,6 +1822,7 @@
loadBrightnessCapForWearBedtimeMode(config);
loadIdleScreenRefreshRateTimeoutConfigs(config);
mVrrSupportEnabled = config.getSupportsVrr();
+ loadDozeBrightness(config);
} else {
Slog.w(TAG, "DisplayDeviceConfig file is null");
}
@@ -1811,6 +1851,7 @@
loadRefreshRateSetting(null);
loadBrightnessCapForWearBedtimeModeFromConfigXml();
loadIdleScreenRefreshRateTimeoutConfigs(null);
+ loadDozeBrightness(null);
mLoadedFrom = "<config.xml>";
}
@@ -2745,6 +2786,37 @@
}
}
+ private void loadDozeBrightness(DisplayConfiguration config) {
+ if (mFlags.isDozeBrightnessFloatEnabled() && config != null
+ && config.getDozeBrightnessSensorValueToBrightness() != null) {
+ List<BigDecimal> values = config.getDozeBrightnessSensorValueToBrightness().getItem();
+ mDozeBrightnessSensorValueToBrightness = new float[values.size()];
+ for (int i = 0; i < values.size(); i++) {
+ float backlight = values.get(i).floatValue();
+ if (backlight != KEEP_CURRENT_BRIGHTNESS) {
+ mDozeBrightnessSensorValueToBrightness[i] =
+ getBrightnessFromBacklight(backlight);
+ } else {
+ mDozeBrightnessSensorValueToBrightness[i] = KEEP_CURRENT_BRIGHTNESS;
+ }
+ }
+ }
+
+ if (mFlags.isDozeBrightnessFloatEnabled() && config != null
+ && config.getDefaultDozeBrightness() != null) {
+ float backlight = config.getDefaultDozeBrightness().floatValue();
+ mDefaultDozeBrightness = getBrightnessFromBacklight(backlight);
+ } else {
+ mDefaultDozeBrightness = mContext.getResources().getFloat(
+ com.android.internal.R.dimen.config_screenBrightnessDozeFloat);
+ if (mDefaultDozeBrightness == INVALID_BRIGHTNESS_IN_CONFIG) {
+ mDefaultDozeBrightness = BrightnessSynchronizer.brightnessIntToFloat(
+ mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_screenBrightnessDoze));
+ }
+ }
+ }
+
private void validateIdleScreenRefreshRateTimeoutConfig(
IdleScreenRefreshRateTimeout idleScreenRefreshRateTimeoutConfig) {
IdleScreenRefreshRateTimeoutLuxThresholds idleScreenRefreshRateTimeoutLuxThresholds =
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 2f3584c..b3a6c1c 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -4726,6 +4726,32 @@
DisplayManagerService.this.mDisplayModeDirector.requestDisplayModes(
token, displayId, modeIds);
}
+
+ @EnforcePermission(android.Manifest.permission.CONTROL_DISPLAY_BRIGHTNESS)
+ @Override // Binder call
+ public float[] getDozeBrightnessSensorValueToBrightness(int displayId) {
+ getDozeBrightnessSensorValueToBrightness_enforcePermission();
+ DisplayDeviceConfig ddc =
+ mDisplayDeviceConfigProvider.getDisplayDeviceConfig(displayId);
+ if (ddc == null) {
+ throw new IllegalArgumentException(
+ "Display ID does not have a config: " + displayId);
+ }
+ return ddc.getDozeBrightnessSensorValueToBrightness();
+ }
+
+ @EnforcePermission(android.Manifest.permission.CONTROL_DISPLAY_BRIGHTNESS)
+ @Override // Binder call
+ public float getDefaultDozeBrightness(int displayId) {
+ getDefaultDozeBrightness_enforcePermission();
+ DisplayDeviceConfig ddc =
+ mDisplayDeviceConfigProvider.getDisplayDeviceConfig(displayId);
+ if (ddc == null) {
+ throw new IllegalArgumentException(
+ "Display ID does not have a config for doze-default: " + displayId);
+ }
+ return ddc.getDefaultDozeBrightness();
+ }
}
private static boolean isValidBrightness(float brightness) {
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 58309c2..5c1e783 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -272,7 +272,7 @@
private final SettingsObserver mSettingsObserver;
// The doze screen brightness.
- private final float mScreenBrightnessDozeConfig;
+ private float mScreenBrightnessDozeConfig;
// True if auto-brightness should be used.
private boolean mUseSoftwareAutoBrightnessConfig;
@@ -550,7 +550,7 @@
// DOZE AND DIM SETTINGS
mScreenBrightnessDozeConfig = BrightnessUtils.clampAbsoluteBrightness(
- pm.getBrightnessConstraint(PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_DOZE));
+ mDisplayDeviceConfig.getDefaultDozeBrightness());
loadBrightnessRampRates();
mSkipScreenOnBrightnessRamp = resources.getBoolean(
R.bool.config_skipScreenOnBrightnessRamp);
@@ -932,6 +932,8 @@
HighBrightnessModeMetadata hbmMetadata) {
// All properties that depend on the associated DisplayDevice and the DDC must be
// updated here.
+ mScreenBrightnessDozeConfig = BrightnessUtils.clampAbsoluteBrightness(
+ mDisplayDeviceConfig.getDefaultDozeBrightness());
loadBrightnessRampRates();
loadNitsRange(mContext.getResources());
setUpAutoBrightness(mContext, mHandler);
diff --git a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
index 3ce7d2a..e1934b0 100644
--- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
+++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
@@ -154,6 +154,10 @@
Flags::useFusionProxSensor
);
+ private final FlagState mDozeBrightnessFloat = new FlagState(
+ Flags.FLAG_DOZE_BRIGHTNESS_FLOAT,
+ Flags::dozeBrightnessFloat);
+
private final FlagState mOffloadControlsDozeAutoBrightness = new FlagState(
Flags.FLAG_OFFLOAD_CONTROLS_DOZE_AUTO_BRIGHTNESS,
Flags::offloadControlsDozeAutoBrightness
@@ -347,6 +351,10 @@
return mUseFusionProxSensor.getName();
}
+ public boolean isDozeBrightnessFloatEnabled() {
+ return mDozeBrightnessFloat.isEnabled();
+ }
+
/**
* @return Whether DisplayOffload should control auto-brightness in doze
*/
@@ -415,6 +423,7 @@
pw.println(" " + mRefactorDisplayPowerController);
pw.println(" " + mResolutionBackupRestore);
pw.println(" " + mUseFusionProxSensor);
+ pw.println(" " + mDozeBrightnessFloat);
pw.println(" " + mOffloadControlsDozeAutoBrightness);
pw.println(" " + mPeakRefreshRatePhysicalLimit);
pw.println(" " + mIgnoreAppPreferredRefreshRate);
diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig
index fd3af23..ac5f97f 100644
--- a/services/core/java/com/android/server/display/feature/display_flags.aconfig
+++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig
@@ -246,6 +246,14 @@
}
flag {
+ name: "doze_brightness_float"
+ namespace: "display_manager"
+ description: "Define doze brightness in the float scale [0, 1]."
+ bug: "343796384"
+ is_fixed_read_only: true
+}
+
+flag {
name: "offload_controls_doze_auto_brightness"
namespace: "display_manager"
description: "Allows the registered DisplayOffloader to control if auto-brightness is used in doze"
diff --git a/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java b/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java
index 91ab872..8ca0458 100644
--- a/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java
+++ b/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java
@@ -18,20 +18,14 @@
import android.annotation.AnyThread;
import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.annotation.WorkerThread;
-import android.content.Context;
-import android.content.pm.UserInfo;
import android.os.Handler;
import android.os.Process;
import android.util.IntArray;
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
-import com.android.internal.inputmethod.DirectBootAwareness;
-import com.android.server.LocalServices;
-import com.android.server.pm.UserManagerInternal;
import java.util.ArrayList;
import java.util.concurrent.locks.Condition;
@@ -225,49 +219,17 @@
sWriter.startThread();
}
- static void initialize(@NonNull Handler ioHandler, @NonNull Context context) {
- final UserManagerInternal userManagerInternal =
- LocalServices.getService(UserManagerInternal.class);
- ioHandler.post(() -> {
- userManagerInternal.addUserLifecycleListener(
- new UserManagerInternal.UserLifecycleListener() {
- @Override
- public void onUserCreated(UserInfo user, @Nullable Object token) {
- final int userId = user.id;
- sWriter.onUserCreated(userId);
- ioHandler.post(() -> {
- synchronized (ImfLock.class) {
- if (!sPerUserMap.contains(userId)) {
- final AdditionalSubtypeMap additionalSubtypeMap =
- AdditionalSubtypeUtils.load(userId);
- sPerUserMap.put(userId, additionalSubtypeMap);
- final InputMethodSettings settings =
- InputMethodManagerService
- .queryInputMethodServicesInternal(context,
- userId,
- additionalSubtypeMap,
- DirectBootAwareness.AUTO);
- InputMethodSettingsRepository.put(userId, settings);
- }
- }
- });
- }
+ @AnyThread
+ static void onUserCreated(@UserIdInt int userId) {
+ sWriter.onUserCreated(userId);
+ }
- @Override
- public void onUserRemoved(UserInfo user) {
- final int userId = user.id;
- sWriter.onUserRemoved(userId);
- ioHandler.post(() -> {
- synchronized (ImfLock.class) {
- sPerUserMap.remove(userId);
- }
- });
- }
- });
+ @AnyThread
+ static void remove(@UserIdInt int userId, @NonNull Handler ioHandler) {
+ sWriter.onUserRemoved(userId);
+ ioHandler.post(() -> {
synchronized (ImfLock.class) {
- for (int userId : userManagerInternal.getUserIds()) {
- sPerUserMap.put(userId, AdditionalSubtypeUtils.load(userId));
- }
+ sPerUserMap.remove(userId);
}
});
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
index 5ab493b..9837ab1 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
@@ -408,7 +408,8 @@
InputMethodManager
.invalidateLocalConnectionlessStylusHandwritingAvailabilityCaches();
}
- mService.initializeImeLocked(mCurMethod, mCurToken, mUserId);
+ mService.initializeImeLocked(mCurMethod, mCurToken,
+ InputMethodBindingController.this);
mService.scheduleNotifyImeUidToAudioService(mCurMethodUid);
mService.reRequestCurrentClientSessionLocked(mUserId);
mAutofillController.performOnCreateInlineSuggestionsRequest();
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodDrawsNavBarResourceMonitor.java b/services/core/java/com/android/server/inputmethod/InputMethodDrawsNavBarResourceMonitor.java
new file mode 100644
index 0000000..b835d05
--- /dev/null
+++ b/services/core/java/com/android/server/inputmethod/InputMethodDrawsNavBarResourceMonitor.java
@@ -0,0 +1,89 @@
+/*
+ * 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.inputmethod;
+
+import static android.content.Intent.ACTION_OVERLAY_CHANGED;
+
+import android.annotation.AnyThread;
+import android.annotation.NonNull;
+import android.annotation.SuppressLint;
+import android.annotation.UserIdInt;
+import android.annotation.WorkerThread;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.os.Handler;
+import android.os.PatternMatcher;
+import android.os.UserHandle;
+import android.util.Slog;
+
+final class InputMethodDrawsNavBarResourceMonitor {
+ private static final String TAG = "InputMethodDrawsNavBarResourceMonitor";
+
+ private static final String SYSTEM_PACKAGE_NAME = "android";
+
+ /**
+ * Not intended to be instantiated.
+ */
+ private InputMethodDrawsNavBarResourceMonitor() {
+ }
+
+ @WorkerThread
+ static boolean evaluate(@NonNull Context context, @UserIdInt int userId) {
+ final Context userAwareContext;
+ if (context.getUserId() == userId) {
+ userAwareContext = context;
+ } else {
+ userAwareContext = context.createContextAsUser(UserHandle.of(userId), 0 /* flags */);
+ }
+ try {
+ return userAwareContext.getPackageManager()
+ .getResourcesForApplication(SYSTEM_PACKAGE_NAME)
+ .getBoolean(com.android.internal.R.bool.config_imeDrawsImeNavBar);
+ } catch (PackageManager.NameNotFoundException e) {
+ Slog.e(TAG, "getResourcesForApplication(\"" + SYSTEM_PACKAGE_NAME + "\") failed",
+ e);
+ return false;
+ }
+ }
+
+ @FunctionalInterface
+ interface OnUpdateCallback {
+ void onUpdate(@UserIdInt int userId);
+ }
+
+ @SuppressLint("MissingPermission")
+ @AnyThread
+ static void registerCallback(@NonNull Context context, @NonNull Handler ioHandler,
+ @NonNull OnUpdateCallback callback) {
+ final IntentFilter intentFilter = new IntentFilter(ACTION_OVERLAY_CHANGED);
+ intentFilter.addDataScheme(IntentFilter.SCHEME_PACKAGE);
+ intentFilter.addDataSchemeSpecificPart(SYSTEM_PACKAGE_NAME, PatternMatcher.PATTERN_LITERAL);
+
+ final BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final int userId = getSendingUserId();
+ callback.onUpdate(userId);
+ }
+ };
+ context.registerReceiverAsUser(broadcastReceiver, UserHandle.ALL, intentFilter,
+ null /* broadcastPermission */, ioHandler, Context.RECEIVER_NOT_EXPORTED);
+ }
+}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 7daf958..f5faeef 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -67,6 +67,7 @@
import android.annotation.Nullable;
import android.annotation.UiThread;
import android.annotation.UserIdInt;
+import android.annotation.WorkerThread;
import android.app.ActivityManagerInternal;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@@ -169,14 +170,12 @@
import com.android.internal.inputmethod.UnbindReason;
import com.android.internal.os.TransferPipe;
import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.ConcurrentUtils;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.Preconditions;
import com.android.server.AccessibilityManagerInternal;
import com.android.server.EventLogTags;
import com.android.server.LocalServices;
import com.android.server.ServiceThread;
-import com.android.server.SystemServerInitThreadPool;
import com.android.server.SystemService;
import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
import com.android.server.input.InputManagerInternal;
@@ -202,7 +201,6 @@
import java.util.OptionalInt;
import java.util.WeakHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
-import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.IntFunction;
@@ -399,15 +397,6 @@
@SharedByAllUsersField
private IntArray mStylusIds;
- @GuardedBy("ImfLock.class")
- @Nullable
- @MultiUserUnawareField
- private OverlayableSystemBooleanResourceWrapper mImeDrawsImeNavBarRes;
- @GuardedBy("ImfLock.class")
- @Nullable
- @MultiUserUnawareField
- Future<?> mImeDrawsImeNavBarResLazyInitFuture;
-
private final ImeTracing.ServiceDumper mDumper = new ImeTracing.ServiceDumper() {
/**
* {@inheritDoc}
@@ -484,13 +473,13 @@
@SharedByAllUsersField
boolean mSystemReady;
- @GuardedBy("ImfLock.class")
+ @AnyThread
@NonNull
UserDataRepository.UserData getUserData(@UserIdInt int userId) {
return mUserDataRepository.getOrCreate(userId);
}
- @GuardedBy("ImfLock.class")
+ @AnyThread
@NonNull
InputMethodBindingController getInputMethodBindingController(@UserIdInt int userId) {
return getUserData(userId).mBindingController;
@@ -933,6 +922,14 @@
// For production code, hook up user lifecycle
mService.mUserManagerInternal.addUserLifecycleListener(this);
+
+ // Hook up resource change first before initializeUsersAsync() starts reading the
+ // seemingly initial data so that we can eliminate the race condition.
+ InputMethodDrawsNavBarResourceMonitor.registerCallback(context, mService.mIoHandler,
+ mService::onUpdateResourceOverlay);
+
+ // Also schedule user init tasks onto an I/O thread.
+ initializeUsersAsync(mService.mUserManagerInternal.getUserIds());
}
@VisibleForTesting
@@ -1015,6 +1012,9 @@
@Override
public void onUserCreated(UserInfo user, @Nullable Object token) {
// Called directly from UserManagerService. Do not block the calling thread.
+ final int userId = user.id;
+ AdditionalSubtypeMapRepository.onUserCreated(userId);
+ initializeUsersAsync(new int[userId]);
}
@Override
@@ -1022,6 +1022,8 @@
// Called directly from UserManagerService. Do not block the calling thread.
final int userId = user.id;
SecureSettingsWrapper.onUserRemoved(userId);
+ AdditionalSubtypeMapRepository.remove(userId, mService.mIoHandler);
+ InputMethodSettingsRepository.remove(userId);
mService.mUserDataRepository.remove(userId);
}
@@ -1049,6 +1051,44 @@
});
}
+ @AnyThread
+ private void initializeUsersAsync(@UserIdInt int[] userIds) {
+ mService.mIoHandler.post(() -> {
+ final var service = mService;
+ final var context = service.mContext;
+ final var userManagerInternal = service.mUserManagerInternal;
+
+ // We first create InputMethodMap for each user without loading AdditionalSubtypes.
+ final int numUsers = userIds.length;
+ final InputMethodMap[] rawMethodMaps = new InputMethodMap[numUsers];
+ for (int i = 0; i < numUsers; ++i) {
+ final int userId = userIds[i];
+ rawMethodMaps[i] = InputMethodManagerService.queryInputMethodServicesInternal(
+ context, userId, AdditionalSubtypeMap.EMPTY_MAP,
+ DirectBootAwareness.AUTO).getMethodMap();
+ final int profileParentId = userManagerInternal.getProfileParentId(userId);
+ final boolean value =
+ InputMethodDrawsNavBarResourceMonitor.evaluate(context,
+ profileParentId);
+ final var userData = mService.getUserData(userId);
+ userData.mImeDrawsNavBar.set(value);
+ }
+
+ // Then create full InputMethodMap for each user. Note that
+ // AdditionalSubtypeMapRepository#get() and InputMethodSettingsRepository#put()
+ // need to be called with ImfLock held (b/352387655).
+ // TODO(b/343601565): Avoid ImfLock after fixing b/352387655.
+ synchronized (ImfLock.class) {
+ for (int i = 0; i < numUsers; ++i) {
+ final int userId = userIds[i];
+ final var map = AdditionalSubtypeMapRepository.get(userId);
+ final var methodMap = rawMethodMaps[i].applyAdditionalSubtypes(map);
+ final var settings = InputMethodSettings.create(methodMap, userId);
+ InputMethodSettingsRepository.put(userId, settings);
+ }
+ }
+ });
+ }
}
void onUnlockUser(@UserIdInt int userId) {
@@ -1121,16 +1161,6 @@
mShowOngoingImeSwitcherForPhones = false;
- // Executing InputMethodSettingsRepository.initialize() does not mean that it
- // immediately becomes ready to return the up-to-date InputMethodSettings for each
- // running user, because we want to return from the constructor as early as possible so
- // as not to delay the system boot process.
- // Search for InputMethodSettingsRepository.put() to find where and when it's actually
- // being updated. In general IMMS should refrain from exposing the existence of IMEs
- // until systemReady().
- InputMethodSettingsRepository.initialize(mIoHandler, mContext);
- AdditionalSubtypeMapRepository.initialize(mIoHandler, mContext);
-
mCurrentUserId = mActivityManagerInternal.getCurrentUserId();
@SuppressWarnings("GuardedBy") final IntFunction<InputMethodBindingController>
bindingControllerFactory = userId -> new InputMethodBindingController(userId,
@@ -1216,36 +1246,6 @@
setSelectedInputMethodAndSubtypeLocked(defIm, NOT_A_SUBTYPE_ID, false, userId);
}
- @GuardedBy("ImfLock.class")
- private void maybeInitImeNavbarConfigLocked(@UserIdInt int targetUserId) {
- // Currently, com.android.internal.R.bool.config_imeDrawsImeNavBar is overlaid only for the
- // profile parent user.
- // TODO(b/221443458): See if we can make OverlayManager be aware of profile groups.
- final int profileParentUserId = mUserManagerInternal.getProfileParentId(targetUserId);
- if (mImeDrawsImeNavBarRes != null
- && mImeDrawsImeNavBarRes.getUserId() != profileParentUserId) {
- mImeDrawsImeNavBarRes.close();
- mImeDrawsImeNavBarRes = null;
- }
- if (mImeDrawsImeNavBarRes == null) {
- final Context userContext;
- if (mContext.getUserId() == profileParentUserId) {
- userContext = mContext;
- } else {
- userContext = mContext.createContextAsUser(UserHandle.of(profileParentUserId),
- 0 /* flags */);
- }
- mImeDrawsImeNavBarRes = OverlayableSystemBooleanResourceWrapper.create(userContext,
- com.android.internal.R.bool.config_imeDrawsImeNavBar, mHandler, resource -> {
- synchronized (ImfLock.class) {
- if (resource == mImeDrawsImeNavBarRes) {
- sendOnNavButtonFlagsChangedLocked();
- }
- }
- });
- }
- }
-
@NonNull
private static PackageManager getPackageManagerForUser(@NonNull Context context,
@UserIdInt int userId) {
@@ -1278,8 +1278,6 @@
// Hereafter we start initializing things for "newUserId".
- maybeInitImeNavbarConfigLocked(newUserId);
-
final var newUserData = getUserData(newUserId);
// TODO(b/342027196): Double check if we need to always reset upon user switching.
@@ -1358,23 +1356,6 @@
});
}
- // TODO(b/32343335): The entire systemRunning() method needs to be revisited.
- mImeDrawsImeNavBarResLazyInitFuture = SystemServerInitThreadPool.submit(() -> {
- // Note that the synchronization block below guarantees that the task
- // can never be completed before the returned Future<?> object is assigned to
- // the "mImeDrawsImeNavBarResLazyInitFuture" field.
- synchronized (ImfLock.class) {
- mImeDrawsImeNavBarResLazyInitFuture = null;
- if (currentUserId != mCurrentUserId) {
- // This means that the current user is already switched to other user
- // before the background task is executed. In this scenario the relevant
- // field should already be initialized.
- return;
- }
- maybeInitImeNavbarConfigLocked(currentUserId);
- }
- }, "Lazily initialize IMMS#mImeDrawsImeNavBarRes");
-
mMyPackageMonitor.register(mContext, UserHandle.ALL, mIoHandler);
SecureSettingsChangeCallback.register(mHandler, mContext.getContentResolver(),
new String[] {
@@ -1410,9 +1391,7 @@
getPackageManagerForUser(mContext, currentUserId),
newSettings.getEnabledInputMethodList());
- final var unused = SystemServerInitThreadPool.submit(
- AdditionalSubtypeMapRepository::startWriterThread,
- "Start AdditionalSubtypeMapRepository's writer thread");
+ AdditionalSubtypeMapRepository.startWriterThread();
if (mConcurrentMultiUserModeEnabled) {
for (int userId : mUserManagerInternal.getUserIds()) {
@@ -1896,7 +1875,8 @@
userData.mCurClient.mUid, true /* direct */);
}
- @InputMethodNavButtonFlags final int navButtonFlags = getInputMethodNavButtonFlagsLocked();
+ @InputMethodNavButtonFlags final int navButtonFlags =
+ getInputMethodNavButtonFlagsLocked(userData);
final SessionState session = userData.mCurClient.mCurSession;
setEnabledSessionLocked(session, userData);
session.mMethod.startInput(startInputToken, userData.mCurInputConnection,
@@ -2288,15 +2268,15 @@
@GuardedBy("ImfLock.class")
void initializeImeLocked(@NonNull IInputMethodInvoker inputMethod, @NonNull IBinder token,
- @UserIdInt int userId) {
+ @NonNull InputMethodBindingController bindingController) {
if (DEBUG) {
Slog.v(TAG, "Sending attach of token: " + token + " for display: "
- + getInputMethodBindingController(userId).getCurTokenDisplayId());
+ + bindingController.getCurTokenDisplayId());
}
+ final int userId = bindingController.getUserId();
inputMethod.initializeInternal(token,
new InputMethodPrivilegedOperationsImpl(this, token, userId),
- // TODO(b/345519864): Make getInputMethodNavButtonFlagsLocked() multi-user aware
- getInputMethodNavButtonFlagsLocked());
+ getInputMethodNavButtonFlagsLocked(getUserData(userId)));
}
@AnyThread
@@ -2595,23 +2575,17 @@
@GuardedBy("ImfLock.class")
@InputMethodNavButtonFlags
- private int getInputMethodNavButtonFlagsLocked() {
- // TODO(b/345519864): Make mImeDrawsImeNavBarRes multi-user aware.
- final int userId = mCurrentUserId;
- final var bindingController = getInputMethodBindingController(userId);
- if (mImeDrawsImeNavBarResLazyInitFuture != null) {
- // TODO(b/225366708): Avoid Future.get(), which is internally used here.
- ConcurrentUtils.waitForFutureNoInterrupt(mImeDrawsImeNavBarResLazyInitFuture,
- "Waiting for the lazy init of mImeDrawsImeNavBarRes");
- }
+ private int getInputMethodNavButtonFlagsLocked(
+ @NonNull UserDataRepository.UserData userData) {
+ final int userId = userData.mUserId;
+ final var bindingController = userData.mBindingController;
// Whether the current display has a navigation bar. When this is false (e.g. emulator),
// the IME should not draw the IME navigation bar.
final int tokenDisplayId = bindingController.getCurTokenDisplayId();
final boolean hasNavigationBar = mWindowManagerInternal
.hasNavigationBar(tokenDisplayId != INVALID_DISPLAY
? tokenDisplayId : DEFAULT_DISPLAY);
- final boolean canImeDrawsImeNavBar =
- mImeDrawsImeNavBarRes != null && mImeDrawsImeNavBarRes.get() && hasNavigationBar;
+ final boolean canImeDrawsImeNavBar = userData.mImeDrawsNavBar.get() && hasNavigationBar;
final boolean shouldShowImeSwitcherWhenImeIsShown = shouldShowImeSwitcherLocked(
InputMethodService.IME_ACTIVE | InputMethodService.IME_VISIBLE, userId);
return (canImeDrawsImeNavBar ? InputMethodNavButtonFlags.IME_DRAWS_IME_NAV_BAR : 0)
@@ -2955,7 +2929,7 @@
final var userData = getUserData(userId);
userData.mSwitchingController.resetCircularListLocked(mContext, settings);
userData.mHardwareKeyboardShortcutController.update(settings);
- sendOnNavButtonFlagsChangedLocked();
+ sendOnNavButtonFlagsChangedLocked(userData);
}
@GuardedBy("ImfLock.class")
@@ -4979,7 +4953,7 @@
case MSG_HARD_KEYBOARD_SWITCH_CHANGED:
mMenuController.handleHardKeyboardStatusChange(msg.arg1 == 1);
synchronized (ImfLock.class) {
- sendOnNavButtonFlagsChangedLocked();
+ sendOnNavButtonFlagsChangedToAllImesLocked();
}
return true;
case MSG_SYSTEM_UNLOCK_USER: {
@@ -5313,7 +5287,7 @@
userData.mSwitchingController.resetCircularListLocked(mContext, settings);
userData.mHardwareKeyboardShortcutController.update(settings);
- sendOnNavButtonFlagsChangedLocked();
+ sendOnNavButtonFlagsChangedLocked(userData);
// Notify InputMethodListListeners of the new installed InputMethods.
final List<InputMethodInfo> inputMethodList = settings.getMethodList();
@@ -5322,14 +5296,38 @@
}
@GuardedBy("ImfLock.class")
- void sendOnNavButtonFlagsChangedLocked() {
- final var bindingController = getInputMethodBindingController(mCurrentUserId);
+ void sendOnNavButtonFlagsChangedToAllImesLocked() {
+ for (int userId : mUserManagerInternal.getUserIds()) {
+ sendOnNavButtonFlagsChangedLocked(getUserData(userId));
+ }
+ }
+
+ @GuardedBy("ImfLock.class")
+ void sendOnNavButtonFlagsChangedLocked(@NonNull UserDataRepository.UserData userData) {
+ final var bindingController = userData.mBindingController;
final IInputMethodInvoker curMethod = bindingController.getCurMethod();
if (curMethod == null) {
// No need to send the data if the IME is not yet bound.
return;
}
- curMethod.onNavButtonFlagsChanged(getInputMethodNavButtonFlagsLocked());
+ curMethod.onNavButtonFlagsChanged(getInputMethodNavButtonFlagsLocked(userData));
+ }
+
+ @WorkerThread
+ private void onUpdateResourceOverlay(@UserIdInt int userId) {
+ final int profileParentId = mUserManagerInternal.getProfileParentId(userId);
+ final boolean value =
+ InputMethodDrawsNavBarResourceMonitor.evaluate(mContext, profileParentId);
+ final var profileUserIds = mUserManagerInternal.getProfileIds(profileParentId, false);
+ final ArrayList<UserDataRepository.UserData> updatedUsers = new ArrayList<>();
+ for (int profileUserId : profileUserIds) {
+ final var userData = getUserData(profileUserId);
+ userData.mImeDrawsNavBar.set(value);
+ updatedUsers.add(userData);
+ }
+ synchronized (ImfLock.class) {
+ updatedUsers.forEach(this::sendOnNavButtonFlagsChangedLocked);
+ }
}
@GuardedBy("ImfLock.class")
@@ -6097,6 +6095,7 @@
u.mImeBindingState.dump(" ", p);
p.println(" enabledSession=" + u.mEnabledSession);
p.println(" inFullscreenMode=" + u.mInFullscreenMode);
+ p.println(" imeDrawsNavBar=" + u.mImeDrawsNavBar.get());
p.println(" switchingController:");
u.mSwitchingController.dump(p, " ");
p.println(" mLastEnabledInputMethodsStr="
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
index 656c87d..06f73f3 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
@@ -202,7 +202,7 @@
attrs.setTitle("Select input method");
w.setAttributes(attrs);
mService.updateSystemUiLocked(userId);
- mService.sendOnNavButtonFlagsChangedLocked();
+ mService.sendOnNavButtonFlagsChangedLocked(mService.getUserData(userId));
mSwitchingDialog.show();
}
@@ -242,7 +242,7 @@
// TODO(b/305849394): Make InputMethodMenuController multi-user aware
final int userId = mService.getCurrentImeUserIdLocked();
mService.updateSystemUiLocked(userId);
- mService.sendOnNavButtonFlagsChangedLocked();
+ mService.sendOnNavButtonFlagsChangedToAllImesLocked();
mDialogBuilder = null;
mIms = null;
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodSettingsRepository.java b/services/core/java/com/android/server/inputmethod/InputMethodSettingsRepository.java
index a4d8ee5..50ba364 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodSettingsRepository.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodSettingsRepository.java
@@ -16,17 +16,12 @@
package com.android.server.inputmethod;
+import android.annotation.AnyThread;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
-import android.content.Context;
-import android.content.pm.UserInfo;
-import android.os.Handler;
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
-import com.android.internal.inputmethod.DirectBootAwareness;
-import com.android.server.LocalServices;
-import com.android.server.pm.UserManagerInternal;
final class InputMethodSettingsRepository {
@GuardedBy("ImfLock.class")
@@ -54,33 +49,10 @@
sPerUserMap.put(userId, obj);
}
- static void initialize(@NonNull Handler ioHandler, @NonNull Context context) {
- final UserManagerInternal userManagerInternal =
- LocalServices.getService(UserManagerInternal.class);
- ioHandler.post(() -> {
- userManagerInternal.addUserLifecycleListener(
- new UserManagerInternal.UserLifecycleListener() {
- @Override
- public void onUserRemoved(UserInfo user) {
- final int userId = user.id;
- ioHandler.post(() -> {
- synchronized (ImfLock.class) {
- sPerUserMap.remove(userId);
- }
- });
- }
- });
- synchronized (ImfLock.class) {
- for (int userId : userManagerInternal.getUserIds()) {
- final InputMethodSettings settings =
- InputMethodManagerService.queryInputMethodServicesInternal(
- context,
- userId,
- AdditionalSubtypeMapRepository.get(userId),
- DirectBootAwareness.AUTO);
- put(userId, settings);
- }
- }
- });
+ @AnyThread
+ static void remove(@UserIdInt int userId) {
+ synchronized (ImfLock.class) {
+ sPerUserMap.remove(userId);
+ }
}
}
diff --git a/services/core/java/com/android/server/inputmethod/OverlayableSystemBooleanResourceWrapper.java b/services/core/java/com/android/server/inputmethod/OverlayableSystemBooleanResourceWrapper.java
deleted file mode 100644
index 33e7a76..0000000
--- a/services/core/java/com/android/server/inputmethod/OverlayableSystemBooleanResourceWrapper.java
+++ /dev/null
@@ -1,159 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.inputmethod;
-
-import static android.content.Intent.ACTION_OVERLAY_CHANGED;
-
-import android.annotation.AnyThread;
-import android.annotation.BoolRes;
-import android.annotation.NonNull;
-import android.annotation.UserHandleAware;
-import android.annotation.UserIdInt;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.PackageManager;
-import android.os.Handler;
-import android.os.PatternMatcher;
-import android.util.Slog;
-
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicReference;
-import java.util.function.Consumer;
-
-/**
- * A wrapper object for any boolean resource defined in {@code "android"} package, in a way that is
- * aware of per-user Runtime Resource Overlay (RRO).
- */
-final class OverlayableSystemBooleanResourceWrapper implements AutoCloseable {
- private static final String TAG = "OverlayableSystemBooleanResourceWrapper";
-
- private static final String SYSTEM_PACKAGE_NAME = "android";
-
- @UserIdInt
- private final int mUserId;
- @NonNull
- private final AtomicBoolean mValueRef;
- @NonNull
- private final AtomicReference<Runnable> mCleanerRef;
-
- /**
- * Creates {@link OverlayableSystemBooleanResourceWrapper} for the given boolean resource ID
- * with a value change callback for the user associated with the {@link Context}.
- *
- * @param userContext The {@link Context} to be used to access the resource. This needs to be
- * associated with the right user because the Runtime Resource Overlay (RRO)
- * is per-user configuration.
- * @param boolResId The resource ID to be queried.
- * @param handler {@link Handler} to be used to dispatch {@code callback}.
- * @param callback The callback to be notified when the specified value might be updated.
- * The callback needs to take care of spurious wakeup. The value returned from
- * {@link #get()} may look to be exactly the same as the previously read value
- * e.g. when the value is changed from {@code false} to {@code true} to
- * {@code false} in a very short period of time, because {@link #get()} always
- * does volatile-read.
- * @return New {@link OverlayableSystemBooleanResourceWrapper}.
- */
- @NonNull
- @UserHandleAware
- static OverlayableSystemBooleanResourceWrapper create(@NonNull Context userContext,
- @BoolRes int boolResId, @NonNull Handler handler,
- @NonNull Consumer<OverlayableSystemBooleanResourceWrapper> callback) {
-
- // Note that we cannot fully trust this initial value due to the dead time between obtaining
- // the value here and setting up a broadcast receiver for change callback below.
- // We will refresh the value again later after setting up the change callback anyway.
- final AtomicBoolean valueRef = new AtomicBoolean(evaluate(userContext, boolResId));
-
- final AtomicReference<Runnable> cleanerRef = new AtomicReference<>();
-
- final OverlayableSystemBooleanResourceWrapper object =
- new OverlayableSystemBooleanResourceWrapper(userContext.getUserId(), valueRef,
- cleanerRef);
-
- final IntentFilter intentFilter = new IntentFilter(ACTION_OVERLAY_CHANGED);
- intentFilter.addDataScheme(IntentFilter.SCHEME_PACKAGE);
- intentFilter.addDataSchemeSpecificPart(SYSTEM_PACKAGE_NAME, PatternMatcher.PATTERN_LITERAL);
-
- final BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- final boolean newValue = evaluate(userContext, boolResId);
- if (newValue != valueRef.getAndSet(newValue)) {
- callback.accept(object);
- }
- }
- };
- userContext.registerReceiver(broadcastReceiver, intentFilter,
- null /* broadcastPermission */, handler,
- Context.RECEIVER_NOT_EXPORTED);
- cleanerRef.set(() -> userContext.unregisterReceiver(broadcastReceiver));
-
- // Make sure that the initial observable value is obtained after the change callback is set.
- valueRef.set(evaluate(userContext, boolResId));
- return object;
- }
-
- private OverlayableSystemBooleanResourceWrapper(@UserIdInt int userId,
- @NonNull AtomicBoolean valueRef, @NonNull AtomicReference<Runnable> cleanerRef) {
- mUserId = userId;
- mValueRef = valueRef;
- mCleanerRef = cleanerRef;
- }
-
- /**
- * @return The boolean resource value.
- */
- @AnyThread
- boolean get() {
- return mValueRef.get();
- }
-
- /**
- * @return The user ID associated with this resource reader.
- */
- @AnyThread
- @UserIdInt
- int getUserId() {
- return mUserId;
- }
-
- @AnyThread
- private static boolean evaluate(@NonNull Context context, @BoolRes int boolResId) {
- try {
- return context.getPackageManager()
- .getResourcesForApplication(SYSTEM_PACKAGE_NAME)
- .getBoolean(boolResId);
- } catch (PackageManager.NameNotFoundException e) {
- Slog.e(TAG, "getResourcesForApplication(\"" + SYSTEM_PACKAGE_NAME + "\") failed", e);
- return false;
- }
- }
-
- /**
- * Cleans up the callback.
- */
- @AnyThread
- @Override
- public void close() {
- final Runnable cleaner = mCleanerRef.getAndSet(null);
- if (cleaner != null) {
- cleaner.run();
- }
- }
-}
diff --git a/services/core/java/com/android/server/inputmethod/UserDataRepository.java b/services/core/java/com/android/server/inputmethod/UserDataRepository.java
index 98d7548..7c68d54 100644
--- a/services/core/java/com/android/server/inputmethod/UserDataRepository.java
+++ b/services/core/java/com/android/server/inputmethod/UserDataRepository.java
@@ -29,6 +29,7 @@
import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection;
import com.android.internal.inputmethod.IRemoteInputConnection;
+import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.function.Consumer;
import java.util.function.IntFunction;
@@ -181,6 +182,12 @@
String mLastEnabledInputMethodsStr = "";
/**
+ * {@code true} when the IME is responsible for drawing the navigation bar and its buttons.
+ */
+ @NonNull
+ final AtomicBoolean mImeDrawsNavBar = new AtomicBoolean();
+
+ /**
* Intended to be instantiated only from this file.
*/
private UserData(@UserIdInt int userId,
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubEventLogger.java b/services/core/java/com/android/server/location/contexthub/ContextHubEventLogger.java
index 058c1c8..e1b1416 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubEventLogger.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubEventLogger.java
@@ -16,10 +16,12 @@
package com.android.server.location.contexthub;
+import android.chre.flags.Flags;
import android.hardware.location.NanoAppMessage;
import android.util.Log;
import java.util.Collection;
+import java.util.Optional;
/**
* A class to log events and useful metrics within the Context Hub service.
@@ -149,10 +151,20 @@
*/
public final NanoAppMessage message;
+ /**
+ * the error code for the message
+ */
+ public Optional<Byte> errorCode;
+
public NanoappMessageEvent(long mTimeStampInMs, int mContextHubId,
NanoAppMessage mMessage, boolean mSuccess) {
super(mTimeStampInMs, mContextHubId, 0, mSuccess);
message = mMessage;
+ errorCode = Optional.empty();
+ }
+
+ public void setErrorCode(byte errorCode) {
+ this.errorCode = Optional.of(errorCode);
}
@Override
@@ -165,6 +177,8 @@
sb.append(message.toString());
sb.append(", success = ");
sb.append(success ? "true" : "false");
+ sb.append(", errorCode = ");
+ sb.append(errorCode.isPresent() ? errorCode.get() : "null");
sb.append(']');
return sb.toString();
}
@@ -312,6 +326,28 @@
}
/**
+ * Logs the status of a reliable message
+ *
+ * @param messageSequenceNumber the message sequence number
+ * @param errorCode the error code
+ */
+ public synchronized void logReliableMessageToNanoappStatus(
+ int messageSequenceNumber, byte errorCode) {
+ if (!Flags.reliableMessage()) {
+ return;
+ }
+
+ for (NanoappMessageEvent event : mMessageToNanoappQueue) {
+ if (event.message.isReliable()
+ && event.message.getMessageSequenceNumber()
+ == messageSequenceNumber) {
+ event.setErrorCode(errorCode);
+ break;
+ }
+ }
+ }
+
+ /**
* Logs a context hub restart event
*
* @param contextHubId the ID of the context hub
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubService.java b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
index a0aad52..ed451ff 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubService.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
@@ -1087,6 +1087,8 @@
* @param messageDeliveryStatus The message delivery status to deliver.
*/
private void handleMessageDeliveryStatusCallback(MessageDeliveryStatus messageDeliveryStatus) {
+ ContextHubEventLogger.getInstance().logReliableMessageToNanoappStatus(
+ messageDeliveryStatus.messageSequenceNumber, messageDeliveryStatus.errorCode);
mTransactionManager.onMessageDeliveryResponse(messageDeliveryStatus.messageSequenceNumber,
messageDeliveryStatus.errorCode == ErrorCode.OK);
}
diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java
index 56e4590..46585a5 100644
--- a/services/core/java/com/android/server/om/OverlayManagerService.java
+++ b/services/core/java/com/android/server/om/OverlayManagerService.java
@@ -80,6 +80,7 @@
import android.util.Slog;
import android.util.SparseArray;
+import com.android.internal.annotations.KeepForWeakReference;
import com.android.internal.content.PackageMonitor;
import com.android.internal.content.om.OverlayConfig;
import com.android.internal.util.ArrayUtils;
@@ -261,6 +262,7 @@
private final OverlayActorEnforcer mActorEnforcer;
+ @KeepForWeakReference
private final PackageMonitor mPackageMonitor = new OverlayManagerPackageMonitor();
private int mPrevStartedUserId = -1;
diff --git a/services/core/java/com/android/server/pdb/PersistentDataBlockService.java b/services/core/java/com/android/server/pdb/PersistentDataBlockService.java
index c5e2bb8..8410cff 100644
--- a/services/core/java/com/android/server/pdb/PersistentDataBlockService.java
+++ b/services/core/java/com/android/server/pdb/PersistentDataBlockService.java
@@ -34,6 +34,7 @@
import android.os.Binder;
import android.os.Build;
import android.os.IBinder;
+import android.os.Process;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ShellCallback;
@@ -370,8 +371,13 @@
}
private void enforceUid(int callingUid) {
- if (callingUid != mAllowedUid && callingUid != UserHandle.AID_ROOT) {
- throw new SecurityException("uid " + callingUid + " not allowed to access PDB");
+ enforceUid(callingUid, /* allowShell= */ false);
+ }
+
+ private void enforceUid(int callingUid, boolean allowShell) {
+ if (callingUid != mAllowedUid && callingUid != UserHandle.AID_ROOT
+ && (callingUid != Process.SHELL_UID || !allowShell)) {
+ throw new SecurityException("Uid " + callingUid + " not allowed to access PDB");
}
}
@@ -864,7 +870,8 @@
private final IBinder mService = new IPersistentDataBlockService.Stub() {
private int printFrpStatus(PrintWriter pw, boolean printSecrets) {
- enforceUid(Binder.getCallingUid());
+ // Only allow SHELL_UID to print the status if printing the secrets is disabled
+ enforceUid(Binder.getCallingUid(), /* allowShell= */ !printSecrets);
pw.println("FRP state");
pw.println("=========");
@@ -872,8 +879,14 @@
pw.println("FRP state: " + mFrpActive);
printFrpDataFilesContents(pw, printSecrets);
printFrpSecret(pw, printSecrets);
- pw.println("OEM unlock state: " + getOemUnlockEnabled());
- pw.println("Bootloader lock state: " + getFlashLockState());
+
+ // Do not print OEM unlock state and flash lock state if the caller is a non-root
+ // shell - it likely won't have permissions anyways.
+ if (Binder.getCallingUid() != Process.SHELL_UID) {
+ pw.println("OEM unlock state: " + getOemUnlockEnabled());
+ pw.println("Bootloader lock state: " + getFlashLockState());
+ }
+
pw.println("Verified boot state: " + getVerifiedBootState());
pw.println("Has FRP credential handle: " + hasFrpCredentialHandle());
pw.println("FRP challenge block size: " + getDataBlockSize());
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index b5c33cd..07c8ee7 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -186,7 +186,6 @@
import com.android.internal.pm.pkg.component.ParsedMainComponent;
import com.android.internal.pm.pkg.parsing.ParsingPackageUtils;
import com.android.internal.telephony.CarrierAppUtils;
-import com.android.internal.telephony.TelephonyPermissions;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.CollectionUtils;
import com.android.internal.util.ConcurrentUtils;
@@ -4493,7 +4492,7 @@
void setSystemAppHiddenUntilInstalled(@NonNull Computer snapshot, String packageName,
boolean hidden) {
final int callingUid = Binder.getCallingUid();
- final boolean calledFromSystemOrPhone = TelephonyPermissions.isSystemOrPhone(callingUid);
+ final boolean calledFromSystemOrPhone = isSystemOrPhone(callingUid);
if (!calledFromSystemOrPhone) {
mContext.enforceCallingOrSelfPermission(Manifest.permission.SUSPEND_APPS,
"setSystemAppHiddenUntilInstalled");
@@ -4518,8 +4517,7 @@
boolean setSystemAppInstallState(@NonNull Computer snapshot, String packageName,
boolean installed, int userId) {
final int callingUid = Binder.getCallingUid();
- final boolean calledFromSystemOrPhone = callingUid == Process.PHONE_UID
- || callingUid == Process.SYSTEM_UID;
+ final boolean calledFromSystemOrPhone = isSystemOrPhone(callingUid);
if (!calledFromSystemOrPhone) {
mContext.enforceCallingOrSelfPermission(Manifest.permission.SUSPEND_APPS,
"setSystemAppHiddenUntilInstalled");
@@ -8123,4 +8121,9 @@
PackageManager.VERSION_CODE_HIGHEST, UserHandle.USER_SYSTEM,
PackageManager.DELETE_ALL_USERS, true /*removedBySystem*/);
}
+
+ private static boolean isSystemOrPhone(int uid) {
+ return UserHandle.isSameApp(uid, Process.SYSTEM_UID)
+ || UserHandle.isSameApp(uid, Process.PHONE_UID);
+ }
}
diff --git a/services/core/java/com/android/server/policy/ModifierShortcutManager.java b/services/core/java/com/android/server/policy/ModifierShortcutManager.java
index 3a79d0d..fde23b7 100644
--- a/services/core/java/com/android/server/policy/ModifierShortcutManager.java
+++ b/services/core/java/com/android/server/policy/ModifierShortcutManager.java
@@ -22,8 +22,10 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.res.XmlResourceParser;
+import android.graphics.drawable.Icon;
import android.hardware.input.InputManager;
import android.os.Handler;
import android.os.RemoteException;
@@ -36,7 +38,11 @@
import android.view.InputDevice;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
+import android.view.KeyboardShortcutGroup;
+import android.view.KeyboardShortcutInfo;
+import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.policy.IShortcutService;
import com.android.internal.util.XmlUtils;
import com.android.server.input.KeyboardMetricsCollector;
@@ -46,7 +52,9 @@
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
+import java.util.ArrayList;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
/**
@@ -183,8 +191,12 @@
String rolePackage = mRoleManager.getDefaultApplication(role);
if (rolePackage != null) {
intent = mPackageManager.getLaunchIntentForPackage(rolePackage);
- intent.putExtra(EXTRA_ROLE, role);
- mRoleIntents.put(role, intent);
+ if (intent != null) {
+ intent.putExtra(EXTRA_ROLE, role);
+ mRoleIntents.put(role, intent);
+ } else {
+ Log.w(TAG, "No launch intent for role " + role);
+ }
} else {
Log.w(TAG, "No default application for role " + role);
}
@@ -198,8 +210,7 @@
private void loadShortcuts() {
try {
- XmlResourceParser parser = mContext.getResources().getXml(
- com.android.internal.R.xml.bookmarks);
+ XmlResourceParser parser = mContext.getResources().getXml(R.xml.bookmarks);
XmlUtils.beginDocument(parser, TAG_BOOKMARKS);
while (true) {
@@ -270,6 +281,9 @@
continue;
}
intent = Intent.makeMainSelectorActivity(Intent.ACTION_MAIN, categoryName);
+ if (intent == null) {
+ Log.w(TAG, "Null selector intent for " + categoryName);
+ }
} else if (roleName != null) {
// We can't resolve the role at the time of this file being parsed as the
// device hasn't finished booting, so we will look it up lazily.
@@ -466,4 +480,131 @@
return false;
}
+
+ /**
+ * @param deviceId The input device id of the input device that will handle the shortcuts.
+ *
+ * @return a {@link KeyboardShortcutGroup} containing the application launch keyboard
+ * shortcuts parsed at boot time from {@code bookmarks.xml}.
+ */
+ public KeyboardShortcutGroup getApplicationLaunchKeyboardShortcuts(int deviceId) {
+ List<KeyboardShortcutInfo> shortcuts = new ArrayList();
+ for (int i = 0; i < mIntentShortcuts.size(); i++) {
+ KeyboardShortcutInfo info = shortcutInfoFromIntent(
+ (char) (mIntentShortcuts.keyAt(i)), mIntentShortcuts.valueAt(i), false);
+ if (info != null) {
+ shortcuts.add(info);
+ }
+ }
+
+ for (int i = 0; i < mShiftShortcuts.size(); i++) {
+ KeyboardShortcutInfo info = shortcutInfoFromIntent(
+ (char) (mShiftShortcuts.keyAt(i)), mShiftShortcuts.valueAt(i), true);
+ if (info != null) {
+ shortcuts.add(info);
+ }
+ }
+
+ for (int i = 0; i < mRoleShortcuts.size(); i++) {
+ String role = mRoleShortcuts.valueAt(i);
+ KeyboardShortcutInfo info = shortcutInfoFromIntent(
+ (char) (mRoleShortcuts.keyAt(i)), getRoleLaunchIntent(role), false);
+ if (info != null) {
+ shortcuts.add(info);
+ }
+ }
+
+ for (int i = 0; i < mShiftRoleShortcuts.size(); i++) {
+ String role = mShiftRoleShortcuts.valueAt(i);
+ KeyboardShortcutInfo info = shortcutInfoFromIntent(
+ (char) (mShiftRoleShortcuts.keyAt(i)), getRoleLaunchIntent(role), true);
+ if (info != null) {
+ shortcuts.add(info);
+ }
+ }
+
+ return new KeyboardShortcutGroup(
+ mContext.getString(R.string.keyboard_shortcut_group_applications),
+ shortcuts);
+ }
+
+ /**
+ * Given an intent to launch an application and the character and shift state that should
+ * trigger it, return a suitable {@link KeyboardShortcutInfo} that contains the label and
+ * icon for the target application.
+ *
+ * @param baseChar the character that triggers the shortcut
+ * @param intent the application launch intent
+ * @param shift whether the shift key is required to be presed.
+ */
+ @VisibleForTesting
+ KeyboardShortcutInfo shortcutInfoFromIntent(char baseChar, Intent intent, boolean shift) {
+ if (intent == null) {
+ return null;
+ }
+
+ CharSequence label;
+ Icon icon;
+ ActivityInfo resolvedActivity = intent.resolveActivityInfo(
+ mPackageManager, PackageManager.MATCH_DEFAULT_ONLY);
+ if (resolvedActivity == null) {
+ return null;
+ }
+ boolean isResolver = com.android.internal.app.ResolverActivity.class.getName().equals(
+ resolvedActivity.name);
+ if (isResolver) {
+ label = getIntentCategoryLabel(mContext,
+ intent.getSelector().getCategories().iterator().next());
+ if (label == null) {
+ return null;
+ }
+ icon = Icon.createWithResource(mContext, R.drawable.sym_def_app_icon);
+
+ } else {
+ label = resolvedActivity.loadLabel(mPackageManager);
+ icon = Icon.createWithResource(
+ resolvedActivity.packageName, resolvedActivity.getIconResource());
+ }
+ int modifiers = KeyEvent.META_META_ON;
+ if (shift) {
+ modifiers |= KeyEvent.META_SHIFT_ON;
+ }
+ return new KeyboardShortcutInfo(label, icon, baseChar, modifiers);
+ }
+
+ @VisibleForTesting
+ static String getIntentCategoryLabel(Context context, CharSequence category) {
+ int resid;
+ switch (category.toString()) {
+ case Intent.CATEGORY_APP_BROWSER:
+ resid = R.string.keyboard_shortcut_group_applications_browser;
+ break;
+ case Intent.CATEGORY_APP_CONTACTS:
+ resid = R.string.keyboard_shortcut_group_applications_contacts;
+ break;
+ case Intent.CATEGORY_APP_EMAIL:
+ resid = R.string.keyboard_shortcut_group_applications_email;
+ break;
+ case Intent.CATEGORY_APP_CALENDAR:
+ resid = R.string.keyboard_shortcut_group_applications_calendar;
+ break;
+ case Intent.CATEGORY_APP_MAPS:
+ resid = R.string.keyboard_shortcut_group_applications_maps;
+ break;
+ case Intent.CATEGORY_APP_MUSIC:
+ resid = R.string.keyboard_shortcut_group_applications_music;
+ break;
+ case Intent.CATEGORY_APP_MESSAGING:
+ resid = R.string.keyboard_shortcut_group_applications_sms;
+ break;
+ case Intent.CATEGORY_APP_CALCULATOR:
+ resid = R.string.keyboard_shortcut_group_applications_calculator;
+ break;
+ default:
+ Log.e(TAG, ("No label for app category " + category));
+ return null;
+ }
+ return context.getString(resid);
+ };
+
}
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 9d0c0e9..8dc9756 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -189,6 +189,7 @@
import android.view.KeyCharacterMap;
import android.view.KeyCharacterMap.FallbackAction;
import android.view.KeyEvent;
+import android.view.KeyboardShortcutGroup;
import android.view.MotionEvent;
import android.view.ViewConfiguration;
import android.view.WindowManager;
@@ -3321,6 +3322,11 @@
eventToLog).sendToTarget();
}
+ @Override
+ public KeyboardShortcutGroup getApplicationLaunchKeyboardShortcuts(int deviceId) {
+ return mModifierShortcutManager.getApplicationLaunchKeyboardShortcuts(deviceId);
+ }
+
// TODO(b/117479243): handle it in InputPolicy
// TODO (b/283241997): Add the remaining keyboard shortcut logging after refactoring
/** {@inheritDoc} */
diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
index 9ca4e27..6c05d70 100644
--- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java
+++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
@@ -82,6 +82,7 @@
import android.view.Display;
import android.view.IDisplayFoldListener;
import android.view.KeyEvent;
+import android.view.KeyboardShortcutGroup;
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
import android.view.WindowManagerPolicyConstants;
@@ -698,6 +699,15 @@
public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags);
/**
+ * Return the set of applicaition launch keyboard shortcuts the system supports.
+ *
+ * @param deviceId The id of the {@link InputDevice} that will trigger the shortcut.
+ *
+ * @return {@link KeyboardShortcutGroup} containing the shortcuts.
+ */
+ KeyboardShortcutGroup getApplicationLaunchKeyboardShortcuts(int deviceId);
+
+ /**
* Called from the input reader thread before a motion is enqueued when the device is in a
* non-interactive state.
*
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index ce0120c..6fe1ccd 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -33,6 +33,7 @@
import static com.android.internal.util.LatencyTracker.ACTION_TURN_ON_SCREEN;
import static com.android.server.deviceidle.Flags.disableWakelocksInLightIdle;
+import static com.android.server.display.DisplayDeviceConfig.INVALID_BRIGHTNESS_IN_CONFIG;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -239,9 +240,6 @@
// This should perhaps be a setting.
private static final int SCREEN_BRIGHTNESS_BOOST_TIMEOUT = 5 * 1000;
- // Float.NaN cannot be stored in config.xml so -2 is used instead
- private static final float INVALID_BRIGHTNESS_IN_CONFIG = -2f;
-
// How long a partial wake lock must be held until we consider it a long wake lock.
static final long MIN_LONG_WAKE_CHECK_INTERVAL = 60*1000;
@@ -619,7 +617,6 @@
public final float mScreenBrightnessMinimum;
public final float mScreenBrightnessMaximum;
public final float mScreenBrightnessDefault;
- public final float mScreenBrightnessDoze;
public final float mScreenBrightnessDim;
// Value we store for tracking face down behavior.
@@ -1219,8 +1216,6 @@
.config_screenBrightnessSettingMaximumFloat);
final float def = mContext.getResources().getFloat(com.android.internal.R.dimen
.config_screenBrightnessSettingDefaultFloat);
- final float doze = mContext.getResources().getFloat(com.android.internal.R.dimen
- .config_screenBrightnessDozeFloat);
final float dim = mContext.getResources().getFloat(com.android.internal.R.dimen
.config_screenBrightnessDimFloat);
@@ -1240,13 +1235,6 @@
mScreenBrightnessMaximum = max;
mScreenBrightnessDefault = def;
}
- if (doze == INVALID_BRIGHTNESS_IN_CONFIG) {
- mScreenBrightnessDoze = BrightnessSynchronizer.brightnessIntToFloat(
- mContext.getResources().getInteger(com.android.internal.R.integer
- .config_screenBrightnessDoze));
- } else {
- mScreenBrightnessDoze = doze;
- }
if (dim == INVALID_BRIGHTNESS_IN_CONFIG) {
mScreenBrightnessDim = BrightnessSynchronizer.brightnessIntToFloat(
mContext.getResources().getInteger(com.android.internal.R.integer
@@ -6090,8 +6078,6 @@
return mScreenBrightnessDefault;
case PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_DIM:
return mScreenBrightnessDim;
- case PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_DOZE:
- return mScreenBrightnessDoze;
default:
return PowerManager.BRIGHTNESS_INVALID_FLOAT;
}
diff --git a/services/core/java/com/android/server/power/stats/flags.aconfig b/services/core/java/com/android/server/power/stats/flags.aconfig
index 6a5a7ac..d34498a 100644
--- a/services/core/java/com/android/server/power/stats/flags.aconfig
+++ b/services/core/java/com/android/server/power/stats/flags.aconfig
@@ -47,3 +47,10 @@
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "add_battery_usage_stats_slice_atom"
+ namespace: "backstage_power"
+ description: "Adds battery_usage_stats_slice atom"
+ bug: "324602949"
+}
diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
index dfccd1a..bca81f52 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -1283,7 +1283,7 @@
}
case FrameworkStatsLog.PROXY_BYTES_TRANSFER_BY_FG_BG: {
final NetworkStats stats = getUidNetworkStatsSnapshotForTemplate(
- new NetworkTemplate.Builder(MATCH_PROXY).build(), /*includeTags=*/true);
+ new NetworkTemplate.Builder(MATCH_PROXY).build(), /*includeTags=*/false);
if (stats != null) {
ret.add(new NetworkStatsExt(sliceNetworkStatsByUidTagAndMetered(stats),
new int[]{TRANSPORT_BLUETOOTH},
diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java
index ddbd809..953aae9 100644
--- a/services/core/java/com/android/server/trust/TrustManagerService.java
+++ b/services/core/java/com/android/server/trust/TrustManagerService.java
@@ -16,6 +16,7 @@
package com.android.server.trust;
+import static android.security.Flags.shouldTrustManagerListenForPrimaryAuth;
import static android.service.trust.GrantTrustResult.STATUS_UNLOCKED_BY_GRANT;
import static android.service.trust.TrustAgentService.FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE;
@@ -84,6 +85,9 @@
import com.android.internal.infra.AndroidFuture;
import com.android.internal.util.DumpUtils;
import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.LockSettingsInternal;
+import com.android.internal.widget.LockSettingsStateListener;
+import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.servicewatcher.CurrentUserServiceSupplier;
import com.android.server.servicewatcher.ServiceWatcher;
@@ -159,6 +163,7 @@
/* package */ final TrustArchive mArchive = new TrustArchive();
private final Context mContext;
+ private final LockSettingsInternal mLockSettings;
private final LockPatternUtils mLockPatternUtils;
private final KeyStoreAuthorization mKeyStoreAuthorization;
private final UserManager mUserManager;
@@ -250,6 +255,20 @@
private final StrongAuthTracker mStrongAuthTracker;
+ // Used to subscribe to device credential auth attempts.
+ private final LockSettingsStateListener mLockSettingsStateListener =
+ new LockSettingsStateListener() {
+ @Override
+ public void onAuthenticationSucceeded(int userId) {
+ mHandler.obtainMessage(MSG_DISPATCH_UNLOCK_ATTEMPT, 1, userId).sendToTarget();
+ }
+
+ @Override
+ public void onAuthenticationFailed(int userId) {
+ mHandler.obtainMessage(MSG_DISPATCH_UNLOCK_ATTEMPT, 0, userId).sendToTarget();
+ }
+ };
+
private boolean mTrustAgentsCanRun = false;
private int mCurrentUser = UserHandle.USER_SYSTEM;
@@ -294,6 +313,7 @@
mHandler = createHandler(injector.getLooper());
mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
mActivityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
+ mLockSettings = LocalServices.getService(LockSettingsInternal.class);
mLockPatternUtils = injector.getLockPatternUtils();
mKeyStoreAuthorization = injector.getKeyStoreAuthorization();
mStrongAuthTracker = new StrongAuthTracker(context, injector.getLooper());
@@ -315,6 +335,9 @@
checkNewAgents();
mPackageMonitor.register(mContext, mHandler.getLooper(), UserHandle.ALL, true);
mReceiver.register(mContext);
+ if (shouldTrustManagerListenForPrimaryAuth()) {
+ mLockSettings.registerLockSettingsStateListener(mLockSettingsStateListener);
+ }
mLockPatternUtils.registerStrongAuthTracker(mStrongAuthTracker);
mFingerprintManager = mContext.getSystemService(FingerprintManager.class);
mFaceManager = mContext.getSystemService(FaceManager.class);
diff --git a/services/core/java/com/android/server/updates/Android.bp b/services/core/java/com/android/server/updates/Android.bp
new file mode 100644
index 0000000..10beebb
--- /dev/null
+++ b/services/core/java/com/android/server/updates/Android.bp
@@ -0,0 +1,11 @@
+aconfig_declarations {
+ name: "updates_flags",
+ package: "com.android.server.updates",
+ container: "system",
+ srcs: ["*.aconfig"],
+}
+
+java_aconfig_library {
+ name: "updates_flags_lib",
+ aconfig_declarations: "updates_flags",
+}
diff --git a/services/core/java/com/android/server/updates/CertificateTransparencyLogInstallReceiver.java b/services/core/java/com/android/server/updates/CertificateTransparencyLogInstallReceiver.java
index 5565b6f..af4025e 100644
--- a/services/core/java/com/android/server/updates/CertificateTransparencyLogInstallReceiver.java
+++ b/services/core/java/com/android/server/updates/CertificateTransparencyLogInstallReceiver.java
@@ -16,17 +16,15 @@
package com.android.server.updates;
+import android.content.Context;
+import android.content.Intent;
import android.os.FileUtils;
import android.system.ErrnoException;
import android.system.Os;
-import android.util.Base64;
import android.util.Slog;
-import com.android.internal.util.HexDump;
-
import libcore.io.Streams;
-import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
@@ -36,10 +34,7 @@
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
-import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
public class CertificateTransparencyLogInstallReceiver extends ConfigUpdateInstallReceiver {
@@ -52,31 +47,31 @@
@Override
protected void install(InputStream inputStream, int version) throws IOException {
- /* Install is complicated here because we translate the input, which is a JSON file
- * containing log information to a directory with a file per log. To support atomically
- * replacing the old configuration directory with the new there's a bunch of steps. We
- * create a new directory with the logs and then do an atomic update of the current symlink
- * to point to the new directory.
- */
+ if (!Flags.certificateTransparencyInstaller()) {
+ return;
+ }
+ // To support atomically replacing the old configuration directory with the new there's a
+ // bunch of steps. We create a new directory with the logs and then do an atomic update of
+ // the current symlink to point to the new directory.
// 1. Ensure that the update dir exists and is readable
updateDir.mkdir();
if (!updateDir.isDirectory()) {
throw new IOException("Unable to make directory " + updateDir.getCanonicalPath());
}
if (!updateDir.setReadable(true, false)) {
- throw new IOException("Unable to set permissions on " +
- updateDir.getCanonicalPath());
+ throw new IOException("Unable to set permissions on " + updateDir.getCanonicalPath());
}
File currentSymlink = new File(updateDir, "current");
File newVersion = new File(updateDir, LOGDIR_PREFIX + String.valueOf(version));
- File oldDirectory;
// 2. Handle the corner case where the new directory already exists.
if (newVersion.exists()) {
// If the symlink has already been updated then the update died between steps 7 and 8
// and so we cannot delete the directory since its in use. Instead just bump the version
// and return.
if (newVersion.getCanonicalPath().equals(currentSymlink.getCanonicalPath())) {
- writeUpdate(updateDir, updateVersion,
+ writeUpdate(
+ updateDir,
+ updateVersion,
new ByteArrayInputStream(Long.toString(version).getBytes()));
deleteOldLogDirectories();
return;
@@ -91,22 +86,12 @@
throw new IOException("Unable to make directory " + newVersion.getCanonicalPath());
}
if (!newVersion.setReadable(true, false)) {
- throw new IOException("Failed to set " +newVersion.getCanonicalPath() +
- " readable");
+ throw new IOException(
+ "Failed to set " + newVersion.getCanonicalPath() + " readable");
}
- // 4. For each log in the log file create the corresponding file in <new_version>/ .
- try {
- byte[] content = Streams.readFullyNoClose(inputStream);
- JSONObject json = new JSONObject(new String(content, StandardCharsets.UTF_8));
- JSONArray logs = json.getJSONArray("logs");
- for (int i = 0; i < logs.length(); i++) {
- JSONObject log = logs.getJSONObject(i);
- installLog(newVersion, log);
- }
- } catch (JSONException e) {
- throw new IOException("Failed to parse logs", e);
- }
+ // 4. Validate the log list json and move the file in <new_version>/ .
+ installLogList(newVersion, inputStream);
// 5. Create the temp symlink. We'll rename this to the target symlink to get an atomic
// update.
@@ -125,49 +110,38 @@
}
Slog.i(TAG, "CT log directory updated to " + newVersion.getAbsolutePath());
// 7. Update the current version information
- writeUpdate(updateDir, updateVersion,
+ writeUpdate(
+ updateDir,
+ updateVersion,
new ByteArrayInputStream(Long.toString(version).getBytes()));
// 8. Cleanup
deleteOldLogDirectories();
}
- private void installLog(File directory, JSONObject logObject) throws IOException {
+ @Override
+ protected void postInstall(Context context, Intent intent) {
+ if (!Flags.certificateTransparencyInstaller()) {
+ return;
+ }
+ }
+
+ private void installLogList(File directory, InputStream inputStream) throws IOException {
try {
- String logFilename = getLogFileName(logObject.getString("key"));
- File file = new File(directory, logFilename);
- try (OutputStreamWriter out =
- new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8)) {
- writeLogEntry(out, "key", logObject.getString("key"));
- writeLogEntry(out, "url", logObject.getString("url"));
- writeLogEntry(out, "description", logObject.getString("description"));
+ byte[] content = Streams.readFullyNoClose(inputStream);
+ if (new JSONObject(new String(content, StandardCharsets.UTF_8)).length() == 0) {
+ throw new IOException("Log list data not valid");
+ }
+
+ File file = new File(directory, "log_list.json");
+ try (FileOutputStream outputStream = new FileOutputStream(file)) {
+ outputStream.write(content);
}
if (!file.setReadable(true, false)) {
throw new IOException("Failed to set permissions on " + file.getCanonicalPath());
}
} catch (JSONException e) {
- throw new IOException("Failed to parse log", e);
+ throw new IOException("Malformed json in log list", e);
}
-
- }
-
- /**
- * Get the filename for a log based on its public key. This must be kept in sync with
- * org.conscrypt.ct.CTLogStoreImpl.
- */
- private String getLogFileName(String base64PublicKey) {
- byte[] keyBytes = Base64.decode(base64PublicKey, Base64.DEFAULT);
- try {
- byte[] id = MessageDigest.getInstance("SHA-256").digest(keyBytes);
- return HexDump.toHexString(id, false);
- } catch (NoSuchAlgorithmException e) {
- // SHA-256 is guaranteed to be available.
- throw new RuntimeException(e);
- }
- }
-
- private void writeLogEntry(OutputStreamWriter out, String key, String value)
- throws IOException {
- out.write(key + ":" + value + "\n");
}
private void deleteOldLogDirectories() throws IOException {
@@ -175,12 +149,14 @@
return;
}
File currentTarget = new File(updateDir, "current").getCanonicalFile();
- FileFilter filter = new FileFilter() {
- @Override
- public boolean accept(File file) {
- return !currentTarget.equals(file) && file.getName().startsWith(LOGDIR_PREFIX);
- }
- };
+ FileFilter filter =
+ new FileFilter() {
+ @Override
+ public boolean accept(File file) {
+ return !currentTarget.equals(file)
+ && file.getName().startsWith(LOGDIR_PREFIX);
+ }
+ };
for (File f : updateDir.listFiles(filter)) {
FileUtils.deleteContentsAndDir(f);
}
diff --git a/services/core/java/com/android/server/updates/flags.aconfig b/services/core/java/com/android/server/updates/flags.aconfig
new file mode 100644
index 0000000..476cb37
--- /dev/null
+++ b/services/core/java/com/android/server/updates/flags.aconfig
@@ -0,0 +1,10 @@
+package: "com.android.server.updates"
+container: "system"
+
+flag {
+ name: "certificate_transparency_installer"
+ is_exported: true
+ namespace: "network_security"
+ description: "Enable certificate transparency installer for log list data"
+ bug: "319829948"
+}
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index 5be5bc5..2c73412 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -47,7 +47,7 @@
import static com.android.server.accessibility.AccessibilityTraceProto.WINDOW_MANAGER_SERVICE;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
-import static com.android.server.wm.WindowTracing.WINSCOPE_EXT;
+import static com.android.server.wm.WindowTracingLegacy.WINSCOPE_EXT;
import android.accessibilityservice.AccessibilityTrace;
import android.animation.ObjectAnimator;
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 8768074..3076b87 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -7173,7 +7173,7 @@
Slog.v(TAG, "Eval win " + w + ": isDrawn=" + w.isDrawn()
+ ", isAnimationSet=" + isAnimationSet);
if (!w.isDrawn()) {
- Slog.v(TAG, "Not displayed: s=" + winAnimator.mSurfaceController
+ Slog.v(TAG, "Not displayed: s=" + winAnimator.mSurfaceControl
+ " pv=" + w.isVisibleByPolicy()
+ " mDrawState=" + winAnimator.drawStateToString()
+ " ph=" + w.isParentWindowHidden() + " th=" + mVisibleRequested
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 59b5da8..ff46b33 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -4709,6 +4709,10 @@
// Update stored global config and notify everyone about the change.
mRootWindowContainer.onConfigurationChanged(mTempConfig);
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+ if ((changes & ActivityInfo.CONFIG_ORIENTATION) != 0) {
+ FrameworkStatsLog.write(FrameworkStatsLog.DEVICE_ORIENTATION_CHANGED,
+ values.orientation);
+ }
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
return changes;
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 3652c4d..9371149 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -4082,12 +4082,12 @@
final Transaction t = mWmService.mTransactionFactory.get();
forAllWindows(w -> {
final WindowStateAnimator wsa = w.mWinAnimator;
- if (wsa.mSurfaceController == null) {
+ if (wsa.mSurfaceControl == null) {
return;
}
if (!mWmService.mSessions.contains(wsa.mSession)) {
Slog.w(TAG_WM, "LEAKED SURFACE (session doesn't exist): "
- + w + " surface=" + wsa.mSurfaceController
+ + w + " surface=" + wsa.mSurfaceControl
+ " token=" + w.mToken
+ " pid=" + w.mSession.mPid
+ " uid=" + w.mSession.mUid);
@@ -4096,7 +4096,7 @@
mTmpWindow = w;
} else if (w.mActivityRecord != null && !w.mActivityRecord.isClientVisible()) {
Slog.w(TAG_WM, "LEAKED SURFACE (app token hidden): "
- + w + " surface=" + wsa.mSurfaceController
+ + w + " surface=" + wsa.mSurfaceControl
+ " token=" + w.mActivityRecord);
ProtoLog.i(WM_SHOW_TRANSACTIONS, "SURFACE LEAK DESTROY: %s", w);
wsa.destroySurface(t);
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index 74dbd15..b496a65 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -623,7 +623,7 @@
// occlusion detection depending on the type or if it's a trusted overlay.
populateOverlayInputInfo(inputWindowHandle, w);
setInputWindowInfoIfNeeded(mInputTransaction,
- w.mWinAnimator.mSurfaceController.mSurfaceControl, inputWindowHandle);
+ w.mWinAnimator.mSurfaceControl, inputWindowHandle);
return;
}
// Skip this window because it cannot possibly receive input.
@@ -687,7 +687,7 @@
if (w.mWinAnimator.hasSurface()) {
populateInputWindowHandle(inputWindowHandle, w);
setInputWindowInfoIfNeeded(mInputTransaction,
- w.mWinAnimator.mSurfaceController.mSurfaceControl, inputWindowHandle);
+ w.mWinAnimator.mSurfaceControl, inputWindowHandle);
}
}
}
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 99697de..f2ccbc4 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -667,7 +667,7 @@
boolean reclaimSomeSurfaceMemory(WindowStateAnimator winAnimator, String operation,
boolean secure) {
- final WindowSurfaceController surfaceController = winAnimator.mSurfaceController;
+ final SurfaceControl surfaceControl = winAnimator.mSurfaceControl;
boolean leakedSurface = false;
boolean killedApps = false;
EventLogTags.writeWmNoSurfaceMemory(winAnimator.mWin.toString(),
@@ -692,7 +692,7 @@
return;
}
final WindowStateAnimator wsa = w.mWinAnimator;
- if (wsa.mSurfaceController != null) {
+ if (wsa.mSurfaceControl != null) {
pidCandidates.append(wsa.mSession.mPid, wsa.mSession.mPid);
}
}, false /* traverseTopToBottom */);
@@ -717,7 +717,7 @@
// app to request another one.
Slog.w(TAG_WM,
"Looks like we have reclaimed some memory, clearing surface for retry.");
- if (surfaceController != null) {
+ if (surfaceControl != null) {
ProtoLog.i(WM_SHOW_SURFACE_ALLOC,
"SURFACE RECOVER DESTROY: %s", winAnimator.mWin);
SurfaceControl.Transaction t = mWmService.mTransactionFactory.get();
diff --git a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
index 31fda77..db0374e 100644
--- a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
+++ b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
@@ -324,7 +324,7 @@
if (!w.mToken.mRoundedCornerOverlay || !w.isVisible() || !w.mWinAnimator.hasSurface()) {
return;
}
- t.setSkipScreenshot(w.mWinAnimator.mSurfaceController.mSurfaceControl, skipScreenshot);
+ t.setSkipScreenshot(w.mWinAnimator.mSurfaceControl, skipScreenshot);
}, false);
if (!skipScreenshot) {
// Use sync apply to apply the change immediately, so that the next
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 75e3e65..f5108f5b 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -109,8 +109,8 @@
private final String mStringName;
SurfaceSession mSurfaceSession;
private final ArrayList<WindowState> mAddedWindows = new ArrayList<>();
- /** Set of visible alert/app-overlay window surfaces connected to this session. */
- private final ArraySet<WindowSurfaceController> mAlertWindowSurfaces = new ArraySet<>();
+ /** Set of visible alert/app-overlay windows connected to this session. */
+ private final ArraySet<WindowState> mAlertWindows = new ArraySet<>();
private final DragDropController mDragDropController;
final boolean mCanAddInternalSystemWindow;
boolean mCanForceShowingInsets;
@@ -769,9 +769,8 @@
return !mAddedWindows.isEmpty();
}
- void onWindowSurfaceVisibilityChanged(WindowSurfaceController surfaceController,
- boolean visible, int type) {
-
+ void onWindowSurfaceVisibilityChanged(WindowState window, boolean visible) {
+ final int type = window.mAttrs.type;
if (!isSystemAlertWindowType(type)) {
return;
}
@@ -782,7 +781,7 @@
final boolean noSystemOverlayPermission =
!mCanAddInternalSystemWindow && !mCanCreateSystemApplicationOverlay;
if (visible) {
- changed = mAlertWindowSurfaces.add(surfaceController);
+ changed = mAlertWindows.add(window);
if (type == TYPE_APPLICATION_OVERLAY) {
MetricsLoggerWrapper.logAppOverlayEnter(mUid, mPackageName, changed, type,
false /* set false to only log for TYPE_APPLICATION_OVERLAY */);
@@ -791,7 +790,7 @@
true /* only log for non-TYPE_APPLICATION_OVERLAY */);
}
} else {
- changed = mAlertWindowSurfaces.remove(surfaceController);
+ changed = mAlertWindows.remove(window);
if (type == TYPE_APPLICATION_OVERLAY) {
MetricsLoggerWrapper.logAppOverlayExit(mUid, mPackageName, changed, type,
false /* set false to only log for TYPE_APPLICATION_OVERLAY */);
@@ -802,7 +801,7 @@
}
if (changed && noSystemOverlayPermission) {
- if (mAlertWindowSurfaces.isEmpty()) {
+ if (mAlertWindows.isEmpty()) {
cancelAlertWindowNotification();
} else if (mAlertWindowNotification == null && !isSatellitePointingUiPackage()) {
mAlertWindowNotification = new AlertWindowNotification(mService, mPackageName);
@@ -815,7 +814,7 @@
if (changed && mPid != WindowManagerService.MY_PID) {
// Notify activity manager that the process contains overlay/alert windows, so it can
// adjust the importance score for the process.
- setHasOverlayUi(!mAlertWindowSurfaces.isEmpty());
+ setHasOverlayUi(!mAlertWindows.isEmpty());
}
}
@@ -859,7 +858,7 @@
}
mSurfaceSession = null;
mAddedWindows.clear();
- mAlertWindowSurfaces.clear();
+ mAlertWindows.clear();
setHasOverlayUi(false);
cancelAlertWindowNotification();
}
@@ -880,7 +879,7 @@
void dump(PrintWriter pw, String prefix) {
pw.print(prefix); pw.print("numWindow="); pw.print(mAddedWindows.size());
pw.print(" mCanAddInternalSystemWindow="); pw.print(mCanAddInternalSystemWindow);
- pw.print(" mAlertWindowSurfaces="); pw.print(mAlertWindowSurfaces);
+ pw.print(" mAlertWindows="); pw.print(mAlertWindows);
pw.print(" mClientDead="); pw.print(mClientDead);
pw.print(" mSurfaceSession="); pw.println(mSurfaceSession);
pw.print(prefix); pw.print("mPackageName="); pw.println(mPackageName);
@@ -896,9 +895,9 @@
/** @return {@code true} if there is an alert window surface on the given display. */
boolean hasAlertWindowSurfaces(DisplayContent displayContent) {
- for (int i = mAlertWindowSurfaces.size() - 1; i >= 0; i--) {
- final WindowSurfaceController surfaceController = mAlertWindowSurfaces.valueAt(i);
- if (surfaceController.mAnimator.mWin.getDisplayContent() == displayContent) {
+ for (int i = mAlertWindows.size() - 1; i >= 0; i--) {
+ final WindowState window = mAlertWindows.valueAt(i);
+ if (window.mDisplayContent == displayContent) {
return true;
}
}
diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
index 439c7bb..561ff7d 100644
--- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
@@ -1169,16 +1169,6 @@
}
}
- @Override
- public boolean isActivityEmbedded(IBinder activityToken) {
- synchronized (mGlobalLock) {
- final ActivityRecord activity = ActivityRecord.forTokenLocked(activityToken);
- return activity != null
- ? activity.isEmbeddedInHostContainer()
- : false;
- }
- }
-
@VisibleForTesting
@NonNull
IApplicationThread getAppThread(int pid, int uid) {
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 2a3e945..5336044 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -2126,6 +2126,16 @@
}
/**
+
+ * Wallpaper will set itself as target if it wants to keep itself visible without a target.
+ */
+ private static boolean wallpaperIsOwnTarget(WallpaperWindowToken wallpaper) {
+ final WindowState target =
+ wallpaper.getDisplayContent().mWallpaperController.getWallpaperTarget();
+ return target != null && target.isDescendantOf(wallpaper);
+ }
+
+ /**
* Reset waitingToshow for all wallpapers, and commit the visibility of the visible ones
*/
private void commitVisibleWallpapers(SurfaceControl.Transaction t) {
@@ -2133,8 +2143,13 @@
for (int i = mParticipants.size() - 1; i >= 0; --i) {
final WallpaperWindowToken wallpaper = mParticipants.valueAt(i).asWallpaperToken();
if (wallpaper != null) {
- if (!wallpaper.isVisible() && wallpaper.isVisibleRequested()) {
+ if (!wallpaper.isVisible() && (wallpaper.isVisibleRequested()
+ || (Flags.ensureWallpaperInTransitions() && showWallpaper))) {
wallpaper.commitVisibility(showWallpaper);
+ } else if (Flags.ensureWallpaperInTransitions() && wallpaper.isVisible()
+ && !showWallpaper && !wallpaper.getDisplayContent().isKeyguardLocked()
+ && !wallpaperIsOwnTarget(wallpaper)) {
+ wallpaper.setVisibleRequested(false);
}
if (showWallpaper && Flags.ensureWallpaperInTransitions()
&& wallpaper.isVisibleRequested()
@@ -2556,11 +2571,10 @@
if (wc.asWindowState() != null) continue;
final ChangeInfo changeInfo = changes.get(wc);
- // Reject no-ops, unless wallpaper
- if (!changeInfo.hasChanged()
- && (!Flags.ensureWallpaperInTransitions() || wc.asWallpaperToken() == null)) {
+ // Reject no-ops
+ if (!changeInfo.hasChanged()) {
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
- " Rejecting as no-op: %s", wc);
+ " Rejecting as no-op: %s vis: %b", wc, wc.isVisibleRequested());
continue;
}
targets.add(changeInfo);
diff --git a/services/core/java/com/android/server/wm/TransparentPolicy.java b/services/core/java/com/android/server/wm/TransparentPolicy.java
index 3044abd..cdb14ab 100644
--- a/services/core/java/com/android/server/wm/TransparentPolicy.java
+++ b/services/core/java/com/android/server/wm/TransparentPolicy.java
@@ -162,10 +162,6 @@
mTransparentPolicyState.clearInheritedCompatDisplayInsets();
}
- TransparentPolicyState getTransparentPolicyState() {
- return mTransparentPolicyState;
- }
-
/**
* In case of translucent activities, it consumes the {@link ActivityRecord} of the first opaque
* activity beneath using the given consumer and returns {@code true}.
@@ -176,7 +172,7 @@
@NonNull
Optional<ActivityRecord> getFirstOpaqueActivity() {
- return isRunning() ? Optional.of(mTransparentPolicyState.mFirstOpaqueActivity)
+ return isRunning() ? Optional.ofNullable(mTransparentPolicyState.mFirstOpaqueActivity)
: Optional.empty();
}
@@ -216,10 +212,6 @@
SMALLEST_SCREEN_WIDTH_DP_UNDEFINED;
}
- private void inheritConfiguration(ActivityRecord firstOpaque) {
- mTransparentPolicyState.inheritFromOpaque(firstOpaque);
- }
-
/**
* Encapsulate the state for the current translucent activity when the transparent policy
* has started.
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 8033122..ebbf6e3 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -283,6 +283,7 @@
import android.view.InsetsSourceControl;
import android.view.InsetsState;
import android.view.KeyEvent;
+import android.view.KeyboardShortcutGroup;
import android.view.MagnificationSpec;
import android.view.RemoteAnimationAdapter;
import android.view.ScrollCaptureResponse;
@@ -334,8 +335,8 @@
import com.android.internal.policy.IShortcutService;
import com.android.internal.policy.KeyInterceptionInfo;
import com.android.internal.protolog.LegacyProtoLogImpl;
-import com.android.internal.protolog.ProtoLogGroup;
import com.android.internal.protolog.ProtoLog;
+import com.android.internal.protolog.ProtoLogGroup;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.FastPrintWriter;
import com.android.internal.util.FrameworkStatsLog;
@@ -2569,7 +2570,7 @@
// surface, let the client use that, but don't create new surface at this
// point.
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "relayoutWindow: getSurface");
- winAnimator.mSurfaceController.getSurfaceControl(outSurfaceControl);
+ winAnimator.getSurfaceControl(outSurfaceControl);
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
} else {
if (DEBUG_VISIBILITY) Slog.i(TAG_WM, "Releasing surface in: " + win);
@@ -2765,15 +2766,15 @@
result |= RELAYOUT_RES_SURFACE_CHANGED;
}
- WindowSurfaceController surfaceController;
+ SurfaceControl surfaceControl;
try {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "createSurfaceControl");
- surfaceController = winAnimator.createSurfaceLocked();
+ surfaceControl = winAnimator.createSurfaceLocked();
} finally {
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
- if (surfaceController != null) {
- surfaceController.getSurfaceControl(outSurfaceControl);
+ if (surfaceControl != null) {
+ winAnimator.getSurfaceControl(outSurfaceControl);
ProtoLog.i(WM_SHOW_TRANSACTIONS, "OUT SURFACE %s: copied", outSurfaceControl);
} else {
@@ -6772,11 +6773,11 @@
if (windowState == null) {
return false;
}
- WindowSurfaceController surfaceController = windowState.mWinAnimator.mSurfaceController;
- if (surfaceController == null) {
+ final SurfaceControl surfaceControl = windowState.mWinAnimator.mSurfaceControl;
+ if (surfaceControl == null) {
return false;
}
- return surfaceController.clearWindowContentFrameStats();
+ return surfaceControl.clearContentFrameStats();
}
}
@@ -6791,15 +6792,15 @@
if (windowState == null) {
return null;
}
- WindowSurfaceController surfaceController = windowState.mWinAnimator.mSurfaceController;
- if (surfaceController == null) {
+ final SurfaceControl surfaceControl = windowState.mWinAnimator.mSurfaceControl;
+ if (surfaceControl == null) {
return null;
}
if (mTempWindowRenderStats == null) {
mTempWindowRenderStats = new WindowContentFrameStats();
}
WindowContentFrameStats stats = mTempWindowRenderStats;
- if (!surfaceController.getWindowContentFrameStats(stats)) {
+ if (!surfaceControl.getContentFrameStats(stats)) {
return null;
}
return stats;
@@ -7440,6 +7441,16 @@
}
@Override
+ public KeyboardShortcutGroup getApplicationLaunchKeyboardShortcuts(int deviceId) {
+ long token = Binder.clearCallingIdentity();
+ try {
+ return mPolicy.getApplicationLaunchKeyboardShortcuts(deviceId);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
public void requestAppKeyboardShortcuts(IResultReceiver receiver, int deviceId) {
enforceRegisterWindowManagerListenersPermission("requestAppKeyboardShortcuts");
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 228eb76..9ebb89d 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -98,6 +98,7 @@
import static android.view.WindowManagerPolicyConstants.TYPE_LAYER_MULTIPLIER;
import static android.view.WindowManagerPolicyConstants.TYPE_LAYER_OFFSET;
+import static com.android.input.flags.Flags.removeInputChannelFromWindowstate;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ADD_REMOVE;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ANIM;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS;
@@ -633,6 +634,9 @@
// Input channel and input window handle used by the input dispatcher.
final InputWindowHandleWrapper mInputWindowHandle;
+ /**
+ * Only populated if flag REMOVE_INPUT_CHANNEL_FROM_WINDOWSTATE is disabled.
+ */
InputChannel mInputChannel;
/**
@@ -1497,7 +1501,7 @@
if (isDrawn()) {
ProtoLog.v(WM_DEBUG_ORIENTATION,
"Orientation not waiting for draw in %s, surfaceController %s", this,
- winAnimator.mSurfaceController);
+ winAnimator.mSurfaceControl);
setOrientationChanging(false);
mLastFreezeDuration = (int)(SystemClock.elapsedRealtime()
- mWmService.mDisplayFreezeTime);
@@ -1877,6 +1881,10 @@
* Input Manager uses when discarding windows from input consideration.
*/
boolean isPotentialDragTarget(boolean targetInterceptsGlobalDrag) {
+ if (removeInputChannelFromWindowstate()) {
+ return (targetInterceptsGlobalDrag || isVisibleNow()) && !mRemoved
+ && mInputChannelToken != null && mInputWindowHandle != null;
+ }
return (targetInterceptsGlobalDrag || isVisibleNow()) && !mRemoved
&& mInputChannel != null && mInputWindowHandle != null;
}
@@ -2417,7 +2425,7 @@
ProtoLog.v(WM_DEBUG_FOCUS, "Remove client=%x, surfaceController=%s Callers=%s",
System.identityHashCode(mClient.asBinder()),
- mWinAnimator.mSurfaceController,
+ mWinAnimator.mSurfaceControl,
Debug.getCallers(5));
final DisplayContent displayContent = getDisplayContent();
@@ -2428,10 +2436,10 @@
mOnBackInvokedCallbackInfo = null;
ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
- "Remove %s: mSurfaceController=%s mAnimatingExit=%b mRemoveOnExit=%b "
+ "Remove %s: mSurfaceControl=%s mAnimatingExit=%b mRemoveOnExit=%b "
+ "mHasSurface=%b surfaceShowing=%b animating=%b app-animation=%b "
+ "mDisplayFrozen=%b callers=%s",
- this, mWinAnimator.mSurfaceController, mAnimatingExit, mRemoveOnExit,
+ this, mWinAnimator.mSurfaceControl, mAnimatingExit, mRemoveOnExit,
mHasSurface, mWinAnimator.getShown(),
isAnimating(TRANSITION | PARENTS),
mActivityRecord != null && mActivityRecord.isAnimating(PARENTS | TRANSITION),
@@ -2608,7 +2616,7 @@
+ isVisibleRequestedOrAdding() + " isVisible: " + (isVisible()
&& mActivityRecord != null && mActivityRecord.isVisible()));
if (!isVisibleRequestedOrAdding()) {
- Slog.i(TAG_WM, " mSurfaceController=" + mWinAnimator.mSurfaceController
+ Slog.i(TAG_WM, " mSurfaceControl=" + mWinAnimator.mSurfaceControl
+ " relayoutCalled=" + mRelayoutCalled
+ " viewVis=" + mViewVisibility
+ " policyVis=" + isVisibleByPolicy()
@@ -2626,6 +2634,19 @@
}
void openInputChannel(@NonNull InputChannel outInputChannel) {
+ if (mInputChannelToken != null) {
+ throw new IllegalStateException("Window already has an input channel token.");
+ }
+ if (removeInputChannelFromWindowstate()) {
+ String name = getName();
+ InputChannel channel = mWmService.mInputManager.createInputChannel(name);
+ mInputChannelToken = channel.getToken();
+ mInputWindowHandle.setToken(mInputChannelToken);
+ mWmService.mInputToWindowMap.put(mInputChannelToken, this);
+ channel.copyTo(outInputChannel);
+ channel.dispose();
+ return;
+ }
if (mInputChannel != null) {
throw new IllegalStateException("Window already has an input channel.");
}
@@ -2657,9 +2678,11 @@
mInputChannelToken = null;
}
- if (mInputChannel != null) {
- mInputChannel.dispose();
- mInputChannel = null;
+ if (!removeInputChannelFromWindowstate()) {
+ if (mInputChannel != null) {
+ mInputChannel.dispose();
+ mInputChannel = null;
+ }
}
mInputWindowHandle.setToken(null);
}
@@ -4434,7 +4457,7 @@
for (int i = mChildren.size() - 1; i >= 0; --i) {
final WindowState c = mChildren.get(i);
- if (c.mWinAnimator.mSurfaceController != null) {
+ if (c.mWinAnimator.mSurfaceControl != null) {
c.performShowLocked();
// It hadn't been shown, which means layout not performed on it, so now we
// want to make sure to do a layout. If called from within the transaction
@@ -4891,7 +4914,7 @@
Slog.v(TAG, "Win " + this + ": isDrawn=" + isDrawn()
+ ", animating=" + isAnimating(TRANSITION | PARENTS));
if (!isDrawn()) {
- Slog.v(TAG, "Not displayed: s=" + mWinAnimator.mSurfaceController
+ Slog.v(TAG, "Not displayed: s=" + mWinAnimator.mSurfaceControl
+ " pv=" + isVisibleByPolicy()
+ " mDrawState=" + mWinAnimator.mDrawState
+ " ph=" + isParentWindowHidden()
@@ -5512,13 +5535,13 @@
// been defined and so we can use static layers and leave it that way.
if (w.mAttrs.type == TYPE_APPLICATION_MEDIA) {
if (mWinAnimator.hasSurface()) {
- w.assignRelativeLayer(t, mWinAnimator.mSurfaceController.mSurfaceControl, -2);
+ w.assignRelativeLayer(t, mWinAnimator.mSurfaceControl, -2);
} else {
w.assignLayer(t, -2);
}
} else if (w.mAttrs.type == TYPE_APPLICATION_MEDIA_OVERLAY) {
if (mWinAnimator.hasSurface()) {
- w.assignRelativeLayer(t, mWinAnimator.mSurfaceController.mSurfaceControl, -1);
+ w.assignRelativeLayer(t, mWinAnimator.mSurfaceControl, -1);
} else {
w.assignLayer(t, -1);
}
@@ -5694,7 +5717,7 @@
}
SurfaceControl getClientViewRootSurface() {
- return mWinAnimator.getSurfaceControl();
+ return mWinAnimator.mSurfaceControl;
}
/** Drops a buffer for this window's view-root from a transaction */
@@ -6094,11 +6117,10 @@
}
getPendingTransaction().setSecure(mSurfaceControl, isSecure);
} else {
- if (mWinAnimator.mSurfaceController == null
- || mWinAnimator.mSurfaceController.mSurfaceControl == null) {
+ if (mWinAnimator.mSurfaceControl == null) {
return;
}
- getPendingTransaction().setSecure(mWinAnimator.mSurfaceController.mSurfaceControl,
+ getPendingTransaction().setSecure(mWinAnimator.mSurfaceControl,
isSecure);
}
if (mDisplayContent != null) {
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 397a6357..24a2a62 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -16,6 +16,10 @@
package com.android.server.wm;
+import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
+import static android.view.SurfaceControl.METADATA_OWNER_PID;
+import static android.view.SurfaceControl.METADATA_OWNER_UID;
+import static android.view.SurfaceControl.METADATA_WINDOW_TYPE;
import static android.view.WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
@@ -44,6 +48,7 @@
import static com.android.server.wm.WindowStateAnimatorProto.DRAW_STATE;
import static com.android.server.wm.WindowStateAnimatorProto.SURFACE;
import static com.android.server.wm.WindowStateAnimatorProto.SYSTEM_DECOR_RECT;
+import static com.android.server.wm.WindowSurfaceControllerProto.SHOWN;
import static com.android.window.flags.Flags.secureWindowState;
import static com.android.window.flags.Flags.setScPropertiesInClient;
@@ -52,6 +57,7 @@
import android.graphics.Rect;
import android.os.Debug;
import android.os.Trace;
+import android.util.EventLog;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
import android.view.Surface.OutOfResourcesException;
@@ -95,12 +101,13 @@
final Session mSession;
final WindowManagerPolicy mPolicy;
final Context mContext;
- final boolean mIsWallpaper;
private final WallpaperController mWallpaperControllerLocked;
boolean mAnimationIsEntrance;
- WindowSurfaceController mSurfaceController;
+ SurfaceControl mSurfaceControl;
+ private boolean mSurfaceShown;
+ private String mTitle;
float mShownAlpha = 0;
float mAlpha = 0;
@@ -164,7 +171,6 @@
mWin = win;
mSession = win.mSession;
mAttrType = win.mAttrs.type;
- mIsWallpaper = win.mIsWallpaper;
mWallpaperControllerLocked = win.getDisplayContent().mWallpaperController;
}
@@ -198,14 +204,30 @@
}
void hide(SurfaceControl.Transaction transaction, String reason) {
- if (!mLastHidden) {
- //dump();
- mLastHidden = true;
-
- if (mSurfaceController != null) {
- mSurfaceController.hide(transaction, reason);
- }
+ if (mLastHidden) {
+ return;
}
+ mLastHidden = true;
+ if (mSurfaceControl == null || !mSurfaceShown) {
+ return;
+ }
+ ProtoLog.i(WM_SHOW_TRANSACTIONS, "SURFACE HIDE ( %s ): %s", reason, mTitle);
+
+ setShown(false);
+ transaction.hide(mSurfaceControl);
+ if (mWin.mIsWallpaper) {
+ final DisplayContent dc = mWin.getDisplayContent();
+ EventLog.writeEvent(EventLogTags.WM_WALLPAPER_SURFACE,
+ dc.mDisplayId, 0 /* request hidden */,
+ String.valueOf(dc.mWallpaperController.getWallpaperTarget()));
+ }
+ }
+
+ private void setShown(boolean surfaceShown) {
+ mSurfaceShown = surfaceShown;
+ mService.updateNonSystemOverlayWindowsVisibilityIfNeeded(mWin, surfaceShown);
+ mWin.onSurfaceShownChanged(surfaceShown);
+ mSession.onWindowSurfaceVisibilityChanged(mWin, mSurfaceShown);
}
boolean finishDrawingLocked(SurfaceControl.Transaction postDrawTransaction) {
@@ -221,7 +243,7 @@
if (mDrawState == DRAW_PENDING) {
ProtoLog.v(WM_DEBUG_DRAW,
"finishDrawingLocked: mDrawState=COMMIT_DRAW_PENDING %s in %s", mWin,
- mSurfaceController);
+ mSurfaceControl);
if (startingWindow) {
ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Draw state now committed in %s", mWin);
}
@@ -248,7 +270,7 @@
return false;
}
ProtoLog.i(WM_DEBUG_ANIM, "commitFinishDrawingLocked: mDrawState=READY_TO_SHOW %s",
- mSurfaceController);
+ mSurfaceControl);
mDrawState = READY_TO_SHOW;
boolean result = false;
final ActivityRecord activity = mWin.mActivityRecord;
@@ -271,11 +293,11 @@
}
}
- WindowSurfaceController createSurfaceLocked() {
+ SurfaceControl createSurfaceLocked() {
final WindowState w = mWin;
- if (mSurfaceController != null) {
- return mSurfaceController;
+ if (mSurfaceControl != null) {
+ return mSurfaceControl;
}
w.setHasSurface(false);
@@ -312,10 +334,22 @@
final boolean isHwAccelerated = (attrs.flags & FLAG_HARDWARE_ACCELERATED) != 0;
final int format = isHwAccelerated ? PixelFormat.TRANSLUCENT : attrs.format;
- mSurfaceController = new WindowSurfaceController(attrs.getTitle().toString(), format,
- flags, this, attrs.type);
+ mTitle = attrs.getTitle().toString();
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "new SurfaceControl");
+ mSurfaceControl = mWin.makeSurface()
+ .setParent(mWin.mSurfaceControl)
+ .setName(mTitle)
+ .setFormat(format)
+ .setFlags(flags)
+ .setMetadata(METADATA_WINDOW_TYPE, attrs.type)
+ .setMetadata(METADATA_OWNER_UID, mSession.mUid)
+ .setMetadata(METADATA_OWNER_PID, mSession.mPid)
+ .setCallsite("WindowSurfaceController")
+ .setBLASTLayer().build();
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+
if (!setScPropertiesInClient()) {
- mSurfaceController.setColorSpaceAgnostic(w.getPendingTransaction(),
+ setColorSpaceAgnosticLocked(
(attrs.privateFlags & LayoutParams.PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC) != 0);
}
@@ -326,7 +360,7 @@
ProtoLog.i(WM_SHOW_SURFACE_ALLOC,
" CREATE SURFACE %s IN SESSION %s: pid=%d format=%d flags=0x%x / %s",
- mSurfaceController, mSession.mSurfaceSession, mSession.mPid, attrs.format,
+ mSurfaceControl, mSession.mSurfaceSession, mSession.mPid, attrs.format,
flags, this);
} catch (OutOfResourcesException e) {
Slog.w(TAG, "OutOfResourcesException creating surface");
@@ -340,7 +374,7 @@
}
if (DEBUG) {
- Slog.v(TAG, "Got surface: " + mSurfaceController
+ Slog.v(TAG, "Got surface: " + mSurfaceControl
+ ", set left=" + w.getFrame().left + " top=" + w.getFrame().top);
}
@@ -353,15 +387,19 @@
mLastHidden = true;
if (DEBUG) Slog.v(TAG, "Created surface " + this);
- return mSurfaceController;
+ return mSurfaceControl;
}
boolean hasSurface() {
- return mSurfaceController != null && mSurfaceController.hasSurface();
+ return mSurfaceControl != null;
+ }
+
+ void getSurfaceControl(SurfaceControl outSurfaceControl) {
+ outSurfaceControl.copyFrom(mSurfaceControl, "WindowStateAnimator.getSurfaceControl");
}
void destroySurfaceLocked(SurfaceControl.Transaction t) {
- if (mSurfaceController == null) {
+ if (mSurfaceControl == null) {
return;
}
@@ -370,7 +408,7 @@
try {
if (DEBUG_VISIBILITY) {
logWithStack(TAG, "Window " + this + " destroying surface "
- + mSurfaceController + ", session " + mSession);
+ + mSurfaceControl + ", session " + mSession);
}
ProtoLog.i(WM_SHOW_SURFACE_ALLOC, "SURFACE DESTROY: %s. %s",
mWin, new RuntimeException().fillInStackTrace());
@@ -384,23 +422,13 @@
}
} catch (RuntimeException e) {
Slog.w(TAG, "Exception thrown when destroying Window " + this
- + " surface " + mSurfaceController + " session " + mSession + ": "
+ + " surface " + mSurfaceControl + " session " + mSession + ": "
+ e.toString());
}
-
- // Whether the surface was preserved (and copied to mPendingDestroySurface) or not, it
- // needs to be cleared to match the WindowState.mHasSurface state. It is also necessary
- // so it can be recreated successfully in mPendingDestroySurface case.
- mWin.setHasSurface(false);
- if (mSurfaceController != null) {
- mSurfaceController.setShown(false);
- }
- mSurfaceController = null;
- mDrawState = NO_SURFACE;
}
void computeShownFrameLocked() {
- if (mIsWallpaper && mService.mRoot.mWallpaperActionPending) {
+ if (mWin.mIsWallpaper && mService.mRoot.mWallpaperActionPending) {
return;
} else if (mWin.isDragResizeChanged()) {
// This window is awaiting a relayout because user just started (or ended)
@@ -454,14 +482,13 @@
mLastAlpha = mShownAlpha;
ProtoLog.i(WM_SHOW_TRANSACTIONS,
"SURFACE controller=%s alpha=%f HScale=%f, VScale=%f: %s",
- mSurfaceController, mShownAlpha, w.mHScale, w.mVScale, w);
+ mSurfaceControl, mShownAlpha, w.mHScale, w.mVScale, w);
- boolean prepared =
- mSurfaceController.prepareToShowInTransaction(t, mShownAlpha);
+ t.setAlpha(mSurfaceControl, mShownAlpha);
- if (prepared && mDrawState == HAS_DRAWN) {
+ if (mDrawState == HAS_DRAWN) {
if (mLastHidden) {
- mSurfaceController.showRobustly(t);
+ showRobustly(t);
mLastHidden = false;
final DisplayContent displayContent = w.getDisplayContent();
if (!displayContent.getLastHasContent()) {
@@ -494,18 +521,38 @@
}
}
- void setOpaqueLocked(boolean isOpaque) {
- if (mSurfaceController == null) {
+ private void showRobustly(SurfaceControl.Transaction t) {
+ if (mSurfaceShown) {
return;
}
- mSurfaceController.setOpaque(isOpaque);
+
+ ProtoLog.i(WM_SHOW_TRANSACTIONS, "SURFACE SHOW (performLayout): %s", mTitle);
+ if (DEBUG_VISIBILITY) Slog.v(TAG, "Showing " + this + " during relayout");
+ setShown(true);
+ t.show(mSurfaceControl);
+ if (mWin.mIsWallpaper) {
+ final DisplayContent dc = mWin.mDisplayContent;
+ EventLog.writeEvent(EventLogTags.WM_WALLPAPER_SURFACE,
+ dc.mDisplayId, 1 /* request shown */,
+ String.valueOf(dc.mWallpaperController.getWallpaperTarget()));
+ }
+ }
+
+ void setOpaqueLocked(boolean isOpaque) {
+ if (mSurfaceControl == null) {
+ return;
+ }
+ ProtoLog.i(WM_SHOW_TRANSACTIONS, "SURFACE isOpaque=%b: %s", isOpaque, mTitle);
+ mWin.getPendingTransaction().setOpaque(mSurfaceControl, isOpaque);
+ mService.scheduleAnimationLocked();
}
void setColorSpaceAgnosticLocked(boolean agnostic) {
- if (mSurfaceController == null) {
+ if (mSurfaceControl == null) {
return;
}
- mSurfaceController.setColorSpaceAgnostic(mWin.getPendingTransaction(), agnostic);
+ ProtoLog.i(WM_SHOW_TRANSACTIONS, "SURFACE isColorSpaceAgnostic=%b: %s", agnostic, mTitle);
+ mWin.getPendingTransaction().setColorSpaceAgnostic(mSurfaceControl, agnostic);
}
void applyEnterAnimationLocked() {
@@ -521,7 +568,7 @@
// should be controlled by ActivityRecord in general. Wallpaper is also excluded because
// WallpaperController should handle it. Also skip play enter animation for the window
// below starting window.
- if (mAttrType != TYPE_BASE_APPLICATION && !mIsWallpaper
+ if (mAttrType != TYPE_BASE_APPLICATION && !mWin.mIsWallpaper
&& !(mWin.mActivityRecord != null && mWin.mActivityRecord.hasStartingWindow())) {
applyAnimationLocked(transit, true);
}
@@ -614,8 +661,10 @@
void dumpDebug(ProtoOutputStream proto, long fieldId) {
final long token = proto.start(fieldId);
- if (mSurfaceController != null) {
- mSurfaceController.dumpDebug(proto, SURFACE);
+ if (mSurfaceControl != null) {
+ final long dumpToken = proto.start(SURFACE);
+ proto.write(SHOWN, mSurfaceShown);
+ proto.end(dumpToken);
}
proto.write(DRAW_STATE, mDrawState);
mSystemDecorRect.dumpDebug(proto, SYSTEM_DECOR_RECT);
@@ -626,8 +675,11 @@
if (mAnimationIsEntrance) {
pw.print(prefix); pw.print(" mAnimationIsEntrance="); pw.print(mAnimationIsEntrance);
}
- if (mSurfaceController != null) {
- mSurfaceController.dump(pw, prefix, dumpAll);
+ if (mSurfaceControl != null) {
+ if (dumpAll) {
+ pw.print(prefix); pw.print("mSurface="); pw.println(mSurfaceControl);
+ }
+ pw.print(prefix); pw.print("Surface: shown="); pw.print(mSurfaceShown);
}
if (dumpAll) {
pw.print(prefix); pw.print("mDrawState="); pw.print(drawStateToString());
@@ -659,31 +711,24 @@
}
boolean getShown() {
- if (mSurfaceController != null) {
- return mSurfaceController.getShown();
- }
- return false;
+ return mSurfaceControl != null && mSurfaceShown;
}
void destroySurface(SurfaceControl.Transaction t) {
- try {
- if (mSurfaceController != null) {
- mSurfaceController.destroy(t);
- }
- } catch (RuntimeException e) {
- Slog.w(TAG, "Exception thrown when destroying surface " + this
- + " surface " + mSurfaceController + " session " + mSession + ": " + e);
- } finally {
- mWin.setHasSurface(false);
- mSurfaceController = null;
- mDrawState = NO_SURFACE;
+ if (mSurfaceControl == null) {
+ return;
}
- }
-
- SurfaceControl getSurfaceControl() {
- if (!hasSurface()) {
- return null;
+ ProtoLog.i(WM_SHOW_SURFACE_ALLOC,
+ "Destroying surface %s called by %s", this, Debug.getCallers(8));
+ if (mWin.mIsWallpaper && !mWin.mWindowRemovalAllowed && !mWin.mRemoveOnExit) {
+ // The wallpaper surface should have the same lifetime as its window.
+ Slog.e(TAG, "Unexpected removing wallpaper surface of " + mWin
+ + " by " + Debug.getCallers(8));
}
- return mSurfaceController.mSurfaceControl;
+ t.remove(mSurfaceControl);
+ setShown(false);
+ mSurfaceControl = null;
+ mWin.setHasSurface(false);
+ mDrawState = NO_SURFACE;
}
}
diff --git a/services/core/java/com/android/server/wm/WindowSurfaceController.java b/services/core/java/com/android/server/wm/WindowSurfaceController.java
deleted file mode 100644
index d9766e0..0000000
--- a/services/core/java/com/android/server/wm/WindowSurfaceController.java
+++ /dev/null
@@ -1,237 +0,0 @@
-/*
- * Copyright (C) 2015 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.wm;
-
-import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
-import static android.view.SurfaceControl.METADATA_OWNER_PID;
-import static android.view.SurfaceControl.METADATA_OWNER_UID;
-import static android.view.SurfaceControl.METADATA_WINDOW_TYPE;
-
-import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_SURFACE_ALLOC;
-import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_TRANSACTIONS;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_VISIBILITY;
-import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
-import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
-import static com.android.server.wm.WindowSurfaceControllerProto.SHOWN;
-
-import android.os.Debug;
-import android.os.Trace;
-import android.util.EventLog;
-import android.util.Slog;
-import android.util.proto.ProtoOutputStream;
-import android.view.SurfaceControl;
-import android.view.WindowContentFrameStats;
-
-import com.android.internal.protolog.ProtoLog;
-
-import java.io.PrintWriter;
-
-class WindowSurfaceController {
- static final String TAG = TAG_WITH_CLASS_NAME ? "WindowSurfaceController" : TAG_WM;
-
- final WindowStateAnimator mAnimator;
-
- SurfaceControl mSurfaceControl;
-
- // Should only be set from within setShown().
- private boolean mSurfaceShown = false;
-
- private final String title;
-
- private final WindowManagerService mService;
-
- private final int mWindowType;
- private final Session mWindowSession;
-
-
- WindowSurfaceController(String name, int format, int flags, WindowStateAnimator animator,
- int windowType) {
- mAnimator = animator;
-
- title = name;
-
- mService = animator.mService;
- final WindowState win = animator.mWin;
- mWindowType = windowType;
- mWindowSession = win.mSession;
-
- Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "new SurfaceControl");
- mSurfaceControl = win.makeSurface()
- .setParent(win.getSurfaceControl())
- .setName(name)
- .setFormat(format)
- .setFlags(flags)
- .setMetadata(METADATA_WINDOW_TYPE, windowType)
- .setMetadata(METADATA_OWNER_UID, mWindowSession.mUid)
- .setMetadata(METADATA_OWNER_PID, mWindowSession.mPid)
- .setCallsite("WindowSurfaceController")
- .setBLASTLayer().build();
-
- Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
- }
-
- void hide(SurfaceControl.Transaction transaction, String reason) {
- ProtoLog.i(WM_SHOW_TRANSACTIONS, "SURFACE HIDE ( %s ): %s", reason, title);
-
- if (mSurfaceShown) {
- hideSurface(transaction);
- }
- }
-
- private void hideSurface(SurfaceControl.Transaction transaction) {
- if (mSurfaceControl == null) {
- return;
- }
- setShown(false);
- try {
- transaction.hide(mSurfaceControl);
- if (mAnimator.mIsWallpaper) {
- final DisplayContent dc = mAnimator.mWin.getDisplayContent();
- EventLog.writeEvent(EventLogTags.WM_WALLPAPER_SURFACE,
- dc.mDisplayId, 0 /* request hidden */,
- String.valueOf(dc.mWallpaperController.getWallpaperTarget()));
- }
- } catch (RuntimeException e) {
- Slog.w(TAG, "Exception hiding surface in " + this);
- }
- }
-
- void destroy(SurfaceControl.Transaction t) {
- ProtoLog.i(WM_SHOW_SURFACE_ALLOC,
- "Destroying surface %s called by %s", this, Debug.getCallers(8));
- try {
- if (mSurfaceControl != null) {
- if (mAnimator.mIsWallpaper && !mAnimator.mWin.mWindowRemovalAllowed
- && !mAnimator.mWin.mRemoveOnExit) {
- // The wallpaper surface should have the same lifetime as its window.
- Slog.e(TAG, "Unexpected removing wallpaper surface of " + mAnimator.mWin
- + " by " + Debug.getCallers(8));
- }
- t.remove(mSurfaceControl);
- }
- } catch (RuntimeException e) {
- Slog.w(TAG, "Error destroying surface in: " + this, e);
- } finally {
- setShown(false);
- mSurfaceControl = null;
- }
- }
-
- boolean prepareToShowInTransaction(SurfaceControl.Transaction t, float alpha) {
- if (mSurfaceControl == null) {
- return false;
- }
-
- t.setAlpha(mSurfaceControl, alpha);
- return true;
- }
-
- void setOpaque(boolean isOpaque) {
- ProtoLog.i(WM_SHOW_TRANSACTIONS, "SURFACE isOpaque=%b: %s", isOpaque, title);
-
- if (mSurfaceControl == null) {
- return;
- }
-
- mAnimator.mWin.getPendingTransaction().setOpaque(mSurfaceControl, isOpaque);
- mService.scheduleAnimationLocked();
- }
-
- void setColorSpaceAgnostic(SurfaceControl.Transaction t, boolean agnostic) {
- ProtoLog.i(WM_SHOW_TRANSACTIONS, "SURFACE isColorSpaceAgnostic=%b: %s", agnostic, title);
-
- if (mSurfaceControl == null) {
- return;
- }
- t.setColorSpaceAgnostic(mSurfaceControl, agnostic);
- }
-
- void showRobustly(SurfaceControl.Transaction t) {
- ProtoLog.i(WM_SHOW_TRANSACTIONS, "SURFACE SHOW (performLayout): %s", title);
- if (DEBUG_VISIBILITY) Slog.v(TAG, "Showing " + this
- + " during relayout");
-
- if (mSurfaceShown) {
- return;
- }
-
- setShown(true);
- t.show(mSurfaceControl);
- if (mAnimator.mIsWallpaper) {
- final DisplayContent dc = mAnimator.mWin.getDisplayContent();
- EventLog.writeEvent(EventLogTags.WM_WALLPAPER_SURFACE,
- dc.mDisplayId, 1 /* request shown */,
- String.valueOf(dc.mWallpaperController.getWallpaperTarget()));
- }
- }
-
- boolean clearWindowContentFrameStats() {
- if (mSurfaceControl == null) {
- return false;
- }
- return mSurfaceControl.clearContentFrameStats();
- }
-
- boolean getWindowContentFrameStats(WindowContentFrameStats outStats) {
- if (mSurfaceControl == null) {
- return false;
- }
- return mSurfaceControl.getContentFrameStats(outStats);
- }
-
- boolean hasSurface() {
- return mSurfaceControl != null;
- }
-
- void getSurfaceControl(SurfaceControl outSurfaceControl) {
- outSurfaceControl.copyFrom(mSurfaceControl, "WindowSurfaceController.getSurfaceControl");
- }
-
- boolean getShown() {
- return mSurfaceShown;
- }
-
- void setShown(boolean surfaceShown) {
- mSurfaceShown = surfaceShown;
-
- mService.updateNonSystemOverlayWindowsVisibilityIfNeeded(mAnimator.mWin, surfaceShown);
-
- mAnimator.mWin.onSurfaceShownChanged(surfaceShown);
-
- if (mWindowSession != null) {
- mWindowSession.onWindowSurfaceVisibilityChanged(this, mSurfaceShown, mWindowType);
- }
- }
-
- void dumpDebug(ProtoOutputStream proto, long fieldId) {
- final long token = proto.start(fieldId);
- proto.write(SHOWN, mSurfaceShown);
- proto.end(token);
- }
-
- public void dump(PrintWriter pw, String prefix, boolean dumpAll) {
- if (dumpAll) {
- pw.print(prefix); pw.print("mSurface="); pw.println(mSurfaceControl);
- }
- pw.print(prefix); pw.print("Surface: shown="); pw.print(mSurfaceShown);
- }
-
- @Override
- public String toString() {
- return mSurfaceControl.toString();
- }
-}
diff --git a/services/core/java/com/android/server/wm/WindowTracing.java b/services/core/java/com/android/server/wm/WindowTracing.java
index ba5323e..21f7eca 100644
--- a/services/core/java/com/android/server/wm/WindowTracing.java
+++ b/services/core/java/com/android/server/wm/WindowTracing.java
@@ -18,89 +18,52 @@
import static android.os.Build.IS_USER;
-import static com.android.server.wm.WindowManagerTraceFileProto.ENTRY;
-import static com.android.server.wm.WindowManagerTraceFileProto.MAGIC_NUMBER;
-import static com.android.server.wm.WindowManagerTraceFileProto.MAGIC_NUMBER_H;
-import static com.android.server.wm.WindowManagerTraceFileProto.MAGIC_NUMBER_L;
-import static com.android.server.wm.WindowManagerTraceFileProto.REAL_TO_ELAPSED_TIME_OFFSET_NANOS;
import static com.android.server.wm.WindowManagerTraceProto.ELAPSED_REALTIME_NANOS;
import static com.android.server.wm.WindowManagerTraceProto.WHERE;
import static com.android.server.wm.WindowManagerTraceProto.WINDOW_MANAGER_SERVICE;
import android.annotation.Nullable;
import android.os.ShellCommand;
-import android.os.SystemClock;
import android.os.Trace;
import android.util.Log;
import android.util.proto.ProtoOutputStream;
import android.view.Choreographer;
import com.android.internal.protolog.LegacyProtoLogImpl;
-import com.android.internal.protolog.common.IProtoLog;
import com.android.internal.protolog.ProtoLog;
-import com.android.internal.util.TraceBuffer;
-import java.io.File;
-import java.io.IOException;
import java.io.PrintWriter;
-import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
/**
- * A class that allows window manager to dump its state continuously to a trace file, such that a
+ * A class that allows window manager to dump its state continuously, such that a
* time series of window manager state can be analyzed after the fact.
*/
-class WindowTracing {
-
- /**
- * Maximum buffer size, currently defined as 5 MB
- * Size was experimentally defined to fit between 100 to 150 elements.
- */
- private static final int BUFFER_CAPACITY_CRITICAL = 5120 * 1024; // 5 MB
- private static final int BUFFER_CAPACITY_TRIM = 10240 * 1024; // 10 MB
- private static final int BUFFER_CAPACITY_ALL = 20480 * 1024; // 20 MB
- static final String WINSCOPE_EXT = ".winscope";
- private static final String TRACE_FILENAME = "/data/misc/wmtrace/wm_trace" + WINSCOPE_EXT;
- private static final String TAG = "WindowTracing";
- private static final long MAGIC_NUMBER_VALUE = ((long) MAGIC_NUMBER_H << 32) | MAGIC_NUMBER_L;
+abstract class WindowTracing {
+ protected static final String TAG = "WindowTracing";
+ protected static final String WHERE_START_TRACING = "trace.enable";
+ protected static final String WHERE_ON_FRAME = "onFrame";
private final WindowManagerService mService;
private final Choreographer mChoreographer;
private final WindowManagerGlobalLock mGlobalLock;
- private final Object mEnabledLock = new Object();
- private final File mTraceFile;
- private final TraceBuffer mBuffer;
private final Choreographer.FrameCallback mFrameCallback = (frameTimeNanos) ->
- log("onFrame" /* where */);
+ log(WHERE_ON_FRAME);
- private @WindowTraceLogLevel int mLogLevel = WindowTraceLogLevel.TRIM;
- private boolean mLogOnFrame = false;
- private boolean mEnabled;
- private volatile boolean mEnabledLockFree;
- private boolean mScheduled;
+ private AtomicBoolean mScheduled = new AtomicBoolean(false);
- private final IProtoLog mProtoLog;
static WindowTracing createDefaultAndStartLooper(WindowManagerService service,
Choreographer choreographer) {
- File file = new File(TRACE_FILENAME);
- return new WindowTracing(file, service, choreographer, BUFFER_CAPACITY_TRIM);
+ return new WindowTracingLegacy(service, choreographer);
}
- private WindowTracing(File file, WindowManagerService service, Choreographer choreographer,
- int bufferCapacity) {
- this(file, service, choreographer, service.mGlobalLock, bufferCapacity);
- }
-
- WindowTracing(File file, WindowManagerService service, Choreographer choreographer,
- WindowManagerGlobalLock globalLock, int bufferCapacity) {
+ protected WindowTracing(WindowManagerService service, Choreographer choreographer,
+ WindowManagerGlobalLock globalLock) {
mChoreographer = choreographer;
mService = service;
mGlobalLock = globalLock;
- mTraceFile = file;
- mBuffer = new TraceBuffer(bufferCapacity);
- setLogLevel(WindowTraceLogLevel.TRIM, null /* pw */);
- mProtoLog = ProtoLog.getSingleInstance();
}
void startTrace(@Nullable PrintWriter pw) {
@@ -108,44 +71,29 @@
logAndPrintln(pw, "Error: Tracing is not supported on user builds.");
return;
}
- synchronized (mEnabledLock) {
- if (!android.tracing.Flags.perfettoProtologTracing()) {
- ((LegacyProtoLogImpl) ProtoLog.getSingleInstance()).startProtoLog(pw);
- }
- logAndPrintln(pw, "Start tracing to " + mTraceFile + ".");
- mBuffer.resetBuffer();
- mEnabled = mEnabledLockFree = true;
+ if (!android.tracing.Flags.perfettoProtologTracing()) {
+ ((LegacyProtoLogImpl) ProtoLog.getSingleInstance()).startProtoLog(pw);
}
- log("trace.enable");
+ startTraceInternal(pw);
}
- /**
- * Stops the trace and write the current buffer to disk
- * @param pw Print writer
- */
void stopTrace(@Nullable PrintWriter pw) {
if (IS_USER) {
logAndPrintln(pw, "Error: Tracing is not supported on user builds.");
return;
}
- synchronized (mEnabledLock) {
- logAndPrintln(pw, "Stop tracing to " + mTraceFile + ". Waiting for traces to flush.");
- mEnabled = mEnabledLockFree = false;
-
- if (mEnabled) {
- logAndPrintln(pw, "ERROR: tracing was re-enabled while waiting for flush.");
- throw new IllegalStateException("tracing enabled while waiting for flush.");
- }
- writeTraceToFileLocked();
- logAndPrintln(pw, "Trace written to " + mTraceFile + ".");
- }
if (!android.tracing.Flags.perfettoProtologTracing()) {
((LegacyProtoLogImpl) ProtoLog.getSingleInstance()).stopProtoLog(pw, true);
}
+ stopTraceInternal(pw);
}
/**
- * Stops the trace and write the current buffer to disk then restart, if it's already running.
+ * If legacy tracing is enabled (either WM or ProtoLog):
+ * 1. Stop tracing
+ * 2. Write trace to disk (to be picked by dumpstate)
+ * 3. Restart tracing
+ *
* @param pw Print writer
*/
void saveForBugreport(@Nullable PrintWriter pw) {
@@ -153,143 +101,24 @@
logAndPrintln(pw, "Error: Tracing is not supported on user builds.");
return;
}
- synchronized (mEnabledLock) {
- if (!mEnabled) {
- return;
- }
- mEnabled = mEnabledLockFree = false;
- logAndPrintln(pw, "Stop tracing to " + mTraceFile + ". Waiting for traces to flush.");
- writeTraceToFileLocked();
- logAndPrintln(pw, "Trace written to " + mTraceFile + ".");
- if (!android.tracing.Flags.perfettoProtologTracing()) {
- ((LegacyProtoLogImpl) mProtoLog).stopProtoLog(pw, true);
- }
- logAndPrintln(pw, "Start tracing to " + mTraceFile + ".");
- mBuffer.resetBuffer();
- mEnabled = mEnabledLockFree = true;
- if (!android.tracing.Flags.perfettoProtologTracing()) {
- ((LegacyProtoLogImpl) mProtoLog).startProtoLog(pw);
- }
+ if (!android.tracing.Flags.perfettoProtologTracing()
+ && ProtoLog.getSingleInstance().isProtoEnabled()) {
+ ((LegacyProtoLogImpl) ProtoLog.getSingleInstance()).stopProtoLog(pw, true);
+ ((LegacyProtoLogImpl) ProtoLog.getSingleInstance()).startProtoLog(pw);
}
+ saveForBugreportInternal(pw);
}
- private void setLogLevel(@WindowTraceLogLevel int logLevel, PrintWriter pw) {
- logAndPrintln(pw, "Setting window tracing log level to " + logLevel);
- mLogLevel = logLevel;
-
- switch (logLevel) {
- case WindowTraceLogLevel.ALL: {
- setBufferCapacity(BUFFER_CAPACITY_ALL, pw);
- break;
- }
- case WindowTraceLogLevel.TRIM: {
- setBufferCapacity(BUFFER_CAPACITY_TRIM, pw);
- break;
- }
- case WindowTraceLogLevel.CRITICAL: {
- setBufferCapacity(BUFFER_CAPACITY_CRITICAL, pw);
- break;
- }
- }
- }
-
- private void setLogFrequency(boolean onFrame, PrintWriter pw) {
- logAndPrintln(pw, "Setting window tracing log frequency to "
- + ((onFrame) ? "frame" : "transaction"));
- mLogOnFrame = onFrame;
- }
-
- private void setBufferCapacity(int capacity, PrintWriter pw) {
- logAndPrintln(pw, "Setting window tracing buffer capacity to " + capacity + "bytes");
- mBuffer.setCapacity(capacity);
- }
-
- boolean isEnabled() {
- return mEnabledLockFree;
- }
-
- int onShellCommand(ShellCommand shell) {
- PrintWriter pw = shell.getOutPrintWriter();
- String cmd = shell.getNextArgRequired();
- switch (cmd) {
- case "start":
- startTrace(pw);
- return 0;
- case "stop":
- stopTrace(pw);
- return 0;
- case "save-for-bugreport":
- saveForBugreport(pw);
- return 0;
- case "status":
- logAndPrintln(pw, getStatus());
- return 0;
- case "frame":
- setLogFrequency(true /* onFrame */, pw);
- mBuffer.resetBuffer();
- return 0;
- case "transaction":
- setLogFrequency(false /* onFrame */, pw);
- mBuffer.resetBuffer();
- return 0;
- case "level":
- String logLevelStr = shell.getNextArgRequired().toLowerCase();
- switch (logLevelStr) {
- case "all": {
- setLogLevel(WindowTraceLogLevel.ALL, pw);
- break;
- }
- case "trim": {
- setLogLevel(WindowTraceLogLevel.TRIM, pw);
- break;
- }
- case "critical": {
- setLogLevel(WindowTraceLogLevel.CRITICAL, pw);
- break;
- }
- default: {
- setLogLevel(WindowTraceLogLevel.TRIM, pw);
- break;
- }
- }
- mBuffer.resetBuffer();
- return 0;
- case "size":
- setBufferCapacity(Integer.parseInt(shell.getNextArgRequired()) * 1024, pw);
- mBuffer.resetBuffer();
- return 0;
- default:
- pw.println("Unknown command: " + cmd);
- pw.println("Window manager trace options:");
- pw.println(" start: Start logging");
- pw.println(" stop: Stop logging");
- pw.println(" save-for-bugreport: Save logging data to file if it's running.");
- pw.println(" frame: Log trace once per frame");
- pw.println(" transaction: Log each transaction");
- pw.println(" size: Set the maximum log size (in KB)");
- pw.println(" status: Print trace status");
- pw.println(" level [lvl]: Set the log level between");
- pw.println(" lvl may be one of:");
- pw.println(" critical: Only visible windows with reduced information");
- pw.println(" trim: All windows with reduced");
- pw.println(" all: All window and information");
- return -1;
- }
- }
-
- String getStatus() {
- return "Status: "
- + ((isEnabled()) ? "Enabled" : "Disabled")
- + "\n"
- + "Log level: "
- + mLogLevel
- + "\n"
- + mBuffer.getStatus();
- }
+ abstract void setLogLevel(@WindowTraceLogLevel int logLevel, PrintWriter pw);
+ abstract void setLogFrequency(boolean onFrame, PrintWriter pw);
+ abstract void setBufferCapacity(int capacity, PrintWriter pw);
+ abstract boolean isEnabled();
+ abstract int onShellCommand(ShellCommand shell);
+ abstract String getStatus();
/**
* If tracing is enabled, log the current state or schedule the next frame to be logged,
- * according to {@link #mLogOnFrame}.
+ * according to the configuration in the derived tracing class.
*
* @param where Logging point descriptor
*/
@@ -298,59 +127,63 @@
return;
}
- if (mLogOnFrame) {
- schedule();
- } else {
+ if (shouldLogOnTransaction()) {
log(where);
}
+
+ if (shouldLogOnFrame()) {
+ schedule();
+ }
}
/**
* Schedule the log to trace the next frame
*/
private void schedule() {
- if (mScheduled) {
+ if (!mScheduled.compareAndSet(false, true)) {
return;
}
- mScheduled = true;
mChoreographer.postFrameCallback(mFrameCallback);
}
/**
- * Write the current frame to the buffer
+ * Write the current frame to proto
*
+ * @param os Proto stream buffer
+ * @param logLevel Log level
* @param where Logging point descriptor
+ * @param elapsedRealtimeNanos Timestamp
*/
- private void log(String where) {
+ protected void dumpToProto(ProtoOutputStream os, @WindowTraceLogLevel int logLevel,
+ String where, long elapsedRealtimeNanos) {
Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "traceStateLocked");
try {
- ProtoOutputStream os = new ProtoOutputStream();
- long tokenOuter = os.start(ENTRY);
- os.write(ELAPSED_REALTIME_NANOS, SystemClock.elapsedRealtimeNanos());
+ os.write(ELAPSED_REALTIME_NANOS, elapsedRealtimeNanos);
os.write(WHERE, where);
- long tokenInner = os.start(WINDOW_MANAGER_SERVICE);
+ long token = os.start(WINDOW_MANAGER_SERVICE);
synchronized (mGlobalLock) {
Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "dumpDebugLocked");
try {
- mService.dumpDebugLocked(os, mLogLevel);
+ mService.dumpDebugLocked(os, logLevel);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
}
}
- os.end(tokenInner);
- os.end(tokenOuter);
- mBuffer.add(os);
- mScheduled = false;
+ os.end(token);
} catch (Exception e) {
Log.wtf(TAG, "Exception while tracing state", e);
} finally {
+ boolean isOnFrameLogEvent = where == WHERE_ON_FRAME;
+ if (isOnFrameLogEvent) {
+ mScheduled.set(false);
+ }
Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
}
}
- private void logAndPrintln(@Nullable PrintWriter pw, String msg) {
+ protected void logAndPrintln(@Nullable PrintWriter pw, String msg) {
Log.i(TAG, msg);
if (pw != null) {
pw.println(msg);
@@ -358,24 +191,10 @@
}
}
- /**
- * Writes the trace buffer to disk. This method has no internal synchronization and should be
- * externally synchronized
- */
- private void writeTraceToFileLocked() {
- try {
- Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "writeTraceToFileLocked");
- ProtoOutputStream proto = new ProtoOutputStream();
- proto.write(MAGIC_NUMBER, MAGIC_NUMBER_VALUE);
- long timeOffsetNs =
- TimeUnit.MILLISECONDS.toNanos(System.currentTimeMillis())
- - SystemClock.elapsedRealtimeNanos();
- proto.write(REAL_TO_ELAPSED_TIME_OFFSET_NANOS, timeOffsetNs);
- mBuffer.writeTraceToFile(mTraceFile, proto);
- } catch (IOException e) {
- Log.e(TAG, "Unable to write buffer to file", e);
- } finally {
- Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
- }
- }
+ protected abstract void startTraceInternal(@Nullable PrintWriter pw);
+ protected abstract void stopTraceInternal(@Nullable PrintWriter pw);
+ protected abstract void saveForBugreportInternal(@Nullable PrintWriter pw);
+ protected abstract void log(String where);
+ protected abstract boolean shouldLogOnFrame();
+ protected abstract boolean shouldLogOnTransaction();
}
diff --git a/services/core/java/com/android/server/wm/WindowTracingLegacy.java b/services/core/java/com/android/server/wm/WindowTracingLegacy.java
new file mode 100644
index 0000000..7a36707
--- /dev/null
+++ b/services/core/java/com/android/server/wm/WindowTracingLegacy.java
@@ -0,0 +1,276 @@
+/*
+ * 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.wm;
+
+import static com.android.server.wm.WindowManagerTraceFileProto.ENTRY;
+import static com.android.server.wm.WindowManagerTraceFileProto.MAGIC_NUMBER;
+import static com.android.server.wm.WindowManagerTraceFileProto.MAGIC_NUMBER_H;
+import static com.android.server.wm.WindowManagerTraceFileProto.MAGIC_NUMBER_L;
+import static com.android.server.wm.WindowManagerTraceFileProto.REAL_TO_ELAPSED_TIME_OFFSET_NANOS;
+
+import android.annotation.Nullable;
+import android.os.ShellCommand;
+import android.os.SystemClock;
+import android.os.Trace;
+import android.util.Log;
+import android.util.proto.ProtoOutputStream;
+import android.view.Choreographer;
+
+import com.android.internal.util.TraceBuffer;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.concurrent.TimeUnit;
+
+class WindowTracingLegacy extends WindowTracing {
+
+ /**
+ * Maximum buffer size, currently defined as 5 MB
+ * Size was experimentally defined to fit between 100 to 150 elements.
+ */
+ private static final int BUFFER_CAPACITY_CRITICAL = 5120 * 1024; // 5 MB
+ private static final int BUFFER_CAPACITY_TRIM = 10240 * 1024; // 10 MB
+ private static final int BUFFER_CAPACITY_ALL = 20480 * 1024; // 20 MB
+ static final String WINSCOPE_EXT = ".winscope";
+ private static final String TRACE_FILENAME = "/data/misc/wmtrace/wm_trace" + WINSCOPE_EXT;
+ private static final String TAG = "WindowTracing";
+ private static final long MAGIC_NUMBER_VALUE = ((long) MAGIC_NUMBER_H << 32) | MAGIC_NUMBER_L;
+
+ private final Object mEnabledLock = new Object();
+ private final File mTraceFile;
+ private final TraceBuffer mBuffer;
+
+ private boolean mEnabled;
+ private volatile boolean mEnabledLockFree;
+
+ protected @WindowTraceLogLevel int mLogLevel = WindowTraceLogLevel.TRIM;
+ protected boolean mLogOnFrame = false;
+
+ WindowTracingLegacy(WindowManagerService service, Choreographer choreographer) {
+ this(new File(TRACE_FILENAME), service, choreographer,
+ service.mGlobalLock, BUFFER_CAPACITY_TRIM);
+ }
+
+ WindowTracingLegacy(File traceFile, WindowManagerService service, Choreographer choreographer,
+ WindowManagerGlobalLock globalLock, int bufferSize) {
+ super(service, choreographer, globalLock);
+ mTraceFile = traceFile;
+ mBuffer = new TraceBuffer(bufferSize);
+ }
+
+ @Override
+ void setLogLevel(@WindowTraceLogLevel int logLevel, PrintWriter pw) {
+ logAndPrintln(pw, "Setting window tracing log level to " + logLevel);
+ mLogLevel = logLevel;
+
+ switch (logLevel) {
+ case WindowTraceLogLevel.ALL: {
+ setBufferCapacity(BUFFER_CAPACITY_ALL, pw);
+ break;
+ }
+ case WindowTraceLogLevel.TRIM: {
+ setBufferCapacity(BUFFER_CAPACITY_TRIM, pw);
+ break;
+ }
+ case WindowTraceLogLevel.CRITICAL: {
+ setBufferCapacity(BUFFER_CAPACITY_CRITICAL, pw);
+ break;
+ }
+ }
+ }
+
+ @Override
+ void setLogFrequency(boolean onFrame, PrintWriter pw) {
+ logAndPrintln(pw, "Setting window tracing log frequency to "
+ + ((onFrame) ? "frame" : "transaction"));
+ mLogOnFrame = onFrame;
+ }
+
+ @Override
+ void setBufferCapacity(int capacity, PrintWriter pw) {
+ logAndPrintln(pw, "Setting window tracing buffer capacity to " + capacity + "bytes");
+ mBuffer.setCapacity(capacity);
+ }
+
+ @Override
+ boolean isEnabled() {
+ return mEnabledLockFree;
+ }
+
+ @Override
+ int onShellCommand(ShellCommand shell) {
+ PrintWriter pw = shell.getOutPrintWriter();
+ String cmd = shell.getNextArgRequired();
+ switch (cmd) {
+ case "start":
+ startTrace(pw);
+ return 0;
+ case "stop":
+ stopTrace(pw);
+ return 0;
+ case "save-for-bugreport":
+ saveForBugreport(pw);
+ return 0;
+ case "status":
+ logAndPrintln(pw, getStatus());
+ return 0;
+ case "frame":
+ setLogFrequency(true /* onFrame */, pw);
+ mBuffer.resetBuffer();
+ return 0;
+ case "transaction":
+ setLogFrequency(false /* onFrame */, pw);
+ mBuffer.resetBuffer();
+ return 0;
+ case "level":
+ String logLevelStr = shell.getNextArgRequired().toLowerCase();
+ switch (logLevelStr) {
+ case "all": {
+ setLogLevel(WindowTraceLogLevel.ALL, pw);
+ break;
+ }
+ case "trim": {
+ setLogLevel(WindowTraceLogLevel.TRIM, pw);
+ break;
+ }
+ case "critical": {
+ setLogLevel(WindowTraceLogLevel.CRITICAL, pw);
+ break;
+ }
+ default: {
+ setLogLevel(WindowTraceLogLevel.TRIM, pw);
+ break;
+ }
+ }
+ mBuffer.resetBuffer();
+ return 0;
+ case "size":
+ setBufferCapacity(Integer.parseInt(shell.getNextArgRequired()) * 1024, pw);
+ mBuffer.resetBuffer();
+ return 0;
+ default:
+ pw.println("Unknown command: " + cmd);
+ pw.println("Window manager trace options:");
+ pw.println(" start: Start logging");
+ pw.println(" stop: Stop logging");
+ pw.println(" save-for-bugreport: Save logging data to file if it's running.");
+ pw.println(" frame: Log trace once per frame");
+ pw.println(" transaction: Log each transaction");
+ pw.println(" size: Set the maximum log size (in KB)");
+ pw.println(" status: Print trace status");
+ pw.println(" level [lvl]: Set the log level between");
+ pw.println(" lvl may be one of:");
+ pw.println(" critical: Only visible windows with reduced information");
+ pw.println(" trim: All windows with reduced");
+ pw.println(" all: All window and information");
+ return -1;
+ }
+ }
+
+ @Override
+ String getStatus() {
+ return "Status: "
+ + ((isEnabled()) ? "Enabled" : "Disabled")
+ + "\n"
+ + "Log level: "
+ + mLogLevel
+ + "\n"
+ + mBuffer.getStatus();
+ }
+
+ @Override
+ protected void startTraceInternal(@Nullable PrintWriter pw) {
+ synchronized (mEnabledLock) {
+ logAndPrintln(pw, "Start tracing to " + mTraceFile + ".");
+ mBuffer.resetBuffer();
+ mEnabled = mEnabledLockFree = true;
+ }
+ log(WHERE_START_TRACING);
+ }
+
+ @Override
+ protected void stopTraceInternal(@Nullable PrintWriter pw) {
+ synchronized (mEnabledLock) {
+ logAndPrintln(pw, "Stop tracing to " + mTraceFile + ". Waiting for traces to flush.");
+ mEnabled = mEnabledLockFree = false;
+
+ if (mEnabled) {
+ logAndPrintln(pw, "ERROR: tracing was re-enabled while waiting for flush.");
+ throw new IllegalStateException("tracing enabled while waiting for flush.");
+ }
+ writeTraceToFileLocked();
+ logAndPrintln(pw, "Trace written to " + mTraceFile + ".");
+ }
+ }
+
+ @Override
+ protected void saveForBugreportInternal(@Nullable PrintWriter pw) {
+ synchronized (mEnabledLock) {
+ if (!mEnabled) {
+ return;
+ }
+ mEnabled = mEnabledLockFree = false;
+ logAndPrintln(pw, "Stop tracing to " + mTraceFile + ". Waiting for traces to flush.");
+ writeTraceToFileLocked();
+ logAndPrintln(pw, "Trace written to " + mTraceFile + ".");
+ logAndPrintln(pw, "Start tracing to " + mTraceFile + ".");
+ mBuffer.resetBuffer();
+ mEnabled = mEnabledLockFree = true;
+ }
+ }
+
+ @Override
+ protected void log(String where) {
+ try {
+ ProtoOutputStream os = new ProtoOutputStream();
+ long token = os.start(ENTRY);
+ dumpToProto(os, mLogLevel, where, SystemClock.elapsedRealtimeNanos());
+ os.end(token);
+ mBuffer.add(os);
+ } catch (Exception e) {
+ Log.wtf(TAG, "Exception while tracing state", e);
+ }
+ }
+
+ @Override
+ protected boolean shouldLogOnFrame() {
+ return mLogOnFrame;
+ }
+
+ @Override
+ protected boolean shouldLogOnTransaction() {
+ return !mLogOnFrame;
+ }
+
+ private void writeTraceToFileLocked() {
+ try {
+ Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "writeTraceToFileLocked");
+ ProtoOutputStream proto = new ProtoOutputStream();
+ proto.write(MAGIC_NUMBER, MAGIC_NUMBER_VALUE);
+ long timeOffsetNs =
+ TimeUnit.MILLISECONDS.toNanos(System.currentTimeMillis())
+ - SystemClock.elapsedRealtimeNanos();
+ proto.write(REAL_TO_ELAPSED_TIME_OFFSET_NANOS, timeOffsetNs);
+ mBuffer.writeTraceToFile(mTraceFile, proto);
+ } catch (IOException e) {
+ Log.e(TAG, "Unable to write buffer to file", e);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+ }
+ }
+}
diff --git a/services/core/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd
index ec7406a..4231149 100644
--- a/services/core/xsd/display-device-config/display-device-config.xsd
+++ b/services/core/xsd/display-device-config/display-device-config.xsd
@@ -179,6 +179,19 @@
<xs:element name="supportsVrr" type="xs:boolean" minOccurs="0">
<xs:annotation name="final"/>
</xs:element>
+ <!-- Table that translates doze brightness sensor values to brightness values in
+ the float scale [0, 1]; -1 means the current brightness should be kept.
+ The following formula should be used for conversion between nits and the float
+ scale: float = (nits - minNits) / (maxNits - minNits). minNits and maxNits are
+ defined in screenBrightnessMap. -->
+ <xs:element type="float-array" name="dozeBrightnessSensorValueToBrightness">
+ <xs:annotation name="final"/>
+ </xs:element>
+ <!-- The default screen brightness in the scale [0, 1] to use while the device is
+ dozing. -->
+ <xs:element type="nonNegativeDecimal" name="defaultDozeBrightness">
+ <xs:annotation name="final"/>
+ </xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
@@ -859,6 +872,12 @@
</xs:sequence>
</xs:complexType>
+ <xs:complexType name="float-array">
+ <xs:sequence>
+ <xs:element name="item" type="nonNegativeDecimal" minOccurs="0" maxOccurs="unbounded"/>
+ </xs:sequence>
+ </xs:complexType>
+
<xs:complexType name="usiVersion">
<xs:element name="majorVersion" type="xs:nonNegativeInteger"
minOccurs="1" maxOccurs="1">
diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt
index 68d74cf..cec2787 100644
--- a/services/core/xsd/display-device-config/schema/current.txt
+++ b/services/core/xsd/display-device-config/schema/current.txt
@@ -125,9 +125,11 @@
method public final java.math.BigInteger getAmbientLightHorizonLong();
method public final java.math.BigInteger getAmbientLightHorizonShort();
method public com.android.server.display.config.AutoBrightness getAutoBrightness();
+ method public final java.math.BigDecimal getDefaultDozeBrightness();
method @Nullable public final com.android.server.display.config.DensityMapping getDensityMapping();
method @NonNull public final com.android.server.display.config.Thresholds getDisplayBrightnessChangeThresholds();
method public final com.android.server.display.config.Thresholds getDisplayBrightnessChangeThresholdsIdle();
+ method public final com.android.server.display.config.FloatArray getDozeBrightnessSensorValueToBrightness();
method public final com.android.server.display.config.EvenDimmerMode getEvenDimmer();
method @Nullable public final com.android.server.display.config.HdrBrightnessConfig getHdrBrightnessConfig();
method public com.android.server.display.config.HighBrightnessMode getHighBrightnessMode();
@@ -163,9 +165,11 @@
method public final void setAmbientLightHorizonLong(java.math.BigInteger);
method public final void setAmbientLightHorizonShort(java.math.BigInteger);
method public void setAutoBrightness(com.android.server.display.config.AutoBrightness);
+ method public final void setDefaultDozeBrightness(java.math.BigDecimal);
method public final void setDensityMapping(@Nullable com.android.server.display.config.DensityMapping);
method public final void setDisplayBrightnessChangeThresholds(@NonNull com.android.server.display.config.Thresholds);
method public final void setDisplayBrightnessChangeThresholdsIdle(com.android.server.display.config.Thresholds);
+ method public final void setDozeBrightnessSensorValueToBrightness(com.android.server.display.config.FloatArray);
method public final void setEvenDimmer(com.android.server.display.config.EvenDimmerMode);
method public final void setHdrBrightnessConfig(@Nullable com.android.server.display.config.HdrBrightnessConfig);
method public void setHighBrightnessMode(com.android.server.display.config.HighBrightnessMode);
@@ -214,6 +218,11 @@
method public void setTransitionPoint(java.math.BigDecimal);
}
+ public class FloatArray {
+ ctor public FloatArray();
+ method public java.util.List<java.math.BigDecimal> getItem();
+ }
+
public class HbmTiming {
ctor public HbmTiming();
method @NonNull public final java.math.BigInteger getTimeMaxSecs_all();
diff --git a/services/manifest_services.xml b/services/manifest_services.xml
index 8ff1a7d..114fe32 100644
--- a/services/manifest_services.xml
+++ b/services/manifest_services.xml
@@ -4,9 +4,4 @@
<version>2</version>
<fqname>IAltitudeService/default</fqname>
</hal>
- <hal format="aidl">
- <name>android.frameworks.vibrator</name>
- <version>1</version>
- <fqname>IVibratorControlService/default</fqname>
- </hal>
</manifest>
diff --git a/services/manifest_services_android.frameworks.vibrator.xml b/services/manifest_services_android.frameworks.vibrator.xml
new file mode 100644
index 0000000..c287643
--- /dev/null
+++ b/services/manifest_services_android.frameworks.vibrator.xml
@@ -0,0 +1,7 @@
+<manifest version="1.0" type="framework">
+ <hal format="aidl">
+ <name>android.frameworks.vibrator</name>
+ <version>1</version>
+ <fqname>IVibratorControlService/default</fqname>
+ </hal>
+</manifest>
diff --git a/services/net/Android.bp b/services/net/Android.bp
index 3d40f64..927146d 100644
--- a/services/net/Android.bp
+++ b/services/net/Android.bp
@@ -21,6 +21,7 @@
":services.net-sources",
],
static_libs: [
+ "modules-utils-build_system",
"netd-client",
"networkstack-client",
"net-utils-services-common",
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
index bacde10..c2a069d 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
@@ -68,7 +68,6 @@
import com.android.internal.view.IInputMethodManager;
import com.android.server.LocalServices;
import com.android.server.ServiceThread;
-import com.android.server.SystemServerInitThreadPool;
import com.android.server.SystemService;
import com.android.server.input.InputManagerInternal;
import com.android.server.pm.UserManagerInternal;
@@ -161,7 +160,7 @@
.strictness(Strictness.LENIENT)
.spyStatic(InputMethodUtils.class)
.mockStatic(ServiceManager.class)
- .mockStatic(SystemServerInitThreadPool.class)
+ .spyStatic(AdditionalSubtypeMapRepository.class)
.startMocking();
mContext = spy(InstrumentationRegistry.getInstrumentation().getContext());
@@ -234,9 +233,8 @@
doNothing().when(() -> InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed(
any(PackageManager.class), anyList()));
- // Used by lazy initializing draw IMS nav bar at InputMethodManagerService#systemRunning(),
- // which is ok to be mocked out for now.
- doReturn(null).when(() -> SystemServerInitThreadPool.submit(any(), anyString()));
+ // The background writer thread in AdditionalSubtypeMapRepository should be stubbed out.
+ doNothing().when(AdditionalSubtypeMapRepository::startWriterThread);
mServiceThread =
new ServiceThread(
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
index 3437923..d450683 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
@@ -965,6 +965,51 @@
assertThat(supportedModeData.vsyncRate).isEqualTo(240);
}
+ @Test
+ public void testDozeBrightness_Ddc() throws IOException {
+ when(mFlags.isDozeBrightnessFloatEnabled()).thenReturn(true);
+ setupDisplayDeviceConfigFromDisplayConfigFile();
+
+ assertArrayEquals(new float[]{ -1, 0.1f, 0.2f, 0.3f, 0.4f },
+ mDisplayDeviceConfig.getDozeBrightnessSensorValueToBrightness(), SMALL_DELTA);
+ assertEquals(0.25f, mDisplayDeviceConfig.getDefaultDozeBrightness(), SMALL_DELTA);
+ }
+
+ @Test
+ public void testDefaultDozeBrightness_FallBackToConfigXmlFloat() throws IOException {
+ setupDisplayDeviceConfigFromConfigResourceFile();
+ when(mFlags.isDozeBrightnessFloatEnabled()).thenReturn(true);
+ when(mResources.getFloat(com.android.internal.R.dimen.config_screenBrightnessDozeFloat))
+ .thenReturn(0.31f);
+ when(mResources.getInteger(com.android.internal.R.integer.config_screenBrightnessDoze))
+ .thenReturn(90);
+
+ // Empty display config file
+ setupDisplayDeviceConfigFromDisplayConfigFile(
+ "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
+ + "<displayConfiguration />\n");
+
+ assertEquals(0.31f, mDisplayDeviceConfig.getDefaultDozeBrightness(), ZERO_DELTA);
+ }
+
+ @Test
+ public void testDefaultDozeBrightness_FallBackToConfigXmlInt() throws IOException {
+ setupDisplayDeviceConfigFromConfigResourceFile();
+ when(mFlags.isDozeBrightnessFloatEnabled()).thenReturn(true);
+ when(mResources.getFloat(com.android.internal.R.dimen.config_screenBrightnessDozeFloat))
+ .thenReturn(DisplayDeviceConfig.INVALID_BRIGHTNESS_IN_CONFIG);
+ when(mResources.getInteger(com.android.internal.R.integer.config_screenBrightnessDoze))
+ .thenReturn(90);
+
+ // Empty display config file
+ setupDisplayDeviceConfigFromDisplayConfigFile(
+ "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
+ + "<displayConfiguration />\n");
+
+ assertEquals(brightnessIntToFloat(90),
+ mDisplayDeviceConfig.getDefaultDozeBrightness(), ZERO_DELTA);
+ }
+
private String getValidLuxThrottling() {
return "<luxThrottling>\n"
+ " <brightnessLimitMap>\n"
@@ -1708,6 +1753,16 @@
+ "</point>"
+ "</luxThresholds>"
+ "</idleScreenRefreshRateTimeout>"
+ + "<dozeBrightnessSensorValueToBrightness>\n"
+ + "<item>-1</item>\n"
+ + "<item>0.1</item>\n"
+ + "<item>0.2</item>\n"
+ + "<item>0.3</item>\n"
+ + "<item>0.4</item>\n"
+ + "</dozeBrightnessSensorValueToBrightness>\n"
+ + "<defaultDozeBrightness>"
+ + "0.25"
+ + "</defaultDozeBrightness>\n"
+ "</displayConfiguration>\n";
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
index 5c29156..624c897 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
@@ -117,6 +117,7 @@
private static final String SECOND_FOLLOWER_UNIQUE_DISPLAY_ID = "unique_id_789";
private static final float PROX_SENSOR_MAX_RANGE = 5;
private static final float DOZE_SCALE_FACTOR = 0.34f;
+ private static final float DEFAULT_DOZE_BRIGHTNESS = 0.121f;
private static final float BRIGHTNESS_RAMP_RATE_MINIMUM = 0.0f;
private static final float BRIGHTNESS_RAMP_RATE_FAST_DECREASE = 0.3f;
@@ -2051,9 +2052,6 @@
@Test
public void testDefaultDozeBrightness() {
- float brightness = 0.121f;
- when(mPowerManagerMock.getBrightnessConstraint(
- PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_DOZE)).thenReturn(brightness);
mContext.getOrCreateTestableResources().addOverride(
com.android.internal.R.bool.config_allowAutoBrightnessWhileDozing, false);
mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
@@ -2069,15 +2067,25 @@
mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
advanceTime(1); // Run updatePowerState
- verify(mHolder.animator).animateTo(eq(brightness), /* linearSecondTarget= */ anyFloat(),
- eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false));
+ verify(mHolder.animator).animateTo(eq(DEFAULT_DOZE_BRIGHTNESS),
+ /* linearSecondTarget= */ anyFloat(), eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE),
+ eq(false));
+
+ // The display device changes and the default doze brightness changes
+ setUpDisplay(DISPLAY_ID, "new_unique_id", mHolder.display, mock(DisplayDevice.class),
+ mHolder.config, /* isEnabled= */ true);
+ when(mHolder.config.getDefaultDozeBrightness()).thenReturn(DEFAULT_DOZE_BRIGHTNESS / 2);
+ mHolder.dpc.onDisplayChanged(mHolder.hbmMetadata, Layout.NO_LEAD_DISPLAY);
+
+ advanceTime(1); // Run updatePowerState
+
+ verify(mHolder.animator).animateTo(eq(DEFAULT_DOZE_BRIGHTNESS / 2),
+ /* linearSecondTarget= */ anyFloat(), eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE),
+ eq(false));
}
@Test
public void testDefaultDozeBrightness_ShouldNotBeUsedIfAutoBrightnessAllowedInDoze() {
- float brightness = 0.121f;
- when(mPowerManagerMock.getBrightnessConstraint(
- PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_DOZE)).thenReturn(brightness);
mContext.getOrCreateTestableResources().addOverride(
com.android.internal.R.bool.config_allowAutoBrightnessWhileDozing, true);
mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
@@ -2093,7 +2101,7 @@
mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
advanceTime(1); // Run updatePowerState
- verify(mHolder.animator, never()).animateTo(eq(brightness),
+ verify(mHolder.animator, never()).animateTo(eq(DEFAULT_DOZE_BRIGHTNESS),
/* linearSecondTarget= */ anyFloat(), /* rate= */ anyFloat(),
/* ignoreAnimationLimits= */ anyBoolean());
}
@@ -2151,6 +2159,8 @@
new SensorData(Sensor.STRING_TYPE_LIGHT, null));
when(displayDeviceConfigMock.getScreenOffBrightnessSensorValueToLux())
.thenReturn(new int[0]);
+ when(displayDeviceConfigMock.getDefaultDozeBrightness())
+ .thenReturn(DEFAULT_DOZE_BRIGHTNESS);
when(displayDeviceConfigMock.getBrightnessRampFastDecrease())
.thenReturn(BRIGHTNESS_RAMP_RATE_FAST_DECREASE);
diff --git a/services/tests/mockingservicestests/Android.bp b/services/tests/mockingservicestests/Android.bp
index 4149e44..5b2c0c6 100644
--- a/services/tests/mockingservicestests/Android.bp
+++ b/services/tests/mockingservicestests/Android.bp
@@ -53,6 +53,7 @@
"mockingservicestests-utils-mockito",
"mockito-target-extended-minus-junit4",
"platform-compat-test-rules",
+ "platform-parametric-runner-lib",
"platform-test-annotations",
"PlatformProperties",
"service-blobstore",
diff --git a/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java
index 00daf41..1a398c5 100644
--- a/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java
@@ -16,6 +16,8 @@
package com.android.server.trust;
+import static android.security.Flags.FLAG_SHOULD_TRUST_MANAGER_LISTEN_FOR_PRIMARY_AUTH;
+import static android.security.Flags.shouldTrustManagerListenForPrimaryAuth;
import static android.service.trust.TrustAgentService.FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
@@ -73,6 +75,8 @@
import android.os.ServiceManager;
import android.os.UserHandle;
import android.os.UserManager;
+import android.platform.test.flag.junit.FlagsParameterization;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings;
import android.security.KeyStoreAuthorization;
import android.service.trust.GrantTrustResult;
@@ -91,6 +95,7 @@
import com.android.internal.widget.LockPatternUtils.StrongAuthTracker;
import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.StrongAuthFlags;
import com.android.internal.widget.LockSettingsInternal;
+import com.android.internal.widget.LockSettingsStateListener;
import com.android.modules.utils.testing.ExtendedMockitoRule;
import com.android.server.LocalServices;
import com.android.server.SystemService;
@@ -101,6 +106,7 @@
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
+import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.ArgumentMatcher;
import org.mockito.Mock;
@@ -112,7 +118,16 @@
import java.util.List;
import java.util.Map;
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
+import platform.test.runner.parameterized.Parameters;
+
+@RunWith(ParameterizedAndroidJunit4.class)
public class TrustManagerServiceTest {
+ @Parameters(name = "{0}")
+ public static List<FlagsParameterization> getParams() {
+ return FlagsParameterization.allCombinationsOf(
+ FLAG_SHOULD_TRUST_MANAGER_LISTEN_FOR_PRIMARY_AUTH);
+ }
@Rule
public final ExtendedMockitoRule mExtendedMockitoRule = new ExtendedMockitoRule.Builder(this)
@@ -122,6 +137,9 @@
.build();
@Rule
+ public final SetFlagsRule mSetFlagsRule;
+
+ @Rule
public final MockContext mMockContext = new MockContext(
ApplicationProvider.getApplicationContext());
@@ -162,6 +180,10 @@
private ITrustManager mTrustManager;
private ActivityManagerInternal mPreviousActivityManagerInternal;
+ public TrustManagerServiceTest(FlagsParameterization flags) {
+ mSetFlagsRule = new SetFlagsRule(SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT, flags);
+ }
+
@Before
public void setUp() throws Exception {
when(mActivityManager.isUserRunning(TEST_USER_ID)).thenReturn(true);
@@ -594,11 +616,27 @@
}
private void attemptSuccessfulUnlock(int userId) throws RemoteException {
- mTrustManager.reportUnlockAttempt(/* successful= */ true, userId);
+ if (shouldTrustManagerListenForPrimaryAuth()) {
+ ArgumentCaptor<LockSettingsStateListener> captor =
+ ArgumentCaptor.forClass(LockSettingsStateListener.class);
+ verify(mLockSettingsInternal).registerLockSettingsStateListener(captor.capture());
+ LockSettingsStateListener listener = captor.getValue();
+ listener.onAuthenticationSucceeded(userId);
+ } else {
+ mTrustManager.reportUnlockAttempt(/* successful= */ true, userId);
+ }
}
private void attemptFailedUnlock(int userId) throws RemoteException {
- mTrustManager.reportUnlockAttempt(/* successful= */ false, userId);
+ if (shouldTrustManagerListenForPrimaryAuth()) {
+ ArgumentCaptor<LockSettingsStateListener> captor =
+ ArgumentCaptor.forClass(LockSettingsStateListener.class);
+ verify(mLockSettingsInternal).registerLockSettingsStateListener(captor.capture());
+ LockSettingsStateListener listener = captor.getValue();
+ listener.onAuthenticationFailed(userId);
+ } else {
+ mTrustManager.reportUnlockAttempt(/* successful= */ false, userId);
+ }
}
private void grantRenewableTrust(ITrustAgentServiceCallback callback) throws RemoteException {
diff --git a/services/tests/servicestests/src/com/android/server/audio/AbsoluteVolumeBehaviorTest.java b/services/tests/servicestests/src/com/android/server/audio/AbsoluteVolumeBehaviorTest.java
index 758c84a..ef9580c 100644
--- a/services/tests/servicestests/src/com/android/server/audio/AbsoluteVolumeBehaviorTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/AbsoluteVolumeBehaviorTest.java
@@ -101,7 +101,7 @@
mAudioService = new AudioService(mContext, mSpyAudioSystem, mSystemServer,
mSettingsAdapter, mAudioVolumeGroupHelper, mMockAudioPolicy,
mTestLooper.getLooper(), mock(AppOpsManager.class), mock(PermissionEnforcer.class),
- mock(AudioServerPermissionProvider.class)) {
+ mock(AudioServerPermissionProvider.class), r -> r.run()) {
@Override
public int getDeviceForStream(int stream) {
return AudioSystem.DEVICE_OUT_SPEAKER;
diff --git a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceVolumeManagerTest.java b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceVolumeManagerTest.java
index 2cb02bd..4645156 100644
--- a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceVolumeManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceVolumeManagerTest.java
@@ -78,7 +78,7 @@
mAudioService = new AudioService(mContext, mSpyAudioSystem, mSystemServer,
mSettingsAdapter, mAudioVolumeGroupHelper, mAudioPolicyMock,
mTestLooper.getLooper(), mock(AppOpsManager.class), mock(PermissionEnforcer.class),
- mock(AudioServerPermissionProvider.class)) {
+ mock(AudioServerPermissionProvider.class), r -> r.run()) {
@Override
public int getDeviceForStream(int stream) {
return AudioSystem.DEVICE_OUT_SPEAKER;
diff --git a/services/tests/servicestests/src/com/android/server/audio/AudioServiceTest.java b/services/tests/servicestests/src/com/android/server/audio/AudioServiceTest.java
index 037c3c0..b7100ea 100644
--- a/services/tests/servicestests/src/com/android/server/audio/AudioServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/AudioServiceTest.java
@@ -87,7 +87,7 @@
.thenReturn(AppOpsManager.MODE_ALLOWED);
mAudioService = new AudioService(mContext, mSpyAudioSystem, mSpySystemServer,
mSettingsAdapter, mAudioVolumeGroupHelper, mMockAudioPolicy, null,
- mMockAppOpsManager, mMockPermissionEnforcer, mMockPermissionProvider);
+ mMockAppOpsManager, mMockPermissionEnforcer, mMockPermissionProvider, r -> r.run());
}
/**
diff --git a/services/tests/servicestests/src/com/android/server/audio/DeviceVolumeBehaviorTest.java b/services/tests/servicestests/src/com/android/server/audio/DeviceVolumeBehaviorTest.java
index 27b552f..746645a 100644
--- a/services/tests/servicestests/src/com/android/server/audio/DeviceVolumeBehaviorTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/DeviceVolumeBehaviorTest.java
@@ -78,7 +78,7 @@
mAudioService = new AudioService(mContext, mAudioSystem, mSystemServer,
mSettingsAdapter, mAudioVolumeGroupHelper, mAudioPolicyMock,
mTestLooper.getLooper(), mock(AppOpsManager.class), mock(PermissionEnforcer.class),
- mock(AudioServerPermissionProvider.class));
+ mock(AudioServerPermissionProvider.class), r -> r.run());
mTestLooper.dispatchAll();
}
diff --git a/services/tests/servicestests/src/com/android/server/audio/VolumeHelperTest.java b/services/tests/servicestests/src/com/android/server/audio/VolumeHelperTest.java
index 8e34ee1..e45ab31 100644
--- a/services/tests/servicestests/src/com/android/server/audio/VolumeHelperTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/VolumeHelperTest.java
@@ -160,7 +160,7 @@
@NonNull PermissionEnforcer enforcer,
AudioServerPermissionProvider permissionProvider) {
super(context, audioSystem, systemServer, settings, audioVolumeGroupHelper,
- audioPolicy, looper, appOps, enforcer, permissionProvider);
+ audioPolicy, looper, appOps, enforcer, permissionProvider, r -> r.run());
}
public void setDeviceForStream(int stream, int device) {
diff --git a/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubEventLoggerTest.java b/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubEventLoggerTest.java
index 8863d27..41cb6fd 100644
--- a/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubEventLoggerTest.java
+++ b/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubEventLoggerTest.java
@@ -18,11 +18,15 @@
import static com.google.common.truth.Truth.assertThat;
+import android.chre.flags.Flags;
import android.hardware.location.NanoAppMessage;
+import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
import androidx.test.ext.junit.runners.AndroidJUnit4;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -33,6 +37,8 @@
public class ContextHubEventLoggerTest {
private static final ContextHubEventLogger sInstance = ContextHubEventLogger.getInstance();
+ @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
@Test
public void testLogNanoappLoad() {
ContextHubEventLogger.NanoappLoadEvent[] events =
@@ -46,10 +52,10 @@
sInstance.clear();
sInstance.logNanoappLoad(-1, 42, -34, 100, false);
sInstance.logNanoappLoad(0, 123, 321, 001, true);
- String sInstanceDump = sInstance.toString();
+ String instanceDump = sInstance.toString();
for (String eventString: eventStrings) {
assertThat(eventString.length() > 0).isTrue();
- assertThat(sInstanceDump.contains(eventString)).isTrue();
+ assertThat(instanceDump.contains(eventString)).isTrue();
}
}
@@ -66,10 +72,10 @@
sInstance.clear();
sInstance.logNanoappUnload(-1, 47, false);
sInstance.logNanoappUnload(1, 0xFFFFFFFF, true);
- String sInstanceDump = sInstance.toString();
+ String instanceDump = sInstance.toString();
for (String eventString: eventStrings) {
assertThat(eventString.length() > 0).isTrue();
- assertThat(sInstanceDump.contains(eventString)).isTrue();
+ assertThat(instanceDump.contains(eventString)).isTrue();
}
}
@@ -90,10 +96,10 @@
sInstance.clear();
sInstance.logMessageFromNanoapp(-123, message1, false);
sInstance.logMessageFromNanoapp(321, message2, true);
- String sInstanceDump = sInstance.toString();
+ String instanceDump = sInstance.toString();
for (String eventString: eventStrings) {
assertThat(eventString.length() > 0).isTrue();
- assertThat(sInstanceDump.contains(eventString)).isTrue();
+ assertThat(instanceDump.contains(eventString)).isTrue();
}
}
@@ -114,10 +120,54 @@
sInstance.clear();
sInstance.logMessageToNanoapp(888, message1, true);
sInstance.logMessageToNanoapp(999, message2, false);
- String sInstanceDump = sInstance.toString();
+ String instanceDump = sInstance.toString();
for (String eventString: eventStrings) {
assertThat(eventString.length() > 0).isTrue();
- assertThat(sInstanceDump.contains(eventString)).isTrue();
+ assertThat(instanceDump.contains(eventString)).isTrue();
+ }
+ }
+
+ @Test
+ @EnableFlags({Flags.FLAG_RELIABLE_MESSAGE,
+ Flags.FLAG_RELIABLE_MESSAGE_IMPLEMENTATION})
+ public void testLogReliableMessageToNanoappStatus() {
+ NanoAppMessage message1 = NanoAppMessage.createMessageToNanoApp(1, 0,
+ new byte[] {0x00, 0x11, 0x22, 0x33});
+ NanoAppMessage message2 = NanoAppMessage.createMessageToNanoApp(0, 1,
+ new byte[] {(byte) 0xDE, (byte) 0xAD, (byte) 0xBE, (byte) 0xEF});
+ message1.setIsReliable(true);
+ message2.setIsReliable(true);
+ message1.setMessageSequenceNumber(0);
+ message2.setMessageSequenceNumber(1);
+
+ ContextHubEventLogger.NanoappMessageEvent[] events =
+ new ContextHubEventLogger.NanoappMessageEvent[] {
+ new ContextHubEventLogger.NanoappMessageEvent(23, 888, message1, true),
+ new ContextHubEventLogger.NanoappMessageEvent(34, 999, message2, false)
+ };
+ String[] eventStrings = generateEventDumpStrings(events);
+
+ // log events and test sInstance.toString() contains event details
+ sInstance.clear();
+ sInstance.logMessageToNanoapp(888, message1, true);
+ sInstance.logMessageToNanoapp(999, message2, false);
+ String instanceDump = sInstance.toString();
+ for (String eventString: eventStrings) {
+ assertThat(eventString.length() > 0).isTrue();
+ assertThat(instanceDump.contains(eventString)).isTrue();
+ }
+
+ // set the error codes for the events and verify
+ sInstance.logReliableMessageToNanoappStatus(0, (byte) 0x02);
+ sInstance.logReliableMessageToNanoappStatus(1, (byte) 0x03);
+ events[0].setErrorCode((byte) 0x02);
+ events[1].setErrorCode((byte) 0x03);
+ eventStrings = generateEventDumpStrings(events);
+
+ instanceDump = sInstance.toString();
+ for (String eventString: eventStrings) {
+ assertThat(eventString.length() > 0).isTrue();
+ assertThat(instanceDump.contains(eventString)).isTrue();
}
}
@@ -134,10 +184,10 @@
sInstance.clear();
sInstance.logContextHubRestart(1);
sInstance.logContextHubRestart(2);
- String sInstanceDump = sInstance.toString();
+ String instanceDump = sInstance.toString();
for (String eventString: eventStrings) {
assertThat(eventString.length() > 0).isTrue();
- assertThat(sInstanceDump.contains(eventString)).isTrue();
+ assertThat(instanceDump.contains(eventString)).isTrue();
}
}
diff --git a/services/tests/wmtests/AndroidManifest.xml b/services/tests/wmtests/AndroidManifest.xml
index 777f618..6e6b70d 100644
--- a/services/tests/wmtests/AndroidManifest.xml
+++ b/services/tests/wmtests/AndroidManifest.xml
@@ -52,6 +52,7 @@
<uses-permission android:name="android.permission.OBSERVE_ROLE_HOLDERS"/>
<uses-permission android:name="android.permission.MANAGE_DEFAULT_APPLICATIONS"/>
<uses-permission android:name="android.permission.DUMP"/>
+ <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
<!-- TODO: Remove largeHeap hack when memory leak is fixed (b/123984854) -->
<application android:debuggable="true"
diff --git a/services/tests/wmtests/res/xml/bookmarks.xml b/services/tests/wmtests/res/xml/bookmarks.xml
new file mode 100644
index 0000000..88419e9
--- /dev/null
+++ b/services/tests/wmtests/res/xml/bookmarks.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<bookmarks>
+ <bookmark
+ role="android.app.role.BROWSER"
+ shortcut="b" />
+ <bookmark
+ category="android.intent.category.APP_CONTACTS"
+ shortcut="c" />
+ <bookmark
+ category="android.intent.category.APP_EMAIL"
+ shortcut="e" />
+ <bookmark
+ category="android.intent.category.APP_CALENDAR"
+ shortcut="k" />
+ <bookmark
+ category="android.intent.category.APP_MAPS"
+ shortcut="m" />
+ <bookmark
+ category="android.intent.category.APP_MUSIC"
+ shortcut="p" />
+ <bookmark
+ role="android.app.role.SMS"
+ shortcut="s" />
+ <bookmark
+ category="android.intent.category.APP_CALCULATOR"
+ shortcut="u" />
+</bookmarks>
diff --git a/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutManagerTests.java b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutManagerTests.java
new file mode 100644
index 0000000..8c375d4
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutManagerTests.java
@@ -0,0 +1,167 @@
+/*
+ * 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.policy;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.anyObject;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.res.Resources;
+import android.content.res.XmlResourceParser;
+import android.os.Handler;
+import android.os.Looper;
+import android.view.KeyEvent;
+import android.view.KeyboardShortcutGroup;
+import android.view.KeyboardShortcutInfo;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.R;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.Collections;
+
+/**
+ * Test class for {@link ModifierShortcutManager}.
+ *
+ * Build/Install/Run:
+ * atest ModifierShortcutManagerTests
+ */
+
+@SmallTest
+public class ModifierShortcutManagerTests {
+ private ModifierShortcutManager mModifierShortcutManager;
+ private Handler mHandler;
+ private Context mContext;
+ private Resources mResources;
+
+ @Before
+ public void setUp() {
+ mHandler = new Handler(Looper.getMainLooper());
+ mContext = spy(getInstrumentation().getTargetContext());
+ mResources = spy(mContext.getResources());
+
+ XmlResourceParser testBookmarks = mResources.getXml(
+ com.android.frameworks.wmtests.R.xml.bookmarks);
+
+ when(mContext.getResources()).thenReturn(mResources);
+ when(mResources.getXml(R.xml.bookmarks)).thenReturn(testBookmarks);
+
+ mModifierShortcutManager = new ModifierShortcutManager(mContext, mHandler);
+ }
+
+ @Test
+ public void test_getApplicationLaunchKeyboardShortcuts() {
+ KeyboardShortcutGroup group =
+ mModifierShortcutManager.getApplicationLaunchKeyboardShortcuts(-1);
+ assertEquals(8, group.getItems().size());
+ }
+
+ @Test
+ public void test_shortcutInfoFromIntent_appIntent() {
+ Intent mockIntent = mock(Intent.class);
+ ActivityInfo mockActivityInfo = mock(ActivityInfo.class);
+ when(mockActivityInfo.loadLabel(anyObject())).thenReturn("label");
+ mockActivityInfo.packageName = "android";
+ when(mockActivityInfo.getIconResource()).thenReturn(R.drawable.sym_def_app_icon);
+ when(mockIntent.resolveActivityInfo(anyObject(), anyInt())).thenReturn(mockActivityInfo);
+
+ KeyboardShortcutInfo info = mModifierShortcutManager.shortcutInfoFromIntent(
+ 'a', mockIntent, true);
+
+ assertEquals("label", info.getLabel().toString());
+ assertEquals('a', info.getBaseCharacter());
+ assertEquals(R.drawable.sym_def_app_icon, info.getIcon().getResId());
+ assertEquals(KeyEvent.META_META_ON | KeyEvent.META_SHIFT_ON, info.getModifiers());
+
+ }
+
+ @Test
+ public void test_shortcutInfoFromIntent_resolverIntent() {
+ Intent mockIntent = mock(Intent.class);
+ Intent mockSelector = mock(Intent.class);
+ ActivityInfo mockActivityInfo = mock(ActivityInfo.class);
+ mockActivityInfo.name = com.android.internal.app.ResolverActivity.class.getName();
+ when(mockIntent.resolveActivityInfo(anyObject(), anyInt())).thenReturn(mockActivityInfo);
+ when(mockIntent.getSelector()).thenReturn(mockSelector);
+ when(mockSelector.getCategories()).thenReturn(
+ Collections.singleton(Intent.CATEGORY_APP_BROWSER));
+
+ KeyboardShortcutInfo info = mModifierShortcutManager.shortcutInfoFromIntent(
+ 'a', mockIntent, false);
+
+ assertEquals(mContext.getString(R.string.keyboard_shortcut_group_applications_browser),
+ info.getLabel().toString());
+ assertEquals('a', info.getBaseCharacter());
+ assertEquals(R.drawable.sym_def_app_icon, info.getIcon().getResId());
+ assertEquals(KeyEvent.META_META_ON, info.getModifiers());
+
+ // validate that an unknown category that we can't present a label to the user for
+ // returns null shortcut info.
+ when(mockSelector.getCategories()).thenReturn(
+ Collections.singleton("not_a_category"));
+ assertEquals(null, mModifierShortcutManager.shortcutInfoFromIntent(
+ 'a', mockIntent, false));
+ }
+
+ @Test
+ public void test_getIntentCategoryLabel() {
+ assertEquals(
+ mContext.getString(R.string.keyboard_shortcut_group_applications_browser),
+ ModifierShortcutManager.getIntentCategoryLabel(
+ mContext, Intent.CATEGORY_APP_BROWSER));
+ assertEquals(
+ mContext.getString(R.string.keyboard_shortcut_group_applications_contacts),
+ ModifierShortcutManager.getIntentCategoryLabel(
+ mContext, Intent.CATEGORY_APP_CONTACTS));
+ assertEquals(
+ mContext.getString(R.string.keyboard_shortcut_group_applications_email),
+ ModifierShortcutManager.getIntentCategoryLabel(
+ mContext, Intent.CATEGORY_APP_EMAIL));
+ assertEquals(
+ mContext.getString(R.string.keyboard_shortcut_group_applications_calendar),
+ ModifierShortcutManager.getIntentCategoryLabel(
+ mContext, Intent.CATEGORY_APP_CALENDAR));
+ assertEquals(
+ mContext.getString(R.string.keyboard_shortcut_group_applications_maps),
+ ModifierShortcutManager.getIntentCategoryLabel(
+ mContext, Intent.CATEGORY_APP_MAPS));
+ assertEquals(
+ mContext.getString(R.string.keyboard_shortcut_group_applications_music),
+ ModifierShortcutManager.getIntentCategoryLabel(
+ mContext, Intent.CATEGORY_APP_MUSIC));
+ assertEquals(
+ mContext.getString(R.string.keyboard_shortcut_group_applications_sms),
+ ModifierShortcutManager.getIntentCategoryLabel(
+ mContext, Intent.CATEGORY_APP_MESSAGING));
+ assertEquals(
+ mContext.getString(R.string.keyboard_shortcut_group_applications_calculator),
+ ModifierShortcutManager.getIntentCategoryLabel(
+ mContext, Intent.CATEGORY_APP_CALCULATOR));
+ assertEquals(null, ModifierShortcutManager.getIntentCategoryLabel(mContext, "foo"));
+ }
+}
diff --git a/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java
index 0ed02dd..59b08a5 100644
--- a/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java
@@ -65,7 +65,8 @@
private static final SparseArray<String> INTENT_SHORTCUTS = new SparseArray<>();
private static final SparseArray<String> ROLE_SHORTCUTS = new SparseArray<>();
static {
- // These shortcuts should align with those defined in bookmarks.xml
+ // These shortcuts should align with those defined in
+ // services/tests/wmtests/res/xml/bookmarks.xml
INTENT_SHORTCUTS.append(KEYCODE_U, Intent.CATEGORY_APP_CALCULATOR);
INTENT_SHORTCUTS.append(KEYCODE_C, Intent.CATEGORY_APP_CONTACTS);
INTENT_SHORTCUTS.append(KEYCODE_E, Intent.CATEGORY_APP_EMAIL);
diff --git a/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java b/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java
index dff4984..f5c8fb8 100644
--- a/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java
+++ b/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java
@@ -49,6 +49,7 @@
import android.content.Context;
import android.content.res.Resources;
+import android.content.res.XmlResourceParser;
import android.platform.test.flag.junit.SetFlagsRule;
import android.util.ArrayMap;
import android.view.InputDevice;
@@ -102,6 +103,9 @@
doReturn(mResources).when(mContext).getResources();
doReturn(mSettingsProviderRule.mockContentResolver(mContext))
.when(mContext).getContentResolver();
+ XmlResourceParser testBookmarks = mResources.getXml(
+ com.android.frameworks.wmtests.R.xml.bookmarks);
+ doReturn(testBookmarks).when(mResources).getXml(com.android.internal.R.xml.bookmarks);
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
index c1be5ca..63e3e5c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
@@ -714,7 +714,7 @@
// Simulate when the window is exiting and cleanupAnimation invoked
// (e.g. screen off during RecentsAnimation animating), will expect the window receives
// onExitAnimationDone to destroy the surface when the removal is allowed.
- win1.mWinAnimator.mSurfaceController = mock(WindowSurfaceController.class);
+ win1.mWinAnimator.mSurfaceControl = mock(SurfaceControl.class);
win1.mHasSurface = true;
win1.mAnimatingExit = true;
win1.mRemoveOnExit = true;
diff --git a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
index 11d9629..0bf27d1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
@@ -43,8 +43,6 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
@@ -758,12 +756,9 @@
// Simulating win1 has shown IME and being IME layering/input target
mDisplayContent.setImeLayeringTarget(win1);
mDisplayContent.setImeInputTarget(win1);
- mImeWindow.mWinAnimator.mSurfaceController = mock(WindowSurfaceController.class);
mImeWindow.mWinAnimator.hide(mDisplayContent.getPendingTransaction(), "test");
spyOn(mDisplayContent);
- doReturn(true).when(mImeWindow.mWinAnimator.mSurfaceController).hasSurface();
- doReturn(true).when(mImeWindow.mWinAnimator.mSurfaceController)
- .prepareToShowInTransaction(any(), anyFloat());
+ mImeWindow.mWinAnimator.mSurfaceControl = mock(SurfaceControl.class);
makeWindowVisibleAndDrawn(mImeWindow);
assertTrue(mImeWindow.isOnScreen());
assertFalse(mImeWindow.isParentWindowHidden());
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
index 00a8842..38ad9a7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
@@ -27,6 +27,7 @@
import android.os.PowerManager.WakeReason;
import android.util.proto.ProtoOutputStream;
import android.view.KeyEvent;
+import android.view.KeyboardShortcutGroup;
import android.view.WindowManager;
import android.view.animation.Animation;
@@ -35,6 +36,7 @@
import com.android.server.policy.WindowManagerPolicy;
import java.io.PrintWriter;
+import java.util.Collections;
class TestWindowManagerPolicy implements WindowManagerPolicy {
@@ -362,4 +364,9 @@
public boolean isGlobalKey(int keyCode) {
return false;
}
+
+ @Override
+ public KeyboardShortcutGroup getApplicationLaunchKeyboardShortcuts(int deviceId) {
+ return new KeyboardShortcutGroup("", Collections.emptyList());
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
index 9b48cb9..c65b76e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
@@ -31,7 +31,6 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
import static com.android.window.flags.Flags.multiCrop;
import static com.google.common.truth.Truth.assertThat;
@@ -551,10 +550,9 @@
}
private static void makeWallpaperWindowShown(WindowState w) {
- final WindowSurfaceController windowSurfaceController = mock(WindowSurfaceController.class);
- w.mWinAnimator.mSurfaceController = windowSurfaceController;
w.mWinAnimator.mLastAlpha = 1;
- when(windowSurfaceController.getShown()).thenReturn(true);
+ spyOn(w.mWinAnimator);
+ doReturn(true).when(w.mWinAnimator).getShown();
}
private WindowState createWallpaperWindow(DisplayContent dc, int width, int height) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
index 4958b90..89abe2f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -253,22 +253,18 @@
final Session session = createTestSession(mAtm, wpc.getPid(), wpc.mUid);
spyOn(session);
assertTrue(session.mCanAddInternalSystemWindow);
- final WindowSurfaceController winSurface = mock(WindowSurfaceController.class);
- session.onWindowSurfaceVisibilityChanged(winSurface, true /* visible */,
- LayoutParams.TYPE_PHONE);
+ final WindowState window = createWindow(null, LayoutParams.TYPE_PHONE, "win");
+ session.onWindowSurfaceVisibilityChanged(window, true /* visible */);
verify(session).setHasOverlayUi(true);
- session.onWindowSurfaceVisibilityChanged(winSurface, false /* visible */,
- LayoutParams.TYPE_PHONE);
+ session.onWindowSurfaceVisibilityChanged(window, false /* visible */);
verify(session).setHasOverlayUi(false);
}
@Test
public void testRelayoutExitingWindow() {
final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, "appWin");
- final WindowSurfaceController surfaceController = mock(WindowSurfaceController.class);
- win.mWinAnimator.mSurfaceController = surfaceController;
win.mWinAnimator.mDrawState = WindowStateAnimator.HAS_DRAWN;
- doReturn(true).when(surfaceController).hasSurface();
+ win.mWinAnimator.mSurfaceControl = mock(SurfaceControl.class);
spyOn(win.mTransitionController);
doReturn(true).when(win.mTransitionController).isShellTransitionsEnabled();
doReturn(true).when(win.mTransitionController).inTransition(
@@ -306,7 +302,7 @@
// and WMS#tryStartExitingAnimation() will destroy the surface directly.
assertFalse(win.mAnimatingExit);
assertFalse(win.mHasSurface);
- assertNull(win.mWinAnimator.mSurfaceController);
+ assertNull(win.mWinAnimator.mSurfaceControl);
// Invisible requested activity should not get the last config even if its view is visible.
mWm.relayoutWindow(win.mSession, win.mClient, win.mAttrs, w, h, View.VISIBLE, 0, 0, 0,
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTracingTest.java b/services/tests/wmtests/src/com/android/server/wm/WindowTracingLegacyTest.java
similarity index 97%
rename from services/tests/wmtests/src/com/android/server/wm/WindowTracingTest.java
rename to services/tests/wmtests/src/com/android/server/wm/WindowTracingLegacyTest.java
index c183403..48a8d55 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTracingTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTracingLegacyTest.java
@@ -63,7 +63,7 @@
*/
@SmallTest
@Presubmit
-public class WindowTracingTest {
+public class WindowTracingLegacyTest {
private static final byte[] MAGIC_HEADER = new byte[]{
0x9, 0x57, 0x49, 0x4e, 0x54, 0x52, 0x41, 0x43, 0x45,
@@ -88,7 +88,7 @@
mFile = testContext.getFileStreamPath("tracing_test.dat");
mFile.delete();
- mWindowTracing = new WindowTracing(mFile, mWmMock, mChoreographer,
+ mWindowTracing = new WindowTracingLegacy(mFile, mWmMock, mChoreographer,
new WindowManagerGlobalLock(), 1024);
}
diff --git a/tests/Input/src/com/android/test/input/PointerIconLoadingTest.kt b/tests/Input/src/com/android/test/input/PointerIconLoadingTest.kt
index d0148fb..abfe549 100644
--- a/tests/Input/src/com/android/test/input/PointerIconLoadingTest.kt
+++ b/tests/Input/src/com/android/test/input/PointerIconLoadingTest.kt
@@ -136,11 +136,20 @@
assumeTrue(enableVectorCursors())
assumeTrue(enableVectorCursorA11ySettings())
+ val theme: Resources.Theme = context.getResources().newTheme()
+ theme.setTo(context.getTheme())
+ theme.applyStyle(
+ PointerIcon.vectorFillStyleToResource(PointerIcon.POINTER_ICON_VECTOR_STYLE_FILL_BLACK),
+ /* force= */ true)
+ theme.applyStyle(
+ PointerIcon.vectorStrokeStyleToResource(
+ PointerIcon.POINTER_ICON_VECTOR_STYLE_STROKE_WHITE),
+ /* force= */ true)
val pointerScale = 2f
val pointerIcon =
PointerIcon.getLoadedSystemIcon(
- context,
+ ContextThemeWrapper(context, theme),
PointerIcon.TYPE_ARROW,
/* useLargeIcons= */ false,
pointerScale)
diff --git a/tests/Internal/src/com/android/internal/protolog/LegacyProtoLogViewerConfigReaderTest.java b/tests/Internal/src/com/android/internal/protolog/LegacyProtoLogViewerConfigReaderTest.java
new file mode 100644
index 0000000..2539653
--- /dev/null
+++ b/tests/Internal/src/com/android/internal/protolog/LegacyProtoLogViewerConfigReaderTest.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2019 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.internal.protolog;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.util.zip.GZIPOutputStream;
+
+@SmallTest
+@Presubmit
+@RunWith(JUnit4.class)
+public class LegacyProtoLogViewerConfigReaderTest {
+ private static final String TEST_VIEWER_CONFIG = "{\n"
+ + " \"version\": \"1.0.0\",\n"
+ + " \"messages\": {\n"
+ + " \"70933285\": {\n"
+ + " \"message\": \"Test completed successfully: %b\",\n"
+ + " \"level\": \"ERROR\",\n"
+ + " \"group\": \"GENERIC_WM\"\n"
+ + " },\n"
+ + " \"1792430067\": {\n"
+ + " \"message\": \"Attempted to add window to a display that does not exist: %d."
+ + " Aborting.\",\n"
+ + " \"level\": \"WARN\",\n"
+ + " \"group\": \"GENERIC_WM\"\n"
+ + " },\n"
+ + " \"1352021864\": {\n"
+ + " \"message\": \"Test 2\",\n"
+ + " \"level\": \"WARN\",\n"
+ + " \"group\": \"GENERIC_WM\"\n"
+ + " },\n"
+ + " \"409412266\": {\n"
+ + " \"message\": \"Window %s is already added\",\n"
+ + " \"level\": \"WARN\",\n"
+ + " \"group\": \"GENERIC_WM\"\n"
+ + " }\n"
+ + " },\n"
+ + " \"groups\": {\n"
+ + " \"GENERIC_WM\": {\n"
+ + " \"tag\": \"WindowManager\"\n"
+ + " }\n"
+ + " }\n"
+ + "}\n";
+
+
+ private LegacyProtoLogViewerConfigReader
+ mConfig = new LegacyProtoLogViewerConfigReader();
+ private File mTestViewerConfig;
+
+ @Before
+ public void setUp() throws IOException {
+ mTestViewerConfig = File.createTempFile("testConfig", ".json.gz");
+ OutputStreamWriter writer = new OutputStreamWriter(
+ new GZIPOutputStream(new FileOutputStream(mTestViewerConfig)));
+ writer.write(TEST_VIEWER_CONFIG);
+ writer.close();
+ }
+
+ @After
+ public void tearDown() {
+ //noinspection ResultOfMethodCallIgnored
+ mTestViewerConfig.delete();
+ }
+
+ @Test
+ public void getViewerString_notLoaded() {
+ assertNull(mConfig.getViewerString(1));
+ }
+
+ @Test
+ public void loadViewerConfig() {
+ mConfig.loadViewerConfig(msg -> {}, mTestViewerConfig.getAbsolutePath());
+ assertEquals("Test completed successfully: %b", mConfig.getViewerString(70933285));
+ assertEquals("Test 2", mConfig.getViewerString(1352021864));
+ assertEquals("Window %s is already added", mConfig.getViewerString(409412266));
+ assertNull(mConfig.getViewerString(1));
+ }
+
+ @Test
+ public void loadViewerConfig_invalidFile() {
+ mConfig.loadViewerConfig(msg -> {}, "/tmp/unknown/file/does/not/exist");
+ // No exception is thrown.
+ assertNull(mConfig.getViewerString(1));
+ }
+}
diff --git a/tests/Internal/src/com/android/internal/protolog/ProtoLogViewerConfigReaderTest.java b/tests/Internal/src/com/android/internal/protolog/ProtoLogViewerConfigReaderTest.java
index dbd85d3..be0e8bc 100644
--- a/tests/Internal/src/com/android/internal/protolog/ProtoLogViewerConfigReaderTest.java
+++ b/tests/Internal/src/com/android/internal/protolog/ProtoLogViewerConfigReaderTest.java
@@ -20,75 +20,77 @@
import static org.junit.Assert.assertNull;
import android.platform.test.annotations.Presubmit;
+import android.util.proto.ProtoInputStream;
-import androidx.test.filters.SmallTest;
-
-import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.OutputStreamWriter;
-import java.util.zip.GZIPOutputStream;
+import perfetto.protos.Protolog;
+import perfetto.protos.ProtologCommon;
-@SmallTest
@Presubmit
@RunWith(JUnit4.class)
public class ProtoLogViewerConfigReaderTest {
- private static final String TEST_VIEWER_CONFIG = "{\n"
- + " \"version\": \"1.0.0\",\n"
- + " \"messages\": {\n"
- + " \"70933285\": {\n"
- + " \"message\": \"Test completed successfully: %b\",\n"
- + " \"level\": \"ERROR\",\n"
- + " \"group\": \"GENERIC_WM\"\n"
- + " },\n"
- + " \"1792430067\": {\n"
- + " \"message\": \"Attempted to add window to a display that does not exist: %d."
- + " Aborting.\",\n"
- + " \"level\": \"WARN\",\n"
- + " \"group\": \"GENERIC_WM\"\n"
- + " },\n"
- + " \"1352021864\": {\n"
- + " \"message\": \"Test 2\",\n"
- + " \"level\": \"WARN\",\n"
- + " \"group\": \"GENERIC_WM\"\n"
- + " },\n"
- + " \"409412266\": {\n"
- + " \"message\": \"Window %s is already added\",\n"
- + " \"level\": \"WARN\",\n"
- + " \"group\": \"GENERIC_WM\"\n"
- + " }\n"
- + " },\n"
- + " \"groups\": {\n"
- + " \"GENERIC_WM\": {\n"
- + " \"tag\": \"WindowManager\"\n"
- + " }\n"
- + " }\n"
- + "}\n";
+ private static final String TEST_GROUP_NAME = "MY_TEST_GROUP";
+ private static final String TEST_GROUP_TAG = "TEST";
+ private static final String OTHER_TEST_GROUP_NAME = "MY_OTHER_TEST_GROUP";
+ private static final String OTHER_TEST_GROUP_TAG = "OTHER_TEST";
- private LegacyProtoLogViewerConfigReader
- mConfig = new LegacyProtoLogViewerConfigReader();
- private File mTestViewerConfig;
+ private static final byte[] TEST_VIEWER_CONFIG =
+ perfetto.protos.Protolog.ProtoLogViewerConfig.newBuilder()
+ .addGroups(
+ perfetto.protos.Protolog.ProtoLogViewerConfig.Group.newBuilder()
+ .setId(1)
+ .setName(TEST_GROUP_NAME)
+ .setTag(TEST_GROUP_TAG)
+ ).addGroups(
+ perfetto.protos.Protolog.ProtoLogViewerConfig.Group.newBuilder()
+ .setId(1)
+ .setName(OTHER_TEST_GROUP_NAME)
+ .setTag(OTHER_TEST_GROUP_TAG)
+ ).addMessages(
+ perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.newBuilder()
+ .setMessageId(1)
+ .setMessage("My Test Log Message 1 %b")
+ .setLevel(ProtologCommon.ProtoLogLevel.PROTOLOG_LEVEL_DEBUG)
+ .setGroupId(1)
+ ).addMessages(
+ perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.newBuilder()
+ .setMessageId(2)
+ .setMessage("My Test Log Message 2 %b")
+ .setLevel(ProtologCommon.ProtoLogLevel.PROTOLOG_LEVEL_VERBOSE)
+ .setGroupId(1)
+ ).addMessages(
+ perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.newBuilder()
+ .setMessageId(3)
+ .setMessage("My Test Log Message 3 %b")
+ .setLevel(ProtologCommon.ProtoLogLevel.PROTOLOG_LEVEL_WARN)
+ .setGroupId(1)
+ ).addMessages(
+ perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.newBuilder()
+ .setMessageId(4)
+ .setMessage("My Test Log Message 4 %b")
+ .setLevel(ProtologCommon.ProtoLogLevel.PROTOLOG_LEVEL_ERROR)
+ .setGroupId(2)
+ ).addMessages(
+ perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.newBuilder()
+ .setMessageId(5)
+ .setMessage("My Test Log Message 5 %b")
+ .setLevel(ProtologCommon.ProtoLogLevel.PROTOLOG_LEVEL_WTF)
+ .setGroupId(2)
+ ).build().toByteArray();
+
+ private final ViewerConfigInputStreamProvider mViewerConfigInputStreamProvider =
+ () -> new ProtoInputStream(TEST_VIEWER_CONFIG);
+
+ private ProtoLogViewerConfigReader mConfig;
@Before
- public void setUp() throws IOException {
- mTestViewerConfig = File.createTempFile("testConfig", ".json.gz");
- OutputStreamWriter writer = new OutputStreamWriter(
- new GZIPOutputStream(new FileOutputStream(mTestViewerConfig)));
- writer.write(TEST_VIEWER_CONFIG);
- writer.close();
- }
-
- @After
- public void tearDown() {
- //noinspection ResultOfMethodCallIgnored
- mTestViewerConfig.delete();
+ public void before() {
+ mConfig = new ProtoLogViewerConfigReader(mViewerConfigInputStreamProvider);
}
@Test
@@ -98,17 +100,26 @@
@Test
public void loadViewerConfig() {
- mConfig.loadViewerConfig(msg -> {}, mTestViewerConfig.getAbsolutePath());
- assertEquals("Test completed successfully: %b", mConfig.getViewerString(70933285));
- assertEquals("Test 2", mConfig.getViewerString(1352021864));
- assertEquals("Window %s is already added", mConfig.getViewerString(409412266));
- assertNull(mConfig.getViewerString(1));
+ mConfig.loadViewerConfig(new String[] { TEST_GROUP_NAME });
+ assertEquals("My Test Log Message 1 %b", mConfig.getViewerString(1));
+ assertEquals("My Test Log Message 2 %b", mConfig.getViewerString(2));
+ assertEquals("My Test Log Message 3 %b", mConfig.getViewerString(3));
+ assertNull(mConfig.getViewerString(4));
+ assertNull(mConfig.getViewerString(5));
}
@Test
- public void loadViewerConfig_invalidFile() {
- mConfig.loadViewerConfig(msg -> {}, "/tmp/unknown/file/does/not/exist");
- // No exception is thrown.
+ public void unloadViewerConfig() {
+ mConfig.loadViewerConfig(new String[] { TEST_GROUP_NAME, OTHER_TEST_GROUP_NAME });
+ mConfig.unloadViewerConfig(new String[] { TEST_GROUP_NAME });
assertNull(mConfig.getViewerString(1));
+ assertNull(mConfig.getViewerString(2));
+ assertNull(mConfig.getViewerString(3));
+ assertEquals("My Test Log Message 4 %b", mConfig.getViewerString(4));
+ assertEquals("My Test Log Message 5 %b", mConfig.getViewerString(5));
+
+ mConfig.unloadViewerConfig(new String[] { OTHER_TEST_GROUP_NAME });
+ assertNull(mConfig.getViewerString(4));
+ assertNull(mConfig.getViewerString(5));
}
}
diff --git a/tests/TrustTests/src/android/trust/test/UnlockAttemptTest.kt b/tests/TrustTests/src/android/trust/test/UnlockAttemptTest.kt
index 2c9361d..f9e004b 100644
--- a/tests/TrustTests/src/android/trust/test/UnlockAttemptTest.kt
+++ b/tests/TrustTests/src/android/trust/test/UnlockAttemptTest.kt
@@ -17,6 +17,7 @@
import android.app.trust.TrustManager
import android.content.Context
+import android.security.Flags.shouldTrustManagerListenForPrimaryAuth
import android.trust.BaseTrustAgentService
import android.trust.TrustTestActivity
import android.trust.test.lib.LockStateTrackingRule
@@ -154,13 +155,17 @@
private fun triggerSuccessfulUnlock() {
screenLockRule.successfulScreenLockAttempt()
- trustAgentRule.reportSuccessfulUnlock()
+ if (!shouldTrustManagerListenForPrimaryAuth()) {
+ trustAgentRule.reportSuccessfulUnlock()
+ }
await()
}
private fun triggerFailedUnlock() {
screenLockRule.failedScreenLockAttempt()
- trustAgentRule.reportFailedUnlock()
+ if (!shouldTrustManagerListenForPrimaryAuth()) {
+ trustAgentRule.reportFailedUnlock()
+ }
await()
}
diff --git a/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/ConcurrentMultiUserTest.java b/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/ConcurrentMultiUserTest.java
index f385179..5f9a710 100644
--- a/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/ConcurrentMultiUserTest.java
+++ b/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/ConcurrentMultiUserTest.java
@@ -54,6 +54,7 @@
import org.junit.After;
import org.junit.Before;
import org.junit.ClassRule;
+import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -110,6 +111,7 @@
}
@Test
+ @Ignore("b/352823913")
public void passengerShowImeNotAffectDriver() throws Exception {
assertDriverImeHidden();
assertPassengerImeHidden();
diff --git a/tools/aapt2/link/ManifestFixer.cpp b/tools/aapt2/link/ManifestFixer.cpp
index cbf2c2f..382b088 100644
--- a/tools/aapt2/link/ManifestFixer.cpp
+++ b/tools/aapt2/link/ManifestFixer.cpp
@@ -198,6 +198,7 @@
android::DiagMessage(el->line_number)
<< "Cannot find symbol for android:configChanges with min sdk: "
<< context->GetMinSdkVersion());
+ return false;
}
std::stringstream new_value;
diff --git a/tools/lint/fix/README.md b/tools/lint/fix/README.md
index a5ac2be..18bda92 100644
--- a/tools/lint/fix/README.md
+++ b/tools/lint/fix/README.md
@@ -6,7 +6,7 @@
It's a python script that runs the framework linter,
and then (optionally) copies modified files back into the source tree.\
-Why python, you ask? Because python is cool ¯\_(ツ)_/¯.
+Why python, you ask? Because python is cool ¯\\\_(ツ)\_/¯.
Incidentally, this exposes a much simpler way to run individual lint checks
against individual modules, so it's useful beyond applying fixes.
@@ -15,7 +15,7 @@
Lint is not allowed to modify source files directly via lint's `--apply-suggestions` flag.
As a compromise, soong zips up the (potentially) modified sources and leaves them in an intermediate
-directory. This script runs the lint, unpacks those files, and copies them back into the tree.
+directory. This script runs the lint, unpacks those files, and copies them back into the tree.
## How do I run it?
**WARNING: You probably want to commit/stash any changes to your working tree before doing this...**