diff options
247 files changed, 5267 insertions, 2857 deletions
diff --git a/Android.bp b/Android.bp index 9933940c436c..f0aa62cc37ae 100644 --- a/Android.bp +++ b/Android.bp @@ -427,7 +427,6 @@ java_defaults { "modules-utils-expresslog", "perfetto_trace_javastream_protos_jarjar", "libaconfig_java_proto_nano", - "aconfig_device_paths_java", ], } diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 7bea0489412f..36b1eaba89df 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -42,7 +42,6 @@ import static android.window.ConfigurationHelper.shouldUpdateResources; import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE; import static com.android.internal.os.SafeZipPathValidatorCallback.VALIDATE_ZIP_PATH_FOR_PATH_TRAVERSAL; import static com.android.sdksandbox.flags.Flags.sandboxActivitySdkBasedContext; -import static com.android.window.flags.Flags.activityWindowInfoFlag; import android.annotation.NonNull; import android.annotation.Nullable; @@ -6854,9 +6853,6 @@ public final class ActivityThread extends ClientTransactionHandler } private void handleActivityWindowInfoChanged(@NonNull ActivityClientRecord r) { - if (!activityWindowInfoFlag()) { - return; - } if (r.mActivityWindowInfo.equals(r.mLastReportedActivityWindowInfo)) { return; } diff --git a/core/java/android/app/servertransaction/ClientTransactionListenerController.java b/core/java/android/app/servertransaction/ClientTransactionListenerController.java index 0c1e7a32ae5e..c281533ee299 100644 --- a/core/java/android/app/servertransaction/ClientTransactionListenerController.java +++ b/core/java/android/app/servertransaction/ClientTransactionListenerController.java @@ -19,8 +19,6 @@ package android.app.servertransaction; import static android.app.WindowConfiguration.areConfigurationsEqualForDisplay; import static android.view.Display.INVALID_DISPLAY; -import static com.android.window.flags.Flags.activityWindowInfoFlag; - import static java.util.Objects.requireNonNull; import android.annotation.NonNull; @@ -102,9 +100,6 @@ public class ClientTransactionListenerController { */ public void registerActivityWindowInfoChangedListener( @NonNull BiConsumer<IBinder, ActivityWindowInfo> listener) { - if (!activityWindowInfoFlag()) { - return; - } synchronized (mLock) { mActivityWindowInfoChangedListeners.add(listener); } @@ -116,9 +111,6 @@ public class ClientTransactionListenerController { */ public void unregisterActivityWindowInfoChangedListener( @NonNull BiConsumer<IBinder, ActivityWindowInfo> listener) { - if (!activityWindowInfoFlag()) { - return; - } synchronized (mLock) { mActivityWindowInfoChangedListeners.remove(listener); } @@ -130,9 +122,6 @@ public class ClientTransactionListenerController { */ public void onActivityWindowInfoChanged(@NonNull IBinder activityToken, @NonNull ActivityWindowInfo activityWindowInfo) { - if (!activityWindowInfoFlag()) { - return; - } final Object[] activityWindowInfoChangedListeners; synchronized (mLock) { if (mActivityWindowInfoChangedListeners.isEmpty()) { diff --git a/core/java/android/hardware/display/BrightnessInfo.java b/core/java/android/hardware/display/BrightnessInfo.java index c09106206c25..6a96a54d93ba 100644 --- a/core/java/android/hardware/display/BrightnessInfo.java +++ b/core/java/android/hardware/display/BrightnessInfo.java @@ -60,7 +60,8 @@ public final class BrightnessInfo implements Parcelable { @IntDef(prefix = {"BRIGHTNESS_MAX_REASON_"}, value = { BRIGHTNESS_MAX_REASON_NONE, BRIGHTNESS_MAX_REASON_THERMAL, - BRIGHTNESS_MAX_REASON_POWER_IC + BRIGHTNESS_MAX_REASON_POWER_IC, + BRIGHTNESS_MAX_REASON_WEAR_BEDTIME_MODE }) @Retention(RetentionPolicy.SOURCE) public @interface BrightnessMaxReason {} @@ -157,6 +158,8 @@ public final class BrightnessInfo implements Parcelable { return "thermal"; case BRIGHTNESS_MAX_REASON_POWER_IC: return "power IC"; + case BRIGHTNESS_MAX_REASON_WEAR_BEDTIME_MODE: + return "wear bedtime"; } return "invalid"; } diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java index 4894fb1ec452..0321e1dfee78 100644 --- a/core/java/android/hardware/display/DisplayManager.java +++ b/core/java/android/hardware/display/DisplayManager.java @@ -395,7 +395,7 @@ public final class DisplayManager { * the display is removed. * * Public virtual displays without this flag will move their content to main display - * stack once they're removed. Private vistual displays will always destroy their + * stack once they're removed. Private virtual displays will always destroy their * content on removal even without this flag. * * @see #createVirtualDisplay diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java index 23e262c43d8e..d7952eb26f7e 100644 --- a/core/java/android/hardware/input/InputManager.java +++ b/core/java/android/hardware/input/InputManager.java @@ -1251,8 +1251,22 @@ public final class InputManager { * 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/security/flags.aconfig b/core/java/android/security/flags.aconfig index 3954bc2318cb..f6f0eff918e3 100644 --- a/core/java/android/security/flags.aconfig +++ b/core/java/android/security/flags.aconfig @@ -92,3 +92,10 @@ flag { 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 0714285c43f6..d8a88b83df99 100644 --- a/core/java/android/util/FeatureFlagUtils.java +++ b/core/java/android/util/FeatureFlagUtils.java @@ -138,12 +138,6 @@ public class FeatureFlagUtils { "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 @@ public class FeatureFlagUtils { 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 cb5a885bd0a0..e5be53191e9e 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -53,6 +53,7 @@ import android.view.IWallpaperVisibilityListener; 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 @@ interface IWindowManager 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 2302dc7bd8c8..ea4abc1b8bc6 100644 --- a/core/java/android/view/InputMonitor.java +++ b/core/java/android/view/InputMonitor.java @@ -50,13 +50,12 @@ public final class InputMonitor implements Parcelable { 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 @@ public final class InputMonitor implements Parcelable { }; @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 000000000000..6f219dbda7ef --- /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 3f6fd646994c..3f49bf3380c1 100644 --- a/core/java/android/view/KeyboardShortcutInfo.java +++ b/core/java/android/view/KeyboardShortcutInfo.java @@ -81,12 +81,29 @@ public final class KeyboardShortcutInfo implements Parcelable { * {@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/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index a6c6c188e8ae..e5b17c8d1001 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -128,7 +128,6 @@ import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodCl import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE; import static com.android.text.flags.Flags.disableHandwritingInitiatorForIme; -import static com.android.window.flags.Flags.activityWindowInfoFlag; import static com.android.window.flags.Flags.enableBufferTransformHintFromDisplay; import static com.android.window.flags.Flags.enableCaptionCompatInsetForceConsumption; import static com.android.window.flags.Flags.insetsControlChangedItem; @@ -1374,9 +1373,6 @@ public final class ViewRootImpl implements ViewParent, */ public void setActivityConfigCallback(@Nullable ActivityConfigCallback callback) { mActivityConfigCallback = callback; - if (!activityWindowInfoFlag()) { - return; - } if (callback == null) { mPendingActivityWindowInfo = null; mLastReportedActivityWindowInfo = null; @@ -9360,7 +9356,7 @@ public final class ViewRootImpl implements ViewParent, onClientWindowFramesChanged(mTmpFrames); - if (activityWindowInfoFlag() && mPendingActivityWindowInfo != null) { + if (mPendingActivityWindowInfo != null) { final ActivityWindowInfo outInfo = mRelayoutResult.activityWindowInfo; if (outInfo != null) { mPendingActivityWindowInfo.set(outInfo); diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index 18006bb7866a..14978ede88cc 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -1668,6 +1668,15 @@ public interface WindowManager extends ViewManager { 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 b667427fd2c5..330e46af6381 100644 --- a/core/java/android/view/WindowManagerImpl.java +++ b/core/java/android/view/WindowManagerImpl.java @@ -237,6 +237,16 @@ public final class WindowManagerImpl implements WindowManager { } @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 946bb823398c..71c500cd1bfa 100644 --- a/core/java/android/window/ActivityWindowInfo.java +++ b/core/java/android/window/ActivityWindowInfo.java @@ -18,6 +18,8 @@ package android.window; 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 @@ public final class ActivityWindowInfo implements Parcelable { + ", 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 2c64b8ed456d..ac57c0056c2b 100644 --- a/core/java/android/window/ITaskFragmentOrganizerController.aidl +++ b/core/java/android/window/ITaskFragmentOrganizerController.aidl @@ -39,12 +39,6 @@ interface ITaskFragmentOrganizerController { 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 d4c3fbee0f79..8e429cb376a6 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_CLOSE; 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.NonNull; 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 com.android.window.flags.Flags; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.Objects; import java.util.concurrent.Executor; /** @@ -324,15 +327,15 @@ public class TaskFragmentOrganizer extends WindowOrganizer { } /** - * 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 */ - 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/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig index f7329298f848..1362f7bf9619 100644 --- a/core/java/android/window/flags/lse_desktop_experience.aconfig +++ b/core/java/android/window/flags/lse_desktop_experience.aconfig @@ -168,4 +168,11 @@ flag { namespace: "lse_desktop_experience" description: "Whether to enable back navigation treatment in desktop windowing." bug: "350421096" -}
\ No newline at end of file +} + +flag { + name: "enable_desktop_windowing_app_handle_education" + namespace: "lse_desktop_experience" + description: "Enables desktop windowing app handle education" + bug: "348208342" +} diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig index 8fd525c5541c..5397e91bd249 100644 --- a/core/java/android/window/flags/windowing_frontend.aconfig +++ b/core/java/android/window/flags/windowing_frontend.aconfig @@ -208,7 +208,7 @@ flag { flag { name: "enforce_shell_thread_model" - namespace: "windowing_frentend" + namespace: "windowing_frontend" description: "Crash the shell process if someone calls in from the wrong thread" bug: "351189446" is_fixed_read_only: true diff --git a/core/java/com/android/internal/jank/Cuj.java b/core/java/com/android/internal/jank/Cuj.java index 2b096eaab237..3e6f18e8e24b 100644 --- a/core/java/com/android/internal/jank/Cuj.java +++ b/core/java/com/android/internal/jank/Cuj.java @@ -188,9 +188,17 @@ public class Cuj { */ 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 @@ public class Cuj { 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 @@ public class Cuj { 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 @@ public class Cuj { 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 30fa4f1a472a..2ad665181e70 100644 --- a/core/java/com/android/internal/os/flags.aconfig +++ b/core/java/com/android/internal/os/flags.aconfig @@ -12,7 +12,7 @@ flag { 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 086fcc896d97..b873175451e1 100644 --- a/core/java/com/android/internal/pm/pkg/component/AconfigFlags.java +++ b/core/java/com/android/internal/pm/pkg/component/AconfigFlags.java @@ -18,7 +18,6 @@ package com.android.internal.pm.pkg.component; import static com.android.internal.pm.pkg.parsing.ParsingUtils.ANDROID_RES_NAMESPACE; -import android.aconfig.DevicePaths; import android.aconfig.nano.Aconfig; import android.aconfig.nano.Aconfig.parsed_flag; import android.aconfig.nano.Aconfig.parsed_flags; @@ -41,6 +40,7 @@ import org.xmlpull.v1.XmlPullParserException; import java.io.File; import java.io.FileInputStream; import java.io.IOException; +import java.util.List; import java.util.Map; /** @@ -54,6 +54,12 @@ import java.util.Map; public class AconfigFlags { private static final String LOG_TAG = "AconfigFlags"; + private static final List<String> sTextProtoFilesOnDevice = List.of( + "/system/etc/aconfig_flags.pb", + "/system_ext/etc/aconfig_flags.pb", + "/product/etc/aconfig_flags.pb", + "/vendor/etc/aconfig_flags.pb"); + public enum Permission { READ_WRITE, READ_ONLY @@ -67,7 +73,7 @@ public class AconfigFlags { Slog.v(LOG_TAG, "Feature disabled, skipped all loading"); return; } - for (String fileName : DevicePaths.parsedFlagsProtoPaths()) { + for (String fileName : sTextProtoFilesOnDevice) { try (var inputStream = new FileInputStream(fileName)) { loadAconfigDefaultValues(inputStream.readAllBytes()); } catch (IOException e) { diff --git a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java index 50fb8d579502..652cba7ed00d 100644 --- a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java +++ b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java @@ -98,6 +98,7 @@ public class PerfettoProtoLogImpl implements IProtoLog { this::onTracingFlush, this::onTracingInstanceStop ); + @Nullable private final ProtoLogViewerConfigReader mViewerConfigReader; private final ViewerConfigInputStreamProvider mViewerConfigInputStreamProvider; private final TreeMap<String, IProtoLogGroup> mLogGroups = new TreeMap<>(); @@ -126,7 +127,7 @@ public class PerfettoProtoLogImpl implements IProtoLog { } public PerfettoProtoLogImpl( - ViewerConfigInputStreamProvider viewerConfigInputStreamProvider, + @Nullable ViewerConfigInputStreamProvider viewerConfigInputStreamProvider, Runnable cacheUpdater ) { this(viewerConfigInputStreamProvider, @@ -136,8 +137,8 @@ public class PerfettoProtoLogImpl implements IProtoLog { @VisibleForTesting public PerfettoProtoLogImpl( - ViewerConfigInputStreamProvider viewerConfigInputStreamProvider, - ProtoLogViewerConfigReader viewerConfigReader, + @Nullable ViewerConfigInputStreamProvider viewerConfigInputStreamProvider, + @Nullable ProtoLogViewerConfigReader viewerConfigReader, Runnable cacheUpdater ) { Producer.init(InitArguments.DEFAULTS); @@ -209,7 +210,9 @@ public class PerfettoProtoLogImpl implements IProtoLog { * @return status code */ public int startLoggingToLogcat(String[] groups, ILogger logger) { - mViewerConfigReader.loadViewerConfig(logger); + if (mViewerConfigReader != null) { + mViewerConfigReader.loadViewerConfig(groups, logger); + } return setTextLogging(true, logger, groups); } @@ -220,7 +223,9 @@ public class PerfettoProtoLogImpl implements IProtoLog { * @return status code */ public int stopLoggingToLogcat(String[] groups, ILogger logger) { - mViewerConfigReader.unloadViewerConfig(); + if (mViewerConfigReader != null) { + mViewerConfigReader.unloadViewerConfig(groups, logger); + } return setTextLogging(false, logger, groups); } @@ -262,7 +267,9 @@ public class PerfettoProtoLogImpl implements IProtoLog { return -1; } case "enable-text" -> { - mViewerConfigReader.loadViewerConfig(logger); + if (mViewerConfigReader != null) { + mViewerConfigReader.loadViewerConfig(groups, logger); + } return setTextLogging(true, logger, groups); } case "disable-text" -> { @@ -420,7 +427,12 @@ public class PerfettoProtoLogImpl implements IProtoLog { private void logToLogcat(String tag, LogLevel level, Message message, @Nullable Object[] args) { - String messageString = message.getMessage(mViewerConfigReader); + String messageString; + if (mViewerConfigReader == null) { + messageString = message.getMessage(); + } else { + messageString = message.getMessage(mViewerConfigReader); + } if (messageString == null) { StringBuilder builder = new StringBuilder("UNKNOWN MESSAGE"); @@ -827,7 +839,11 @@ public class PerfettoProtoLogImpl implements IProtoLog { return mMessageMask; } - private String getMessage(ProtoLogViewerConfigReader viewerConfigReader) { + private String getMessage() { + return mMessageString; + } + + private String getMessage(@NonNull ProtoLogViewerConfigReader viewerConfigReader) { if (mMessageString != null) { return mMessageString; } diff --git a/core/java/com/android/internal/protolog/ProtoLogViewerConfigReader.java b/core/java/com/android/internal/protolog/ProtoLogViewerConfigReader.java index b7b24241c26a..bb6c8b7a9698 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 @@ public class ProtoLogViewerConfigReader { * 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 { + Map<Long, String> mappings = loadViewerConfigMappingForGroup(group); + mGroupHashes.put(group, mappings.keySet()); + for (Long key : mappings.keySet()) { + mLogMessageMap.put(key, mappings.get(key)); + } - 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); + 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 @@ public class ProtoLogViewerConfigReader { 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 @@ public class ProtoLogViewerConfigReader { 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 @@ public class ProtoLogViewerConfigReader { 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 f4ad487f48ac..19c6f51ff9a7 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; 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 @@ public class LockPatternUtils { return; } getDevicePolicyManager().reportFailedPasswordAttempt(userId); - getTrustManager().reportUnlockAttempt(false /* authenticated */, userId); + if (!reportPrimaryAuthAttempts() || !shouldTrustManagerListenForPrimaryAuth()) { + getTrustManager().reportUnlockAttempt(/* authenticated= */ false, userId); + } } @UnsupportedAppUsage @@ -423,7 +427,9 @@ public class LockPatternUtils { 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_util_Process.cpp b/core/jni/android_util_Process.cpp index 982189e30beb..1a1d83cb6fb6 100644 --- a/core/jni/android_util_Process.cpp +++ b/core/jni/android_util_Process.cpp @@ -1063,8 +1063,8 @@ jboolean android_os_Process_readProcFile(JNIEnv* env, jobject clazz, } 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 @@ jboolean android_os_Process_readProcFile(JNIEnv* env, jobject clazz, 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; + 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; } - readBuffer = readBufferHeap.get(); } // parseProcLineArray below modifies the buffer while parsing! diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml index cdd8557393b8..61c7a8cde7d5 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 6b71f97e3f17..46b154163224 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -6534,4 +6534,23 @@ ul.</string> <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 d25f59d7c488..c50b961f74cd 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/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java index f87a9e2b3643..e8a0762f70c0 100644 --- a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java +++ b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java @@ -25,8 +25,6 @@ import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.INVALID_DISPLAY; -import static com.android.window.flags.Flags.FLAG_ACTIVITY_WINDOW_INFO_FLAG; - import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; @@ -810,7 +808,6 @@ public class ActivityThreadTest { @Test public void testActivityWindowInfoChanged_activityLaunch() { - mSetFlagsRule.enableFlags(FLAG_ACTIVITY_WINDOW_INFO_FLAG); ClientTransactionListenerController.getInstance().registerActivityWindowInfoChangedListener( mActivityWindowInfoListener); @@ -825,7 +822,6 @@ public class ActivityThreadTest { @Test public void testActivityWindowInfoChanged_activityRelaunch() { - mSetFlagsRule.enableFlags(FLAG_ACTIVITY_WINDOW_INFO_FLAG); ClientTransactionListenerController.getInstance().registerActivityWindowInfoChangedListener( mActivityWindowInfoListener); @@ -866,7 +862,6 @@ public class ActivityThreadTest { @Test public void testActivityWindowInfoChanged_activityConfigurationChanged() { - mSetFlagsRule.enableFlags(FLAG_ACTIVITY_WINDOW_INFO_FLAG); ClientTransactionListenerController.getInstance().registerActivityWindowInfoChangedListener( mActivityWindowInfoListener); diff --git a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java index 0b270d485b97..d2a444f0d0df 100644 --- a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java +++ b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java @@ -53,8 +53,6 @@ import android.window.WindowTokenClient; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; -import com.android.window.flags.Flags; - import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -167,8 +165,6 @@ public class ClientTransactionListenerControllerTest { @Test public void testActivityWindowInfoChangedListener() { - mSetFlagsRule.enableFlags(Flags.FLAG_ACTIVITY_WINDOW_INFO_FLAG); - mController.registerActivityWindowInfoChangedListener(mActivityWindowInfoListener); final ActivityWindowInfo activityWindowInfo = new ActivityWindowInfo(); activityWindowInfo.set(true /* isEmbedded */, new Rect(0, 0, 1000, 2000), 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 3261a370d2ca..8e1fde066277 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_PINNED; 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.ActivityWindowInfo; 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; @@ -204,11 +206,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen /** Listener registered to {@link ClientTransactionListenerController}. */ @GuardedBy("mLock") - @Nullable + @NonNull private final BiConsumer<IBinder, ActivityWindowInfo> mActivityWindowInfoListener = - Flags.activityWindowInfoFlag() - ? this::onActivityWindowInfoChanged - : null; + this::onActivityWindowInfoChanged; private final Handler mHandler; private final MainThreadExecutor mExecutor; @@ -2555,9 +2555,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen 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()); } @@ -3094,22 +3094,14 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen */ @Override public boolean isActivityEmbedded(@NonNull Activity activity) { - Objects.requireNonNull(activity); synchronized (mLock) { - if (Flags.activityWindowInfoFlag()) { - final ActivityWindowInfo activityWindowInfo = getActivityWindowInfo(activity); - return activityWindowInfo != null && activityWindowInfo.isEmbedded(); - } - return mPresenter.isActivityEmbedded(activity.getActivityToken()); + return TaskFragmentOrganizer.isActivityEmbedded(activity); } } @Override public void setEmbeddedActivityWindowInfoCallback(@NonNull Executor executor, @NonNull Consumer<EmbeddedActivityWindowInfo> callback) { - if (!Flags.activityWindowInfoFlag()) { - return; - } Objects.requireNonNull(executor); Objects.requireNonNull(callback); synchronized (mLock) { @@ -3123,9 +3115,6 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen @Override public void clearEmbeddedActivityWindowInfoCallback() { - if (!Flags.activityWindowInfoFlag()) { - return; - } synchronized (mLock) { if (mEmbeddedActivityWindowInfoCallback == null) { return; @@ -3146,9 +3135,6 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen @Nullable @Override public EmbeddedActivityWindowInfo getEmbeddedActivityWindowInfo(@NonNull Activity activity) { - if (!Flags.activityWindowInfoFlag()) { - return null; - } synchronized (mLock) { final ActivityWindowInfo activityWindowInfo = getActivityWindowInfo(activity); return activityWindowInfo != null @@ -3179,15 +3165,6 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen } } - @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 efeec82b782e..d852204b88a8 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 @@ -103,8 +103,6 @@ import androidx.window.common.DeviceStateManagerFoldingFeatureProducer; import androidx.window.extensions.layout.WindowLayoutComponentImpl; import androidx.window.extensions.layout.WindowLayoutInfo; -import com.android.window.flags.Flags; - import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -167,6 +165,7 @@ public class SplitControllerTest { private Consumer<List<SplitInfo>> mEmbeddingCallback; private List<SplitInfo> mSplitInfos; private TransactionManager mTransactionManager; + private ActivityThread mCurrentActivityThread; @Before public void setUp() { @@ -183,10 +182,12 @@ public class SplitControllerTest { }; 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); @@ -1557,8 +1558,6 @@ public class SplitControllerTest { @Test public void testIsActivityEmbedded() { - mSetFlagRule.enableFlags(Flags.FLAG_ACTIVITY_WINDOW_INFO_FLAG); - assertFalse(mSplitController.isActivityEmbedded(mActivity)); doReturn(true).when(mActivityWindowInfo).isEmbedded(); @@ -1568,8 +1567,6 @@ public class SplitControllerTest { @Test public void testGetEmbeddedActivityWindowInfo() { - mSetFlagRule.enableFlags(Flags.FLAG_ACTIVITY_WINDOW_INFO_FLAG); - final boolean isEmbedded = true; final Rect taskBounds = new Rect(0, 0, 1000, 2000); final Rect activityStackBounds = new Rect(0, 0, 500, 2000); @@ -1584,8 +1581,6 @@ public class SplitControllerTest { @Test public void testSetEmbeddedActivityWindowInfoCallback() { - mSetFlagRule.enableFlags(Flags.FLAG_ACTIVITY_WINDOW_INFO_FLAG); - final ClientTransactionListenerController controller = ClientTransactionListenerController .getInstance(); spyOn(controller); @@ -1676,7 +1671,8 @@ public class SplitControllerTest { 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 dbcad8aab45b..a00d003001e6 100644 --- a/libs/WindowManager/Shell/Android.bp +++ b/libs/WindowManager/Shell/Android.bp @@ -224,7 +224,6 @@ android_library { "//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/dagger/pip/Pip1Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java index 037fbb235bd4..1a9c304e2aee 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 @@ public abstract class Pip1Module { @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 6315e6906842..580724666949 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 @@ -1107,6 +1107,10 @@ class DesktopTasksController( if (useDesktopOverrideDensity()) { wct.setDensityDpi(taskInfo.token, getDefaultDensityDpi()) } + if (desktopModeTaskRepository.isOnlyVisibleNonClosingTask(taskInfo.taskId)) { + // Remove wallpaper activity when leaving desktop mode + removeWallpaperActivity(wct) + } } /** @@ -1122,6 +1126,10 @@ class DesktopTasksController( // 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 b27c428f1693..a749019046f8 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 @@ public interface Pip { 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 6e1e44b7ede0..ff40d4c6e231 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 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, }); mPipTransitionController.setPipOrganizer(this); displayController.addDisplayWindowListener(this); - pipTransitionController.registerPipTransitionCallback( - mPipTransitionCallback, mMainExecutor); + pipTransitionController.registerPipTransitionCallback(mPipTransitionCallback); } } @@ -496,9 +495,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, 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 87692ac52908..e5633de2a3a2 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 @@ public class PipTransition extends PipTransitionController { } 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 8d36db97c511..b1dd4f1c1395 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.DefaultMixedHandler; 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 @@ public abstract class PipTransitionController implements Transitions.TransitionH 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 @@ public abstract class PipTransitionController implements Transitions.TransitionH /** * 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 @@ public abstract class PipTransitionController implements Transitions.TransitionH 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 @@ public abstract class PipTransitionController implements Transitions.TransitionH 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 0cb7e17e41e9..26b7e58bc602 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.List; 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 @@ public class PipController implements PipTransitionController.PipTransitionCallb 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 @@ public class PipController implements PipTransitionController.PipTransitionCallb } @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 df3803d54d9d..e8d6576bab3b 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.internal.protolog.ProtoLog; 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.PipTaskOrganizer; 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 class PipMotionHelper implements PipAppOpsListener.Callback, 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 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, 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 0ed5079b7fba..62c0944f230b 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 @@ public class TvPipController implements PipTransitionController.PipTransitionCal } 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 c12219c8a9e0..b939b169d8bd 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.ShellController; 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 @@ public class PipController implements ConfigurationChangeListener, @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/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt index f67043488bec..8558a77e4e7e 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 @@ -935,6 +935,24 @@ class DesktopTasksControllerTest : ShellTestCase() { } @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 @@ class DesktopTasksControllerTest : ShellTestCase() { } @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() @@ -1769,6 +1825,49 @@ class DesktopTasksControllerTest : ShellTestCase() { } @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 +2076,7 @@ class DesktopTasksControllerTest : ShellTestCase() { eq(null)) } + @Test fun enterSplit_freeformTaskIsMovedToSplit() { val task1 = setUpFreeformTask() val task2 = setUpFreeformTask() @@ -1986,14 +2086,67 @@ class DesktopTasksControllerTest : ShellTestCase() { 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 75d21457b60b..6888de5472e2 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 @@ public class PipControllerTest extends ShellTestCase { @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 66f8c0b9558d..ace09a82d71c 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 @@ public class PipResizeGestureHandlerTest extends ShellTestCase { 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 6d18e3696f84..92762fa68550 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 @@ public class PipTouchHandlerTest extends ShellTestCase { 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/libs/hwui/apex/LayoutlibLoader.cpp b/libs/hwui/apex/LayoutlibLoader.cpp index 073bc8de3659..b4e6b7243ddc 100644 --- a/libs/hwui/apex/LayoutlibLoader.cpp +++ b/libs/hwui/apex/LayoutlibLoader.cpp @@ -28,6 +28,7 @@ using namespace std; extern int register_android_graphics_Bitmap(JNIEnv*); extern int register_android_graphics_BitmapFactory(JNIEnv*); +extern int register_android_graphics_BitmapRegionDecoder(JNIEnv*); extern int register_android_graphics_ByteBufferStreamAdaptor(JNIEnv* env); extern int register_android_graphics_Camera(JNIEnv* env); extern int register_android_graphics_CreateJavaOutputStreamAdaptor(JNIEnv* env); @@ -53,8 +54,11 @@ extern int register_android_graphics_ColorSpace(JNIEnv* env); extern int register_android_graphics_DrawFilter(JNIEnv* env); extern int register_android_graphics_FontFamily(JNIEnv* env); extern int register_android_graphics_Gainmap(JNIEnv* env); +extern int register_android_graphics_HardwareBufferRenderer(JNIEnv* env); extern int register_android_graphics_HardwareRendererObserver(JNIEnv* env); extern int register_android_graphics_Matrix(JNIEnv* env); +extern int register_android_graphics_Mesh(JNIEnv* env); +extern int register_android_graphics_MeshSpecification(JNIEnv* env); extern int register_android_graphics_Paint(JNIEnv* env); extern int register_android_graphics_Path(JNIEnv* env); extern int register_android_graphics_PathIterator(JNIEnv* env); @@ -87,6 +91,8 @@ struct RegJNIRec { static const std::unordered_map<std::string, RegJNIRec> gRegJNIMap = { {"android.graphics.Bitmap", REG_JNI(register_android_graphics_Bitmap)}, {"android.graphics.BitmapFactory", REG_JNI(register_android_graphics_BitmapFactory)}, + {"android.graphics.BitmapRegionDecoder", + REG_JNI(register_android_graphics_BitmapRegionDecoder)}, {"android.graphics.ByteBufferStreamAdaptor", REG_JNI(register_android_graphics_ByteBufferStreamAdaptor)}, {"android.graphics.Camera", REG_JNI(register_android_graphics_Camera)}, @@ -101,6 +107,8 @@ static const std::unordered_map<std::string, RegJNIRec> gRegJNIMap = { {"android.graphics.FontFamily", REG_JNI(register_android_graphics_FontFamily)}, {"android.graphics.Gainmap", REG_JNI(register_android_graphics_Gainmap)}, {"android.graphics.Graphics", REG_JNI(register_android_graphics_Graphics)}, + {"android.graphics.HardwareBufferRenderer", + REG_JNI(register_android_graphics_HardwareBufferRenderer)}, {"android.graphics.HardwareRenderer", REG_JNI(register_android_view_ThreadedRenderer)}, {"android.graphics.HardwareRendererObserver", REG_JNI(register_android_graphics_HardwareRendererObserver)}, @@ -108,6 +116,9 @@ static const std::unordered_map<std::string, RegJNIRec> gRegJNIMap = { {"android.graphics.Interpolator", REG_JNI(register_android_graphics_Interpolator)}, {"android.graphics.MaskFilter", REG_JNI(register_android_graphics_MaskFilter)}, {"android.graphics.Matrix", REG_JNI(register_android_graphics_Matrix)}, + {"android.graphics.Mesh", REG_JNI(register_android_graphics_Mesh)}, + {"android.graphics.MeshSpecification", + REG_JNI(register_android_graphics_MeshSpecification)}, {"android.graphics.NinePatch", REG_JNI(register_android_graphics_NinePatch)}, {"android.graphics.Paint", REG_JNI(register_android_graphics_Paint)}, {"android.graphics.Path", REG_JNI(register_android_graphics_Path)}, diff --git a/libs/hwui/jni/MeshSpecification.cpp b/libs/hwui/jni/MeshSpecification.cpp index ae9792df3d82..b943496ae9f7 100644 --- a/libs/hwui/jni/MeshSpecification.cpp +++ b/libs/hwui/jni/MeshSpecification.cpp @@ -126,7 +126,7 @@ static void MeshSpecification_safeUnref(SkMeshSpecification* meshSpec) { SkSafeUnref(meshSpec); } -static jlong getMeshSpecificationFinalizer() { +static jlong getMeshSpecificationFinalizer(CRITICAL_JNI_PARAMS) { return static_cast<jlong>(reinterpret_cast<uintptr_t>(&MeshSpecification_safeUnref)); } diff --git a/libs/hwui/jni/android_graphics_HardwareBufferRenderer.cpp b/libs/hwui/jni/android_graphics_HardwareBufferRenderer.cpp index e3cdee6e7034..3b1b86160f3a 100644 --- a/libs/hwui/jni/android_graphics_HardwareBufferRenderer.cpp +++ b/libs/hwui/jni/android_graphics_HardwareBufferRenderer.cpp @@ -135,7 +135,7 @@ static void android_graphics_HardwareBufferRenderer_setLightAlpha(JNIEnv* env, j proxy->setLightAlpha((uint8_t)(255 * ambientShadowAlpha), (uint8_t)(255 * spotShadowAlpha)); } -static jlong android_graphics_HardwareBufferRenderer_getFinalizer() { +static jlong android_graphics_HardwareBufferRenderer_getFinalizer(CRITICAL_JNI_PARAMS) { return static_cast<jlong>(reinterpret_cast<uintptr_t>(&HardwareBufferRenderer_destroy)); } 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 7065e3a8f68a..46c72730cf5b 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.ListenableFuture; 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 @@ import java.util.function.Predicate; /** 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 @@ public class TestUtils { } /** - * 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 bbf0315aa475..4387b6f061c3 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 class IllustrationPreference extends Preference { 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 @@ public class IllustrationPreference extends Preference { (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 df5644b8aad0..26453609fc43 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 91bd7916b0ab..ffd28798d82f 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.CategoryPageProvider 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 @@ class GallerySpaEnvironment(context: Context) : SpaEnvironment(context) { 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 96de1a778c97..6d1d34628efa 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.runtime.remember 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.SettingsPageProvider 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.Compan 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 @@ object PreferencePageProvider : SettingsPageProvider { 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 @@ object PreferencePageProvider : SettingsPageProvider { } } ) - } - .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 @@ object PreferencePageProvider : SettingsPageProvider { } } ) - } - .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 @@ object PreferencePageProvider : SettingsPageProvider { 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 2d956d5eddb2..6e5132bbb53e 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 @@ abstract class SpaEnvironment(context: Context) { 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 @@ abstract class SpaEnvironment(context: Context) { // 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 7a4750dfb134..000000000000 --- 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 f3628903dc6d..ec89c7cd3a6c 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 @@ fun SliceUri.getEntryId(): String? { 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 @@ fun Uri.Builder.appendSpaParams( 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 39cb43180f58..000000000000 --- 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 3496f02a70e4..000000000000 --- 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 007f47bd3c82..000000000000 --- 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 e4a738631474..000000000000 --- 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 341a4a5134f9..000000000000 --- 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 d1c4e5110f60..b489afdae157 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 22a5ca328755..4f8fd794b248 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 @@ class SpaEnvironmentForTest( 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 b949cd58a862..ac0b9b45aba6 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.ComponentInfo; 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.ArrayMap; 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 @@ public class TileUtils { /** 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 @@ public class TileUtils { } /** + * 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 @@ public class TileUtils { 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 @@ public class TileUtils { } } - 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 @@ public class TileUtils { } /** 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 7f6a8ed1718e..7886e85cbad8 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 @@ package com.android.settingslib.notification.data.repository 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 @@ class FakeZenModeRepository : ZenModeRepository { 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 @@ class FakeZenModeRepository : ZenModeRepository { 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 @@ fun FakeZenModeRepository.updateNotificationPolicy( 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 72c3c1719f70..ef9452648a70 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 @@ -18,18 +18,26 @@ package com.android.settingslib.notification.data.repository import android.app.NotificationManager 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 +52,16 @@ interface ZenModeRepository { /** @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 @@ -87,7 +100,6 @@ class ZenModeRepositoryImpl( .let { if (Flags.volumePanelBroadcastFix()) { it.flowOn(backgroundCoroutineContext) - .stateIn(scope, SharingStarted.WhileSubscribed(), null) } else { it.shareIn( started = SharingStarted.WhileSubscribed(), @@ -121,4 +133,45 @@ class ZenModeRepositoryImpl( .onStart { emit(mapper()) } .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 000000000000..7b994d59d963 --- /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 06333b61eeb1..6e11e1f612ef 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,18 @@ package com.android.settingslib.notification.data.repository 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.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 +41,7 @@ import org.junit.Before 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 +58,16 @@ class ZenModeRepositoryTest { @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 +80,8 @@ class ZenModeRepositoryTest { ZenModeRepositoryImpl( context, notificationManager, + zenModesBackend, + contentResolver, testScope.backgroundScope, testScope.testScheduler, backgroundHandler = null, @@ -128,11 +144,61 @@ class ZenModeRepositoryTest { } } + @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) { verify(context).registerReceiver(receiverCaptor.capture(), any(), any(), any()) receiverCaptor.value.onReceive(context, Intent(action)) } + 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 { val testPolicy1 = NotificationManager.Policy( diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp index db71d72752ce..187187326ad5 100644 --- a/packages/SystemUI/Android.bp +++ b/packages/SystemUI/Android.bp @@ -581,7 +581,6 @@ android_library { "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 6e6e4b2facb3..71f5511af4f5 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -1171,3 +1171,19 @@ flag { 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 f6535ec0b710..8f247f60bfc4 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 @@ -94,6 +94,7 @@ import androidx.compose.runtime.mutableStateOf 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 +172,11 @@ fun CommunalHub( 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 +192,8 @@ fun CommunalHub( val contentPadding = gridContentPadding(viewModel.isEditMode, toolbarSize) val contentOffset = beforeContentPadding(contentPadding).toOffset() + ObserveScrollEffect(gridState, viewModel) + if (!viewModel.isEditMode) { ScrollOnUpdatedLiveContentEffect(communalContent, gridState) } @@ -420,6 +427,20 @@ private fun DisclaimerBottomSheetContent(onButtonClicked: () -> Unit) { } } +@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. 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 12ca99720513..a184cf3afb39 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 @@ fun SceneScope.NotificationScrollingStack( // 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 d95b388eb347..20b1303ae6bd 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 @@ internal class DraggableHandlerImpl( ): 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 @@ private object DefaultSwipeDistance : UserActionDistance { /** 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 @@ private class Swipes( 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 @@ internal class NestedScrollHandlerImpl( 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 b0dc3a144533..97c0cef30388 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.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 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 936f4ba0efef..a49f1af97183 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 @@ internal class Scene( 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 @@ internal class Scene( } 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 45758c53d69a..0c467b181cd8 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.Modifier 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 @@ interface ElementBoxScope { @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 @@ interface SwipeSource { 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 @@ internal fun SceneTransitionLayoutForTesting( 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 @@ internal fun SceneTransitionLayoutForTesting( // 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 @@ internal fun SceneTransitionLayoutForTesting( } 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 609541925bc5..3e48c429ba7d 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.node.ModifierNodeElement 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 typealias MovableElementContent = @Composable (@Composable () -> Unit) 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 @@ internal class SceneTransitionLayoutImpl( 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 @@ internal class SceneTransitionLayoutImpl( 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 @@ internal class SceneTransitionLayoutImpl( ) { 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 @@ internal class SceneTransitionLayoutImpl( key, this@SceneTransitionLayoutImpl, content, - userActions, + resolvedUserActions, zIndex, ) } @@ -213,7 +220,7 @@ internal class SceneTransitionLayoutImpl( @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 171e2430c004..aeb62628a8f4 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 @@ private class SwipeToSceneNode( /** 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 aa8dc38fdd8f..7daefd0d5d77 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 @@ internal class EdgeTranslate( 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 ff83d4b93af8..7a5a84e2c3f1 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.NestedScrollConnection 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 @@ class DraggableHandlerTest { 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 @@ class DraggableHandlerTest { SceneTransitionLayoutImpl( state = layoutState, density = Density(1f), + layoutDirection = LayoutDirection.Ltr, swipeSourceDetector = DefaultEdgeDetector, transitionInterceptionThreshold = transitionInterceptionThreshold, builder = scenesBuilder, @@ -466,10 +484,8 @@ class DraggableHandlerTest { 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 @@ class DraggableHandlerTest { 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 @@ class DraggableHandlerTest { 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 @@ class DraggableHandlerTest { 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 @@ class DraggableHandlerTest { 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 @@ class DraggableHandlerTest { @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 25ea2eef85e9..0766e00bfccc 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.fillMaxSize 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.performTouchInput 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 @@ class SwipeToSceneTest { // 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 51c008ab686a..9b725eb9eb5a 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.ActivityManager 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.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.ClockProviderPlugin import com.android.systemui.plugins.clocks.ClockSettings @@ -341,6 +341,7 @@ open class ClockRegistry( } private var isClockChanged = AtomicBoolean(false) + private fun triggerOnCurrentClockChanged() { val shouldSchedule = isClockChanged.compareAndSet(false, true) if (!shouldSchedule) { @@ -355,6 +356,7 @@ open class ClockRegistry( } private var isClockListChanged = AtomicBoolean(false) + private fun triggerOnAvailableClocksChanged() { val shouldSchedule = isClockListChanged.compareAndSet(false, true) if (!shouldSchedule) { @@ -458,6 +460,7 @@ open class ClockRegistry( } private var isQueued = AtomicBoolean(false) + fun verifyLoadedProviders() { val shouldSchedule = isQueued.compareAndSet(false, true) if (!shouldSchedule) { @@ -565,8 +568,8 @@ open class ClockRegistry( 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 9e0af97e76e4..4802e3447eb2 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 @@ package com.android.systemui.shared.clocks 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 @@ class DefaultClockProvider( ) } - 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 525839de0a60..b4c839f08607 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/data/db/DefaultWidgetPopulationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/db/DefaultWidgetPopulationTest.kt new file mode 100644 index 000000000000..ad2c42fd1f09 --- /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 17234a9067da..c707ebf0a2c4 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.backup.CommunalBackupUtils 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 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() { backupUtils, packageChangeRepository, userManager, + kosmos.defaultWidgetPopulation, ) } 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 a2f6796c6a38..b138fb3b779a 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.fakeCommunalMediaRepository 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 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 @@ class CommunalEditModeViewModelTest : SysuiTestCase() { 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 @@ class CommunalEditModeViewModelTest : SysuiTestCase() { 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 @@ class CommunalEditModeViewModelTest : SysuiTestCase() { underTest = CommunalEditModeViewModel( communalSceneInteractor, - kosmos.communalInteractor, + communalInteractor, kosmos.communalSettingsInteractor, kosmos.keyguardTransitionInteractor, mediaHost, @@ -346,6 +351,16 @@ class CommunalEditModeViewModelTest : SysuiTestCase() { 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 74a048d038bd..c480aa8e4a3d 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.fakeCommunalSceneRepository 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.Mock 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 @@ class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { 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 @@ class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { kosmos.powerInteractor.setAwakeForTest() + communalInteractor = spy(kosmos.communalInteractor) + underTest = CommunalViewModel( kosmos.testDispatcher, @@ -164,7 +170,7 @@ class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { kosmos.keyguardInteractor, mock<KeyguardIndicationController>(), kosmos.communalSceneInteractor, - kosmos.communalInteractor, + communalInteractor, kosmos.communalSettingsInteractor, kosmos.communalTutorialInteractor, kosmos.shadeInteractor, @@ -779,6 +785,16 @@ class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { .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/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 d7b7cfe11610..09dca25d693e 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.app.Flags 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 @@ import org.junit.runner.RunWith @OptIn(ExperimentalCoroutinesApi::class) @SmallTest -@EnabledOnRavenwood @RunWith(AndroidJUnit4::class) class ModesTileDataInteractorTest : SysuiTestCase() { private val zenModeRepository = FakeZenModeRepository() @@ -67,24 +62,28 @@ class ModesTileDataInteractorTest : SysuiTestCase() { @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 b5e47d167fa3..fd1b21332973 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.ExperimentalCoroutinesApi 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 @@ class SceneContainerStartableTest : SysuiTestCase() { 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 5ef3485a8e51..8b4265f552fe 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.ext.junit.runners.AndroidJUnit4 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 @@ class HeadsUpNotificationInteractorTest : SysuiTestCase() { } @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 ba7ddce10958..f8e633731c42 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 @@ class NotificationListViewModelTest(flags: FlagsParameterization) : SysuiTestCas @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 @@ class NotificationListViewModelTest(flags: FlagsParameterization) : SysuiTestCas fakeKeyguardRepository.setStatusBarState(StatusBarState.KEYGUARD) runCurrent() - assertThat(animationsEnabled).isFalse() + assertThat(animationsEnabled).isTrue() } } 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 663cf1c44dad..d0ddbffecf9a 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 android.testing.TestableLooper.RunWithLooper 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.notification.shared.NotificationsHeadsUpRe 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 org.mockito.kotlin.whenever import platform.test.runner.parameterized.ParameterizedAndroidJunit4 import platform.test.runner.parameterized.Parameters +@OptIn(ExperimentalCoroutinesApi::class) @SmallTest @RunWith(ParameterizedAndroidJunit4::class) @RunWithLooper @@ -58,6 +67,9 @@ class HeadsUpManagerPhoneTest(flags: FlagsParameterization) : BaseHeadsUpManager 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 @@ class HeadsUpManagerPhoneTest(flags: FlagsParameterization) : BaseHeadsUpManager @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 @@ class HeadsUpManagerPhoneTest(flags: FlagsParameterization) : BaseHeadsUpManager 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 @@ class HeadsUpManagerPhoneTest(flags: FlagsParameterization) : BaseHeadsUpManager 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 c7998f089a6d..4812ff03ef36 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 @@ interface ClockProvider { /** 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 @@ class DefaultClockFaceLayout(val view: View) : ClockFaceLayout { // 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 @@ data class ClockMetadata( 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 @@ data class ClockConfig( /** 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/strings.xml b/packages/SystemUI/res/values/strings.xml index 52e5dea1cca4..2bd97d9a2f91 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 4ef1f93481f7..484e758bf973 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 class QuickStepContract { 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 @@ public class QuickStepContract { 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 @@ public class QuickStepContract { 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/data/db/CommunalWidgetDao.kt b/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt index 4dcd9bfd361b..933a25a21578 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 androidx.sqlite.db.SupportSQLiteDatabase 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 com.android.systemui.log.dagger.CommunalLog 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 @@ constructor( 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 @@ constructor( 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/CommunalWidgetRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt index ab4c9d20b368..e65e5e5688f5 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.common.shared.model.PackageInstallSession 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 @@ constructor( 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 @@ constructor( } 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 597a2ce06692..3fffd76ab6a9 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 @@ constructor( ) } } + + /** + * {@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/ui/viewmodel/BaseCommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt index 6ec6ec1113a0..19d7ceba2310 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 @@ abstract class BaseCommunalViewModel( /** 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 @@ abstract class BaseCommunalViewModel( /** 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 91f4c1c96504..7b0aadfdcebd 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 @@ constructor( /** 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 780bf70f3f7b..02ecfe1b0cf9 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 @@ constructor( 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/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepository.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepository.kt index 9e5379283f49..49817b263583 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepository.kt @@ -20,6 +20,7 @@ import android.content.Context import android.graphics.drawable.Icon import android.hardware.input.InputManager import android.util.Log +import android.view.InputDevice import android.view.KeyCharacterMap import android.view.KeyEvent import android.view.KeyboardShortcutGroup @@ -47,8 +48,11 @@ import com.android.systemui.keyboard.shortcut.shared.model.ShortcutKey import com.android.systemui.keyboard.shortcut.shared.model.ShortcutSubCategory import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.withContext @SysUISingleton @@ -56,6 +60,7 @@ class ShortcutHelperCategoriesRepository @Inject constructor( private val context: Context, + @Background private val backgroundScope: CoroutineScope, @Background private val backgroundDispatcher: CoroutineDispatcher, @SystemShortcuts private val systemShortcutsSource: KeyboardShortcutGroupsSource, @MultitaskingShortcuts private val multitaskingShortcutsSource: KeyboardShortcutGroupsSource, @@ -75,81 +80,82 @@ constructor( } } - val systemShortcutsCategory = - activeInputDevice.map { - if (it != null) { - toShortcutCategory( - it.keyCharacterMap, - System, - systemShortcutsSource.shortcutGroups(it.id), - keepIcons = true, + val categories: Flow<List<ShortcutCategory>> = + activeInputDevice + .map { + if (it == null) { + return@map emptyList() + } + return@map listOfNotNull( + fetchSystemShortcuts(it), + fetchMultiTaskingShortcuts(it), + fetchAppCategoriesShortcuts(it), + fetchImeShortcuts(it), + fetchCurrentAppShortcuts(it), ) - } else { - null } - } + .stateIn( + scope = backgroundScope, + started = SharingStarted.Lazily, + initialValue = emptyList(), + ) - val multitaskingShortcutsCategory = - activeInputDevice.map { - if (it != null) { - toShortcutCategory( - it.keyCharacterMap, - MultiTasking, - multitaskingShortcutsSource.shortcutGroups(it.id), - keepIcons = true, - ) - } else { - null - } - } + private suspend fun fetchSystemShortcuts(inputDevice: InputDevice) = + toShortcutCategory( + inputDevice.keyCharacterMap, + System, + systemShortcutsSource.shortcutGroups(inputDevice.id), + keepIcons = true, + ) - val appCategoriesShortcutsCategory = - activeInputDevice.map { - if (it != null) { - toShortcutCategory( - it.keyCharacterMap, - AppCategories, - appCategoriesShortcutsSource.shortcutGroups(it.id), - keepIcons = true, - ) - } else { - null - } - } + private suspend fun fetchMultiTaskingShortcuts(inputDevice: InputDevice) = + toShortcutCategory( + inputDevice.keyCharacterMap, + MultiTasking, + multitaskingShortcutsSource.shortcutGroups(inputDevice.id), + keepIcons = true, + ) - val imeShortcutsCategory = - activeInputDevice.map { - if (it != null) { - toShortcutCategory( - it.keyCharacterMap, - InputMethodEditor, - inputShortcutsSource.shortcutGroups(it.id), - keepIcons = false, - ) - } else { - null - } + private suspend fun fetchAppCategoriesShortcuts(inputDevice: InputDevice) = + toShortcutCategory( + inputDevice.keyCharacterMap, + AppCategories, + appCategoriesShortcutsSource.shortcutGroups(inputDevice.id), + keepIcons = true, + ) + + private suspend fun fetchImeShortcuts(inputDevice: InputDevice) = + toShortcutCategory( + inputDevice.keyCharacterMap, + InputMethodEditor, + inputShortcutsSource.shortcutGroups(inputDevice.id), + keepIcons = false, + ) + + private suspend fun fetchCurrentAppShortcuts(inputDevice: InputDevice): ShortcutCategory? { + val shortcutGroups = currentAppShortcutsSource.shortcutGroups(inputDevice.id) + val categoryType = getCurrentAppShortcutCategoryType(shortcutGroups) + return if (categoryType == null) { + null + } else { + toShortcutCategory( + inputDevice.keyCharacterMap, + categoryType, + shortcutGroups, + keepIcons = false + ) } + } - val currentAppShortcutsCategory: Flow<ShortcutCategory?> = - activeInputDevice.map { - if (it != null) { - val shortcutGroups = currentAppShortcutsSource.shortcutGroups(it.id) - val categoryType = getCurrentAppShortcutCategoryType(shortcutGroups) - if (categoryType == null) { - null - } else { - toShortcutCategory( - it.keyCharacterMap, - categoryType, - shortcutGroups, - keepIcons = false - ) - } - } else { - null - } + private fun getCurrentAppShortcutCategoryType( + shortcutGroups: List<KeyboardShortcutGroup> + ): ShortcutCategoryType? { + return if (shortcutGroups.isEmpty()) { + null + } else { + CurrentApp(packageName = shortcutGroups[0].packageName.toString()) } + } private fun toShortcutCategory( keyCharacterMap: KeyCharacterMap, @@ -174,16 +180,6 @@ constructor( } } - private fun getCurrentAppShortcutCategoryType( - shortcutGroups: List<KeyboardShortcutGroup> - ): ShortcutCategoryType? { - return if (shortcutGroups.isEmpty()) { - null - } else { - CurrentApp(packageName = shortcutGroups[0].packageName.toString()) - } - } - private fun toShortcuts( keyCharacterMap: KeyCharacterMap, infoList: List<KeyboardShortcutInfo>, diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractor.kt index f215c74b3255..6f19561dd87b 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractor.kt @@ -23,7 +23,7 @@ import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategory import com.android.systemui.keyboard.shortcut.shared.model.ShortcutSubCategory import javax.inject.Inject import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.map @SysUISingleton class ShortcutHelperCategoriesInteractor @@ -33,14 +33,8 @@ constructor( ) { val shortcutCategories: Flow<List<ShortcutCategory>> = - combine( - categoriesRepository.systemShortcutsCategory, - categoriesRepository.multitaskingShortcutsCategory, - categoriesRepository.imeShortcutsCategory, - categoriesRepository.appCategoriesShortcutsCategory, - categoriesRepository.currentAppShortcutsCategory - ) { shortcutCategories -> - shortcutCategories.filterNotNull().map { groupSubCategoriesInCategory(it) } + categoriesRepository.categories.map { categories -> + categories.map { category -> groupSubCategoriesInCategory(category) } } private fun groupSubCategoriesInCategory(shortcutCategory: ShortcutCategory): ShortcutCategory { 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 9e9368d3ffd3..b4828624d29c 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 @@ fun ShortcutHelper( ShortcutHelperSinglePane( modifier, shortcutsUiState.shortcutCategories, + shortcutsUiState.defaultSelectedCategory, onKeyboardSettingsClicked ) } else { @@ -146,6 +147,7 @@ private fun shouldUseSinglePane() = private fun ShortcutHelperSinglePane( modifier: Modifier = Modifier, categories: List<ShortcutCategory>, + defaultSelectedCategory: ShortcutCategoryType, onKeyboardSettingsClicked: () -> Unit, ) { Column( @@ -159,7 +161,7 @@ private fun ShortcutHelperSinglePane( 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 @@ private fun ShortcutHelperSinglePane( @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 e602cad30daa..25574ea551b4 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,7 +19,9 @@ package com.android.systemui.keyboard.shortcut.ui.viewmodel 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.ShortcutHelperState +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 @@ -40,8 +42,8 @@ constructor( ) { val shouldShow = - stateInteractor.state - .map { it is ShortcutHelperState.Active } + categoriesInteractor.shortcutCategories + .map { it.isNotEmpty() } .distinctUntilChanged() .flowOn(backgroundDispatcher) @@ -53,7 +55,7 @@ constructor( } else { ShortcutsUiState.Active( shortcutCategories = it, - defaultSelectedCategory = it.first().type, + defaultSelectedCategory = getDefaultSelectedCategory(it), ) } } @@ -63,6 +65,13 @@ constructor( 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 fe81b20c5367..e46a7cbf9bb0 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.Looper; 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.keyguard.shared.model.TransitionStep; 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 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, private final SecureSettings mSecureSettings; private final SystemSettings mSystemSettings; private final SystemClock mSystemClock; + private final ProcessWrapper mProcessWrapper; private final SystemPropertiesHelper mSystemPropertiesHelper; /** @@ -1459,10 +1460,12 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, 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 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, 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 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, */ 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 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, mKeyguardDisplayManager.show(); scheduleNonStrongBiometricIdleTimeout(); - - Trace.endSection(); } /** @@ -3065,6 +3074,17 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, 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 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, onKeyguardExitFinished(); } } - - Trace.endSection(); } private void onKeyguardExitFinished() { @@ -3496,12 +3514,20 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, // 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 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, } 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 15dac0981117..a43bfd3a8fff 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 android.os.PowerManager; 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.shared.quickaffordance.KeyguardQuickAfforda 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 @@ public interface KeyguardModule { 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 @@ public interface KeyguardModule { 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 ab432d6e26c3..c0049d4e2e6c 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.ClockController 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 @@ constructor( 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/log/SessionTracker.java b/packages/SystemUI/src/com/android/systemui/log/SessionTracker.java index d848b431bcc9..e8ded03e3b38 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 static android.app.StatusBarManager.SESSION_KEYGUARD; 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.keyguard.KeyguardUpdateMonitorCallback; 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 @@ public class SessionTracker implements CoreStartable { 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 @@ public class SessionTracker implements CoreStartable { 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 @@ public class SessionTracker implements CoreStartable { 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 @@ public class SessionTracker implements CoreStartable { 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 bda006915c94..e7c2a454e16c 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 @@ public class RingtonePlayer implements CoreStartable { 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 @@ public class RingtonePlayer implements CoreStartable { @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 @@ public class RingtonePlayer implements CoreStartable { } 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 071d8f8c752b..c90f1970671d 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 @@ -36,14 +36,16 @@ import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.flow.stateIn -import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @SysUISingleton +@OptIn(ExperimentalCoroutinesApi::class) class MediaProjectionManagerRepository @Inject constructor( @@ -67,7 +69,11 @@ constructor( } 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> = @@ -76,12 +82,12 @@ constructor( object : MediaProjectionManager.Callback() { override fun onStart(info: MediaProjectionInfo?) { Log.d(TAG, "MediaProjectionManager.Callback#onStart") - trySendWithFailureLogging(MediaProjectionState.NotProjecting, TAG) + trySendWithFailureLogging(CallbackEvent.OnStart, TAG) } override fun onStop(info: MediaProjectionInfo?) { Log.d(TAG, "MediaProjectionManager.Callback#onStop") - trySendWithFailureLogging(MediaProjectionState.NotProjecting, TAG) + trySendWithFailureLogging(CallbackEvent.OnStop, TAG) } override fun onRecordingSessionSet( @@ -89,14 +95,36 @@ constructor( session: ContentRecordingSession? ) { Log.d(TAG, "MediaProjectionManager.Callback#onSessionStarted: $session") - launch { - trySendWithFailureLogging(stateForSession(info, session), TAG) - } + trySendWithFailureLogging( + CallbackEvent.OnRecordingSessionSet(info, session), + TAG, + ) } } mediaProjectionManager.addCallback(callback, handler) awaitClose { mediaProjectionManager.removeCallback(callback) } } + // When we get an #onRecordingSessionSet event, we need to do some work in the + // background before emitting the right state value. But when we get an #onStop + // event, we immediately know what state value to emit. + // + // Without `mapLatest`, this could be a problem if an #onRecordingSessionSet event + // comes in and then an #onStop event comes in shortly afterwards (b/352483752): + // 1. #onRecordingSessionSet -> start some work in the background + // 2. #onStop -> immediately emit "Not Projecting" + // 3. onRecordingSessionSet work finishes -> emit "Projecting" + // + // At step 3, we *shouldn't* emit "Projecting" because #onStop was the last callback + // event we received, so we should be "Not Projecting". This `mapLatest` ensures + // that if an #onStop event comes in, we cancel any ongoing work for + // #onRecordingSessionSet and we don't emit "Projecting". + .mapLatest { + when (it) { + is CallbackEvent.OnStart, + is CallbackEvent.OnStop -> MediaProjectionState.NotProjecting + is CallbackEvent.OnRecordingSessionSet -> stateForSession(it.info, it.session) + } + } .stateIn( scope = applicationScope, started = SharingStarted.Lazily, @@ -129,6 +157,21 @@ constructor( return MediaProjectionState.Projecting.SingleTask(hostPackage, hostDeviceName, matchingTask) } + /** + * Translates [MediaProjectionManager.Callback] events into objects so that we always maintain + * the correct callback ordering. + */ + sealed interface CallbackEvent { + data object OnStart : CallbackEvent + + data object OnStop : CallbackEvent + + data class OnRecordingSessionSet( + val info: MediaProjectionInfo, + val session: ContentRecordingSession?, + ) : CallbackEvent + } + companion object { private const val TAG = "MediaProjectionMngrRepo" } 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 000000000000..16bf0ffbf9e9 --- /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 c07e3a0e7cec..df5dae4dd33a 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 998d76ced9f7..debb667bbb15 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 @@ package com.android.systemui.mediarouter.data.repository 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.CoroutineScope 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 @@ class MediaRouterRepositoryImpl 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 b4cc196b89ed..294d0c75167a 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 @@ public class ProcessWrapper { } /** + * 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/tileimpl/QSTileViewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt index 787fd1ab7170..6b3dfe1b90ad 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 @@ constructor( @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 @@ constructor( } } - 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 @@ constructor( 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 c971f547c302..edc49cac2f92 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 @@ public class InternetDialogDelegate implements 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 da4d2f1c0085..930109a87545 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 @@ package com.android.systemui.qs.tiles.impl.modes.domain.interactor 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 @@ import kotlinx.coroutines.flow.map 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 3dc207063060..af1b6e1127ad 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 @@ public class RecordingController * 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/shade/domain/interactor/ShadeInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt index c1f8a0be646d..45f359efbb7a 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 @@ interface ShadeInteractor : BaseShadeInteractor { /** 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 8214a24a9a38..a8199a402ef1 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 @@ sealed interface ShadeMode { /** * 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 5bb2936c91d4..c997ac5ad9df 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 @@ package com.android.systemui.statusbar; 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 @@ public final class KeyboardShortcutListSearch { 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 @@ public final class KeyboardShortcutListSearch { 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 @@ public final class KeyboardShortcutListSearch { if (sInstance != null && !sInstance.mContext.equals(context)) { dismiss(); } - getInstance(context).showKeyboardShortcuts(deviceId); + getInstance(context, deviceId).showKeyboardShortcuts(deviceId); } } @@ -367,7 +368,7 @@ public final class KeyboardShortcutListSearch { 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 @@ public final class KeyboardShortcutListSearch { 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 @@ public final class KeyboardShortcutListSearch { 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<>(); - - 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) - }; - int[] keyCodes = { + 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[] 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 = { KeyEvent.KEYCODE_B, KeyEvent.KEYCODE_C, KeyEvent.KEYCODE_E, @@ -776,52 +792,44 @@ public final class KeyboardShortcutListSearch { 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 @@ public final class KeyboardShortcutListSearch { 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 a49ca3889876..766c391b14d8 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.content.Context.LAYOUT_INFLATER_SERVICE; 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.ArrayList; 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 @@ public final class KeyboardShortcuts { @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 @@ public final class KeyboardShortcuts { mReceivedAppShortcutGroups = null; mReceivedImeShortcutGroups = null; + mDefaultApplicationShortcuts = getDefaultApplicationShortcuts(deviceId); mWindowManager.requestAppKeyboardShortcuts( result -> { mBackgroundHandler.post(() -> { @@ -443,10 +447,8 @@ public final class KeyboardShortcuts { 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 @@ public final class KeyboardShortcuts { return systemGroup; } - private KeyboardShortcutGroup getDefaultApplicationShortcuts() { + private KeyboardShortcutGroup getDefaultApplicationShortcuts(int deviceId) { final int userId = mContext.getUserId(); List<KeyboardShortcutInfo> keyboardShortcutInfoAppItems = new ArrayList<>(); @@ -524,70 +526,82 @@ public final class KeyboardShortcuts { 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 @@ public final class KeyboardShortcuts { // 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 @@ public final class KeyboardShortcuts { 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 ed1756aff8c1..11ccdff687a1 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.ContentDescription 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 @@ constructor( 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 @@ constructor( } 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 @@ constructor( 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 6917f468ce14..7c95f1e42080 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 @@ package com.android.systemui.statusbar.chips.casttootherdevice.domain.interactor 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 @@ class MediaRouterChipInteractor 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 @@ constructor( 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 @@ constructor( 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 f5e17df71ff9..afa9ccefab86 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.ContentDescription 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 @@ constructor( 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 @@ constructor( 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 @@ constructor( /** 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 @@ constructor( startTimeMs = systemClock.elapsedRealtime(), createDialogLaunchOnClickListener( createCastScreenToOtherDeviceDialogDelegate(state), + logger, + TAG, ), ) } @@ -188,6 +207,8 @@ constructor( colors = ColorsModel.Red, createDialogLaunchOnClickListener( createGenericCastToOtherDeviceDialogDelegate(deviceName), + logger, + TAG, ), ) } @@ -212,5 +233,6 @@ constructor( 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 e201652242a5..0c349810257a 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.ContentDescription 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 @@ constructor( 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 @@ constructor( startTimeMs = systemClock.elapsedRealtime(), createDialogLaunchOnClickListener( createDelegate(state.recordedTask), + logger, + TAG, ), ) } @@ -90,12 +96,18 @@ constructor( 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 45260e18e1ff..ddebd3a0e3c2 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.ContentDescription 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 @@ constructor( 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 @@ constructor( /** Stops the currently active projection. */ private fun stopProjecting() { + logger.log(TAG, LogLevel.INFO, {}, { "Stop sharing requested" }) mediaProjectionChipInteractor.stopProjecting() } @@ -87,7 +92,7 @@ constructor( 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 @@ constructor( 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 65f94ac756cc..ee010f7a818b 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 @@ interface OngoingActivityChipViewModel { /** 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 b63ee4c52e14..ca5f49d28823 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.internal.jank.InteractionJankMonitor; 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 @@ public interface NotificationsModule { @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 eebbb13005b9..bf44b9f3cf78 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.shade.domain.interactor.ShadeInteractor 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 @@ constructor( 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 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 { + // 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 - } else { - // When the last pinned entry is removed from the [HeadsUpRepository], - // there might be a delay before the View starts animating. - 50L + val isHeadsUpOrAnimatingAway: Flow<Boolean> by lazy { + if (NotificationsHeadsUpRefactor.isUnexpectedlyInLegacyMode()) { + flowOf(false) + } else { + combine(hasPinnedRows, headsUpRepository.isHeadsUpAnimatingAway) { + hasPinnedRows, + animatingAway -> + hasPinnedRows || animatingAway } - } - .onStart { emit(false) } // emit false, so we don't wait for the initial update - .distinctUntilChanged() + } + } private val canShowHeadsUp: Flow<Boolean> = combine( @@ -109,10 +112,15 @@ constructor( } } - 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 b54f9c4c6d32..5fba615e020b 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 @@ constructor( 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/DozeParameters.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java index a11cbc3bf231..98869bef5bf0 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 androidx.annotation.VisibleForTesting; 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.statusbar.policy.DevicePostureController; 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 @@ public class DozeParameters implements 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 @@ public class DozeParameters implements ConfigurationController configurationController, StatusBarStateController statusBarStateController, UserTracker userTracker, - DozeInteractor dozeInteractor) { + DozeInteractor dozeInteractor, + SecureSettings secureSettings) { mResources = resources; mAmbientDisplayConfiguration = ambientDisplayConfiguration; mAlwaysOnPolicy = alwaysOnDisplayPolicy; @@ -144,6 +148,7 @@ public class DozeParameters implements mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController; mUserTracker = userTracker; mDozeInteractor = dozeInteractor; + mSecureSettings = secureSettings; keyguardUpdateMonitor.registerCallback(mKeyguardVisibilityCallback); tunerService.addTunable( @@ -160,7 +165,8 @@ public class DozeParameters implements 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 @@ public class DozeParameters implements 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 97791acfb43a..316e1f13bc2b 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.content.res.Resources 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 @@ class KeyguardBypassController @Inject constructor( } 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/policy/CastControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java index 45cb52a8b1ad..994a0d0ca76e 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 @@ public class CastControllerImpl implements CastController { @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 5fc160bd5da3..68edd758808c 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 @@ data class CastDevice( ) { 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 3bf5b6511eb3..025354b51133 100644 --- a/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.kt +++ b/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.kt @@ -21,6 +21,7 @@ import android.database.ContentObserver import android.net.Uri import android.os.UserHandle import android.provider.Settings.SettingNotFoundException +import androidx.annotation.WorkerThread import com.android.app.tracing.TraceUtils.trace import com.android.systemui.settings.UserTracker import com.android.systemui.util.settings.SettingsProxy.Companion.parseFloat @@ -199,6 +200,24 @@ interface UserSettingsProxy : SettingsProxy { } /** + * 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. diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java index 3f1ec85ae99a..ec9b5cfbdeb2 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_B 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.OneHandedEventCallback; 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 @@ public final class WMShell implements 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 b23dfdc68a87..859517839388 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.Rect; 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 @@ public class TestableWindowManager implements WindowManager { } @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/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepositoryTest.kt new file mode 100644 index 000000000000..14837f219862 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepositoryTest.kt @@ -0,0 +1,90 @@ +/* + * 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.keyboard.shortcut.data.repository + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.keyboard.shortcut.data.source.FakeKeyboardShortcutGroupsSource +import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts +import com.android.systemui.keyboard.shortcut.shortcutHelperAppCategoriesShortcutsSource +import com.android.systemui.keyboard.shortcut.shortcutHelperCategoriesRepository +import com.android.systemui.keyboard.shortcut.shortcutHelperCurrentAppShortcutsSource +import com.android.systemui.keyboard.shortcut.shortcutHelperInputShortcutsSource +import com.android.systemui.keyboard.shortcut.shortcutHelperMultiTaskingShortcutsSource +import com.android.systemui.keyboard.shortcut.shortcutHelperSystemShortcutsSource +import com.android.systemui.keyboard.shortcut.shortcutHelperTestHelper +import com.android.systemui.kosmos.testDispatcher +import com.android.systemui.kosmos.testScope +import com.android.systemui.testKosmos +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(AndroidJUnit4::class) +class ShortcutHelperCategoriesRepositoryTest : SysuiTestCase() { + + private val fakeSystemSource = FakeKeyboardShortcutGroupsSource() + private val fakeMultiTaskingSource = FakeKeyboardShortcutGroupsSource() + + private val kosmos = + testKosmos().also { + it.testDispatcher = UnconfinedTestDispatcher() + it.shortcutHelperSystemShortcutsSource = fakeSystemSource + it.shortcutHelperMultiTaskingShortcutsSource = fakeMultiTaskingSource + it.shortcutHelperAppCategoriesShortcutsSource = FakeKeyboardShortcutGroupsSource() + it.shortcutHelperInputShortcutsSource = FakeKeyboardShortcutGroupsSource() + it.shortcutHelperCurrentAppShortcutsSource = FakeKeyboardShortcutGroupsSource() + } + + private val repo = kosmos.shortcutHelperCategoriesRepository + private val helper = kosmos.shortcutHelperTestHelper + private val testScope = kosmos.testScope + + @Before + fun setUp() { + fakeSystemSource.setGroups(TestShortcuts.systemGroups) + fakeMultiTaskingSource.setGroups(TestShortcuts.multitaskingGroups) + } + + @Test + fun categories_multipleSubscribers_replaysExistingValueToNewSubscribers() = + testScope.runTest { + fakeSystemSource.setGroups(TestShortcuts.systemGroups) + fakeMultiTaskingSource.setGroups(TestShortcuts.multitaskingGroups) + helper.showFromActivity() + val firstCategories by collectLastValue(repo.categories) + + // Intentionally change shortcuts now. This simulates "current app" shortcuts changing + // when our helper is shown. + // We still want to return the shortcuts that were returned before our helper was + // showing. + fakeSystemSource.setGroups(emptyList()) + + val secondCategories by collectLastValue(repo.categories) + // Make sure the second subscriber receives the same value as the first subscriber, even + // though fetching shortcuts again would have returned a new result. + assertThat(secondCategories).isEqualTo(firstCategories) + } +} 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 4fba7e355df8..c9c39b3ebf66 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 @@ object TestShortcuts { 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 @@ object TestShortcuts { ) ) + 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/domain/interactor/ShortcutHelperCategoriesInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractorTest.kt index d20ce3f1f0e9..57c8b444b922 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractorTest.kt @@ -28,6 +28,7 @@ import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType. import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.System import com.android.systemui.keyboard.shortcut.shortcutHelperAppCategoriesShortcutsSource import com.android.systemui.keyboard.shortcut.shortcutHelperCategoriesInteractor +import com.android.systemui.keyboard.shortcut.shortcutHelperCurrentAppShortcutsSource import com.android.systemui.keyboard.shortcut.shortcutHelperMultiTaskingShortcutsSource import com.android.systemui.keyboard.shortcut.shortcutHelperSystemShortcutsSource import com.android.systemui.keyboard.shortcut.shortcutHelperTestHelper @@ -48,14 +49,14 @@ class ShortcutHelperCategoriesInteractorTest : SysuiTestCase() { private val systemShortcutsSource = FakeKeyboardShortcutGroupsSource() private val multitaskingShortcutsSource = FakeKeyboardShortcutGroupsSource() - private val defaultAppsShortcutsSource = FakeKeyboardShortcutGroupsSource() @OptIn(ExperimentalCoroutinesApi::class) private val kosmos = testKosmos().also { it.testDispatcher = UnconfinedTestDispatcher() it.shortcutHelperSystemShortcutsSource = systemShortcutsSource it.shortcutHelperMultiTaskingShortcutsSource = multitaskingShortcutsSource - it.shortcutHelperAppCategoriesShortcutsSource = defaultAppsShortcutsSource + it.shortcutHelperAppCategoriesShortcutsSource = FakeKeyboardShortcutGroupsSource() + it.shortcutHelperCurrentAppShortcutsSource = FakeKeyboardShortcutGroupsSource() } private val testScope = kosmos.testScope diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/ShortcutHelperActivityStarterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/ShortcutHelperActivityStarterTest.kt index 0757ea156bbf..f8e2f47f939a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/ShortcutHelperActivityStarterTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/ShortcutHelperActivityStarterTest.kt @@ -20,8 +20,15 @@ import android.content.Intent import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.keyboard.shortcut.data.source.FakeKeyboardShortcutGroupsSource +import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts import com.android.systemui.keyboard.shortcut.fakeShortcutHelperStartActivity import com.android.systemui.keyboard.shortcut.shortcutHelperActivityStarter +import com.android.systemui.keyboard.shortcut.shortcutHelperAppCategoriesShortcutsSource +import com.android.systemui.keyboard.shortcut.shortcutHelperCurrentAppShortcutsSource +import com.android.systemui.keyboard.shortcut.shortcutHelperInputShortcutsSource +import com.android.systemui.keyboard.shortcut.shortcutHelperMultiTaskingShortcutsSource +import com.android.systemui.keyboard.shortcut.shortcutHelperSystemShortcutsSource import com.android.systemui.keyboard.shortcut.shortcutHelperTestHelper import com.android.systemui.keyboard.shortcut.ui.view.ShortcutHelperActivity import com.android.systemui.kosmos.Kosmos @@ -32,6 +39,7 @@ import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runTest +import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -40,10 +48,18 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class ShortcutHelperActivityStarterTest : SysuiTestCase() { + private val fakeSystemSource = FakeKeyboardShortcutGroupsSource() + private val fakeMultiTaskingSource = FakeKeyboardShortcutGroupsSource() + private val kosmos = Kosmos().also { it.testCase = this it.testDispatcher = UnconfinedTestDispatcher() + it.shortcutHelperSystemShortcutsSource = fakeSystemSource + it.shortcutHelperMultiTaskingShortcutsSource = fakeMultiTaskingSource + it.shortcutHelperAppCategoriesShortcutsSource = FakeKeyboardShortcutGroupsSource() + it.shortcutHelperInputShortcutsSource = FakeKeyboardShortcutGroupsSource() + it.shortcutHelperCurrentAppShortcutsSource = FakeKeyboardShortcutGroupsSource() } private val testScope = kosmos.testScope @@ -51,6 +67,12 @@ class ShortcutHelperActivityStarterTest : SysuiTestCase() { private val fakeStartActivity = kosmos.fakeShortcutHelperStartActivity private val starter = kosmos.shortcutHelperActivityStarter + @Before + fun setUp() { + fakeSystemSource.setGroups(TestShortcuts.systemGroups) + fakeMultiTaskingSource.setGroups(TestShortcuts.multitaskingGroups) + } + @Test fun start_doesNotStartByDefault() = testScope.runTest { @@ -70,13 +92,30 @@ class ShortcutHelperActivityStarterTest : SysuiTestCase() { } @Test + fun start_onToggle_noShortcuts_doesNotStartActivity() = + testScope.runTest { + fakeSystemSource.setGroups(emptyList()) + fakeMultiTaskingSource.setGroups(emptyList()) + + starter.start() + + testHelper.toggle(deviceId = 456) + + assertThat(fakeStartActivity.startIntents).isEmpty() + } + + @Test fun start_onToggle_multipleTimesStartsActivityOnlyWhenNotStarted() = testScope.runTest { starter.start() + // Starts testHelper.toggle(deviceId = 456) + // Stops testHelper.toggle(deviceId = 456) + // Starts again testHelper.toggle(deviceId = 456) + // Stops testHelper.toggle(deviceId = 456) verifyShortcutHelperActivityStarted(numTimes = 2) @@ -93,6 +132,18 @@ class ShortcutHelperActivityStarterTest : SysuiTestCase() { } @Test + fun start_onRequestShowShortcuts_noShortcuts_doesNotStartActivity() = + testScope.runTest { + fakeSystemSource.setGroups(emptyList()) + fakeMultiTaskingSource.setGroups(emptyList()) + starter.start() + + testHelper.showFromActivity() + + assertThat(fakeStartActivity.startIntents).isEmpty() + } + + @Test fun start_onRequestShowShortcuts_multipleTimes_startsActivityOnlyOnce() = testScope.runTest { starter.start() @@ -109,13 +160,21 @@ class ShortcutHelperActivityStarterTest : SysuiTestCase() { testScope.runTest { starter.start() + // No-op. Already hidden. testHelper.hideFromActivity() + // No-op. Already hidden. testHelper.hideForSystem() + // Show 1st time. testHelper.toggle(deviceId = 987) + // No-op. Already shown. testHelper.showFromActivity() + // Hidden. testHelper.hideFromActivity() + // No-op. Already hidden. testHelper.hideForSystem() + // Show 2nd time. testHelper.toggle(deviceId = 456) + // No-op. Already shown. testHelper.showFromActivity() verifyShortcutHelperActivityStarted(numTimes = 2) 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 80d487cab50d..69fc463a576a 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 @@ -21,6 +21,14 @@ 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.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 +import com.android.systemui.keyboard.shortcut.shortcutHelperMultiTaskingShortcutsSource +import com.android.systemui.keyboard.shortcut.shortcutHelperSystemShortcutsSource import com.android.systemui.keyboard.shortcut.shortcutHelperTestHelper import com.android.systemui.keyboard.shortcut.shortcutHelperViewModel import com.android.systemui.keyboard.shortcut.ui.model.ShortcutsUiState @@ -34,6 +42,7 @@ import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runTest +import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -42,10 +51,19 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class ShortcutHelperViewModelTest : SysuiTestCase() { + private val fakeSystemSource = FakeKeyboardShortcutGroupsSource() + private val fakeMultiTaskingSource = FakeKeyboardShortcutGroupsSource() + private val fakeCurrentAppsSource = FakeKeyboardShortcutGroupsSource() + private val kosmos = Kosmos().also { it.testCase = this it.testDispatcher = UnconfinedTestDispatcher() + it.shortcutHelperSystemShortcutsSource = fakeSystemSource + it.shortcutHelperMultiTaskingShortcutsSource = fakeMultiTaskingSource + it.shortcutHelperAppCategoriesShortcutsSource = FakeKeyboardShortcutGroupsSource() + it.shortcutHelperInputShortcutsSource = FakeKeyboardShortcutGroupsSource() + it.shortcutHelperCurrentAppShortcutsSource = fakeCurrentAppsSource } private val testScope = kosmos.testScope @@ -53,6 +71,12 @@ class ShortcutHelperViewModelTest : SysuiTestCase() { private val sysUiState = kosmos.sysUiState private val viewModel = kosmos.shortcutHelperViewModel + @Before + fun setUp() { + fakeSystemSource.setGroups(TestShortcuts.systemGroups) + fakeMultiTaskingSource.setGroups(TestShortcuts.multitaskingGroups) + } + @Test fun shouldShow_falseByDefault() = testScope.runTest { @@ -194,4 +218,17 @@ class ShortcutHelperViewModelTest : SysuiTestCase() { 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 03afcb77d88f..e68a4a57de75 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 androidx.test.filters.SmallTest; 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.keyguard.domain.interactor.KeyguardInteractor; 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 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { 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 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { 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 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { .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 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { () -> 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 fbeb6d8d0a6f..732bef1f9803 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.KeyguardUpdateMonitor; 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 @@ public class SessionTrackerTest extends SysuiTestCase { private KeyguardStateController mKeyguardStateController; @Mock private UiEventLogger mUiEventLogger; + @Mock + private ProcessWrapper mProcessWrapper; @Captor ArgumentCaptor<KeyguardUpdateMonitorCallback> mKeyguardUpdateMonitorCallbackCaptor; @@ -86,13 +89,15 @@ public class SessionTrackerTest extends SysuiTestCase { @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/mediaprojection/data/repository/MediaProjectionManagerRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionManagerRepositoryTest.kt index c604b6ab8d8a..5db898115f2d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionManagerRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionManagerRepositoryTest.kt @@ -19,6 +19,7 @@ package com.android.systemui.mediaprojection.data.repository import android.hardware.display.displayManager import android.media.projection.MediaProjectionInfo import android.os.Binder +import android.os.Handler import android.os.UserHandle import android.view.ContentRecordingSession import android.view.Display @@ -26,11 +27,14 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.kosmos.applicationCoroutineScope +import com.android.systemui.kosmos.testDispatcher import com.android.systemui.kosmos.testScope import com.android.systemui.mediaprojection.data.model.MediaProjectionState import com.android.systemui.mediaprojection.taskswitcher.FakeActivityTaskManager.Companion.createTask import com.android.systemui.mediaprojection.taskswitcher.FakeActivityTaskManager.Companion.createToken import com.android.systemui.mediaprojection.taskswitcher.FakeMediaProjectionManager.Companion.createDisplaySession +import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeTasksRepository import com.android.systemui.mediaprojection.taskswitcher.fakeActivityTaskManager import com.android.systemui.mediaprojection.taskswitcher.fakeMediaProjectionManager import com.android.systemui.mediaprojection.taskswitcher.taskSwitcherKosmos @@ -253,6 +257,50 @@ class MediaProjectionManagerRepositoryTest : SysuiTestCase() { .isNull() } + /** Regression test for b/352483752. */ + @Test + fun mediaProjectionState_sessionStartedThenImmediatelyStopped_emitsOnlyNotProjecting() = + testScope.runTest { + val fakeTasksRepo = FakeTasksRepository() + val repoWithTimingControl = + MediaProjectionManagerRepository( + // fakeTasksRepo lets us have control over when the background dispatcher + // finishes fetching the tasks info. + tasksRepository = fakeTasksRepo, + mediaProjectionManager = fakeMediaProjectionManager.mediaProjectionManager, + displayManager = displayManager, + handler = Handler.getMain(), + applicationScope = kosmos.applicationCoroutineScope, + backgroundDispatcher = kosmos.testDispatcher, + mediaProjectionServiceHelper = fakeMediaProjectionManager.helper, + ) + + val state by collectLastValue(repoWithTimingControl.mediaProjectionState) + + val token = createToken() + val task = createTask(taskId = 1, token = token) + + // Dispatch a session using a task session so that MediaProjectionManagerRepository + // has to ask TasksRepository for the tasks info. + fakeMediaProjectionManager.dispatchOnSessionSet( + session = ContentRecordingSession.createTaskSession(token.asBinder()) + ) + // FakeTasksRepository is set up to not return the tasks info until the test manually + // calls [FakeTasksRepository#setRunningTaskResult]. At this point, + // MediaProjectionManagerRepository is waiting for the tasks info and hasn't emitted + // anything yet. + + // Before the tasks info comes back, dispatch a stop event. + fakeMediaProjectionManager.dispatchOnStop() + + // Then let the tasks info come back. + fakeTasksRepo.setRunningTaskResult(task) + + // Verify that MediaProjectionManagerRepository threw away the tasks info because + // a newer callback event (#onStop) occurred. + assertThat(state).isEqualTo(MediaProjectionState.NotProjecting) + } + @Test fun stopProjecting_invokesManager() = testScope.runTest { diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeTasksRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeTasksRepository.kt new file mode 100644 index 000000000000..ce2b9830951b --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeTasksRepository.kt @@ -0,0 +1,46 @@ +/* + * 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.mediaprojection.taskswitcher.data.repository + +import android.app.ActivityManager +import android.os.IBinder +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.emptyFlow + +/** + * Fake tasks repository that gives us fine-grained control over when the result of + * [findRunningTaskFromWindowContainerToken] gets emitted. + */ +class FakeTasksRepository : TasksRepository { + override suspend fun launchRecentTask(taskInfo: ActivityManager.RunningTaskInfo) {} + + private val findRunningTaskResult: CompletableDeferred<ActivityManager.RunningTaskInfo?> = + CompletableDeferred() + + override suspend fun findRunningTaskFromWindowContainerToken( + windowContainerToken: IBinder + ): ActivityManager.RunningTaskInfo? { + return findRunningTaskResult.await() + } + + fun setRunningTaskResult(task: ActivityManager.RunningTaskInfo?) { + findRunningTaskResult.complete(task) + } + + override val foregroundTask: Flow<ActivityManager.RunningTaskInfo> = emptyFlow() +} 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 415cc7c99b2c..988769fe6660 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 @@ class QSTileViewImplTest : SysuiTestCase() { } @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 @@ class QSTileViewImplTest : SysuiTestCase() { // 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 @@ class QSTileViewImplTest : SysuiTestCase() { } @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 2f52248dab6f..150f53d4ad25 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.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.ClockProviderPlugin import com.android.systemui.plugins.clocks.ClockSettings import com.android.systemui.plugins.PluginLifecycleManager @@ -74,6 +75,7 @@ class ClockRegistryTest : SysuiTestCase() { 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 @@ class ClockRegistryTest : SysuiTestCase() { 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 @@ class ClockRegistryTest : SysuiTestCase() { 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 @@ class ClockRegistryTest : SysuiTestCase() { 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 @@ class ClockRegistryTest : SysuiTestCase() { @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 @@ class ClockRegistryTest : SysuiTestCase() { 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 2522ed7f9a3c..bbe03f001b03 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 @@ class DefaultClockProviderTest : SysuiTestCase() { .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 @@ class DefaultClockProviderTest : SysuiTestCase() { // 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 6985a27a59c8..63e56eeb730f 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 @@ public class KeyboardShortcutListSearchTest extends SysuiTestCase { @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 6ad8b8bc3637..105cf168995c 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.junit.MockitoRule; import org.mockito.stubbing.Answer; import java.util.Arrays; +import java.util.Collections; import java.util.List; @SmallTest @@ -71,6 +72,8 @@ public class KeyboardShortcutsTest extends SysuiTestCase { @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 2e0c7735c837..ca043f163854 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 @@ package com.android.systemui.statusbar.chips.ui.viewmodel 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 @@ class OngoingActivityChipViewModelTest : SysuiTestCase() { @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 7cb41f119c9a..10d07a0ce004 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 @@ -35,8 +35,8 @@ import android.os.Handler; import android.os.PowerManager; import android.provider.Settings; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.SysuiTestCase; @@ -52,6 +52,7 @@ import com.android.systemui.statusbar.policy.ConfigurationController; 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 org.junit.Assert; import org.junit.Before; @@ -130,7 +131,8 @@ public class DozeParametersTest extends SysuiTestCase { mConfigurationController, mStatusBarStateController, mUserTracker, - mDozeInteractor + mDozeInteractor, + new FakeSettings() ); 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 ef4e7341db74..cc2ef53c6cdb 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.domain.interactor.sceneInteractor 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 @@ class KeyguardStatusBarViewModelTest(flags: FlagsParameterization) : SysuiTestCa } @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 e3e20c8ed501..5f7420d5a16b 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.FakeUserTracker 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 @@ class UserSettingsProxyTest : SysuiTestCase() { } @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 @@ class UserSettingsProxyTest : SysuiTestCase() { } @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 @@ class UserSettingsProxyTest : SysuiTestCase() { 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 @@ class UserSettingsProxyTest : SysuiTestCase() { } @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 @@ class UserSettingsProxyTest : SysuiTestCase() { 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 @@ class UserSettingsProxyTest : SysuiTestCase() { } @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 @@ class UserSettingsProxyTest : SysuiTestCase() { eq(MAIN_USER_ID) ) } - } + @OptIn(ExperimentalCoroutinesApi::class) @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 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() = + 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 @@ class UserSettingsProxyTest : SysuiTestCase() { } @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 @@ class UserSettingsProxyTest : SysuiTestCase() { 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,33 +343,26 @@ class UserSettingsProxyTest : SysuiTestCase() { } @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)) - } - - @Test - fun registerContentObserverSuspend_inputUri_notifyForDescendants_true() = + fun registerContentObserver_inputUri_notifyForDescendants_true() = testScope.runTest { - mSettings.registerContentObserver(TEST_SETTING_URI, mContentObserver) + mSettings.registerContentObserverSync( + TEST_SETTING_URI, + notifyForDescendants = true, + mContentObserver + ) verify(mSettings.getContentResolver()) .registerContentObserver( eq(TEST_SETTING_URI), - eq(false), + eq(true), eq(mContentObserver), eq(0) ) } @Test - fun registerContentObserverAsync_inputUri_notifyForDescendants_true() { - mSettings.registerContentObserverAsync(TEST_SETTING_URI, mContentObserver) - testScope.launch { + fun registerContentObserverSuspend_inputUri_notifyForDescendants_true() = + testScope.runTest { + mSettings.registerContentObserver(TEST_SETTING_URI, mContentObserver) verify(mSettings.getContentResolver()) .registerContentObserver( eq(TEST_SETTING_URI), @@ -348,7 +371,21 @@ class UserSettingsProxyTest : SysuiTestCase() { eq(0) ) } - } + + @Test + 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 000000000000..8d01fcd9fea3 --- /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/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt index 530df8aca442..001b55b99919 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt @@ -68,16 +68,17 @@ val Kosmos.shortcutHelperStateRepository by ) } -val Kosmos.shortcutHelperInputShortcutsSource by +var Kosmos.shortcutHelperInputShortcutsSource: KeyboardShortcutGroupsSource by Kosmos.Fixture { InputShortcutsSource(mainResources, windowManager) } -val Kosmos.shortcutHelperCurrentAppShortcutsSource by +var Kosmos.shortcutHelperCurrentAppShortcutsSource: KeyboardShortcutGroupsSource by Kosmos.Fixture { CurrentAppShortcutsSource(windowManager) } val Kosmos.shortcutHelperCategoriesRepository by Kosmos.Fixture { ShortcutHelperCategoriesRepository( applicationContext, + applicationCoroutineScope, testDispatcher, shortcutHelperSystemShortcutsSource, shortcutHelperMultiTaskingShortcutsSource, @@ -96,7 +97,8 @@ val Kosmos.shortcutHelperTestHelper by applicationContext, broadcastDispatcher, fakeCommandQueue, - windowManager + fakeInputManager, + windowManager, ) } 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 40510db24f47..6ca5cd81b665 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 @@ -18,6 +18,7 @@ package com.android.systemui.keyboard.shortcut.data.repository import android.content.Context import android.content.Intent +import android.hardware.input.FakeInputManager import android.view.KeyboardShortcutGroup import android.view.WindowManager import android.view.WindowManager.KeyboardShortcutsReceiver @@ -31,6 +32,7 @@ class ShortcutHelperTestHelper( private val context: Context, private val fakeBroadcastDispatcher: FakeBroadcastDispatcher, private val fakeCommandQueue: FakeCommandQueue, + private val fakeInputManager: FakeInputManager, windowManager: WindowManager ) { @@ -39,6 +41,7 @@ class ShortcutHelperTestHelper( } private var imeShortcuts: List<KeyboardShortcutGroup> = emptyList() + private var currentAppsShortcuts: List<KeyboardShortcutGroup> = emptyList() init { whenever(windowManager.requestImeKeyboardShortcuts(any(), any())).thenAnswer { @@ -46,6 +49,11 @@ class ShortcutHelperTestHelper( 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() } @@ -57,6 +65,14 @@ class ShortcutHelperTestHelper( 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, @@ -79,6 +95,7 @@ class ShortcutHelperTestHelper( } fun toggle(deviceId: Int) { + fakeInputManager.addPhysicalKeyboard(deviceId) fakeCommandQueue.doForEachCallback { it.toggleKeyboardShortcutsMenu(deviceId) } } 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 eec99207446a..e1ecc517ec9f 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 @@ package com.android.systemui.mediarouter.data.repository 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 @@ val Kosmos.realMediaRouterRepository by 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 ab71b5e2d6b9..1e304d979e03 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.Kosmos 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 @@ val Kosmos.callChipViewModel: CallChipViewModel by 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 cb18b687d5ed..1737bc4c7dfb 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 @@ package com.android.systemui.statusbar.chips.casttootherdevice.domain.interactor 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 2335f21b2094..3d85a4abbd68 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.kosmos.applicationCoroutineScope 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 @@ val Kosmos.castToOtherDeviceChipViewModel: CastToOtherDeviceChipViewModel by 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 2773f825f368..e4bb1665a432 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.Kosmos 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 @@ val Kosmos.screenRecordChipViewModel: ScreenRecordChipViewModel by 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 1b3108cdb5df..8ed7f9684d86 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.Kosmos 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 @@ val Kosmos.shareToAppChipViewModel: ShareToAppChipViewModel by mediaProjectionChipInteractor = mediaProjectionChipInteractor, systemClock = fakeSystemClock, endMediaProjectionDialogHelper = endMediaProjectionDialogHelper, + logger = statusBarChipsLogger, ) } diff --git a/services/Android.bp b/services/Android.bp index cd974c5f562d..dce6aa71daea 100644 --- a/services/Android.bp +++ b/services/Android.bp @@ -186,7 +186,15 @@ art_profile_java_defaults { // 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 @@ java_library { "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 1cd20ed0f7cd..9d4310c21cf9 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -215,6 +215,7 @@ java_library_static { "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/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java index 320122390681..458749d93e0a 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 @@ public class AccountManagerService 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 00183ac0350b..67985efcd7bc 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.ResultReceiver; 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.CodingErrorAction; 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 @@ public final class BatteryStatsService extends IBatteryStats.Stub 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 final class BatteryStatsService extends IBatteryStats.Stub 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 @@ public final class BatteryStatsService extends IBatteryStats.Stub .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 @@ public final class BatteryStatsService extends IBatteryStats.Stub .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 @@ public final class BatteryStatsService extends IBatteryStats.Stub 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 @@ public final class BatteryStatsService extends IBatteryStats.Stub } } + 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 @@ public final class BatteryStatsService extends IBatteryStats.Stub 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 @@ public final class BatteryStatsService extends IBatteryStats.Stub 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 d21478807d4d..5315167b46eb 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 { 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 { 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/display/DisplayBrightnessState.java b/services/core/java/com/android/server/display/DisplayBrightnessState.java index 222c5a83a551..12ec24819c78 100644 --- a/services/core/java/com/android/server/display/DisplayBrightnessState.java +++ b/services/core/java/com/android/server/display/DisplayBrightnessState.java @@ -16,6 +16,7 @@ package com.android.server.display; +import android.hardware.display.BrightnessInfo; import android.text.TextUtils; import com.android.server.display.brightness.BrightnessEvent; @@ -50,6 +51,8 @@ public final class DisplayBrightnessState { private final boolean mIsUserInitiatedChange; + private @BrightnessInfo.BrightnessMaxReason int mBrightnessMaxReason; + private DisplayBrightnessState(Builder builder) { mBrightness = builder.getBrightness(); mHdrBrightness = builder.getHdrBrightness(); @@ -64,6 +67,7 @@ public final class DisplayBrightnessState { mBrightnessEvent = builder.getBrightnessEvent(); mBrightnessAdjustmentFlag = builder.getBrightnessAdjustmentFlag(); mIsUserInitiatedChange = builder.isUserInitiatedChange(); + mBrightnessMaxReason = builder.getBrightnessMaxReason(); } /** @@ -159,6 +163,13 @@ public final class DisplayBrightnessState { return mIsUserInitiatedChange; } + /** + * Gets reason for max brightness restriction + */ + public @BrightnessInfo.BrightnessMaxReason int getBrightnessMaxReason() { + return mBrightnessMaxReason; + } + @Override public String toString() { StringBuilder stringBuilder = new StringBuilder("DisplayBrightnessState:"); @@ -180,6 +191,8 @@ public final class DisplayBrightnessState { .append(Objects.toString(mBrightnessEvent, "null")); stringBuilder.append("\n mBrightnessAdjustmentFlag:").append(mBrightnessAdjustmentFlag); stringBuilder.append("\n mIsUserInitiatedChange:").append(mIsUserInitiatedChange); + stringBuilder.append("\n mBrightnessMaxReason:") + .append(BrightnessInfo.briMaxReasonToString(mBrightnessMaxReason)); return stringBuilder.toString(); } @@ -212,7 +225,8 @@ public final class DisplayBrightnessState { == otherState.shouldUpdateScreenBrightnessSetting() && Objects.equals(mBrightnessEvent, otherState.getBrightnessEvent()) && mBrightnessAdjustmentFlag == otherState.getBrightnessAdjustmentFlag() - && mIsUserInitiatedChange == otherState.isUserInitiatedChange(); + && mIsUserInitiatedChange == otherState.isUserInitiatedChange() + && mBrightnessMaxReason == otherState.getBrightnessMaxReason(); } @Override @@ -221,7 +235,7 @@ public final class DisplayBrightnessState { mShouldUseAutoBrightness, mIsSlowChange, mMaxBrightness, mMinBrightness, mCustomAnimationRate, mShouldUpdateScreenBrightnessSetting, mBrightnessEvent, mBrightnessAdjustmentFlag, - mIsUserInitiatedChange); + mIsUserInitiatedChange, mBrightnessMaxReason); } /** @@ -245,12 +259,11 @@ public final class DisplayBrightnessState { private float mMinBrightness; private float mCustomAnimationRate = CUSTOM_ANIMATION_RATE_NOT_SET; private boolean mShouldUpdateScreenBrightnessSetting; - private BrightnessEvent mBrightnessEvent; - - public int mBrightnessAdjustmentFlag = 0; - + private int mBrightnessAdjustmentFlag = 0; private boolean mIsUserInitiatedChange; + private @BrightnessInfo.BrightnessMaxReason int mBrightnessMaxReason = + BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE; /** * Create a builder starting with the values from the specified {@link @@ -274,6 +287,7 @@ public final class DisplayBrightnessState { builder.setBrightnessEvent(state.getBrightnessEvent()); builder.setBrightnessAdjustmentFlag(state.getBrightnessAdjustmentFlag()); builder.setIsUserInitiatedChange(state.isUserInitiatedChange()); + builder.setBrightnessMaxReason(state.getBrightnessMaxReason()); return builder; } @@ -496,5 +510,21 @@ public final class DisplayBrightnessState { mIsUserInitiatedChange = isUserInitiatedChange; return this; } + + /** + * Gets reason for max brightness restriction + */ + public @BrightnessInfo.BrightnessMaxReason int getBrightnessMaxReason() { + return mBrightnessMaxReason; + } + + /** + * Sets reason for max brightness restriction + */ + public Builder setBrightnessMaxReason( + @BrightnessInfo.BrightnessMaxReason int brightnessMaxReason) { + mBrightnessMaxReason = brightnessMaxReason; + return this; + } } } diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index 58309c2c751a..01604b8bea81 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -1580,7 +1580,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call // brightness sources (such as an app override) are not saved to the setting, but should be // reflected in HBM calculations. mBrightnessRangeController.onBrightnessChanged(brightnessState, unthrottledBrightnessState, - mBrightnessClamperController.getBrightnessMaxReason()); + clampedState.getBrightnessMaxReason()); // Animate the screen brightness when the screen is on or dozing. // Skip the animation when the screen is off or suspended. @@ -1783,7 +1783,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call if (userSetBrightnessChanged || newEvent.getReason().getReason() != BrightnessReason.REASON_TEMPORARY) { - logBrightnessEvent(newEvent, unthrottledBrightnessState); + logBrightnessEvent(newEvent, unthrottledBrightnessState, clampedState); } if (mBrightnessEventRingBuffer != null) { mBrightnessEventRingBuffer.append(newEvent); @@ -1976,6 +1976,9 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call synchronized (mCachedBrightnessInfo) { float stateMax = state != null ? state.getMaxBrightness() : PowerManager.BRIGHTNESS_MAX; float stateMin = state != null ? state.getMinBrightness() : PowerManager.BRIGHTNESS_MAX; + @BrightnessInfo.BrightnessMaxReason int maxReason = + state != null ? state.getBrightnessMaxReason() + : BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE; final float minBrightness = Math.max(stateMin, Math.min( mBrightnessRangeController.getCurrentBrightnessMin(), stateMax)); final float maxBrightness = Math.min( @@ -2002,7 +2005,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call mBrightnessRangeController.getTransitionPoint()); changed |= mCachedBrightnessInfo.checkAndSetInt(mCachedBrightnessInfo.brightnessMaxReason, - mBrightnessClamperController.getBrightnessMaxReason()); + maxReason); return changed; } } @@ -2902,7 +2905,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call return FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__ENTIRE_REASON__REASON_UNKNOWN; } - private void logBrightnessEvent(BrightnessEvent event, float unmodifiedBrightness) { + private void logBrightnessEvent(BrightnessEvent event, float unmodifiedBrightness, + DisplayBrightnessState brightnessState) { int modifier = event.getReason().getModifier(); int flags = event.getFlags(); // It's easier to check if the brightness is at maximum level using the brightness @@ -2939,7 +2943,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call event.getHbmMode() == BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT, event.getHbmMode() == BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR, (modifier & BrightnessReason.MODIFIER_LOW_POWER) > 0, - mBrightnessClamperController.getBrightnessMaxReason(), + brightnessState.getBrightnessMaxReason(), // TODO: (flc) add brightnessMinReason here too. (modifier & BrightnessReason.MODIFIER_DIMMED) > 0, event.isRbcEnabled(), diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java index 88d2c007cf37..d1fb0091e28b 100644 --- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java +++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java @@ -139,6 +139,7 @@ public class BrightnessClamperController { builder.setBrightness(cappedBrightness); builder.setMaxBrightness(mBrightnessCap); builder.setCustomAnimationRate(mCustomAnimationRate); + builder.setBrightnessMaxReason(getBrightnessMaxReason()); if (mClamperType != null) { builder.getBrightnessReason().addModifier(BrightnessReason.MODIFIER_THROTTLED); @@ -163,19 +164,8 @@ public class BrightnessClamperController { return builder.build(); } - /** - * See BrightnessThrottler.getBrightnessMaxReason: - * used in: - * 1) DPC2.CachedBrightnessInfo to determine changes - * 2) DPC2.logBrightnessEvent - * 3) HBMController - for logging - * Method is called in mHandler thread (DisplayControllerHandler), in the same thread - * recalculateBrightnessCap and DPC2.updatePowerStateInternal are called. - * Should be moved to DisplayBrightnessState OR derived from DisplayBrightnessState - * TODO: b/263362199 - */ @BrightnessInfo.BrightnessMaxReason - public int getBrightnessMaxReason() { + private int getBrightnessMaxReason() { if (mClamperType == null) { return BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE; } else if (mClamperType == Type.THERMAL) { diff --git a/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java b/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java index 91ab8720ac0b..8ca045834981 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 @@ package com.android.server.inputmethod; 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 @@ final class AdditionalSubtypeMapRepository { 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 5ab493b25859..9837ab16a310 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 @@ final class InputMethodBindingController { 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 000000000000..b835d058f00f --- /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 7daf9582cdd6..f5faeef9258c 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.NonNull; 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.StartInputReason; 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.Objects; 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 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. @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 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. @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 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. // 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 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. @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 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. // 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 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. }); } + @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 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. 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 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. 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 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. // 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 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. }); } - // 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 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. 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 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. 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 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. @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 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. @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 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. final var userData = getUserData(userId); userData.mSwitchingController.resetCircularListLocked(mContext, settings); userData.mHardwareKeyboardShortcutController.update(settings); - sendOnNavButtonFlagsChangedLocked(); + sendOnNavButtonFlagsChangedLocked(userData); } @GuardedBy("ImfLock.class") @@ -4979,7 +4953,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. 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 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. 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 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } @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 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. 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 656c87deac6b..06f73f34e427 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 @@ final class InputMethodMenuController { attrs.setTitle("Select input method"); w.setAttributes(attrs); mService.updateSystemUiLocked(userId); - mService.sendOnNavButtonFlagsChangedLocked(); + mService.sendOnNavButtonFlagsChangedLocked(mService.getUserData(userId)); mSwitchingDialog.show(); } @@ -242,7 +242,7 @@ final class InputMethodMenuController { // 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 a4d8ee566755..50ba36450bda 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 @@ final class InputMethodSettingsRepository { 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 33e7a7621340..000000000000 --- 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 98d7548d3dd2..7c68d547ecd3 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.annotations.GuardedBy; 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 @@ final class UserDataRepository { 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 058c1c8efda5..e1b1416169cd 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 class ContextHubEventLogger { */ 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 @@ public class ContextHubEventLogger { 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 @@ public class ContextHubEventLogger { } /** + * 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 a0aad5215f00..ed451ff0c194 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 @@ public class ContextHubService extends IContextHubService.Stub { * @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 56e459057bfa..46585a50ea36 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.EventLog; 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 @@ public final class OverlayManagerService extends SystemService { 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 c5e2bb87afd3..8410cff74265 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.content.pm.PackageManagerInternal; 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 @@ public class PersistentDataBlockService extends SystemService { } 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 @@ public class PersistentDataBlockService extends SystemService { 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 @@ public class PersistentDataBlockService extends SystemService { 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/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java index 8ee02dc4f901..303371bd9a92 100644 --- a/services/core/java/com/android/server/pm/InstallPackageHelper.java +++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java @@ -2629,18 +2629,28 @@ final class InstallPackageHelper { String packageName = pkgLite.packageName; synchronized (mPm.mLock) { - // Package which currently owns the data that the new package will own if installed. - // If an app is uninstalled while keeping data (e.g. adb uninstall -k), installedPkg - // will be null whereas dataOwnerPkg will contain information about the package - // which was uninstalled while keeping its data. - AndroidPackage dataOwnerPkg = mPm.mPackages.get(packageName); PackageSetting dataOwnerPs = mPm.mSettings.getPackageLPr(packageName); - if (dataOwnerPkg == null) { - if (dataOwnerPs != null) { - dataOwnerPkg = dataOwnerPs.getPkg(); + if (dataOwnerPs == null) { + if (requiredInstalledVersionCode != PackageManager.VERSION_CODE_HIGHEST) { + String errorMsg = "Required installed version code was " + + requiredInstalledVersionCode + + " but package is not installed"; + Slog.w(TAG, errorMsg); + return Pair.create( + PackageManager.INSTALL_FAILED_WRONG_INSTALLED_VERSION, errorMsg); } + // The package doesn't exist in the system, don't need to check the version + // replacing. + return Pair.create(PackageManager.INSTALL_SUCCEEDED, null); } + // Package which currently owns the data that the new package will own if installed. + // If an app is uninstalled while keeping data (e.g. adb uninstall -k), dataOwnerPkg + // will be null whereas dataOwnerPs will contain information about the package + // which was uninstalled while keeping its data. The AndroidPackage object that the + // PackageSetting refers to is the same object that is stored in mPackages. + AndroidPackage dataOwnerPkg = dataOwnerPs.getPkg(); + if (requiredInstalledVersionCode != PackageManager.VERSION_CODE_HIGHEST) { if (dataOwnerPkg == null) { String errorMsg = "Required installed version code was " @@ -2662,7 +2672,27 @@ final class InstallPackageHelper { } } - if (dataOwnerPkg != null && !dataOwnerPkg.isSdkLibrary()) { + // If dataOwnerPkg is null but dataOwnerPs is not null, there is always data on + // some users. Wwe should do the downgrade check. E.g. DELETE_KEEP_DATA and + // archived apps + if (dataOwnerPkg == null) { + if (!PackageManagerServiceUtils.isDowngradePermitted(installFlags, + dataOwnerPs.isDebuggable())) { + // The data exists on some users and downgrade is not permitted; a lower + // version of the app will not be allowed. + try { + PackageManagerServiceUtils.checkDowngrade(dataOwnerPs, pkgLite); + } catch (PackageManagerException e) { + String errorMsg = "Downgrade detected on app uninstalled with" + + " DELETE_KEEP_DATA: " + e.getMessage(); + Slog.w(TAG, errorMsg); + return Pair.create( + PackageManager.INSTALL_FAILED_VERSION_DOWNGRADE, errorMsg); + } + } + // dataOwnerPs.getPkg() is not null on system apps case. Don't need to consider + // system apps case like below. + } else if (dataOwnerPkg != null && !dataOwnerPkg.isSdkLibrary()) { if (!PackageManagerServiceUtils.isDowngradePermitted(installFlags, dataOwnerPkg.isDebuggable())) { // Downgrade is not permitted; a lower version of the app will not be allowed diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index b5c33cd68d89..07c8ee75f932 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.ParsedInstrumentation; 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 @@ public class PackageManagerService implements PackageSender, TestUtilityService 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 @@ public class PackageManagerService implements PackageSender, TestUtilityService 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 @@ public class PackageManagerService implements PackageSender, TestUtilityService 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/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java index 924b36cef79a..c3cac2032a91 100644 --- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java +++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java @@ -1419,10 +1419,23 @@ public class PackageManagerServiceUtils { /** * Check and throw if the given before/after packages would be considered a - * downgrade. + * downgrade with {@link PackageSetting}. */ - public static void checkDowngrade(AndroidPackage before, PackageInfoLite after) - throws PackageManagerException { + public static void checkDowngrade(@NonNull PackageSetting before, + @NonNull PackageInfoLite after) throws PackageManagerException { + if (after.getLongVersionCode() < before.getVersionCode()) { + throw new PackageManagerException(INSTALL_FAILED_VERSION_DOWNGRADE, + "Update version code " + after.versionCode + " is older than current " + + before.getVersionCode()); + } + } + + /** + * Check and throw if the given before/after packages would be considered a + * downgrade with {@link AndroidPackage}. + */ + public static void checkDowngrade(@NonNull AndroidPackage before, + @NonNull PackageInfoLite after) throws PackageManagerException { if (after.getLongVersionCode() < before.getLongVersionCode()) { throw new PackageManagerException(INSTALL_FAILED_VERSION_DOWNGRADE, "Update version code " + after.versionCode + " is older than current " diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java index 7870b1735af4..82df527edcc3 100644 --- a/services/core/java/com/android/server/pm/PackageSetting.java +++ b/services/core/java/com/android/server/pm/PackageSetting.java @@ -97,6 +97,7 @@ public class PackageSetting extends SettingBase implements PackageStateInternal FORCE_QUERYABLE_OVERRIDE, SCANNED_AS_STOPPED_SYSTEM_APP, PENDING_RESTORE, + DEBUGGABLE, }) public @interface Flags { } @@ -105,6 +106,7 @@ public class PackageSetting extends SettingBase implements PackageStateInternal private static final int FORCE_QUERYABLE_OVERRIDE = 1 << 2; private static final int SCANNED_AS_STOPPED_SYSTEM_APP = 1 << 3; private static final int PENDING_RESTORE = 1 << 4; + private static final int DEBUGGABLE = 1 << 5; } private int mBooleans; @@ -562,6 +564,20 @@ public class PackageSetting extends SettingBase implements PackageStateInternal return getBoolean(Booleans.PENDING_RESTORE); } + /** + * @see PackageState#isDebuggable + */ + public PackageSetting setDebuggable(boolean value) { + setBoolean(Booleans.DEBUGGABLE, value); + onChanged(); + return this; + } + + @Override + public boolean isDebuggable() { + return getBoolean(Booleans.DEBUGGABLE); + } + @Override public String toString() { return "PackageSetting{" diff --git a/services/core/java/com/android/server/pm/ScanPackageUtils.java b/services/core/java/com/android/server/pm/ScanPackageUtils.java index 9ab6016f3d57..d8ce38e0cd2c 100644 --- a/services/core/java/com/android/server/pm/ScanPackageUtils.java +++ b/services/core/java/com/android/server/pm/ScanPackageUtils.java @@ -437,6 +437,9 @@ final class ScanPackageUtils { pkgSetting.setIsOrphaned(true); } + // update debuggable to packageSetting + pkgSetting.setDebuggable(parsedPackage.isDebuggable()); + // Take care of first install / last update times. final long scanFileTime = getLastModifiedTime(parsedPackage); final long existingFirstInstallTime = userId == UserHandle.USER_ALL diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index 39565526f33e..0d16b009d9a5 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -3255,6 +3255,9 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile if (pkg.isPendingRestore()) { serializer.attributeBoolean(null, "pendingRestore", true); } + if (pkg.isDebuggable()) { + serializer.attributeBoolean(null, "debuggable", true); + } if (pkg.isLoading()) { serializer.attributeBoolean(null, "isLoading", true); } @@ -3269,7 +3272,6 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile serializer.attributeInt(null, "appMetadataSource", pkg.getAppMetadataSource()); - writeUsesSdkLibLPw(serializer, pkg.getUsesSdkLibraries(), pkg.getUsesSdkLibrariesVersionsMajor(), pkg.getUsesSdkLibrariesOptional()); @@ -4059,6 +4061,7 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile long versionCode = 0; boolean installedForceQueryable = false; boolean isPendingRestore = false; + boolean isDebuggable = false; float loadingProgress = 0; long loadingCompletedTime = 0; UUID domainSetId; @@ -4085,6 +4088,7 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile updateAvailable = parser.getAttributeBoolean(null, "updateAvailable", false); installedForceQueryable = parser.getAttributeBoolean(null, "forceQueryable", false); isPendingRestore = parser.getAttributeBoolean(null, "pendingRestore", false); + isDebuggable = parser.getAttributeBoolean(null, "debuggable", false); loadingProgress = parser.getAttributeFloat(null, "loadingProgress", 0); loadingCompletedTime = parser.getAttributeLongHex(null, "loadingCompletedTime", 0); @@ -4259,6 +4263,7 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile .setUpdateAvailable(updateAvailable) .setForceQueryableOverride(installedForceQueryable) .setPendingRestore(isPendingRestore) + .setDebuggable(isDebuggable) .setLoadingProgress(loadingProgress) .setLoadingCompletedTime(loadingCompletedTime) .setAppMetadataFilePath(appMetadataFilePath) diff --git a/services/core/java/com/android/server/pm/pkg/PackageState.java b/services/core/java/com/android/server/pm/pkg/PackageState.java index e0ee199a343d..58761886ecb9 100644 --- a/services/core/java/com/android/server/pm/pkg/PackageState.java +++ b/services/core/java/com/android/server/pm/pkg/PackageState.java @@ -274,6 +274,14 @@ public interface PackageState { boolean isPendingRestore(); /** + * @see ApplicationInfo#FLAG_DEBUGGABLE + * @see R.styleable#AndroidManifestApplication_debuggable + * @see AndroidPackage#isDebuggable + * @hide + */ + boolean isDebuggable(); + + /** * Retrieves the shared user app ID. Note that the actual shared user data is not available here * and must be queried separately. * diff --git a/services/core/java/com/android/server/policy/ModifierShortcutManager.java b/services/core/java/com/android/server/policy/ModifierShortcutManager.java index 3a79d0df4819..fde23b726572 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.ActivityNotFoundException; 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.util.SparseArray; 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.XmlPullParser; 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 @@ public class ModifierShortcutManager { 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 @@ public class ModifierShortcutManager { 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 @@ public class ModifierShortcutManager { 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 @@ public class ModifierShortcutManager { 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 9d0c0e9b36bb..8dc97566d1d2 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.InputDevice; 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 @@ public class PhoneWindowManager implements WindowManagerPolicy { 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 9ca4e273ac39..6c05d70f8513 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.util.proto.ProtoOutputStream; 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 interface WindowManagerPolicy extends WindowManagerPolicyConstants { 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/stats/flags.aconfig b/services/core/java/com/android/server/power/stats/flags.aconfig index 6a5a7ac8d240..d34498a3764b 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 @@ flag { 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/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java index ddbd8091b601..953aae9588dd 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.content.PackageMonitor; 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 @@ public class TrustManagerService extends SystemService { /* 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 @@ public class TrustManagerService extends SystemService { 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 @@ public class TrustManagerService extends SystemService { 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 @@ public class TrustManagerService extends SystemService { 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 000000000000..10beebb82711 --- /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 5565b6ffb5ac..af4025e1db7c 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.FileFilter; 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 @@ public class CertificateTransparencyLogInstallReceiver extends ConfigUpdateInsta @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 @@ public class CertificateTransparencyLogInstallReceiver extends ConfigUpdateInsta 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,62 +110,53 @@ public class CertificateTransparencyLogInstallReceiver extends ConfigUpdateInsta } 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); - } - - } - - /** - * 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); + throw new IOException("Malformed json in log list", e); } } - private void writeLogEntry(OutputStreamWriter out, String key, String value) - throws IOException { - out.write(key + ":" + value + "\n"); - } - private void deleteOldLogDirectories() throws IOException { if (!updateDir.exists()) { 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 000000000000..476cb3723c97 --- /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 5be5bc5e3952..2c734127b7ea 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.WHERE; 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 a1e71a8235da..3076b873058d 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -1102,11 +1102,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A pw.println(prefix + "mLastReportedConfigurations:"); mLastReportedConfiguration.dump(pw, prefix + " "); - if (Flags.activityWindowInfoFlag()) { - pw.print(prefix); - pw.print("mLastReportedActivityWindowInfo="); - pw.println(mLastReportedActivityWindowInfo); - } + pw.print(prefix); + pw.print("mLastReportedActivityWindowInfo="); + pw.println(mLastReportedActivityWindowInfo); pw.print(prefix); pw.print("CurrentConfiguration="); pw.println(getConfiguration()); if (!getRequestedOverrideConfiguration().equals(EMPTY)) { @@ -3161,7 +3159,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A @NonNull ActivityWindowInfo getActivityWindowInfo() { - if (!Flags.activityWindowInfoFlag() || !isAttached()) { + if (!isAttached()) { return mTmpActivityWindowInfo; } if (isFixedRotationTransforming()) { @@ -7175,7 +7173,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A 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 @@ -8284,9 +8282,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } void setLastReportedActivityWindowInfo(@NonNull ActivityWindowInfo activityWindowInfo) { - if (Flags.activityWindowInfoFlag()) { - mLastReportedActivityWindowInfo.set(activityWindowInfo); - } + mLastReportedActivityWindowInfo.set(activityWindowInfo); } @Nullable @@ -9702,8 +9698,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // the combine configurations are equal, but would otherwise differ in the override config mTmpConfig.setTo(mLastReportedConfiguration.getMergedConfiguration()); final ActivityWindowInfo newActivityWindowInfo = getActivityWindowInfo(); - final boolean isActivityWindowInfoChanged = Flags.activityWindowInfoFlag() - && !mLastReportedActivityWindowInfo.equals(newActivityWindowInfo); + final boolean isActivityWindowInfoChanged = + !mLastReportedActivityWindowInfo.equals(newActivityWindowInfo); if (!displayChanged && !isActivityWindowInfoChanged && getConfiguration().equals(mTmpConfig)) { ProtoLog.v(WM_DEBUG_CONFIGURATION, "Configuration & display " diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 59b5da8eeb51..ff46b33571f3 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 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { // 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 3652c4d11cb3..93711497f590 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 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp 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 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp 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 74dbd15d1399..b496a65ba4a6 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 @@ final class InputMonitor { // 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 @@ final class InputMonitor { 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 99697de6e2fa..f2ccbc4e1aeb 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 @@ class RootWindowContainer extends WindowContainer<DisplayContent> 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 @@ class RootWindowContainer extends WindowContainer<DisplayContent> 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 @@ class RootWindowContainer extends WindowContainer<DisplayContent> // 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 31fda77a8a7d..db0374e52b1a 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 @@ class ScreenRotationAnimation { 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 75e3e6547aa8..f5108f5bc93f 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 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { 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 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { 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 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { 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 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { 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 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { } if (changed && noSystemOverlayPermission) { - if (mAlertWindowSurfaces.isEmpty()) { + if (mAlertWindows.isEmpty()) { cancelAlertWindowNotification(); } else if (mAlertWindowNotification == null && !isSatellitePointingUiPackage()) { mAlertWindowNotification = new AlertWindowNotification(mService, mPackageName); @@ -815,7 +814,7 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { 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 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { } mSurfaceSession = null; mAddedWindows.clear(); - mAlertWindowSurfaces.clear(); + mAlertWindows.clear(); setHasOverlayUi(false); cancelAlertWindowNotification(); } @@ -880,7 +879,7 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { 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 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { /** @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 439c7bb4050b..561ff7db5b9d 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 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr } } - @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/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 803312214fc3..ebbf6e346e91 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.InsetsFrameProvider; 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.IKeyguardLockedStateListener; 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 @@ public class WindowManagerService extends IWindowManager.Stub // 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 @@ public class WindowManagerService extends IWindowManager.Stub 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 @@ public class WindowManagerService extends IWindowManager.Stub 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 @@ public class WindowManagerService extends IWindowManager.Stub 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 @@ public class WindowManagerService extends IWindowManager.Stub } @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 de73e6cfad12..9ebb89dfe9b6 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.WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME; 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 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP // 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; /** @@ -1101,7 +1105,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP mPolicy = mWmService.mPolicy; mContext = mWmService.mContext; mForceSeamlesslyRotate = token.mRoundedCornerOverlay; - mLastReportedActivityWindowInfo = Flags.activityWindowInfoFlag() && mActivityRecord != null + mLastReportedActivityWindowInfo = mActivityRecord != null ? new ActivityWindowInfo() : null; mInputWindowHandle = new InputWindowHandleWrapper(new InputWindowHandle( @@ -1497,7 +1501,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP 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 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP * 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 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP 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 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP 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 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP + 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 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } 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 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP 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 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP 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 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP 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 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP // 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 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } SurfaceControl getClientViewRootSurface() { - return mWinAnimator.getSurfaceControl(); + return mWinAnimator.mSurfaceControl; } /** Drops a buffer for this window's view-root from a transaction */ @@ -6094,11 +6117,10 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } 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 397a6357fb66..24a2a626fe50 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.WindowManagerService.logWithStack; 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.PixelFormat; 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 @@ class WindowStateAnimator { 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 @@ class WindowStateAnimator { mWin = win; mSession = win.mSession; mAttrType = win.mAttrs.type; - mIsWallpaper = win.mIsWallpaper; mWallpaperControllerLocked = win.getDisplayContent().mWallpaperController; } @@ -198,16 +204,32 @@ class WindowStateAnimator { } void hide(SurfaceControl.Transaction transaction, String reason) { - if (!mLastHidden) { - //dump(); - mLastHidden = true; + if (mLastHidden) { + return; + } + mLastHidden = true; + if (mSurfaceControl == null || !mSurfaceShown) { + return; + } + ProtoLog.i(WM_SHOW_TRANSACTIONS, "SURFACE HIDE ( %s ): %s", reason, mTitle); - if (mSurfaceController != null) { - mSurfaceController.hide(transaction, reason); - } + 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) { final boolean startingWindow = mWin.mAttrs.type == WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; @@ -221,7 +243,7 @@ class WindowStateAnimator { 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 @@ class WindowStateAnimator { 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 @@ class WindowStateAnimator { } } - 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 @@ class WindowStateAnimator { 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 @@ class WindowStateAnimator { 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 @@ class WindowStateAnimator { } 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 @@ class WindowStateAnimator { 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 @@ class WindowStateAnimator { 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 @@ class WindowStateAnimator { } } 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 @@ class WindowStateAnimator { 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 @@ class WindowStateAnimator { } } + private void showRobustly(SurfaceControl.Transaction t) { + if (mSurfaceShown) { + return; + } + + 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 (mSurfaceController == null) { + if (mSurfaceControl == null) { return; } - mSurfaceController.setOpaque(isOpaque); + 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 @@ class WindowStateAnimator { // 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 @@ class WindowStateAnimator { 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 @@ class WindowStateAnimator { 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 @@ class WindowStateAnimator { } 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 d9766e0dfa61..000000000000 --- 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 ba5323ee1f8f..21f7eca5627a 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 @@ package com.android.server.wm; 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 @@ class WindowTracing { 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 @@ class WindowTracing { 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); - } - } - } - - 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; + if (!android.tracing.Flags.perfettoProtologTracing() + && ProtoLog.getSingleInstance().isProtoEnabled()) { + ((LegacyProtoLogImpl) ProtoLog.getSingleInstance()).stopProtoLog(pw, true); + ((LegacyProtoLogImpl) ProtoLog.getSingleInstance()).startProtoLog(pw); } + saveForBugreportInternal(pw); } - 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 @@ class WindowTracing { 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 @@ class WindowTracing { } } - /** - * 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 000000000000..7a36707fd95a --- /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/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp index ed08d21b7f8a..5719810abc5a 100644 --- a/services/core/jni/com_android_server_input_InputManagerService.cpp +++ b/services/core/jni/com_android_server_input_InputManagerService.cpp @@ -314,6 +314,7 @@ public: void getReaderConfiguration(InputReaderConfiguration* outConfig) override; void notifyInputDevicesChanged(const std::vector<InputDeviceInfo>& inputDevices) override; + void notifyConfigurationChanged(nsecs_t when) override; std::shared_ptr<KeyCharacterMap> getKeyboardLayoutOverlay( const InputDeviceIdentifier& identifier, const std::optional<KeyboardLayoutInfo> keyboardLayoutInfo) override; @@ -331,7 +332,6 @@ public: void notifySwitch(nsecs_t when, uint32_t switchValues, uint32_t switchMask, uint32_t policyFlags) override; - void notifyConfigurationChanged(nsecs_t when) override; // ANR-related callbacks -- start void notifyNoFocusedWindowAnr(const std::shared_ptr<InputApplicationHandle>& handle) override; void notifyWindowUnresponsive(const sp<IBinder>& token, std::optional<gui::Pid> pid, diff --git a/services/manifest_services.xml b/services/manifest_services.xml index 8ff1a7dfa37b..114fe324f016 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 000000000000..c287643e8697 --- /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 3d40f6445834..927146db0a24 100644 --- a/services/net/Android.bp +++ b/services/net/Android.bp @@ -21,6 +21,7 @@ java_library_static { ":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 bacde101bf36..c2a069d17446 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.inputmethod.InputBindResult; 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 @@ public class InputMethodManagerServiceTestBase { .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 @@ public class InputMethodManagerServiceTestBase { 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/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java index 89b4aea216f9..71383069d08a 100644 --- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java +++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java @@ -1027,6 +1027,25 @@ public class PackageManagerSettingsTests { } @Test + public void testWriteReadDebuggable() { + Settings settings = makeSettings(); + PackageSetting packageSetting = createPackageSetting(PACKAGE_NAME_1); + packageSetting.setAppId(Process.FIRST_APPLICATION_UID); + packageSetting.setPkg(PackageImpl.forTesting(PACKAGE_NAME_1).hideAsParsed() + .setUid(packageSetting.getAppId()) + .hideAsFinal()); + + packageSetting.setDebuggable(true); + settings.mPackages.put(PACKAGE_NAME_1, packageSetting); + + settings.writeLPr(computer, /* sync= */ true); + settings.mPackages.clear(); + + assertThat(settings.readLPw(computer, createFakeUsers()), is(true)); + assertThat(settings.getPackageLPr(PACKAGE_NAME_1).isDebuggable(), is(true)); + } + + @Test public void testWriteReadArchiveState() { Settings settings = makeSettings(); PackageSetting packageSetting = createPackageSetting(PACKAGE_NAME_1); diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayBrightnessStateTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayBrightnessStateTest.java index f690b1bbfccf..2d4a29b3ed6f 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayBrightnessStateTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayBrightnessStateTest.java @@ -18,6 +18,8 @@ package com.android.server.display; import static org.junit.Assert.assertEquals; +import android.hardware.display.BrightnessInfo; + import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; @@ -112,7 +114,10 @@ public class DisplayBrightnessStateTest { .append("\n mBrightnessAdjustmentFlag:") .append(displayBrightnessState.getBrightnessAdjustmentFlag()) .append("\n mIsUserInitiatedChange:") - .append(displayBrightnessState.isUserInitiatedChange()); + .append(displayBrightnessState.isUserInitiatedChange()) + .append("\n mBrightnessMaxReason:") + .append(BrightnessInfo.briMaxReasonToString( + displayBrightnessState.getBrightnessMaxReason())); return sb.toString(); } } diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java index e982153acbd1..93dfbcb7f07f 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java @@ -30,7 +30,6 @@ import static org.mockito.Mockito.when; import android.content.Context; import android.hardware.SensorManager; -import android.hardware.display.BrightnessInfo; import android.hardware.display.DisplayManagerInternal; import android.os.Handler; import android.os.PowerManager; @@ -155,12 +154,6 @@ public class BrightnessClamperControllerTest { } @Test - public void testMaxReasonIsNoneOnInit() { - assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE, - mClamperController.getBrightnessMaxReason()); - } - - @Test public void testOnDisplayChanged_DelegatesToClamper() { mClamperController.onDisplayChanged(mMockDisplayDeviceData); diff --git a/services/tests/mockingservicestests/Android.bp b/services/tests/mockingservicestests/Android.bp index 4149e44a2ee9..5b2c0c651058 100644 --- a/services/tests/mockingservicestests/Android.bp +++ b/services/tests/mockingservicestests/Android.bp @@ -53,6 +53,7 @@ android_test { "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 00daf415375e..1a398c5f1ec3 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.RemoteException; 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; 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.Before; 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.HashMap; 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 @@ public class TrustManagerServiceTest { .build(); @Rule + public final SetFlagsRule mSetFlagsRule; + + @Rule public final MockContext mMockContext = new MockContext( ApplicationProvider.getApplicationContext()); @@ -162,6 +180,10 @@ public class TrustManagerServiceTest { 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 @@ public class TrustManagerServiceTest { } 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/location/contexthub/ContextHubEventLoggerTest.java b/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubEventLoggerTest.java index 8863d2796acf..41cb6fd99d9b 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 @@ package com.android.server.location.contexthub; 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 @@ import java.util.Arrays; 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 @@ public class ContextHubEventLoggerTest { 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 @@ public class ContextHubEventLoggerTest { 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 @@ public class ContextHubEventLoggerTest { 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 @@ public class ContextHubEventLoggerTest { 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(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(sInstanceDump.contains(eventString)).isTrue(); + assertThat(instanceDump.contains(eventString)).isTrue(); } } @@ -134,10 +184,10 @@ public class ContextHubEventLoggerTest { 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 777f6189fdea..6e6b70d319ab 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 000000000000..88419e9c441b --- /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 000000000000..8c375d413950 --- /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 0ed02dd0f429..59b08a53a530 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 @@ public class ModifierShortcutTests extends ShortcutKeyTestBase { 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 dff4984adb80..f5c8fb803fd8 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 static java.util.Collections.unmodifiableMap; 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 @@ class ShortcutKeyTestBase { 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 c1be5ca562b1..63e3e5cf865a 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 @@ public class RecentsAnimationControllerTest extends WindowTestsBase { // 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 11d9629cf25e..0bf27d11493b 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.assertEquals; 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 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { // 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 00a8842c358e..38ad9a7e0dca 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.GoToSleepReason; 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.internal.policy.IShortcutService; import com.android.server.policy.WindowManagerPolicy; import java.io.PrintWriter; +import java.util.Collections; class TestWindowManagerPolicy implements WindowManagerPolicy { @@ -362,4 +364,9 @@ class TestWindowManagerPolicy implements WindowManagerPolicy { 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 9b48cb9d328c..c65b76efd614 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 android.view.WindowManager.TRANSIT_OPEN; 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 @@ public class WallpaperControllerTests extends WindowTestsBase { } 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 fcf7a3fe79c1..89abe2ff0866 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 @@ public class WindowManagerServiceTests extends WindowTestsBase { 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 @@ public class WindowManagerServiceTests extends WindowTestsBase { // 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, @@ -1294,8 +1290,6 @@ public class WindowManagerServiceTests extends WindowTestsBase { @Test public void testRelayout_appWindowSendActivityWindowInfo() { - mSetFlagsRule.enableFlags(Flags.FLAG_ACTIVITY_WINDOW_INFO_FLAG); - // Skip unnecessary operations of relayout. spyOn(mWm.mWindowPlacerLocked); doNothing().when(mWm.mWindowPlacerLocked).performSurfacePlacement(anyBoolean()); diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTracingTest.java b/services/tests/wmtests/src/com/android/server/wm/WindowTracingLegacyTest.java index c1834037f791..48a8d5502c64 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 @@ import java.nio.charset.StandardCharsets; */ @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 @@ public class WindowTracingTest { 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/Internal/src/com/android/internal/protolog/LegacyProtoLogViewerConfigReaderTest.java b/tests/Internal/src/com/android/internal/protolog/LegacyProtoLogViewerConfigReaderTest.java new file mode 100644 index 000000000000..253965337824 --- /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 dbd85d38b7f2..be0e8bc0fc07 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.assertEquals; 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(); - @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(); - } + private final ViewerConfigInputStreamProvider mViewerConfigInputStreamProvider = + () -> new ProtoInputStream(TEST_VIEWER_CONFIG); + + private ProtoLogViewerConfigReader mConfig; - @After - public void tearDown() { - //noinspection ResultOfMethodCallIgnored - mTestViewerConfig.delete(); + @Before + public void before() { + mConfig = new ProtoLogViewerConfigReader(mViewerConfigInputStreamProvider); } @Test @@ -98,17 +100,26 @@ public class ProtoLogViewerConfigReaderTest { @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 2c9361df63fd..f9e004bcd29e 100644 --- a/tests/TrustTests/src/android/trust/test/UnlockAttemptTest.kt +++ b/tests/TrustTests/src/android/trust/test/UnlockAttemptTest.kt @@ -17,6 +17,7 @@ package android.trust.test 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 @@ class UnlockAttemptTest { 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/tools/aapt2/link/ManifestFixer.cpp b/tools/aapt2/link/ManifestFixer.cpp index cbf2c2fe8a9c..382b088e4bba 100644 --- a/tools/aapt2/link/ManifestFixer.cpp +++ b/tools/aapt2/link/ManifestFixer.cpp @@ -198,6 +198,7 @@ static bool UpdateConfigChangesIfNeeded(xml::Element* el, IAaptContext* context) android::DiagMessage(el->line_number) << "Cannot find symbol for android:configChanges with min sdk: " << context->GetMinSdkVersion()); + return false; } std::stringstream new_value; |