diff options
263 files changed, 5624 insertions, 1121 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp index fbe4905d9754..c6ce799f0a24 100644 --- a/AconfigFlags.bp +++ b/AconfigFlags.bp @@ -24,6 +24,7 @@ aconfig_declarations_group { "android-sdk-flags-java", "android.adaptiveauth.flags-aconfig-java", "android.app.appfunctions.flags-aconfig-java", + "android.app.assist.flags-aconfig-java", "android.app.contextualsearch.flags-aconfig-java", "android.app.flags-aconfig-java", "android.app.jank.flags-aconfig-java", @@ -64,6 +65,7 @@ aconfig_declarations_group { "android.server.app.flags-aconfig-java", "android.service.autofill.flags-aconfig-java", "android.service.chooser.flags-aconfig-java", + "android.service.compat.flags-aconfig-java", "android.service.controls.flags-aconfig-java", "android.service.dreams.flags-aconfig-java", "android.service.notification.flags-aconfig-java", @@ -862,6 +864,21 @@ java_aconfig_library { defaults: ["framework-minus-apex-aconfig-java-defaults"], } +aconfig_declarations { + name: "android.service.compat.flags-aconfig", + package: "com.android.server.compat", + container: "system", + srcs: [ + "services/core/java/com/android/server/compat/*.aconfig", + ], +} + +java_aconfig_library { + name: "android.service.compat.flags-aconfig-java", + aconfig_declarations: "android.service.compat.flags-aconfig", + defaults: ["framework-minus-apex-aconfig-java-defaults"], +} + // Multi user aconfig_declarations { name: "android.multiuser.flags-aconfig", @@ -1230,6 +1247,20 @@ java_aconfig_library { defaults: ["framework-minus-apex-aconfig-java-defaults"], } +// Assist +aconfig_declarations { + name: "android.app.assist.flags-aconfig", + package: "android.app.assist.flags", + container: "system", + srcs: ["core/java/android/app/assist/flags.aconfig"], +} + +java_aconfig_library { + name: "android.app.assist.flags-aconfig-java", + aconfig_declarations: "android.app.assist.flags-aconfig", + defaults: ["framework-minus-apex-aconfig-java-defaults"], +} + // Smartspace aconfig_declarations { name: "android.app.smartspace.flags-aconfig", diff --git a/apex/jobscheduler/service/aconfig/job.aconfig b/apex/jobscheduler/service/aconfig/job.aconfig index e5389b4f96fb..11c5b51e23ae 100644 --- a/apex/jobscheduler/service/aconfig/job.aconfig +++ b/apex/jobscheduler/service/aconfig/job.aconfig @@ -75,3 +75,10 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + name: "enforce_quota_policy_to_fgs_jobs" + namespace: "backstage_power" + description: "Applies the normal quota policy to FGS jobs" + bug: "341201311" +} diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java index a1c72fb4c06c..03a3a0d51891 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java @@ -99,10 +99,10 @@ import java.util.function.Predicate; * the number of jobs or sessions that can run within the window. Regardless of bucket, apps will * not be allowed to run more than 20 jobs within the past 10 minutes. * - * Jobs are throttled while an app is not in a foreground state. All jobs are allowed to run - * freely when an app enters the foreground state and are restricted when the app leaves the - * foreground state. However, jobs that are started while the app is in the TOP state do not count - * towards any quota and are not restricted regardless of the app's state change. + * Jobs are throttled while an app is not in a TOP or BOUND_TOP state. All jobs are allowed to run + * freely when an app enters the TOP or BOUND_TOP state and are restricted when the app leaves those + * states. However, jobs that are started while the app is in the TOP state do not count towards any + * quota and are not restricted regardless of the app's state change. * * Jobs will not be throttled when the device is charging. The device is considered to be charging * once the {@link BatteryManager#ACTION_CHARGING} intent has been broadcast. @@ -567,6 +567,11 @@ public final class QuotaController extends StateController { ActivityManager.getService().registerUidObserver(new QcUidObserver(), ActivityManager.UID_OBSERVER_PROCSTATE, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, null); + if (Flags.enforceQuotaPolicyToFgsJobs()) { + ActivityManager.getService().registerUidObserver(new QcUidObserver(), + ActivityManager.UID_OBSERVER_PROCSTATE, + ActivityManager.PROCESS_STATE_BOUND_TOP, null); + } ActivityManager.getService().registerUidObserver(new QcUidObserver(), ActivityManager.UID_OBSERVER_PROCSTATE, ActivityManager.PROCESS_STATE_TOP, null); @@ -2706,6 +2711,12 @@ public final class QuotaController extends StateController { } } + @VisibleForTesting + int getProcessStateQuotaFreeThreshold() { + return Flags.enforceQuotaPolicyToFgsJobs() ? ActivityManager.PROCESS_STATE_BOUND_TOP : + ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE; + } + private class QcHandler extends Handler { QcHandler(Looper looper) { @@ -2832,15 +2843,15 @@ public final class QuotaController extends StateController { mTopAppCache.put(uid, true); mTopAppGraceCache.delete(uid); if (mForegroundUids.get(uid)) { - // Went from FGS to TOP. We don't need to reprocess timers or - // jobs. + // Went from a process state with quota free to TOP. We don't + // need to reprocess timers or jobs. break; } mForegroundUids.put(uid, true); isQuotaFree = true; } else { final boolean reprocess; - if (procState <= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) { + if (procState <= getProcessStateQuotaFreeThreshold()) { reprocess = !mForegroundUids.get(uid); mForegroundUids.put(uid, true); isQuotaFree = true; diff --git a/core/java/android/app/assist/AssistStructure.java b/core/java/android/app/assist/AssistStructure.java index 508077ed43cc..1af2437a5d6a 100644 --- a/core/java/android/app/assist/AssistStructure.java +++ b/core/java/android/app/assist/AssistStructure.java @@ -1,5 +1,6 @@ package android.app.assist; +import static android.app.assist.flags.Flags.addPlaceholderViewForNullChild; import static android.credentials.Constants.FAILURE_CREDMAN_SELECTOR; import static android.credentials.Constants.SUCCESS_CREDMAN_SELECTOR; import static android.service.autofill.Flags.FLAG_AUTOFILL_CREDMAN_DEV_INTEGRATION; @@ -284,12 +285,18 @@ public class AssistStructure implements Parcelable { mCurViewStackEntry = entry; } - void writeView(ViewNode child, Parcel out, PooledStringWriter pwriter, int levelAdj) { + void writeView(@Nullable ViewNode child, Parcel out, PooledStringWriter pwriter, + int levelAdj) { if (DEBUG_PARCEL) Log.d(TAG, "write view: at " + out.dataPosition() + ", windows=" + mNumWrittenWindows + ", views=" + mNumWrittenViews + ", level=" + (mCurViewStackPos+levelAdj)); out.writeInt(VALIDATE_VIEW_TOKEN); + if (addPlaceholderViewForNullChild() && child == null) { + if (DEBUG_PARCEL_TREE) Log.d(TAG, "Detected an empty child" + + "; writing a placeholder for the child."); + child = new ViewNode(); + } int flags = child.writeSelfToParcel(out, pwriter, mSanitizeOnWrite, mTmpMatrix, /*willWriteChildren=*/true); mNumWrittenViews++; @@ -2545,7 +2552,7 @@ public class AssistStructure implements Parcelable { ensureData(); } Log.i(TAG, "Task id: " + mTaskId); - Log.i(TAG, "Activity: " + (mActivityComponent != null + Log.i(TAG, "Activity: " + (mActivityComponent != null ? mActivityComponent.flattenToShortString() : null)); Log.i(TAG, "Sanitize on write: " + mSanitizeOnWrite); diff --git a/core/java/android/app/assist/flags.aconfig b/core/java/android/app/assist/flags.aconfig new file mode 100644 index 000000000000..bf0aeacbc7f3 --- /dev/null +++ b/core/java/android/app/assist/flags.aconfig @@ -0,0 +1,13 @@ +package: "android.app.assist.flags" +container: "system" + +flag { + name: "add_placeholder_view_for_null_child" + namespace: "machine_learning" + description: "Flag to add a placeholder view when a child view is null." + bug: "369503426" + is_fixed_read_only: true + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java index 96700a9a3c18..70211bfd77b1 100644 --- a/core/java/android/companion/virtual/VirtualDeviceManager.java +++ b/core/java/android/companion/virtual/VirtualDeviceManager.java @@ -1135,8 +1135,11 @@ public final class VirtualDeviceManager { /** * Sets the visibility of the pointer icon for this VirtualDevice's associated displays. * + * <p>Only applicable to trusted displays.</p> + * * @param showPointerIcon True if the pointer should be shown; false otherwise. The default * visibility is true. + * @see DisplayManager#VIRTUAL_DISPLAY_FLAG_TRUSTED */ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void setShowPointerIcon(boolean showPointerIcon) { diff --git a/core/java/android/companion/virtual/VirtualDeviceParams.java b/core/java/android/companion/virtual/VirtualDeviceParams.java index 03b72bdb8823..65f9cbefb052 100644 --- a/core/java/android/companion/virtual/VirtualDeviceParams.java +++ b/core/java/android/companion/virtual/VirtualDeviceParams.java @@ -159,7 +159,7 @@ public final class VirtualDeviceParams implements Parcelable { * @hide */ @IntDef(prefix = "POLICY_TYPE_", value = {POLICY_TYPE_SENSORS, POLICY_TYPE_AUDIO, - POLICY_TYPE_RECENTS, POLICY_TYPE_ACTIVITY, POLICY_TYPE_CAMERA, + POLICY_TYPE_RECENTS, POLICY_TYPE_ACTIVITY, POLICY_TYPE_CLIPBOARD, POLICY_TYPE_CAMERA, POLICY_TYPE_BLOCKED_ACTIVITY}) @Retention(RetentionPolicy.SOURCE) @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE}) @@ -220,11 +220,16 @@ public final class VirtualDeviceParams implements Parcelable { * Tells the activity manager how to handle recents entries for activities run on this device. * * <ul> - * <li>{@link #DEVICE_POLICY_DEFAULT}: Activities launched on VirtualDisplays owned by this + * <li>{@link #DEVICE_POLICY_DEFAULT}: Activities launched on trusted displays owned by this * device will appear in the host device recents. - * <li>{@link #DEVICE_POLICY_CUSTOM}: Activities launched on VirtualDisplays owned by this + * <li>{@link #DEVICE_POLICY_CUSTOM}: Activities launched on trusted displays owned by this * device will not appear in recents. * </ul> + * + * <p>Activities launched on untrusted displays will always show in the host device recents, + * regardless of the policy.</p> + * + * @see android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_TRUSTED */ public static final int POLICY_TYPE_RECENTS = 2; @@ -254,8 +259,10 @@ public final class VirtualDeviceParams implements Parcelable { * not shared with other devices' clipboards, including the clipboard of the default device. * <li>{@link #DEVICE_POLICY_CUSTOM}: The device's clipboard is shared with the default * device's clipboard. Any clipboard operation on the virtual device is as if it was done on - * the default device. + * the default device. Requires all displays of the virtual device to be trusted. * </ul> + * + * @see android.hardware.display.DisplayManager#VIRTUAL_DISPLAY_FLAG_TRUSTED */ @FlaggedApi(Flags.FLAG_CROSS_DEVICE_CLIPBOARD) public static final int POLICY_TYPE_CLIPBOARD = 4; @@ -821,8 +828,8 @@ public final class VirtualDeviceParams implements Parcelable { } /** - * Specifies a component to be used as input method on all displays owned by this virtual - * device. + * Specifies a component to be used as input method on all trusted displays owned by this + * virtual device. * * @param inputMethodComponent The component name to be used as input method. Must comply to * all general input method requirements described in the guide to @@ -831,6 +838,7 @@ public final class VirtualDeviceParams implements Parcelable { * may interact with the virtual device, then there will effectively be no IME on this * device's displays for that user. * + * @see android.hardware.display.DisplayManager#VIRTUAL_DISPLAY_FLAG_TRUSTED * @see android.inputmethodservice.InputMethodService * @attr ref android.R.styleable#InputMethod_isVirtualDeviceOnly * @attr ref android.R.styleable#InputMethod_showInInputMethodPicker diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig index dcf82bf35828..ff0a3ddc746e 100644 --- a/core/java/android/content/pm/multiuser.aconfig +++ b/core/java/android/content/pm/multiuser.aconfig @@ -47,13 +47,6 @@ flag { } flag { - name: "start_user_before_scheduled_alarms" - namespace: "multiuser" - description: "Persist list of users with alarms scheduled and wakeup stopped users before alarms are due" - bug: "314907186" -} - -flag { name: "add_ui_for_sounds_from_background_users" namespace: "multiuser" description: "Allow foreground user to dismiss sounds that are coming from background users" diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java index 6c1aa90c831b..75ffcc3a8863 100644 --- a/core/java/android/hardware/display/DisplayManagerInternal.java +++ b/core/java/android/hardware/display/DisplayManagerInternal.java @@ -461,6 +461,16 @@ public abstract class DisplayManagerInternal { public abstract void stylusGestureStarted(long eventTime); /** + * Called by {@link com.android.server.wm.ContentRecorder} to verify whether + * the display is allowed to mirror primary display's content. + * @param displayId the id of the display where we mirror to. + * @return true if the mirroring dialog is confirmed (display is enabled), or + * {@link com.android.server.display.ExternalDisplayPolicy#ENABLE_ON_CONNECT} + * system property is enabled. + */ + public abstract boolean isDisplayReadyForMirroring(int displayId); + + /** * Describes the requested power state of the display. * * This object is intended to describe the general characteristics of the diff --git a/core/java/android/hardware/input/InputSettings.java b/core/java/android/hardware/input/InputSettings.java index 177ee6f1540a..897ce4a7b022 100644 --- a/core/java/android/hardware/input/InputSettings.java +++ b/core/java/android/hardware/input/InputSettings.java @@ -24,6 +24,8 @@ import static com.android.hardware.input.Flags.keyboardA11yBounceKeysFlag; import static com.android.hardware.input.Flags.keyboardA11ySlowKeysFlag; import static com.android.hardware.input.Flags.keyboardA11yStickyKeysFlag; import static com.android.hardware.input.Flags.keyboardA11yMouseKeys; +import static com.android.hardware.input.Flags.mouseReverseVerticalScrolling; +import static com.android.hardware.input.Flags.mouseSwapPrimaryButton; import static com.android.hardware.input.Flags.touchpadTapDragging; import static com.android.hardware.input.Flags.touchpadVisualizer; import static com.android.input.flags.Flags.enableInputFilterRustImpl; @@ -363,6 +365,22 @@ public class InputSettings { } /** + * Returns true if the feature flag for mouse reverse vertical scrolling is enabled. + * @hide + */ + public static boolean isMouseReverseVerticalScrollingFeatureFlagEnabled() { + return mouseReverseVerticalScrolling(); + } + + /** + * Returns true if the feature flag for mouse swap primary button is enabled. + * @hide + */ + public static boolean isMouseSwapPrimaryButtonFeatureFlagEnabled() { + return mouseSwapPrimaryButton(); + } + + /** * Returns true if the touchpad visualizer is allowed to appear. * * @param context The application context. @@ -501,6 +519,86 @@ public class InputSettings { } /** + * Whether mouse vertical scrolling is enabled, this applies only to connected mice. + * + * @param context The application context. + * @return Whether the mouse will have its vertical scrolling reversed + * (scroll down to move up). + * + * @hide + */ + public static boolean isMouseReverseVerticalScrollingEnabled(@NonNull Context context) { + if (!isMouseReverseVerticalScrollingFeatureFlagEnabled()) { + return false; + } + + return Settings.System.getIntForUser(context.getContentResolver(), + Settings.System.MOUSE_REVERSE_VERTICAL_SCROLLING, 0, UserHandle.USER_CURRENT) + != 0; + } + + /** + * Sets whether the connected mouse will have its vertical scrolling reversed. + * + * @param context The application context. + * @param reverseScrolling Whether reverse scrolling is enabled. + * + * @hide + */ + @RequiresPermission(Manifest.permission.WRITE_SETTINGS) + public static void setMouseReverseVerticalScrolling(@NonNull Context context, + boolean reverseScrolling) { + if (!isMouseReverseVerticalScrollingFeatureFlagEnabled()) { + return; + } + + Settings.System.putIntForUser(context.getContentResolver(), + Settings.System.MOUSE_REVERSE_VERTICAL_SCROLLING, reverseScrolling ? 1 : 0, + UserHandle.USER_CURRENT); + } + + /** + * Whether the primary mouse button is swapped on connected mice. + * + * @param context The application context. + * @return Whether mice will have their primary buttons swapped, so that left clicking will + * perform the secondary action (e.g. show menu) and right clicking will perform the primary + * action. + * + * @hide + */ + public static boolean isMouseSwapPrimaryButtonEnabled(@NonNull Context context) { + if (!isMouseSwapPrimaryButtonFeatureFlagEnabled()) { + return false; + } + + return Settings.System.getIntForUser(context.getContentResolver(), + Settings.System.MOUSE_SWAP_PRIMARY_BUTTON, 0, UserHandle.USER_CURRENT) + != 0; + } + + /** + * Sets whether mice will have their primary buttons swapped between left and right + * clicks. + * + * @param context The application context. + * @param swapPrimaryButton Whether swapping the primary button is enabled. + * + * @hide + */ + @RequiresPermission(Manifest.permission.WRITE_SETTINGS) + public static void setMouseSwapPrimaryButton(@NonNull Context context, + boolean swapPrimaryButton) { + if (!isMouseSwapPrimaryButtonFeatureFlagEnabled()) { + return; + } + + Settings.System.putIntForUser(context.getContentResolver(), + Settings.System.MOUSE_SWAP_PRIMARY_BUTTON, swapPrimaryButton ? 1 : 0, + UserHandle.USER_CURRENT); + } + + /** * Whether Accessibility bounce keys feature is enabled. * * <p> diff --git a/core/java/android/hardware/input/VirtualInputDeviceConfig.java b/core/java/android/hardware/input/VirtualInputDeviceConfig.java index e8ef8cd11585..3b74d7f5253d 100644 --- a/core/java/android/hardware/input/VirtualInputDeviceConfig.java +++ b/core/java/android/hardware/input/VirtualInputDeviceConfig.java @@ -163,7 +163,6 @@ public abstract class VirtualInputDeviceConfig { return self(); } - /** * Sets the product id of the device, uniquely identifying the device within the address * space of a given vendor, identified by the device's vendor id. @@ -179,6 +178,10 @@ public abstract class VirtualInputDeviceConfig { * * <p>The input device is restricted to the display with the given ID and may not send * events to any other display.</p> + * <p>The corresponding display must be trusted or mirror display.</p> + * + * @see android.hardware.display.DisplayManager#VIRTUAL_DISPLAY_FLAG_TRUSTED + * @see android.hardware.display.DisplayManager#VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR */ @NonNull public T setAssociatedDisplayId(int displayId) { diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index 3ae951170759..f1964e7bc024 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -6368,8 +6368,12 @@ public class UserManager { Settings.Global.DEVICE_DEMO_MODE, 0) > 0; } - /** @hide */ - public static final void invalidateUserSerialNumberCache() { + + /** + * This method is used to invalidate caches, when user was added or removed. + * @hide + */ + public static final void invalidateCacheOnUserListChange() { UserManagerCache.invalidateUserSerialNumber(); } @@ -6382,7 +6386,7 @@ public class UserManager { * @hide */ @UnsupportedAppUsage - @CachedProperty(modsFlagOnOrNone = {}) + @CachedProperty(modsFlagOnOrNone = {}, api = "user_manager_users") public int getUserSerialNumber(@UserIdInt int userId) { // Read only flag should is to fix early access to this API // cacheUserSerialNumber to be removed after the diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig index a1bfe39c0fc4..81987907452f 100644 --- a/core/java/android/os/flags.aconfig +++ b/core/java/android/os/flags.aconfig @@ -232,3 +232,19 @@ flag { bug: "361329788" is_exported: true } + +flag { + name: "enable_angle_allow_list" + namespace: "gpu" + description: "Whether to read from angle allowlist to determine if app should use ANGLE" + is_fixed_read_only: true + bug: "370845648" +} + +flag { + name: "api_for_backported_fixes" + namespace: "media_reliability" + description: "Public API app developers use to check if a known issue is fixed on a device." + bug: "308461809" + is_exported: true +} diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig index feeb339c1200..1d8fcec8cf31 100644 --- a/core/java/android/permission/flags.aconfig +++ b/core/java/android/permission/flags.aconfig @@ -255,3 +255,14 @@ flag { description: "This fixed read-only flag is used to enable replacing permission BODY_SENSORS (and BODY_SENSORS_BACKGROUND) with granular health permission READ_HEART_RATE (and READ_HEALTH_DATA_IN_BACKGROUND)" bug: "364638912" } + +flag { + name: "delay_uid_state_changes_from_capability_updates" + is_fixed_read_only: true + namespace: "permissions" + description: "If proc state is decreasing over the restriction threshold and capability is changed, delay if no new capabilities are added" + bug: "347891382" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 1a15d09c0a50..594005c3ebd6 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -2351,6 +2351,11 @@ public final class Settings { /** * Activity Action: Show the permission screen for allowing apps to post promoted notifications. + * Properly formatted priority notifications are elevated in appearance. For example they may be + * able to use colors, have richer progress bars, show as chips in the status bar, and/or + * permanently appear on always-on-displays. This functionality is intended to be reserved for + * user initiated ongoing activities like navigation, phone calls, and ride sharing. + * * <p> * Input: {@link #EXTRA_APP_PACKAGE}, the package to display. * <p> @@ -6205,6 +6210,25 @@ public final class Settings { public static final String TOUCHPAD_RIGHT_CLICK_ZONE = "touchpad_right_click_zone"; /** + * Whether to enable reversed vertical scrolling for connected mice. + * + * When enabled, scrolling down on the mouse wheel will move the screen up and vice versa. + * @hide + */ + public static final String MOUSE_REVERSE_VERTICAL_SCROLLING = + "mouse_reverse_vertical_scrolling"; + + /** + * Whether to enable swapping the primary button for connected mice. + * + * When enabled, right clicking will be the primary button and left clicking will be the + * secondary button (e.g. show menu). + * @hide + */ + public static final String MOUSE_SWAP_PRIMARY_BUTTON = + "mouse_swap_primary_button"; + + /** * Pointer fill style, specified by * {@link android.view.PointerIcon.PointerIconVectorStyleFill} constants. * @@ -6442,6 +6466,8 @@ public final class Settings { PRIVATE_SETTINGS.add(SCREEN_FLASH_NOTIFICATION); PRIVATE_SETTINGS.add(SCREEN_FLASH_NOTIFICATION_COLOR); PRIVATE_SETTINGS.add(DEFAULT_DEVICE_FONT_SCALE); + PRIVATE_SETTINGS.add(MOUSE_REVERSE_VERTICAL_SCROLLING); + PRIVATE_SETTINGS.add(MOUSE_SWAP_PRIMARY_BUTTON); } /** diff --git a/core/java/android/window/DesktopModeFlags.java b/core/java/android/window/DesktopModeFlags.java index 6fb82af2b292..8e35843e2193 100644 --- a/core/java/android/window/DesktopModeFlags.java +++ b/core/java/android/window/DesktopModeFlags.java @@ -65,7 +65,9 @@ public enum DesktopModeFlags { ENABLE_DESKTOP_WINDOWING_TASKBAR_RUNNING_APPS( Flags::enableDesktopWindowingTaskbarRunningApps, true), ENABLE_DESKTOP_WINDOWING_TRANSITIONS(Flags::enableDesktopWindowingTransitions, false), - ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS(Flags::enableDesktopWindowingExitTransitions, false); + ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS(Flags::enableDesktopWindowingExitTransitions, false), + ENABLE_WINDOWING_TRANSITION_HANDLERS_OBSERVERS( + Flags::enableWindowingTransitionHandlersObservers, false); private static final String TAG = "DesktopModeFlagsUtil"; // Function called to obtain aconfig flag value. diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig index 31bb3a610376..155494fb3b25 100644 --- a/core/java/android/window/flags/lse_desktop_experience.aconfig +++ b/core/java/android/window/flags/lse_desktop_experience.aconfig @@ -228,7 +228,7 @@ flag { name: "enable_desktop_windowing_app_handle_education" namespace: "lse_desktop_experience" description: "Enables desktop windowing app handle education" - bug: "348208342" + bug: "316006079" } flag { diff --git a/core/jni/android_view_WindowManagerGlobal.cpp b/core/jni/android_view_WindowManagerGlobal.cpp index abc621d8dc90..4202de39adb0 100644 --- a/core/jni/android_view_WindowManagerGlobal.cpp +++ b/core/jni/android_view_WindowManagerGlobal.cpp @@ -69,8 +69,8 @@ void removeInputChannel(const sp<IBinder>& clientToken) { JNIEnv* env = AndroidRuntime::getJNIEnv(); ScopedLocalRef<jobject> clientTokenObj(env, javaObjectForIBinder(env, clientToken)); - env->CallStaticObjectMethod(gWindowManagerGlobal.clazz, gWindowManagerGlobal.removeInputChannel, - clientTokenObj.get()); + env->CallStaticVoidMethod(gWindowManagerGlobal.clazz, gWindowManagerGlobal.removeInputChannel, + clientTokenObj.get()); } int register_android_view_WindowManagerGlobal(JNIEnv* env) { @@ -88,4 +88,4 @@ int register_android_view_WindowManagerGlobal(JNIEnv* env) { return NO_ERROR; } -} // namespace android
\ No newline at end of file +} // namespace android diff --git a/core/jni/platform/host/HostRuntime.cpp b/core/jni/platform/host/HostRuntime.cpp index 88b3e1c1ed9d..27417c0c9929 100644 --- a/core/jni/platform/host/HostRuntime.cpp +++ b/core/jni/platform/host/HostRuntime.cpp @@ -115,9 +115,9 @@ static const std::unordered_map<std::string, RegJNIRec> gRegJNIMap = { #ifdef __linux__ {"android.content.res.ApkAssets", REG_JNI(register_android_content_res_ApkAssets)}, {"android.content.res.AssetManager", REG_JNI(register_android_content_AssetManager)}, +#endif {"android.content.res.StringBlock", REG_JNI(register_android_content_StringBlock)}, {"android.content.res.XmlBlock", REG_JNI(register_android_content_XmlBlock)}, -#endif {"android.database.CursorWindow", REG_JNI(register_android_database_CursorWindow)}, {"android.database.sqlite.SQLiteConnection", REG_JNI(register_android_database_SQLiteConnection)}, diff --git a/core/proto/android/providers/settings/system.proto b/core/proto/android/providers/settings/system.proto index e795e8096641..9779dc0e00b8 100644 --- a/core/proto/android/providers/settings/system.proto +++ b/core/proto/android/providers/settings/system.proto @@ -220,6 +220,15 @@ message SystemSettingsProto { } optional Touchpad touchpad = 36; + message Mouse { + option (android.msg_privacy).dest = DEST_EXPLICIT; + + optional SettingProto reverse_vertical_scrolling = 1 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto swap_primary_button = 2 [ (android.privacy).dest = DEST_AUTOMATIC ]; + } + + optional Mouse mouse = 38; + optional SettingProto tty_mode = 31 [ (android.privacy).dest = DEST_AUTOMATIC ]; message Vibrate { @@ -277,5 +286,5 @@ message SystemSettingsProto { // Please insert fields in alphabetical order and group them into messages // if possible (to avoid reaching the method limit). - // Next tag = 38; + // Next tag = 39; } diff --git a/core/tests/coretests/src/android/app/ActivityManagerTest.java b/core/tests/coretests/src/android/app/ActivityManagerTest.java index d850f86070bc..85ff8463725e 100644 --- a/core/tests/coretests/src/android/app/ActivityManagerTest.java +++ b/core/tests/coretests/src/android/app/ActivityManagerTest.java @@ -60,7 +60,6 @@ public class ActivityManagerTest { public void testProcState() throws Exception { // For the moment mostly want to confirm we don't crash assertNotNull(ActivityManager.procStateToString(PROCESS_STATE_SERVICE)); - assertNotNull(ActivityManager.processStateAmToProto(PROCESS_STATE_SERVICE)); assertTrue(ActivityManager.isProcStateBackground(PROCESS_STATE_SERVICE)); assertFalse(ActivityManager.isProcStateCached(PROCESS_STATE_SERVICE)); assertFalse(ActivityManager.isForegroundService(PROCESS_STATE_SERVICE)); diff --git a/core/tests/coretests/src/android/app/activity/ActivityManagerTest.java b/core/tests/coretests/src/android/app/activity/ActivityManagerTest.java index b972882e68e6..cd524214e6af 100644 --- a/core/tests/coretests/src/android/app/activity/ActivityManagerTest.java +++ b/core/tests/coretests/src/android/app/activity/ActivityManagerTest.java @@ -111,12 +111,6 @@ public class ActivityManagerTest extends AndroidTestCase { assertEquals(config.reqKeyboardType, vconfig.keyboard); assertEquals(config.reqTouchScreen, vconfig.touchscreen); assertEquals(config.reqNavigation, vconfig.navigation); - if (vconfig.navigation == Configuration.NAVIGATION_NONAV) { - assertNotNull(config.reqInputFeatures & ConfigurationInfo.INPUT_FEATURE_FIVE_WAY_NAV); - } - if (vconfig.keyboard != Configuration.KEYBOARD_UNDEFINED) { - assertNotNull(config.reqInputFeatures & ConfigurationInfo.INPUT_FEATURE_HARD_KEYBOARD); - } } @SmallTest diff --git a/core/tests/coretests/src/android/app/assist/AssistStructureTest.java b/core/tests/coretests/src/android/app/assist/AssistStructureTest.java index a28b2f66aaa8..51e79e7ac4e1 100644 --- a/core/tests/coretests/src/android/app/assist/AssistStructureTest.java +++ b/core/tests/coretests/src/android/app/assist/AssistStructureTest.java @@ -24,6 +24,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; import android.app.assist.AssistStructure.ViewNode; import android.app.assist.AssistStructure.ViewNodeBuilder; @@ -37,6 +38,7 @@ import android.os.Bundle; import android.os.LocaleList; import android.os.OutcomeReceiver; import android.os.Parcel; +import android.os.PooledStringWriter; import android.os.SystemClock; import android.text.InputFilter; import android.util.Log; @@ -355,6 +357,18 @@ public class AssistStructureTest { } + @Test + public void testParcelTransferWriter_writeNull() { + AssistStructure structure = new AssistStructure(mActivity, FOR_AUTOFILL, NO_FLAGS); + Parcel parcel = Parcel.obtain(); + AssistStructure.ParcelTransferWriter writer = + new AssistStructure.ParcelTransferWriter(structure, parcel); + writer.writeView(null, parcel, new PooledStringWriter(parcel), 0); + + // No throw any exception. + assertTrue(true); + } + private EditText newSmallView() { EditText view = new EditText(mContext); view.setText("I AM GROOT"); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt index 5a277316ffd4..379e052e7b38 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt @@ -156,6 +156,21 @@ class DesktopModeEventLogger { ) } + fun logTaskInfoStateInit() { + logTaskUpdate( + FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_INIT_STATSD, + /* session_id */ 0, + TaskUpdate( + visibleTaskCount = 0, + instanceId = 0, + uid = 0, + taskHeight = 0, + taskWidth = 0, + taskX = 0, + taskY = 0) + ) + } + private fun logTaskUpdate(taskEvent: Int, sessionId: Int, taskUpdate: TaskUpdate) { FrameworkStatsLog.write( DESKTOP_MODE_TASK_UPDATE_ATOM_ID, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt index b8507e3b2764..f847aa8918c2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt @@ -102,6 +102,7 @@ class DesktopModeLoggerTransitionObserver( SystemProperties.set( VISIBLE_TASKS_COUNTER_SYSTEM_PROPERTY, VISIBLE_TASKS_COUNTER_SYSTEM_PROPERTY_DEFAULT_VALUE) + desktopModeEventLogger.logTaskInfoStateInit() } override fun onTransitionReady( diff --git a/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/OpenTransparentActivityTest.kt b/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/OpenTransparentActivityTest.kt index 2980d5113bba..e176f47d4094 100644 --- a/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/OpenTransparentActivityTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/OpenTransparentActivityTest.kt @@ -17,7 +17,6 @@ package com.android.wm.shell.flicker.appcompat import android.platform.test.annotations.Postsubmit -import android.tools.Rotation import android.tools.flicker.assertions.FlickerTest import android.tools.flicker.junit.FlickerParametersRunnerFactory import android.tools.flicker.legacy.FlickerBuilder @@ -109,9 +108,7 @@ class OpenTransparentActivityTest(flicker: LegacyFlickerTest) : TransparentBaseA @Parameterized.Parameters(name = "{0}") @JvmStatic fun getParams(): Collection<FlickerTest> { - return LegacyFlickerTestFactory.nonRotationTests( - supportedRotations = listOf(Rotation.ROTATION_90) - ) + return LegacyFlickerTestFactory.nonRotationTests() } } } diff --git a/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/QuickSwitchLauncherToLetterboxAppTest.kt b/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/QuickSwitchLauncherToLetterboxAppTest.kt index 2484f675b236..9b8c949a1705 100644 --- a/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/QuickSwitchLauncherToLetterboxAppTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/QuickSwitchLauncherToLetterboxAppTest.kt @@ -20,7 +20,6 @@ import android.graphics.Rect import android.platform.test.annotations.Postsubmit import android.platform.test.annotations.RequiresDevice import android.tools.NavBar -import android.tools.Rotation import android.tools.flicker.assertions.FlickerTest import android.tools.flicker.junit.FlickerParametersRunnerFactory import android.tools.flicker.legacy.FlickerBuilder @@ -266,8 +265,7 @@ class QuickSwitchLauncherToLetterboxAppTest(flicker: LegacyFlickerTest) : BaseAp @JvmStatic fun getParams(): Collection<FlickerTest> { return LegacyFlickerTestFactory.nonRotationTests( - supportedNavigationModes = listOf(NavBar.MODE_GESTURAL), - supportedRotations = listOf(Rotation.ROTATION_90) + supportedNavigationModes = listOf(NavBar.MODE_GESTURAL) ) } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt index d7a132dfa1be..dde9fda13ea9 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt @@ -16,7 +16,6 @@ package com.android.wm.shell.desktopmode -import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags import android.platform.test.flag.junit.SetFlagsRule import com.android.dx.mockito.inline.extended.ExtendedMockito.verify @@ -397,6 +396,37 @@ class DesktopModeEventLoggerTest : ShellTestCase() { } } + @Test + fun logTaskInfoStateInit_logsTaskInfoChangedStateInit() { + desktopModeEventLogger.logTaskInfoStateInit() + verify { + FrameworkStatsLog.write(eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE), + /* task_event */ + eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_INIT_STATSD), + /* instance_id */ + eq(0), + /* uid */ + eq(0), + /* task_height */ + eq(0), + /* task_width */ + eq(0), + /* task_x */ + eq(0), + /* task_y */ + eq(0), + /* session_id */ + eq(0), + /* minimize_reason */ + eq(UNSET_MINIMIZE_REASON), + /* unminimize_reason */ + eq(UNSET_UNMINIMIZE_REASON), + /* visible_task_count */ + eq(0) + ) + } + } + private companion object { private const val SESSION_ID = 1 private const val TASK_ID = 1 diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt index daf7e7d5397b..e7593b5b9324 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt @@ -115,6 +115,9 @@ class DesktopModeLoggerTransitionObserverTest : ShellTestCase() { val initRunnableCaptor = ArgumentCaptor.forClass(Runnable::class.java) verify(mockShellInit).addInitCallback(initRunnableCaptor.capture(), same(transitionObserver)) initRunnableCaptor.value.run() + // verify this initialisation interaction to leave the desktopmodeEventLogger mock in a + // consistent state with no outstanding interactions when test cases start executing. + verify(desktopModeEventLogger).logTaskInfoStateInit() } @Test diff --git a/nfc/api/system-current.txt b/nfc/api/system-current.txt index 4428adee818d..24e14e69637b 100644 --- a/nfc/api/system-current.txt +++ b/nfc/api/system-current.txt @@ -63,7 +63,7 @@ package android.nfc { method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean isAutoChangeEnabled(); method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean isTagPresent(); method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void maybeTriggerFirmwareUpdate(); - method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void overwriteRoutingTable(int, int, int); + method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void overwriteRoutingTable(int, int, int, int); method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void pausePolling(int); method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void registerCallback(@NonNull java.util.concurrent.Executor, @NonNull android.nfc.NfcOemExtension.Callback); method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void resumePolling(); diff --git a/nfc/java/android/nfc/INfcCardEmulation.aidl b/nfc/java/android/nfc/INfcCardEmulation.aidl index 1eae3c6f30f1..8535e4a9cfd2 100644 --- a/nfc/java/android/nfc/INfcCardEmulation.aidl +++ b/nfc/java/android/nfc/INfcCardEmulation.aidl @@ -54,5 +54,5 @@ interface INfcCardEmulation void setAutoChangeStatus(boolean state); boolean isAutoChangeEnabled(); List<String> getRoutingStatus(); - void overwriteRoutingTable(int userHandle, String emptyAid, String protocol, String tech); + void overwriteRoutingTable(int userHandle, String emptyAid, String protocol, String tech, String sc); } diff --git a/nfc/java/android/nfc/NfcOemExtension.java b/nfc/java/android/nfc/NfcOemExtension.java index fb63b5c03d00..bc410c7b8ba5 100644 --- a/nfc/java/android/nfc/NfcOemExtension.java +++ b/nfc/java/android/nfc/NfcOemExtension.java @@ -647,24 +647,29 @@ public final class NfcOemExtension { * {@link ProtocolAndTechnologyRoute} * @param emptyAid Zero-length AID route destination, where the possible inputs are defined in * {@link ProtocolAndTechnologyRoute} + * @param systemCode System Code route destination, where the possible inputs are defined in + * {@link ProtocolAndTechnologyRoute} */ @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS) @FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION) public void overwriteRoutingTable( @CardEmulation.ProtocolAndTechnologyRoute int protocol, @CardEmulation.ProtocolAndTechnologyRoute int technology, - @CardEmulation.ProtocolAndTechnologyRoute int emptyAid) { + @CardEmulation.ProtocolAndTechnologyRoute int emptyAid, + @CardEmulation.ProtocolAndTechnologyRoute int systemCode) { String protocolRoute = routeIntToString(protocol); String technologyRoute = routeIntToString(technology); String emptyAidRoute = routeIntToString(emptyAid); + String systemCodeRoute = routeIntToString(systemCode); NfcAdapter.callService(() -> NfcAdapter.sCardEmulationService.overwriteRoutingTable( mContext.getUser().getIdentifier(), emptyAidRoute, protocolRoute, - technologyRoute + technologyRoute, + systemCodeRoute )); } diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt index ab95162fb142..5dca63724faf 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt @@ -85,4 +85,6 @@ object SettingsDimension { val illustrationMaxHeight = 300.dp val illustrationPadding = paddingLarge val illustrationCornerRadius = 28.dp + + val preferenceMinHeight = 72.dp } diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialogWithIcon.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialogWithIcon.kt index 58a83fa72532..4cf270dca2bb 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialogWithIcon.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialogWithIcon.kt @@ -17,7 +17,6 @@ package com.android.settingslib.spa.widget.dialog import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.width import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll @@ -26,12 +25,13 @@ import androidx.compose.material.icons.filled.WarningAmber import androidx.compose.material3.AlertDialog import androidx.compose.material3.Button import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedButton import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.window.DialogProperties +import com.android.settingslib.spa.framework.theme.isSpaExpressiveEnabled @Composable fun SettingsAlertDialogWithIcon( @@ -57,7 +57,9 @@ fun SettingsAlertDialogWithIcon( title?.let { { CenterRow { - Text(it, modifier = Modifier.fillMaxWidth(), textAlign = TextAlign.Center) + if (isSpaExpressiveEnabled) + Text(it, style = MaterialTheme.typography.bodyLarge) + else Text(it) } } }, diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BaseLayout.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BaseLayout.kt index c68ec78b1ba6..acb96be64a34 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BaseLayout.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BaseLayout.kt @@ -22,6 +22,7 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width @@ -35,6 +36,7 @@ import androidx.compose.ui.semantics.semantics import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.min import com.android.settingslib.spa.framework.theme.SettingsDimension import com.android.settingslib.spa.framework.theme.SettingsOpacity.alphaForEnabled import com.android.settingslib.spa.framework.theme.SettingsShape @@ -62,7 +64,8 @@ internal fun BaseLayout( .semantics(mergeDescendants = true) {} .then( if (isSpaExpressiveEnabled) - Modifier.clip(SettingsShape.CornerExtraSmall) + Modifier.heightIn(min = SettingsDimension.preferenceMinHeight) + .clip(SettingsShape.CornerExtraSmall) .background(MaterialTheme.colorScheme.surfaceBright) else Modifier ) diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/MainSwitchPreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/MainSwitchPreference.kt index b28e88eb8af8..e6a23662e9f0 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/MainSwitchPreference.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/MainSwitchPreference.kt @@ -17,6 +17,7 @@ package com.android.settingslib.spa.widget.preference import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.CircleShape import androidx.compose.material3.MaterialTheme @@ -25,6 +26,7 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.min import com.android.settingslib.spa.framework.theme.SettingsDimension import com.android.settingslib.spa.framework.theme.SettingsShape import com.android.settingslib.spa.framework.theme.SettingsTheme @@ -35,13 +37,19 @@ import com.android.settingslib.spa.framework.util.EntryHighlight fun MainSwitchPreference(model: SwitchPreferenceModel) { EntryHighlight { Surface( - modifier = Modifier.padding(SettingsDimension.itemPaddingEnd), - color = when (model.checked()) { - true -> MaterialTheme.colorScheme.primaryContainer - else -> MaterialTheme.colorScheme.secondaryContainer - }, - shape = if (isSpaExpressiveEnabled) CircleShape - else SettingsShape.CornerExtraLarge, + modifier = + Modifier.padding(SettingsDimension.itemPaddingEnd) + .then( + if (isSpaExpressiveEnabled) + Modifier.heightIn(min = SettingsDimension.preferenceMinHeight) + else Modifier + ), + color = + when (model.checked()) { + true -> MaterialTheme.colorScheme.primaryContainer + else -> MaterialTheme.colorScheme.secondaryContainer + }, + shape = if (isSpaExpressiveEnabled) CircleShape else SettingsShape.CornerExtraLarge, ) { InternalSwitchPreference( title = model.title, @@ -61,16 +69,20 @@ fun MainSwitchPreference(model: SwitchPreferenceModel) { private fun MainSwitchPreferencePreview() { SettingsTheme { Column { - MainSwitchPreference(object : SwitchPreferenceModel { - override val title = "Use Dark theme" - override val checked = { true } - override val onCheckedChange: (Boolean) -> Unit = {} - }) - MainSwitchPreference(object : SwitchPreferenceModel { - override val title = "Use Dark theme" - override val checked = { false } - override val onCheckedChange: (Boolean) -> Unit = {} - }) + MainSwitchPreference( + object : SwitchPreferenceModel { + override val title = "Use Dark theme" + override val checked = { true } + override val onCheckedChange: (Boolean) -> Unit = {} + } + ) + MainSwitchPreference( + object : SwitchPreferenceModel { + override val title = "Use Dark theme" + override val checked = { false } + override val onCheckedChange: (Boolean) -> Unit = {} + } + ) } } } diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/ZeroStatePreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/ZeroStatePreference.kt index b771f367e697..541922335387 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/ZeroStatePreference.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/ZeroStatePreference.kt @@ -54,7 +54,7 @@ fun ZeroStatePreference(icon: ImageVector, text: String? = null, description: St val zeroStateShape = remember { RoundedPolygon.star( numVerticesPerRadius = 6, - innerRadius = 0.75f, + innerRadius = 0.8f, rounding = CornerRounding(0.3f) ) } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/IDeviceSettingsConfigProviderService.aidl b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/IDeviceSettingsConfigProviderService.aidl index 9cf49070a62c..c85756ed067a 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/IDeviceSettingsConfigProviderService.aidl +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/IDeviceSettingsConfigProviderService.aidl @@ -20,5 +20,5 @@ import com.android.settingslib.bluetooth.devicesettings.DeviceInfo; import com.android.settingslib.bluetooth.devicesettings.IGetDeviceSettingsConfigCallback; interface IDeviceSettingsConfigProviderService { - oneway void getDeviceSettingsConfig(in DeviceInfo device, in IGetDeviceSettingsConfigCallback callback); + void getDeviceSettingsConfig(in DeviceInfo device, in IGetDeviceSettingsConfigCallback callback); }
\ No newline at end of file diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingServiceConnection.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingServiceConnection.kt index 4af0504bd73a..a33fcc6747b4 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingServiceConnection.kt +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingServiceConnection.kt @@ -23,6 +23,7 @@ import android.content.Intent import android.content.ServiceConnection import android.os.IBinder import android.os.IInterface +import android.os.RemoteException import android.text.TextUtils import android.util.Log import com.android.settingslib.bluetooth.BluetoothUtils @@ -55,6 +56,7 @@ import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.emitAll import kotlinx.coroutines.flow.filterIsInstance import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.flatMapConcat import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flow @@ -100,6 +102,9 @@ class DeviceSettingServiceConnection( private var isServiceEnabled = coroutineScope.async(backgroundCoroutineContext, start = CoroutineStart.LAZY) { val states = getSettingsProviderServices()?.values ?: return@async false + if (states.isEmpty()) { + return@async true + } combine(states) { it.toList() } .mapNotNull { allStatus -> if (allStatus.any { it is ServiceConnectionStatus.Failed }) { @@ -114,7 +119,7 @@ class DeviceSettingServiceConnection( null } } - .first() + .firstOrNull() ?: false } private var config = @@ -131,9 +136,15 @@ class DeviceSettingServiceConnection( is ServiceConnectionStatus.Connected -> flowOf( getDeviceSettingsConfigFromService( - deviceInfo { setBluetoothAddress(cachedDevice.address) }, - it.service, - ) + deviceInfo { setBluetoothAddress(cachedDevice.address) }, + it.service, + ) + .also { config -> + Log.i( + TAG, + "device setting config for $cachedDevice is $config", + ) + } ) ServiceConnectionStatus.Connecting -> flowOf() ServiceConnectionStatus.Failed -> flowOf(null) @@ -146,21 +157,26 @@ class DeviceSettingServiceConnection( deviceInfo: DeviceInfo, service: IDeviceSettingsConfigProviderService, ): DeviceSettingsConfig? = suspendCancellableCoroutine { continuation -> - service.getDeviceSettingsConfig( - deviceInfo, - object : IGetDeviceSettingsConfigCallback.Stub() { - override fun onResult( - status: DeviceSettingsConfigServiceStatus, - config: DeviceSettingsConfig?, - ) { - if (!status.success) { - continuation.resume(null) - } else { - continuation.resume(config) + try { + service.getDeviceSettingsConfig( + deviceInfo, + object : IGetDeviceSettingsConfigCallback.Stub() { + override fun onResult( + status: DeviceSettingsConfigServiceStatus, + config: DeviceSettingsConfig?, + ) { + if (!status.success) { + continuation.resume(null) + } else { + continuation.resume(config) + } } - } - }, - ) + }, + ) + } catch (e: RemoteException) { + Log.i(TAG, "Fail to get config") + continuation.resume(null) + } } private val settingIdToItemMapping = @@ -298,13 +314,16 @@ class DeviceSettingServiceConnection( val serviceConnection = object : ServiceConnection { override fun onServiceConnected(name: ComponentName, service: IBinder) { + Log.i(TAG, "Service connected for $intent") launch { send(ServiceConnectionStatus.Connected(transform(service))) } } override fun onServiceDisconnected(name: ComponentName?) { + Log.i(TAG, "Service disconnected for $intent") launch { send(ServiceConnectionStatus.Connecting) } } } + Log.i(TAG, "Try to bind service for $intent") if ( !context.bindService( intent, diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InputMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/InputMediaDevice.java index 1d17b004fb6a..6335e712f904 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/InputMediaDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/InputMediaDevice.java @@ -49,19 +49,23 @@ public class InputMediaDevice extends MediaDevice { private final boolean mIsVolumeFixed; + private final String mProductName; + private InputMediaDevice( @NonNull Context context, @NonNull String id, @AudioDeviceType int audioDeviceInfoType, int maxVolume, int currentVolume, - boolean isVolumeFixed) { + boolean isVolumeFixed, + @Nullable String productName) { super(context, /* info= */ null, /* item= */ null); mId = id; mAudioDeviceInfoType = audioDeviceInfoType; mMaxVolume = maxVolume; mCurrentVolume = currentVolume; mIsVolumeFixed = isVolumeFixed; + mProductName = productName; initDeviceRecord(); } @@ -72,13 +76,20 @@ public class InputMediaDevice extends MediaDevice { @AudioDeviceType int audioDeviceInfoType, int maxVolume, int currentVolume, - boolean isVolumeFixed) { + boolean isVolumeFixed, + @Nullable String productName) { if (!isSupportedInputDevice(audioDeviceInfoType)) { return null; } return new InputMediaDevice( - context, id, audioDeviceInfoType, maxVolume, currentVolume, isVolumeFixed); + context, + id, + audioDeviceInfoType, + maxVolume, + currentVolume, + isVolumeFixed, + productName); } public @AudioDeviceType int getAudioDeviceInfoType() { @@ -98,18 +109,25 @@ public class InputMediaDevice extends MediaDevice { }; } + @Nullable + public String getProductName() { + return mProductName; + } + @Override public @NonNull String getName() { - CharSequence name = switch (mAudioDeviceInfoType) { - case TYPE_WIRED_HEADSET -> mContext.getString( - R.string.media_transfer_wired_device_mic_name); - case TYPE_USB_DEVICE, TYPE_USB_HEADSET, TYPE_USB_ACCESSORY -> mContext.getString( - R.string.media_transfer_usb_device_mic_name); - case TYPE_BLUETOOTH_SCO -> mContext.getString( - R.string.media_transfer_bt_device_mic_name); + return switch (mAudioDeviceInfoType) { + case TYPE_WIRED_HEADSET -> + mContext.getString(R.string.media_transfer_wired_device_mic_name); + case TYPE_USB_DEVICE, TYPE_USB_HEADSET, TYPE_USB_ACCESSORY -> + // The product name is assumed to be a well-formed string if it's not null. + mProductName != null + ? mProductName + : mContext.getString(R.string.media_transfer_usb_device_mic_name); + case TYPE_BLUETOOTH_SCO -> + mContext.getString(R.string.media_transfer_bt_device_mic_name); default -> mContext.getString(R.string.media_transfer_this_device_name_desktop); }; - return name.toString(); } @Override diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InputRouteManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InputRouteManager.java index a72ba8db848d..727662b80d98 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/InputRouteManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/InputRouteManager.java @@ -151,7 +151,8 @@ public final class InputRouteManager { info.getType(), getMaxInputGain(), getCurrentInputGain(), - isInputGainFixed()); + isInputGainFixed(), + getProductNameFromAudioDeviceInfo(info)); if (mediaDevice != null) { if (info.getType() == selectedInputDeviceAttributesType) { mediaDevice.setState(STATE_SELECTED); @@ -169,6 +170,25 @@ public final class InputRouteManager { } } + /** + * Gets the product name for the given {@link AudioDeviceInfo}. + * + * @return The product name for the given {@link AudioDeviceInfo}, or null if a suitable name + * cannot be found. + */ + @Nullable + private String getProductNameFromAudioDeviceInfo(AudioDeviceInfo deviceInfo) { + CharSequence productName = deviceInfo.getProductName(); + if (productName == null) { + return null; + } + String productNameString = productName.toString(); + if (productNameString.isBlank()) { + return null; + } + return productNameString; + } + public void selectDevice(@NonNull MediaDevice device) { if (!(device instanceof InputMediaDevice)) { Slog.w(TAG, "This device is not an InputMediaDevice: " + device.getName()); diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputMediaDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputMediaDeviceTest.java index 30e4637f59ff..6c1cb7015225 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputMediaDeviceTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputMediaDeviceTest.java @@ -41,6 +41,9 @@ public class InputMediaDeviceTest { private final int MAX_VOLUME = 1; private final int CURRENT_VOLUME = 0; private final boolean IS_VOLUME_FIXED = true; + private static final String PRODUCT_NAME_BUILTIN_MIC = "Built-in Mic"; + private static final String PRODUCT_NAME_WIRED_HEADSET = "My Wired Headset"; + private static final String PRODUCT_NAME_USB_HEADSET = "My USB Headset"; @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); @@ -60,7 +63,8 @@ public class InputMediaDeviceTest { AudioDeviceInfo.TYPE_BUILTIN_MIC, MAX_VOLUME, CURRENT_VOLUME, - IS_VOLUME_FIXED); + IS_VOLUME_FIXED, + PRODUCT_NAME_BUILTIN_MIC); assertThat(builtinMediaDevice).isNotNull(); assertThat(builtinMediaDevice.getDrawableResId()).isEqualTo(R.drawable.ic_media_microphone); } @@ -74,7 +78,8 @@ public class InputMediaDeviceTest { AudioDeviceInfo.TYPE_BUILTIN_MIC, MAX_VOLUME, CURRENT_VOLUME, - IS_VOLUME_FIXED); + IS_VOLUME_FIXED, + PRODUCT_NAME_BUILTIN_MIC); assertThat(builtinMediaDevice).isNotNull(); assertThat(builtinMediaDevice.getName()) .isEqualTo(mContext.getString(R.string.media_transfer_this_device_name_desktop)); @@ -89,7 +94,8 @@ public class InputMediaDeviceTest { AudioDeviceInfo.TYPE_WIRED_HEADSET, MAX_VOLUME, CURRENT_VOLUME, - IS_VOLUME_FIXED); + IS_VOLUME_FIXED, + PRODUCT_NAME_WIRED_HEADSET); assertThat(wiredMediaDevice).isNotNull(); assertThat(wiredMediaDevice.getName()) .isEqualTo(mContext.getString(R.string.media_transfer_wired_device_mic_name)); @@ -104,7 +110,23 @@ public class InputMediaDeviceTest { AudioDeviceInfo.TYPE_USB_HEADSET, MAX_VOLUME, CURRENT_VOLUME, - IS_VOLUME_FIXED); + IS_VOLUME_FIXED, + PRODUCT_NAME_USB_HEADSET); + assertThat(usbMediaDevice).isNotNull(); + assertThat(usbMediaDevice.getName()).isEqualTo(PRODUCT_NAME_USB_HEADSET); + } + + @Test + public void getName_returnCorrectName_usbHeadset_nullProductName() { + InputMediaDevice usbMediaDevice = + InputMediaDevice.create( + mContext, + String.valueOf(USB_HEADSET_ID), + AudioDeviceInfo.TYPE_USB_HEADSET, + MAX_VOLUME, + CURRENT_VOLUME, + IS_VOLUME_FIXED, + null); assertThat(usbMediaDevice).isNotNull(); assertThat(usbMediaDevice.getName()) .isEqualTo(mContext.getString(R.string.media_transfer_usb_device_mic_name)); @@ -119,7 +141,8 @@ public class InputMediaDeviceTest { AudioDeviceInfo.TYPE_BLUETOOTH_SCO, MAX_VOLUME, CURRENT_VOLUME, - IS_VOLUME_FIXED); + IS_VOLUME_FIXED, + null); assertThat(btMediaDevice).isNotNull(); assertThat(btMediaDevice.getName()) .isEqualTo(mContext.getString(R.string.media_transfer_bt_device_mic_name)); diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputRouteManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputRouteManagerTest.java index 29cc4034d4b4..f63bfc70140d 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputRouteManagerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputRouteManagerTest.java @@ -58,6 +58,11 @@ public class InputRouteManagerTest { private static final int MAX_VOLUME = 1; private static final int CURRENT_VOLUME = 0; private static final boolean VOLUME_FIXED_TRUE = true; + private static final String PRODUCT_NAME_BUILTIN_MIC = "Built-in Mic"; + private static final String PRODUCT_NAME_WIRED_HEADSET = "My Wired Headset"; + private static final String PRODUCT_NAME_USB_HEADSET = "My USB Headset"; + private static final String PRODUCT_NAME_USB_DEVICE = "My USB Device"; + private static final String PRODUCT_NAME_USB_ACCESSORY = "My USB Accessory"; private final Context mContext = spy(RuntimeEnvironment.application); private InputRouteManager mInputRouteManager; @@ -75,25 +80,31 @@ public class InputRouteManagerTest { final AudioDeviceInfo info1 = mock(AudioDeviceInfo.class); when(info1.getType()).thenReturn(AudioDeviceInfo.TYPE_BUILTIN_MIC); when(info1.getId()).thenReturn(BUILTIN_MIC_ID); + when(info1.getProductName()).thenReturn(PRODUCT_NAME_BUILTIN_MIC); final AudioDeviceInfo info2 = mock(AudioDeviceInfo.class); when(info2.getType()).thenReturn(AudioDeviceInfo.TYPE_WIRED_HEADSET); when(info2.getId()).thenReturn(INPUT_WIRED_HEADSET_ID); + when(info2.getProductName()).thenReturn(PRODUCT_NAME_WIRED_HEADSET); final AudioDeviceInfo info3 = mock(AudioDeviceInfo.class); when(info3.getType()).thenReturn(AudioDeviceInfo.TYPE_USB_DEVICE); when(info3.getId()).thenReturn(INPUT_USB_DEVICE_ID); + when(info3.getProductName()).thenReturn(PRODUCT_NAME_USB_DEVICE); final AudioDeviceInfo info4 = mock(AudioDeviceInfo.class); when(info4.getType()).thenReturn(AudioDeviceInfo.TYPE_USB_HEADSET); when(info4.getId()).thenReturn(INPUT_USB_HEADSET_ID); + when(info4.getProductName()).thenReturn(PRODUCT_NAME_USB_HEADSET); final AudioDeviceInfo info5 = mock(AudioDeviceInfo.class); when(info5.getType()).thenReturn(AudioDeviceInfo.TYPE_USB_ACCESSORY); when(info5.getId()).thenReturn(INPUT_USB_ACCESSORY_ID); + when(info5.getProductName()).thenReturn(PRODUCT_NAME_USB_ACCESSORY); final AudioDeviceInfo unsupportedInfo = mock(AudioDeviceInfo.class); when(unsupportedInfo.getType()).thenReturn(AudioDeviceInfo.TYPE_HDMI); + when(unsupportedInfo.getProductName()).thenReturn("HDMI device"); final AudioManager audioManager = mock(AudioManager.class); AudioDeviceInfo[] devices = {info1, info2, info3, info4, info5, unsupportedInfo}; @@ -142,10 +153,12 @@ public class InputRouteManagerTest { final AudioDeviceInfo info1 = mock(AudioDeviceInfo.class); when(info1.getType()).thenReturn(AudioDeviceInfo.TYPE_WIRED_HEADSET); when(info1.getId()).thenReturn(INPUT_WIRED_HEADSET_ID); + when(info1.getProductName()).thenReturn(PRODUCT_NAME_WIRED_HEADSET); final AudioDeviceInfo info2 = mock(AudioDeviceInfo.class); when(info2.getType()).thenReturn(AudioDeviceInfo.TYPE_BUILTIN_MIC); when(info2.getId()).thenReturn(BUILTIN_MIC_ID); + when(info2.getProductName()).thenReturn(PRODUCT_NAME_BUILTIN_MIC); final AudioManager audioManager = mock(AudioManager.class); AudioDeviceInfo[] devices = {info1, info2}; @@ -171,10 +184,12 @@ public class InputRouteManagerTest { final AudioDeviceInfo info1 = mock(AudioDeviceInfo.class); when(info1.getType()).thenReturn(AudioDeviceInfo.TYPE_WIRED_HEADSET); when(info1.getId()).thenReturn(INPUT_WIRED_HEADSET_ID); + when(info1.getProductName()).thenReturn(PRODUCT_NAME_WIRED_HEADSET); final AudioDeviceInfo info2 = mock(AudioDeviceInfo.class); when(info2.getType()).thenReturn(AudioDeviceInfo.TYPE_BUILTIN_MIC); when(info2.getId()).thenReturn(BUILTIN_MIC_ID); + when(info2.getProductName()).thenReturn(PRODUCT_NAME_BUILTIN_MIC); final AudioManager audioManager = mock(AudioManager.class); AudioDeviceInfo[] devices = {info1, info2}; @@ -204,10 +219,12 @@ public class InputRouteManagerTest { final AudioDeviceInfo info1 = mock(AudioDeviceInfo.class); when(info1.getType()).thenReturn(AudioDeviceInfo.TYPE_WIRED_HEADSET); when(info1.getId()).thenReturn(INPUT_WIRED_HEADSET_ID); + when(info1.getProductName()).thenReturn(PRODUCT_NAME_WIRED_HEADSET); final AudioDeviceInfo info2 = mock(AudioDeviceInfo.class); when(info2.getType()).thenReturn(AudioDeviceInfo.TYPE_BUILTIN_MIC); when(info2.getId()).thenReturn(BUILTIN_MIC_ID); + when(info2.getProductName()).thenReturn(PRODUCT_NAME_BUILTIN_MIC); final AudioManager audioManager = mock(AudioManager.class); AudioDeviceInfo[] devices = {info1, info2}; @@ -239,7 +256,8 @@ public class InputRouteManagerTest { AudioDeviceInfo.TYPE_BUILTIN_MIC, MAX_VOLUME, CURRENT_VOLUME, - VOLUME_FIXED_TRUE); + VOLUME_FIXED_TRUE, + PRODUCT_NAME_BUILTIN_MIC); inputRouteManager.selectDevice(inputMediaDevice); AudioDeviceAttributes deviceAttributes = @@ -267,4 +285,51 @@ public class InputRouteManagerTest { public void isInputGainFixed() { assertThat(mInputRouteManager.isInputGainFixed()).isTrue(); } + + @Test + public void onAudioDevicesAdded_shouldSetProductNameCorrectly() { + final AudioDeviceInfo info1 = mock(AudioDeviceInfo.class); + when(info1.getType()).thenReturn(AudioDeviceInfo.TYPE_WIRED_HEADSET); + when(info1.getId()).thenReturn(INPUT_WIRED_HEADSET_ID); + String firstProductName = "My first headset"; + when(info1.getProductName()).thenReturn(firstProductName); + + final AudioDeviceInfo info2 = mock(AudioDeviceInfo.class); + when(info2.getType()).thenReturn(AudioDeviceInfo.TYPE_WIRED_HEADSET); + when(info2.getId()).thenReturn(INPUT_WIRED_HEADSET_ID); + String secondProductName = "My second headset"; + when(info2.getProductName()).thenReturn(secondProductName); + + final AudioDeviceInfo infoWithNullProductName = mock(AudioDeviceInfo.class); + when(infoWithNullProductName.getType()).thenReturn(AudioDeviceInfo.TYPE_WIRED_HEADSET); + when(infoWithNullProductName.getId()).thenReturn(INPUT_WIRED_HEADSET_ID); + when(infoWithNullProductName.getProductName()).thenReturn(null); + + final AudioDeviceInfo infoWithBlankProductName = mock(AudioDeviceInfo.class); + when(infoWithBlankProductName.getType()).thenReturn(AudioDeviceInfo.TYPE_WIRED_HEADSET); + when(infoWithBlankProductName.getId()).thenReturn(INPUT_WIRED_HEADSET_ID); + when(infoWithBlankProductName.getProductName()).thenReturn(""); + + final AudioManager audioManager = mock(AudioManager.class); + AudioDeviceInfo[] devices = { + info1, info2, infoWithNullProductName, infoWithBlankProductName + }; + when(audioManager.getDevices(AudioManager.GET_DEVICES_INPUTS)).thenReturn(devices); + + InputRouteManager inputRouteManager = new InputRouteManager(mContext, audioManager); + + assertThat(inputRouteManager.mInputMediaDevices).isEmpty(); + + inputRouteManager.mAudioDeviceCallback.onAudioDevicesAdded(devices); + + assertThat(getProductNameAtIndex(inputRouteManager, 1)).isEqualTo(firstProductName); + assertThat(getProductNameAtIndex(inputRouteManager, 2)).isEqualTo(secondProductName); + assertThat(getProductNameAtIndex(inputRouteManager, 3)).isNull(); + assertThat(getProductNameAtIndex(inputRouteManager, 4)).isNull(); + } + + private String getProductNameAtIndex(InputRouteManager inputRouteManager, int index) { + return ((InputMediaDevice) inputRouteManager.mInputMediaDevices.get(index)) + .getProductName(); + } } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiUtilsTest.java index 529301138da3..d8b6707b9118 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiUtilsTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiUtilsTest.java @@ -207,20 +207,6 @@ public class WifiUtilsTest { } @Test - public void getHotspotIconResource_deviceTypeExists_shouldNotNull() { - assertThat(WifiUtils.getHotspotIconResource(NetworkProviderInfo.DEVICE_TYPE_PHONE)) - .isNotNull(); - assertThat(WifiUtils.getHotspotIconResource(NetworkProviderInfo.DEVICE_TYPE_TABLET)) - .isNotNull(); - assertThat(WifiUtils.getHotspotIconResource(NetworkProviderInfo.DEVICE_TYPE_LAPTOP)) - .isNotNull(); - assertThat(WifiUtils.getHotspotIconResource(NetworkProviderInfo.DEVICE_TYPE_WATCH)) - .isNotNull(); - assertThat(WifiUtils.getHotspotIconResource(NetworkProviderInfo.DEVICE_TYPE_AUTO)) - .isNotNull(); - } - - @Test public void testInternetIconInjector_getIcon_returnsCorrectValues() { WifiUtils.InternetIconInjector iconInjector = new WifiUtils.InternetIconInjector(mContext); diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java index 2cdd0aee3d85..3530e0f5f9de 100644 --- a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java +++ b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java @@ -106,6 +106,8 @@ public class SystemSettings { Settings.System.UNREAD_NOTIFICATION_DOT_INDICATOR, Settings.System.AUTO_LAUNCH_MEDIA_CONTROLS, Settings.System.LOCALE_PREFERENCES, + Settings.System.MOUSE_REVERSE_VERTICAL_SCROLLING, + Settings.System.MOUSE_SWAP_PRIMARY_BUTTON, Settings.System.TOUCHPAD_POINTER_SPEED, Settings.System.TOUCHPAD_NATURAL_SCROLLING, Settings.System.TOUCHPAD_TAP_TO_CLICK, diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java index 282327739fc6..509b88b257fe 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java @@ -221,6 +221,8 @@ public class SystemSettingsValidators { POINTER_ICON_VECTOR_STYLE_STROKE_END)); VALIDATORS.put(System.POINTER_SCALE, new InclusiveFloatRangeValidator(DEFAULT_POINTER_SCALE, LARGE_POINTER_SCALE)); + VALIDATORS.put(System.MOUSE_REVERSE_VERTICAL_SCROLLING, BOOLEAN_VALIDATOR); + VALIDATORS.put(System.MOUSE_SWAP_PRIMARY_BUTTON, BOOLEAN_VALIDATOR); VALIDATORS.put(System.TOUCHPAD_POINTER_SPEED, new InclusiveIntegerRangeValidator(-7, 7)); VALIDATORS.put(System.TOUCHPAD_NATURAL_SCROLLING, BOOLEAN_VALIDATOR); VALIDATORS.put(System.TOUCHPAD_TAP_TO_CLICK, BOOLEAN_VALIDATOR); diff --git a/packages/SystemUI/TEST_MAPPING b/packages/SystemUI/TEST_MAPPING index 07a1e630e1ad..380344a23c99 100644 --- a/packages/SystemUI/TEST_MAPPING +++ b/packages/SystemUI/TEST_MAPPING @@ -148,5 +148,10 @@ { "name": "SystemUIGoogleRobo2RNGTests" } + ], + "imports": [ + { + "path": "cts/tests/tests/multiuser" + } ] } diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ComposedDigitalLayerController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ComposedDigitalLayerController.kt new file mode 100644 index 000000000000..9b94c91a348c --- /dev/null +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ComposedDigitalLayerController.kt @@ -0,0 +1,203 @@ +/* + * 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.shared.clocks + +import android.content.Context +import android.content.res.Resources +import android.graphics.Rect +import androidx.annotation.VisibleForTesting +import com.android.systemui.log.core.Logger +import com.android.systemui.log.core.MessageBuffer +import com.android.systemui.plugins.clocks.AlarmData +import com.android.systemui.plugins.clocks.ClockAnimations +import com.android.systemui.plugins.clocks.ClockEvents +import com.android.systemui.plugins.clocks.ClockFaceConfig +import com.android.systemui.plugins.clocks.ClockFaceEvents +import com.android.systemui.plugins.clocks.ClockReactiveSetting +import com.android.systemui.plugins.clocks.WeatherData +import com.android.systemui.plugins.clocks.ZenData +import com.android.systemui.shared.clocks.view.DigitalClockFaceView +import com.android.systemui.shared.clocks.view.FlexClockView +import java.util.Locale +import java.util.TimeZone + +class ComposedDigitalLayerController( + private val ctx: Context, + private val assets: AssetLoader, + private val layer: ComposedDigitalHandLayer, + private val isLargeClock: Boolean, + messageBuffer: MessageBuffer, +) : SimpleClockLayerController { + private val logger = Logger(messageBuffer, ComposedDigitalLayerController::class.simpleName!!) + + val layerControllers = mutableListOf<SimpleClockLayerController>() + val dozeState = DefaultClockController.AnimationState(1F) + var isRegionDark = true + + override var view: DigitalClockFaceView = + when (layer.customizedView) { + "FlexClockView" -> FlexClockView(ctx, assets, messageBuffer) + else -> { + throw IllegalStateException("CustomizedView string is not valid") + } + } + + // Matches LayerControllerConstructor + internal constructor( + ctx: Context, + assets: AssetLoader, + layer: ClockLayer, + isLargeClock: Boolean, + messageBuffer: MessageBuffer, + ) : this(ctx, assets, layer as ComposedDigitalHandLayer, isLargeClock, messageBuffer) + + init { + layer.digitalLayers.forEach { + val controller = + SimpleClockLayerController.Factory.create( + ctx, + assets, + it, + isLargeClock, + messageBuffer, + ) + view.addView(controller.view) + layerControllers.add(controller) + } + } + + private fun refreshTime() { + layerControllers.forEach { it.faceEvents.onTimeTick() } + view.refreshTime() + } + + override val events = + object : ClockEvents { + override fun onTimeZoneChanged(timeZone: TimeZone) { + layerControllers.forEach { it.events.onTimeZoneChanged(timeZone) } + refreshTime() + } + + override fun onTimeFormatChanged(is24Hr: Boolean) { + layerControllers.forEach { it.events.onTimeFormatChanged(is24Hr) } + refreshTime() + } + + override fun onLocaleChanged(locale: Locale) { + layerControllers.forEach { it.events.onLocaleChanged(locale) } + view.onLocaleChanged(locale) + refreshTime() + } + + override fun onWeatherDataChanged(data: WeatherData) { + view.onWeatherDataChanged(data) + } + + override fun onAlarmDataChanged(data: AlarmData) { + view.onAlarmDataChanged(data) + } + + override fun onZenDataChanged(data: ZenData) { + view.onZenDataChanged(data) + } + + override fun onColorPaletteChanged(resources: Resources) {} + + override fun onSeedColorChanged(seedColor: Int?) {} + + override fun onReactiveAxesChanged(axes: List<ClockReactiveSetting>) {} + + override var isReactiveTouchInteractionEnabled + get() = view.isReactiveTouchInteractionEnabled + set(value) { + view.isReactiveTouchInteractionEnabled = value + } + } + + override fun updateColors() { + view.updateColors(assets, isRegionDark) + } + + override val animations = + object : ClockAnimations { + override fun enter() { + refreshTime() + } + + override fun doze(fraction: Float) { + val (hasChanged, hasJumped) = dozeState.update(fraction) + if (hasChanged) view.animateDoze(dozeState.isActive, !hasJumped) + view.dozeFraction = fraction + view.invalidate() + } + + override fun fold(fraction: Float) { + refreshTime() + } + + override fun charge() { + view.animateCharge() + } + + override fun onPositionUpdated(fromLeft: Int, direction: Int, fraction: Float) { + view.onPositionUpdated(fromLeft, direction, fraction) + } + + override fun onPositionUpdated(distance: Float, fraction: Float) {} + + override fun onPickerCarouselSwiping(swipingFraction: Float) { + view.onPickerCarouselSwiping(swipingFraction) + } + } + + override val faceEvents = + object : ClockFaceEvents { + override fun onTimeTick() { + refreshTime() + } + + override fun onRegionDarknessChanged(isRegionDark: Boolean) { + this@ComposedDigitalLayerController.isRegionDark = isRegionDark + updateColors() + } + + override fun onFontSettingChanged(fontSizePx: Float) { + view.onFontSettingChanged(fontSizePx) + } + + override fun onTargetRegionChanged(targetRegion: Rect?) {} + + override fun onSecondaryDisplayChanged(onSecondaryDisplay: Boolean) {} + } + + override val config = + ClockFaceConfig( + hasCustomWeatherDataDisplay = view.hasCustomWeatherDataDisplay, + hasCustomPositionUpdatedAnimation = view.hasCustomPositionUpdatedAnimation, + useCustomClockScene = view.useCustomClockScene, + ) + + @VisibleForTesting + override var fakeTimeMills: Long? = null + get() = field + set(timeInMills) { + field = timeInMills + for (layerController in layerControllers) { + layerController.fakeTimeMills = timeInMills + } + } +} 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 07191c671a34..ac268420fb75 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 @@ -24,6 +24,8 @@ 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 +import com.android.systemui.shared.clocks.view.HorizontalAlignment +import com.android.systemui.shared.clocks.view.VerticalAlignment private val TAG = DefaultClockProvider::class.simpleName const val DEFAULT_CLOCK_ID = "DEFAULT" @@ -33,8 +35,9 @@ class DefaultClockProvider( val ctx: Context, val layoutInflater: LayoutInflater, val resources: Resources, - val hasStepClockAnimation: Boolean = false, - val migratedClocks: Boolean = false, + private val hasStepClockAnimation: Boolean = false, + private val migratedClocks: Boolean = false, + private val clockReactiveVariants: Boolean = false, ) : ClockProvider { private var messageBuffers: ClockMessageBuffers? = null @@ -49,15 +52,23 @@ class DefaultClockProvider( throw IllegalArgumentException("${settings.clockId} is unsupported by $TAG") } - return DefaultClockController( - ctx, - layoutInflater, - resources, - settings, - hasStepClockAnimation, - migratedClocks, - messageBuffers, - ) + return if (clockReactiveVariants) { + // TODO handle the case here where only the smallClock message buffer is added + val assetLoader = + AssetLoader(ctx, ctx, "clocks/", messageBuffers?.smallClockMessageBuffer!!) + + SimpleClockController(ctx, assetLoader, FLEX_DESIGN, messageBuffers) + } else { + DefaultClockController( + ctx, + layoutInflater, + resources, + settings, + hasStepClockAnimation, + migratedClocks, + messageBuffers, + ) + } } override fun getClockPickerConfig(id: ClockId): ClockPickerConfig { @@ -73,4 +84,163 @@ class DefaultClockProvider( resources.getDrawable(R.drawable.clock_default_thumbnail, null), ) } + + companion object { + val FLEX_DESIGN = run { + val largeLayer = + listOf( + ComposedDigitalHandLayer( + layerBounds = LayerBounds.FIT, + customizedView = "FlexClockView", + digitalLayers = + listOf( + DigitalHandLayer( + layerBounds = LayerBounds.FIT, + timespec = DigitalTimespec.FIRST_DIGIT, + style = + FontTextStyle( + fontFamily = "google_sans_flex.ttf", + lineHeight = 147.25f, + fontVariation = + "'wght' 603, 'wdth' 100, 'opsz' 144, 'ROND' 100", + ), + aodStyle = + FontTextStyle( + fontVariation = + "'wght' 74, 'wdth' 43, 'opsz' 144, 'ROND' 100", + fontFamily = "google_sans_flex.ttf", + fillColorLight = "#FFFFFFFF", + outlineColor = "#00000000", + renderType = RenderType.CHANGE_WEIGHT, + transitionInterpolator = InterpolatorEnum.EMPHASIZED, + transitionDuration = 750, + ), + alignment = + DigitalAlignment( + HorizontalAlignment.CENTER, + VerticalAlignment.CENTER + ), + dateTimeFormat = "hh" + ), + DigitalHandLayer( + layerBounds = LayerBounds.FIT, + timespec = DigitalTimespec.SECOND_DIGIT, + style = + FontTextStyle( + fontFamily = "google_sans_flex.ttf", + lineHeight = 147.25f, + fontVariation = + "'wght' 603, 'wdth' 100, 'opsz' 144, 'ROND' 100", + ), + aodStyle = + FontTextStyle( + fontVariation = + "'wght' 74, 'wdth' 43, 'opsz' 144, 'ROND' 100", + fontFamily = "google_sans_flex.ttf", + fillColorLight = "#FFFFFFFF", + outlineColor = "#00000000", + renderType = RenderType.CHANGE_WEIGHT, + transitionInterpolator = InterpolatorEnum.EMPHASIZED, + transitionDuration = 750, + ), + alignment = + DigitalAlignment( + HorizontalAlignment.CENTER, + VerticalAlignment.CENTER + ), + dateTimeFormat = "hh" + ), + DigitalHandLayer( + layerBounds = LayerBounds.FIT, + timespec = DigitalTimespec.FIRST_DIGIT, + style = + FontTextStyle( + fontFamily = "google_sans_flex.ttf", + lineHeight = 147.25f, + fontVariation = + "'wght' 603, 'wdth' 100, 'opsz' 144, 'ROND' 100", + ), + aodStyle = + FontTextStyle( + fontVariation = + "'wght' 74, 'wdth' 43, 'opsz' 144, 'ROND' 100", + fontFamily = "google_sans_flex.ttf", + fillColorLight = "#FFFFFFFF", + outlineColor = "#00000000", + renderType = RenderType.CHANGE_WEIGHT, + transitionInterpolator = InterpolatorEnum.EMPHASIZED, + transitionDuration = 750, + ), + alignment = + DigitalAlignment( + HorizontalAlignment.CENTER, + VerticalAlignment.CENTER + ), + dateTimeFormat = "mm" + ), + DigitalHandLayer( + layerBounds = LayerBounds.FIT, + timespec = DigitalTimespec.SECOND_DIGIT, + style = + FontTextStyle( + fontFamily = "google_sans_flex.ttf", + lineHeight = 147.25f, + fontVariation = + "'wght' 603, 'wdth' 100, 'opsz' 144, 'ROND' 100", + ), + aodStyle = + FontTextStyle( + fontVariation = + "'wght' 74, 'wdth' 43, 'opsz' 144, 'ROND' 100", + fontFamily = "google_sans_flex.ttf", + fillColorLight = "#FFFFFFFF", + outlineColor = "#00000000", + renderType = RenderType.CHANGE_WEIGHT, + transitionInterpolator = InterpolatorEnum.EMPHASIZED, + transitionDuration = 750, + ), + alignment = + DigitalAlignment( + HorizontalAlignment.CENTER, + VerticalAlignment.CENTER + ), + dateTimeFormat = "mm" + ) + ) + ) + ) + + val smallLayer = + listOf( + DigitalHandLayer( + layerBounds = LayerBounds.FIT, + timespec = DigitalTimespec.TIME_FULL_FORMAT, + style = + FontTextStyle( + fontFamily = "google_sans_flex.ttf", + fontVariation = "'wght' 600, 'wdth' 100, 'opsz' 144, 'ROND' 100", + fontSizeScale = 0.98f, + ), + aodStyle = + FontTextStyle( + fontFamily = "google_sans_flex.ttf", + fontVariation = "'wght' 133, 'wdth' 43, 'opsz' 144, 'ROND' 100", + fillColorLight = "#FFFFFFFF", + outlineColor = "#00000000", + renderType = RenderType.CHANGE_WEIGHT, + ), + alignment = DigitalAlignment(HorizontalAlignment.LEFT, null), + dateTimeFormat = "h:mm" + ) + ) + + ClockDesign( + id = DEFAULT_CLOCK_ID, + name = "@string/clock_default_name", + description = "@string/clock_default_description", + large = ClockFace(layers = largeLayer), + small = ClockFace(layers = smallLayer) + ) + } + } } diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/LayoutUtils.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/LayoutUtils.kt new file mode 100644 index 000000000000..ef8bee0875d2 --- /dev/null +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/LayoutUtils.kt @@ -0,0 +1,35 @@ +/* + * 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.shared.clocks + +import android.graphics.Rect +import android.view.View + +fun computeLayoutDiff( + view: View, + targetRegion: Rect, + isLargeClock: Boolean, +): Pair<Float, Float> { + val parent = view.parent + if (parent is View && parent.isLaidOut() && isLargeClock) { + return Pair( + targetRegion.centerX() - parent.width / 2f, + targetRegion.centerY() - parent.height / 2f + ) + } + return Pair(0f, 0f) +} diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleClockController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleClockController.kt new file mode 100644 index 000000000000..ec7779825bda --- /dev/null +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleClockController.kt @@ -0,0 +1,152 @@ +/* + * 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.shared.clocks + +import android.content.Context +import android.content.res.Resources +import com.android.systemui.monet.Style as MonetStyle +import com.android.systemui.plugins.clocks.AlarmData +import com.android.systemui.plugins.clocks.ClockConfig +import com.android.systemui.plugins.clocks.ClockController +import com.android.systemui.plugins.clocks.ClockEvents +import com.android.systemui.plugins.clocks.ClockMessageBuffers +import com.android.systemui.plugins.clocks.ClockReactiveSetting +import com.android.systemui.plugins.clocks.WeatherData +import com.android.systemui.plugins.clocks.ZenData +import java.io.PrintWriter +import java.util.Locale +import java.util.TimeZone + +/** Controller for a simple json specified clock */ +class SimpleClockController( + private val ctx: Context, + private val assets: AssetLoader, + val design: ClockDesign, + val messageBuffers: ClockMessageBuffers?, +) : ClockController { + override val smallClock = run { + val buffer = messageBuffers?.smallClockMessageBuffer ?: LogUtil.DEFAULT_MESSAGE_BUFFER + SimpleClockFaceController( + ctx, + assets.copy(messageBuffer = buffer), + design.small ?: design.large!!, + false, + buffer, + ) + } + + override val largeClock = run { + val buffer = messageBuffers?.largeClockMessageBuffer ?: LogUtil.DEFAULT_MESSAGE_BUFFER + SimpleClockFaceController( + ctx, + assets.copy(messageBuffer = buffer), + design.large ?: design.small!!, + true, + buffer, + ) + } + + override val config: ClockConfig by lazy { + ClockConfig( + design.id, + design.name?.let { assets.tryReadString(it) ?: it } ?: "", + design.description?.let { assets.tryReadString(it) ?: it } ?: "", + isReactiveToTone = + design.colorPalette == null || design.colorPalette == MonetStyle.CLOCK, + useAlternateSmartspaceAODTransition = + smallClock.config.hasCustomWeatherDataDisplay || + largeClock.config.hasCustomWeatherDataDisplay, + useCustomClockScene = + smallClock.config.useCustomClockScene || largeClock.config.useCustomClockScene, + ) + } + + override val events = + object : ClockEvents { + override var isReactiveTouchInteractionEnabled = false + set(value) { + field = value + smallClock.events.isReactiveTouchInteractionEnabled = value + largeClock.events.isReactiveTouchInteractionEnabled = value + } + + override fun onTimeZoneChanged(timeZone: TimeZone) { + smallClock.events.onTimeZoneChanged(timeZone) + largeClock.events.onTimeZoneChanged(timeZone) + } + + override fun onTimeFormatChanged(is24Hr: Boolean) { + smallClock.events.onTimeFormatChanged(is24Hr) + largeClock.events.onTimeFormatChanged(is24Hr) + } + + override fun onLocaleChanged(locale: Locale) { + smallClock.events.onLocaleChanged(locale) + largeClock.events.onLocaleChanged(locale) + } + + override fun onColorPaletteChanged(resources: Resources) { + assets.refreshColorPalette(design.colorPalette) + smallClock.assets.refreshColorPalette(design.colorPalette) + largeClock.assets.refreshColorPalette(design.colorPalette) + + smallClock.events.onColorPaletteChanged(resources) + largeClock.events.onColorPaletteChanged(resources) + } + + override fun onSeedColorChanged(seedColor: Int?) { + assets.setSeedColor(seedColor, design.colorPalette) + smallClock.assets.setSeedColor(seedColor, design.colorPalette) + largeClock.assets.setSeedColor(seedColor, design.colorPalette) + + smallClock.events.onSeedColorChanged(seedColor) + largeClock.events.onSeedColorChanged(seedColor) + } + + override fun onWeatherDataChanged(data: WeatherData) { + smallClock.events.onWeatherDataChanged(data) + largeClock.events.onWeatherDataChanged(data) + } + + override fun onAlarmDataChanged(data: AlarmData) { + smallClock.events.onAlarmDataChanged(data) + largeClock.events.onAlarmDataChanged(data) + } + + override fun onZenDataChanged(data: ZenData) { + smallClock.events.onZenDataChanged(data) + largeClock.events.onZenDataChanged(data) + } + + override fun onReactiveAxesChanged(axes: List<ClockReactiveSetting>) { + smallClock.events.onReactiveAxesChanged(axes) + largeClock.events.onReactiveAxesChanged(axes) + } + } + + override fun initialize(resources: Resources, dozeFraction: Float, foldFraction: Float) { + events.onColorPaletteChanged(resources) + smallClock.animations.doze(dozeFraction) + largeClock.animations.doze(dozeFraction) + smallClock.animations.fold(foldFraction) + largeClock.animations.fold(foldFraction) + smallClock.events.onTimeTick() + largeClock.events.onTimeTick() + } + + override fun dump(pw: PrintWriter) {} +} diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleClockFaceController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleClockFaceController.kt new file mode 100644 index 000000000000..ef398d1a52a0 --- /dev/null +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleClockFaceController.kt @@ -0,0 +1,314 @@ +/* + * 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.shared.clocks + +import android.content.Context +import android.content.res.Resources +import android.graphics.Rect +import android.view.Gravity +import android.view.View +import android.view.ViewGroup.LayoutParams.MATCH_PARENT +import android.widget.FrameLayout +import com.android.systemui.log.core.MessageBuffer +import com.android.systemui.plugins.clocks.AlarmData +import com.android.systemui.plugins.clocks.ClockAnimations +import com.android.systemui.plugins.clocks.ClockEvents +import com.android.systemui.plugins.clocks.ClockFaceConfig +import com.android.systemui.plugins.clocks.ClockFaceController +import com.android.systemui.plugins.clocks.ClockFaceEvents +import com.android.systemui.plugins.clocks.ClockFaceLayout +import com.android.systemui.plugins.clocks.ClockReactiveSetting +import com.android.systemui.plugins.clocks.ClockTickRate +import com.android.systemui.plugins.clocks.DefaultClockFaceLayout +import com.android.systemui.plugins.clocks.WeatherData +import com.android.systemui.plugins.clocks.ZenData +import com.android.systemui.shared.clocks.view.DigitalClockFaceView +import java.util.Locale +import java.util.TimeZone +import kotlin.math.max + +interface ClockEventUnion : ClockEvents, ClockFaceEvents + +class SimpleClockFaceController( + ctx: Context, + val assets: AssetLoader, + face: ClockFace, + isLargeClock: Boolean, + messageBuffer: MessageBuffer, +) : ClockFaceController { + override val view: View + override val config: ClockFaceConfig by lazy { + ClockFaceConfig( + hasCustomWeatherDataDisplay = layers.any { it.config.hasCustomWeatherDataDisplay }, + hasCustomPositionUpdatedAnimation = + layers.any { it.config.hasCustomPositionUpdatedAnimation }, + tickRate = getTickRate(), + useCustomClockScene = layers.any { it.config.useCustomClockScene }, + ) + } + + val layers = mutableListOf<SimpleClockLayerController>() + + val timespecHandler = DigitalTimespecHandler(DigitalTimespec.TIME_FULL_FORMAT, "hh:mm") + + init { + val lp = FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT) + lp.gravity = Gravity.CENTER + view = + if (face.layers.size == 1) { + // Optimize a clocks with a single layer by excluding the face level view group. We + // expect the view container from the host process to always be a FrameLayout. + val layer = face.layers[0] + val controller = + SimpleClockLayerController.Factory.create( + ctx, + assets, + layer, + isLargeClock, + messageBuffer, + ) + layers.add(controller) + controller.view.layoutParams = lp + controller.view + } else { + // For multiple views, we use an intermediate RelativeLayout so that we can do some + // intelligent laying out between the children views. + val group = SimpleClockRelativeLayout(ctx, face.faceLayout) + group.layoutParams = lp + group.gravity = Gravity.CENTER + group.clipChildren = false + for (layer in face.layers) { + face.faceLayout?.let { + if (layer is DigitalHandLayer) { + layer.faceLayout = it + } + } + val controller = + SimpleClockLayerController.Factory.create( + ctx, + assets, + layer, + isLargeClock, + messageBuffer, + ) + group.addView(controller.view) + layers.add(controller) + } + group + } + } + + override val layout: ClockFaceLayout = + DefaultClockFaceLayout(view).apply { + views[0].id = + if (isLargeClock) { + assets.getResourcesId("lockscreen_clock_view_large") + } else { + assets.getResourcesId("lockscreen_clock_view") + } + } + + override val events = + object : ClockEventUnion { + override var isReactiveTouchInteractionEnabled = false + get() = field + set(value) { + field = value + layers.forEach { it.events.isReactiveTouchInteractionEnabled = value } + } + + override fun onTimeTick() { + timespecHandler.updateTime() + if ( + config.tickRate == ClockTickRate.PER_MINUTE || + view.contentDescription != timespecHandler.getContentDescription() + ) { + view.contentDescription = timespecHandler.getContentDescription() + } + layers.forEach { it.faceEvents.onTimeTick() } + } + + override fun onTimeZoneChanged(timeZone: TimeZone) { + timespecHandler.timeZone = timeZone + layers.forEach { it.events.onTimeZoneChanged(timeZone) } + } + + override fun onTimeFormatChanged(is24Hr: Boolean) { + timespecHandler.is24Hr = is24Hr + layers.forEach { it.events.onTimeFormatChanged(is24Hr) } + } + + override fun onLocaleChanged(locale: Locale) { + timespecHandler.updateLocale(locale) + layers.forEach { it.events.onLocaleChanged(locale) } + } + + override fun onFontSettingChanged(fontSizePx: Float) { + layers.forEach { it.faceEvents.onFontSettingChanged(fontSizePx) } + } + + override fun onColorPaletteChanged(resources: Resources) { + layers.forEach { + it.events.onColorPaletteChanged(resources) + it.updateColors() + } + } + + override fun onSeedColorChanged(seedColor: Int?) { + layers.forEach { + it.events.onSeedColorChanged(seedColor) + it.updateColors() + } + } + + override fun onRegionDarknessChanged(isRegionDark: Boolean) { + layers.forEach { it.faceEvents.onRegionDarknessChanged(isRegionDark) } + } + + override fun onReactiveAxesChanged(axes: List<ClockReactiveSetting>) {} + + /** + * targetRegion passed to all customized clock applies counter translationY of + * KeyguardStatusView and keyguard_large_clock_top_margin from default clock + */ + override fun onTargetRegionChanged(targetRegion: Rect?) { + // When a clock needs to be aligned with screen, like weather clock + // it needs to offset back the translation of keyguard_large_clock_top_margin + if (view is DigitalClockFaceView && view.isAlignedWithScreen()) { + val topMargin = getKeyguardLargeClockTopMargin(assets) + targetRegion?.let { + val (_, yDiff) = computeLayoutDiff(view, it, isLargeClock) + // In LS, we use yDiff to counter translate + // the translation of KeyguardLargeClockTopMargin + // With the targetRegion passed from picker, + // we will have yDiff = 0, no translation is needed for weather clock + if (yDiff.toInt() != 0) view.translationY = yDiff - topMargin / 2 + } + return + } + + var maxWidth = 0f + var maxHeight = 0f + + for (layer in layers) { + layer.faceEvents.onTargetRegionChanged(targetRegion) + maxWidth = max(maxWidth, layer.view.layoutParams.width.toFloat()) + maxHeight = max(maxHeight, layer.view.layoutParams.height.toFloat()) + } + + val lp = + if (maxHeight <= 0 || maxWidth <= 0 || targetRegion == null) { + // No specified width/height. Just match parent size. + FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT) + } else { + // Scale to fit in targetRegion based on largest child elements. + val ratio = maxWidth / maxHeight + val targetRatio = targetRegion.width() / targetRegion.height().toFloat() + val scale = + if (ratio > targetRatio) targetRegion.width() / maxWidth + else targetRegion.height() / maxHeight + + FrameLayout.LayoutParams( + (maxWidth * scale).toInt(), + (maxHeight * scale).toInt(), + ) + } + + lp.gravity = Gravity.CENTER + view.layoutParams = lp + targetRegion?.let { + val (xDiff, yDiff) = computeLayoutDiff(view, it, isLargeClock) + view.translationX = xDiff + view.translationY = yDiff + } + } + + override fun onSecondaryDisplayChanged(onSecondaryDisplay: Boolean) {} + + override fun onWeatherDataChanged(data: WeatherData) { + layers.forEach { it.events.onWeatherDataChanged(data) } + } + + override fun onAlarmDataChanged(data: AlarmData) { + layers.forEach { it.events.onAlarmDataChanged(data) } + } + + override fun onZenDataChanged(data: ZenData) { + layers.forEach { it.events.onZenDataChanged(data) } + } + } + + override val animations = + object : ClockAnimations { + override fun enter() { + layers.forEach { it.animations.enter() } + } + + override fun doze(fraction: Float) { + layers.forEach { it.animations.doze(fraction) } + } + + override fun fold(fraction: Float) { + layers.forEach { it.animations.fold(fraction) } + } + + override fun charge() { + layers.forEach { it.animations.charge() } + } + + override fun onPickerCarouselSwiping(swipingFraction: Float) { + face.pickerScale?.let { + view.scaleX = swipingFraction * (1 - it.scaleX) + it.scaleX + view.scaleY = swipingFraction * (1 - it.scaleY) + it.scaleY + } + if (!(view is DigitalClockFaceView && view.isAlignedWithScreen())) { + val topMargin = getKeyguardLargeClockTopMargin(assets) + view.translationY = topMargin / 2F * swipingFraction + } + layers.forEach { it.animations.onPickerCarouselSwiping(swipingFraction) } + view.invalidate() + } + + override fun onPositionUpdated(fromLeft: Int, direction: Int, fraction: Float) { + layers.forEach { it.animations.onPositionUpdated(fromLeft, direction, fraction) } + } + + override fun onPositionUpdated(distance: Float, fraction: Float) { + layers.forEach { it.animations.onPositionUpdated(distance, fraction) } + } + } + + private fun getTickRate(): ClockTickRate { + var tickRate = ClockTickRate.PER_MINUTE + for (layer in layers) { + if (layer.config.tickRate.value < tickRate.value) { + tickRate = layer.config.tickRate + } + } + return tickRate + } + + private fun getKeyguardLargeClockTopMargin(assets: AssetLoader): Int { + val topMarginRes = + assets.resolveResourceId(null, "dimen", "keyguard_large_clock_top_margin") + if (topMarginRes != null) { + val (res, id) = topMarginRes + return res.getDimensionPixelSize(id) + } + return 0 + } +} diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleClockLayerController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleClockLayerController.kt new file mode 100644 index 000000000000..f71543efa650 --- /dev/null +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleClockLayerController.kt @@ -0,0 +1,102 @@ +/* + * 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.shared.clocks + +import android.content.Context +import android.view.View +import androidx.annotation.VisibleForTesting +import com.android.systemui.log.core.MessageBuffer +import com.android.systemui.plugins.clocks.ClockAnimations +import com.android.systemui.plugins.clocks.ClockEvents +import com.android.systemui.plugins.clocks.ClockFaceConfig +import com.android.systemui.plugins.clocks.ClockFaceEvents +import com.android.systemui.shared.clocks.view.SimpleDigitalClockTextView +import kotlin.reflect.KClass + +typealias LayerControllerConstructor = + ( + ctx: Context, + assets: AssetLoader, + layer: ClockLayer, + isLargeClock: Boolean, + messageBuffer: MessageBuffer, + ) -> SimpleClockLayerController + +interface SimpleClockLayerController { + val view: View + val events: ClockEvents + val animations: ClockAnimations + val faceEvents: ClockFaceEvents + val config: ClockFaceConfig + + @VisibleForTesting var fakeTimeMills: Long? + + // Called immediately after either onColorPaletteChanged or onSeedColorChanged is called. + // Provided for convience to not duplicate color update logic after state updated. + fun updateColors() {} + + companion object Factory { + val constructorMap = mutableMapOf<Pair<KClass<*>, KClass<*>?>, LayerControllerConstructor>() + + internal inline fun <reified TLayer> registerConstructor( + noinline constructor: LayerControllerConstructor, + ) where TLayer : ClockLayer { + constructorMap[Pair(TLayer::class, null)] = constructor + } + + inline fun <reified TLayer, reified TStyle> registerTextConstructor( + noinline constructor: LayerControllerConstructor, + ) where TLayer : ClockLayer, TStyle : TextStyle { + constructorMap[Pair(TLayer::class, TStyle::class)] = constructor + } + + init { + registerConstructor<ComposedDigitalHandLayer>(::ComposedDigitalLayerController) + registerTextConstructor<DigitalHandLayer, FontTextStyle>(::createSimpleDigitalLayer) + } + + private fun createSimpleDigitalLayer( + ctx: Context, + assets: AssetLoader, + layer: ClockLayer, + isLargeClock: Boolean, + messageBuffer: MessageBuffer + ): SimpleClockLayerController { + val view = SimpleDigitalClockTextView(ctx, messageBuffer) + return SimpleDigitalHandLayerController( + ctx, + assets, + layer as DigitalHandLayer, + view, + messageBuffer + ) + } + + fun create( + ctx: Context, + assets: AssetLoader, + layer: ClockLayer, + isLargeClock: Boolean, + messageBuffer: MessageBuffer + ): SimpleClockLayerController { + val styleClass = if (layer is DigitalHandLayer) layer.style::class else null + val key = Pair(layer::class, styleClass) + return constructorMap[key]?.invoke(ctx, assets, layer, isLargeClock, messageBuffer) + ?: throw IllegalArgumentException("Unrecognized ClockLayer type: $key") + } + } +} diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleClockRelativeLayout.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleClockRelativeLayout.kt new file mode 100644 index 000000000000..6e1b9aabf86d --- /dev/null +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleClockRelativeLayout.kt @@ -0,0 +1,47 @@ +/* + * 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.shared.clocks + +import android.content.Context +import android.view.View.MeasureSpec.EXACTLY +import android.widget.RelativeLayout +import androidx.core.view.children +import com.android.systemui.shared.clocks.view.SimpleDigitalClockView + +class SimpleClockRelativeLayout(context: Context, val faceLayout: DigitalFaceLayout?) : + RelativeLayout(context) { + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + // For migrate_clocks_to_blueprint, mode is EXACTLY + // when the flag is turned off, we won't execute this codes + if (MeasureSpec.getMode(heightMeasureSpec) == EXACTLY) { + if ( + faceLayout == DigitalFaceLayout.TWO_PAIRS_VERTICAL || + faceLayout == DigitalFaceLayout.FOUR_DIGITS_ALIGN_CENTER + ) { + val constrainedHeight = MeasureSpec.getSize(heightMeasureSpec) / 2F + children.forEach { + // The assumption here is the height of text view is linear to font size + (it as SimpleDigitalClockView).applyTextSize( + constrainedHeight, + constrainedByHeight = true, + ) + } + } + } + super.onMeasure(widthMeasureSpec, heightMeasureSpec) + } +} diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleDigitalHandLayerController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleDigitalHandLayerController.kt new file mode 100644 index 000000000000..a3240f81e499 --- /dev/null +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleDigitalHandLayerController.kt @@ -0,0 +1,328 @@ +/* + * 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.shared.clocks + +import android.content.Context +import android.content.res.Resources +import android.graphics.Rect +import android.view.View +import android.view.ViewGroup +import android.widget.RelativeLayout +import androidx.annotation.VisibleForTesting +import com.android.systemui.customization.R +import com.android.systemui.log.core.Logger +import com.android.systemui.log.core.MessageBuffer +import com.android.systemui.plugins.clocks.AlarmData +import com.android.systemui.plugins.clocks.ClockAnimations +import com.android.systemui.plugins.clocks.ClockEvents +import com.android.systemui.plugins.clocks.ClockFaceConfig +import com.android.systemui.plugins.clocks.ClockFaceEvents +import com.android.systemui.plugins.clocks.ClockReactiveSetting +import com.android.systemui.plugins.clocks.WeatherData +import com.android.systemui.plugins.clocks.ZenData +import com.android.systemui.shared.clocks.view.SimpleDigitalClockView +import java.util.Locale +import java.util.TimeZone + +private val TAG = SimpleDigitalHandLayerController::class.simpleName!! + +open class SimpleDigitalHandLayerController<T>( + private val ctx: Context, + private val assets: AssetLoader, + private val layer: DigitalHandLayer, + override val view: T, + messageBuffer: MessageBuffer, +) : SimpleClockLayerController where T : View, T : SimpleDigitalClockView { + private val logger = Logger(messageBuffer, TAG) + val timespec = DigitalTimespecHandler(layer.timespec, layer.dateTimeFormat) + + @VisibleForTesting + fun hasLeadingZero() = layer.dateTimeFormat.startsWith("hh") || timespec.is24Hr + + @VisibleForTesting + override var fakeTimeMills: Long? + get() = timespec.fakeTimeMills + set(value) { + timespec.fakeTimeMills = value + } + + override val config = ClockFaceConfig() + var dozeState: DefaultClockController.AnimationState? = null + var isRegionDark: Boolean = true + + init { + view.layoutParams = + RelativeLayout.LayoutParams( + ViewGroup.LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ) + if (layer.alignment != null) { + layer.alignment.verticalAlignment?.let { view.verticalAlignment = it } + layer.alignment.horizontalAlignment?.let { view.horizontalAlignment = it } + } + view.applyStyles(assets, layer.style, layer.aodStyle) + view.id = + ctx.resources.getIdentifier( + generateDigitalLayerIdString(layer), + "id", + ctx.getPackageName(), + ) + } + + fun applyLayout(layout: DigitalFaceLayout?) { + when (layout) { + DigitalFaceLayout.FOUR_DIGITS_ALIGN_CENTER, + DigitalFaceLayout.FOUR_DIGITS_HORIZONTAL -> applyFourDigitsLayout(layout) + DigitalFaceLayout.TWO_PAIRS_HORIZONTAL, + DigitalFaceLayout.TWO_PAIRS_VERTICAL -> applyTwoPairsLayout(layout) + else -> { + // one view always use FrameLayout + // no need to change here + } + } + applyMargin() + } + + private fun applyMargin() { + if (view.layoutParams is RelativeLayout.LayoutParams) { + val lp = view.layoutParams as RelativeLayout.LayoutParams + layer.marginRatio?.let { + lp.setMargins( + /* left = */ (it.left * view.measuredWidth).toInt(), + /* top = */ (it.top * view.measuredHeight).toInt(), + /* right = */ (it.right * view.measuredWidth).toInt(), + /* bottom = */ (it.bottom * view.measuredHeight).toInt(), + ) + } + view.layoutParams = lp + } + } + + private fun applyTwoPairsLayout(twoPairsLayout: DigitalFaceLayout) { + val lp = view.layoutParams as RelativeLayout.LayoutParams + lp.addRule(RelativeLayout.TEXT_ALIGNMENT_CENTER) + if (twoPairsLayout == DigitalFaceLayout.TWO_PAIRS_HORIZONTAL) { + when (view.id) { + R.id.HOUR_DIGIT_PAIR -> { + lp.addRule(RelativeLayout.CENTER_VERTICAL) + lp.addRule(RelativeLayout.ALIGN_PARENT_START) + } + R.id.MINUTE_DIGIT_PAIR -> { + lp.addRule(RelativeLayout.CENTER_VERTICAL) + lp.addRule(RelativeLayout.END_OF, R.id.HOUR_DIGIT_PAIR) + } + else -> { + throw Exception("cannot apply two pairs layout to view ${view.id}") + } + } + } else { + when (view.id) { + R.id.HOUR_DIGIT_PAIR -> { + lp.addRule(RelativeLayout.CENTER_HORIZONTAL) + lp.addRule(RelativeLayout.ALIGN_PARENT_TOP) + } + R.id.MINUTE_DIGIT_PAIR -> { + lp.addRule(RelativeLayout.CENTER_HORIZONTAL) + lp.addRule(RelativeLayout.BELOW, R.id.HOUR_DIGIT_PAIR) + } + else -> { + throw Exception("cannot apply two pairs layout to view ${view.id}") + } + } + } + view.layoutParams = lp + } + + private fun applyFourDigitsLayout(fourDigitsfaceLayout: DigitalFaceLayout) { + val lp = view.layoutParams as RelativeLayout.LayoutParams + when (fourDigitsfaceLayout) { + DigitalFaceLayout.FOUR_DIGITS_ALIGN_CENTER -> { + when (view.id) { + R.id.HOUR_FIRST_DIGIT -> { + lp.addRule(RelativeLayout.ALIGN_PARENT_START) + lp.addRule(RelativeLayout.ALIGN_PARENT_TOP) + } + R.id.HOUR_SECOND_DIGIT -> { + lp.addRule(RelativeLayout.END_OF, R.id.HOUR_FIRST_DIGIT) + lp.addRule(RelativeLayout.ALIGN_TOP, R.id.HOUR_FIRST_DIGIT) + } + R.id.MINUTE_FIRST_DIGIT -> { + lp.addRule(RelativeLayout.ALIGN_START, R.id.HOUR_FIRST_DIGIT) + lp.addRule(RelativeLayout.BELOW, R.id.HOUR_FIRST_DIGIT) + } + R.id.MINUTE_SECOND_DIGIT -> { + lp.addRule(RelativeLayout.ALIGN_START, R.id.HOUR_SECOND_DIGIT) + lp.addRule(RelativeLayout.BELOW, R.id.HOUR_SECOND_DIGIT) + } + else -> { + throw Exception("cannot apply four digits layout to view ${view.id}") + } + } + } + DigitalFaceLayout.FOUR_DIGITS_HORIZONTAL -> { + when (view.id) { + R.id.HOUR_FIRST_DIGIT -> { + lp.addRule(RelativeLayout.CENTER_VERTICAL) + lp.addRule(RelativeLayout.ALIGN_PARENT_START) + } + R.id.HOUR_SECOND_DIGIT -> { + lp.addRule(RelativeLayout.CENTER_VERTICAL) + lp.addRule(RelativeLayout.END_OF, R.id.HOUR_FIRST_DIGIT) + } + R.id.MINUTE_FIRST_DIGIT -> { + lp.addRule(RelativeLayout.CENTER_VERTICAL) + lp.addRule(RelativeLayout.END_OF, R.id.HOUR_SECOND_DIGIT) + } + R.id.MINUTE_SECOND_DIGIT -> { + lp.addRule(RelativeLayout.CENTER_VERTICAL) + lp.addRule(RelativeLayout.END_OF, R.id.MINUTE_FIRST_DIGIT) + } + else -> { + throw Exception("cannot apply FOUR_DIGITS_HORIZONTAL to view ${view.id}") + } + } + } + else -> { + throw IllegalArgumentException( + "applyFourDigitsLayout function should not " + + "have parameters as ${layer.faceLayout}" + ) + } + } + if (lp == view.layoutParams) { + return + } + view.layoutParams = lp + } + + fun refreshTime() { + timespec.updateTime() + val text = timespec.getDigitString() + if (view.text != text) { + view.text = text + view.refreshTime() + logger.d({ "refreshTime: new text=$str1" }) { str1 = text } + } + } + + override val events = + object : ClockEvents { + override var isReactiveTouchInteractionEnabled = false + + override fun onLocaleChanged(locale: Locale) { + timespec.updateLocale(locale) + refreshTime() + } + + /** Call whenever the text time format changes (12hr vs 24hr) */ + override fun onTimeFormatChanged(is24Hr: Boolean) { + timespec.is24Hr = is24Hr + refreshTime() + } + + override fun onTimeZoneChanged(timeZone: TimeZone) { + timespec.timeZone = timeZone + refreshTime() + } + + override fun onColorPaletteChanged(resources: Resources) {} + + override fun onSeedColorChanged(seedColor: Int?) {} + + override fun onWeatherDataChanged(data: WeatherData) {} + + override fun onAlarmDataChanged(data: AlarmData) {} + + override fun onZenDataChanged(data: ZenData) {} + + override fun onReactiveAxesChanged(axes: List<ClockReactiveSetting>) {} + } + + override fun updateColors() { + view.updateColors(assets, isRegionDark) + refreshTime() + } + + override val animations = + object : ClockAnimations { + override fun enter() { + applyLayout(layer.faceLayout) + refreshTime() + } + + override fun doze(fraction: Float) { + if (dozeState == null) { + dozeState = DefaultClockController.AnimationState(fraction) + view.animateDoze(dozeState!!.isActive, false) + } else { + val (hasChanged, hasJumped) = dozeState!!.update(fraction) + if (hasChanged) view.animateDoze(dozeState!!.isActive, !hasJumped) + } + view.dozeFraction = fraction + } + + override fun fold(fraction: Float) { + applyLayout(layer.faceLayout) + refreshTime() + } + + override fun charge() { + view.animateCharge() + } + + override fun onPickerCarouselSwiping(swipingFraction: Float) {} + + override fun onPositionUpdated(fromLeft: Int, direction: Int, fraction: Float) {} + + override fun onPositionUpdated(distance: Float, fraction: Float) {} + } + + override val faceEvents = + object : ClockFaceEvents { + override fun onTimeTick() { + refreshTime() + if ( + layer.timespec == DigitalTimespec.TIME_FULL_FORMAT || + layer.timespec == DigitalTimespec.DATE_FORMAT + ) { + view.contentDescription = timespec.getContentDescription() + } + } + + override fun onFontSettingChanged(fontSizePx: Float) { + view.applyTextSize(fontSizePx) + applyMargin() + } + + override fun onRegionDarknessChanged(isRegionDark: Boolean) { + this@SimpleDigitalHandLayerController.isRegionDark = isRegionDark + updateColors() + } + + override fun onTargetRegionChanged(targetRegion: Rect?) {} + + override fun onSecondaryDisplayChanged(onSecondaryDisplay: Boolean) {} + } + + companion object { + private val DEFAULT_LIGHT_COLOR = "@android:color/system_accent1_100+0" + private val DEFAULT_DARK_COLOR = "@android:color/system_accent2_600+0" + + fun getDefaultColor(assets: AssetLoader, isRegionDark: Boolean) = + assets.readColor(if (isRegionDark) DEFAULT_LIGHT_COLOR else DEFAULT_DARK_COLOR) + } +} diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/TimespecHandler.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/TimespecHandler.kt new file mode 100644 index 000000000000..ed6a403a7c7e --- /dev/null +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/TimespecHandler.kt @@ -0,0 +1,161 @@ +/* + * 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.shared.clocks + +import android.icu.text.DateFormat +import android.icu.text.SimpleDateFormat +import android.icu.util.TimeZone as IcuTimeZone +import android.icu.util.ULocale +import androidx.annotation.VisibleForTesting +import java.util.Calendar +import java.util.Locale +import java.util.TimeZone + +open class TimespecHandler( + val cal: Calendar, +) { + var timeZone: TimeZone + get() = cal.timeZone + set(value) { + cal.timeZone = value + onTimeZoneChanged() + } + + @VisibleForTesting var fakeTimeMills: Long? = null + + fun updateTime() { + var timeMs = fakeTimeMills ?: System.currentTimeMillis() + cal.timeInMillis = (timeMs * TIME_TRAVEL_SCALE).toLong() + } + + protected open fun onTimeZoneChanged() {} + + companion object { + // Modifying this will cause the clock to run faster or slower. This is a useful way of + // manually checking that clocks are correctly animating through time. + private const val TIME_TRAVEL_SCALE = 1.0 + } +} + +class DigitalTimespecHandler( + val timespec: DigitalTimespec, + private val timeFormat: String, + cal: Calendar = Calendar.getInstance(), +) : TimespecHandler(cal) { + var is24Hr = false + set(value) { + field = value + applyPattern() + } + + private var dateFormat = updateSimpleDateFormat(Locale.getDefault()) + private var contentDescriptionFormat = getContentDescriptionFormat(Locale.getDefault()) + + init { + applyPattern() + } + + override fun onTimeZoneChanged() { + dateFormat.timeZone = IcuTimeZone.getTimeZone(cal.timeZone.id) + contentDescriptionFormat?.timeZone = IcuTimeZone.getTimeZone(cal.timeZone.id) + applyPattern() + } + + fun updateLocale(locale: Locale) { + dateFormat = updateSimpleDateFormat(locale) + contentDescriptionFormat = getContentDescriptionFormat(locale) + onTimeZoneChanged() + } + + private fun updateSimpleDateFormat(locale: Locale): DateFormat { + if ( + locale.language.equals(Locale.ENGLISH.language) || + timespec != DigitalTimespec.DATE_FORMAT + ) { + // force date format in English, and time format to use format defined in json + return SimpleDateFormat(timeFormat, timeFormat, ULocale.forLocale(locale)) + } else { + return SimpleDateFormat.getInstanceForSkeleton(timeFormat, locale) + } + } + + private fun getContentDescriptionFormat(locale: Locale): DateFormat? { + return when (timespec) { + DigitalTimespec.TIME_FULL_FORMAT -> + SimpleDateFormat.getInstanceForSkeleton("hh:mm", locale) + DigitalTimespec.DATE_FORMAT -> + SimpleDateFormat.getInstanceForSkeleton("EEEE MMMM d", locale) + else -> { + null + } + } + } + + private fun applyPattern() { + val timeFormat24Hour = timeFormat.replace("hh", "h").replace("h", "HH") + val format = if (is24Hr) timeFormat24Hour else timeFormat + if (timespec != DigitalTimespec.DATE_FORMAT) { + (dateFormat as SimpleDateFormat).applyPattern(format) + (contentDescriptionFormat as? SimpleDateFormat)?.applyPattern( + if (is24Hr) CONTENT_DESCRIPTION_TIME_FORMAT_24_HOUR + else CONTENT_DESCRIPTION_TIME_FORMAT_12_HOUR + ) + } + } + + private fun getSingleDigit(): String { + val isFirstDigit = timespec == DigitalTimespec.FIRST_DIGIT + val text = dateFormat.format(cal.time).toString() + return text.substring( + if (isFirstDigit) 0 else text.length - 1, + if (isFirstDigit) text.length - 1 else text.length + ) + } + + fun getDigitString(): String { + return when (timespec) { + DigitalTimespec.FIRST_DIGIT, + DigitalTimespec.SECOND_DIGIT -> getSingleDigit() + DigitalTimespec.DIGIT_PAIR -> { + dateFormat.format(cal.time).toString() + } + DigitalTimespec.TIME_FULL_FORMAT -> { + dateFormat.format(cal.time).toString() + } + DigitalTimespec.DATE_FORMAT -> { + dateFormat.format(cal.time).toString().uppercase() + } + } + } + + fun getContentDescription(): String? { + return when (timespec) { + DigitalTimespec.TIME_FULL_FORMAT, + DigitalTimespec.DATE_FORMAT -> { + contentDescriptionFormat?.format(cal.time).toString() + } + else -> { + return null + } + } + } + + companion object { + const val CONTENT_DESCRIPTION_TIME_FORMAT_12_HOUR = "hh:mm" + const val CONTENT_DESCRIPTION_TIME_FORMAT_24_HOUR = "HH:mm" + } +} diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt index d63e728cf443..d63e728cf443 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt index 2c1dacdfae73..2c1dacdfae73 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt index 5fe5cb3fe43c..5fe5cb3fe43c 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java index 7bb6ef1c8895..7bb6ef1c8895 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt index 9cd52153eff6..9cd52153eff6 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt index 3c229975eef5..3c229975eef5 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSliceViewControllerTest.java index 8b5372a1f035..8b5372a1f035 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewControllerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSliceViewControllerTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlaySuppressionControllerImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/clipboardoverlay/ClipboardOverlaySuppressionControllerImplTest.kt index 86788d35a670..86788d35a670 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlaySuppressionControllerImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/clipboardoverlay/ClipboardOverlaySuppressionControllerImplTest.kt 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 179ba2256442..cecc11e5ffd4 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 @@ -19,7 +19,6 @@ package com.android.systemui.communal.view.viewmodel import android.appwidget.AppWidgetProviderInfo import android.content.ActivityNotFoundException import android.content.ComponentName -import android.content.Intent import android.content.pm.PackageManager import android.content.pm.UserInfo import android.provider.Settings @@ -27,7 +26,6 @@ import android.view.accessibility.AccessibilityEvent import android.view.accessibility.AccessibilityManager import android.view.accessibility.accessibilityManager import android.widget.RemoteViews -import androidx.activity.result.ActivityResultLauncher import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.logging.UiEventLogger @@ -88,7 +86,6 @@ class CommunalEditModeViewModelTest : SysuiTestCase() { @Mock private lateinit var mediaHost: MediaHost @Mock private lateinit var uiEventLogger: UiEventLogger @Mock private lateinit var packageManager: PackageManager - @Mock private lateinit var activityResultLauncher: ActivityResultLauncher<Intent> @Mock private lateinit var metricsLogger: CommunalMetricsLogger private val kosmos = testKosmos() @@ -117,10 +114,7 @@ class CommunalEditModeViewModelTest : SysuiTestCase() { communalSceneInteractor = kosmos.communalSceneInteractor communalInteractor = spy(kosmos.communalInteractor) kosmos.fakeUserRepository.setUserInfos(listOf(MAIN_USER_INFO)) - kosmos.fakeUserTracker.set( - userInfos = listOf(MAIN_USER_INFO), - selectedUserIndex = 0, - ) + kosmos.fakeUserTracker.set(userInfos = listOf(MAIN_USER_INFO), selectedUserIndex = 0) kosmos.fakeFeatureFlagsClassic.set(Flags.COMMUNAL_SERVICE_ENABLED, true) accessibilityManager = kosmos.accessibilityManager @@ -257,10 +251,13 @@ class CommunalEditModeViewModelTest : SysuiTestCase() { @Test fun onOpenWidgetPicker_launchesWidgetPickerActivity() { testScope.runTest { + var activityStarted = false val success = - underTest.onOpenWidgetPicker(testableResources.resources, activityResultLauncher) + underTest.onOpenWidgetPicker(testableResources.resources) { _ -> + run { activityStarted = true } + } - verify(activityResultLauncher).launch(any()) + assertTrue(activityStarted) assertTrue(success) } } @@ -268,14 +265,10 @@ class CommunalEditModeViewModelTest : SysuiTestCase() { @Test fun onOpenWidgetPicker_activityLaunchThrowsException_failure() { testScope.runTest { - whenever(activityResultLauncher.launch(any())) - .thenThrow(ActivityNotFoundException::class.java) - val success = - underTest.onOpenWidgetPicker( - testableResources.resources, - activityResultLauncher, - ) + underTest.onOpenWidgetPicker(testableResources.resources) { _ -> + run { throw ActivityNotFoundException() } + } assertFalse(success) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/lifecycle/HydratorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/HydratorTest.kt index b0e93fbecbb9..b0e93fbecbb9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/lifecycle/HydratorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/HydratorTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/NavigationBarButtonTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarButtonTest.java index 00e79f5a3ac2..00e79f5a3ac2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/NavigationBarButtonTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarButtonTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt index c7da03dbbf30..c7da03dbbf30 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenrecord/data/model/ScreenRecordModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenrecord/data/model/ScreenRecordModelTest.kt index 9331c8df46d4..0bbf47c2275c 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenrecord/data/model/ScreenRecordModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenrecord/data/model/ScreenRecordModelTest.kt @@ -16,13 +16,16 @@ package com.android.systemui.screenrecord.data.model +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.screenrecord.data.model.ScreenRecordModel.Starting.Companion.toCountdownSeconds import com.google.common.truth.Truth.assertThat +import org.junit.runner.RunWith import kotlin.test.Test @SmallTest +@RunWith(AndroidJUnit4::class) class ScreenRecordModelTest : SysuiTestCase() { @Test fun countdownSeconds_millis0_is0() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotDataTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/ScreenshotDataTest.kt index 4ede90ec4466..4ede90ec4466 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotDataTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/ScreenshotDataTest.kt diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java index 0e9ef06aa1b9..0454317b5f04 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java @@ -22,6 +22,10 @@ import static com.android.systemui.log.LogBufferHelperKt.logcatLogBuffer; import static com.google.common.truth.Truth.assertThat; +import static kotlinx.coroutines.flow.FlowKt.emptyFlow; +import static kotlinx.coroutines.flow.SharedFlowKt.MutableSharedFlow; +import static kotlinx.coroutines.flow.StateFlowKt.MutableStateFlow; + import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyFloat; @@ -36,10 +40,6 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import static kotlinx.coroutines.flow.FlowKt.emptyFlow; -import static kotlinx.coroutines.flow.SharedFlowKt.MutableSharedFlow; -import static kotlinx.coroutines.flow.StateFlowKt.MutableStateFlow; - import android.animation.Animator; import android.annotation.IdRes; import android.content.ContentResolver; @@ -201,6 +201,12 @@ import com.android.systemui.util.time.FakeSystemClock; import com.android.systemui.util.time.SystemClock; import com.android.wm.shell.animation.FlingAnimationUtils; +import dagger.Lazy; + +import kotlinx.coroutines.CoroutineDispatcher; +import kotlinx.coroutines.channels.BufferOverflow; +import kotlinx.coroutines.test.TestScope; + import org.junit.After; import org.junit.Before; import org.junit.Rule; @@ -215,11 +221,6 @@ import java.util.HashSet; import java.util.List; import java.util.Optional; -import dagger.Lazy; -import kotlinx.coroutines.CoroutineDispatcher; -import kotlinx.coroutines.channels.BufferOverflow; -import kotlinx.coroutines.test.TestScope; - public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { protected static final int SPLIT_SHADE_FULL_TRANSITION_DISTANCE = 400; @@ -461,7 +462,8 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { () -> mKosmos.getSceneInteractor(), () -> mKosmos.getSceneContainerOcclusionInteractor(), () -> mKosmos.getKeyguardClockInteractor(), - () -> mKosmos.getSceneBackInteractor()); + () -> mKosmos.getSceneBackInteractor(), + () -> mKosmos.getAlternateBouncerInteractor()); KeyguardStatusView keyguardStatusView = new KeyguardStatusView(mContext); keyguardStatusView.setId(R.id.keyguard_status_view); @@ -622,7 +624,8 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { () -> mKosmos.getSceneInteractor(), () -> mKosmos.getSceneContainerOcclusionInteractor(), () -> mKosmos.getKeyguardClockInteractor(), - () -> mKosmos.getSceneBackInteractor()), + () -> mKosmos.getSceneBackInteractor(), + () -> mKosmos.getAlternateBouncerInteractor()), mKeyguardBypassController, mDozeParameters, mScreenOffAnimationController, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/OperatorNameViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/OperatorNameViewControllerTest.kt index d6b3b919913f..d6b3b919913f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/OperatorNameViewControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/OperatorNameViewControllerTest.kt diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt index db274cc311dd..f8720b4fe5f8 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt @@ -28,6 +28,8 @@ import com.android.internal.logging.testing.UiEventLoggerFake import com.android.systemui.SysuiTestCase import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository import com.android.systemui.authentication.shared.model.AuthenticationMethodModel +import com.android.systemui.bouncer.domain.interactor.alternateBouncerInteractor +import com.android.systemui.bouncer.domain.interactor.givenCanShowAlternateBouncer import com.android.systemui.coroutines.collectLastValue import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor import com.android.systemui.flags.DisableSceneContainer @@ -83,8 +85,9 @@ class StatusBarStateControllerImplTest(flags: FlagsParameterization) : SysuiTest private val kosmos = testKosmos() private val testScope = kosmos.testScope - private val sceneInteractor = kosmos.sceneInteractor - private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository + private val sceneInteractor by lazy { kosmos.sceneInteractor } + private val keyguardTransitionRepository by lazy { kosmos.fakeKeyguardTransitionRepository } + private val alternateBouncerInteractor by lazy { kosmos.alternateBouncerInteractor } private val mockDarkAnimator = mock<ObjectAnimator>() private lateinit var underTest: StatusBarStateControllerImpl @@ -121,6 +124,7 @@ class StatusBarStateControllerImplTest(flags: FlagsParameterization) : SysuiTest { kosmos.sceneContainerOcclusionInteractor }, { kosmos.keyguardClockInteractor }, { kosmos.sceneBackInteractor }, + { kosmos.alternateBouncerInteractor }, ) { override fun createDarkAnimator(): ObjectAnimator { return mockDarkAnimator @@ -299,6 +303,52 @@ class StatusBarStateControllerImplTest(flags: FlagsParameterization) : SysuiTest @Test @EnableSceneContainer + @DisableFlags(DualShade.FLAG_NAME) + fun start_hydratesStatusBarState_withAlternateBouncer() = + testScope.runTest { + var statusBarState = underTest.state + val listener = + object : StatusBarStateController.StateListener { + override fun onStateChanged(newState: Int) { + statusBarState = newState + } + } + underTest.addCallback(listener) + + val currentScene by collectLastValue(sceneInteractor.currentScene) + val deviceUnlockStatus by + collectLastValue(kosmos.deviceUnlockedInteractor.deviceUnlockStatus) + val alternateBouncerIsVisible by collectLastValue(alternateBouncerInteractor.isVisible) + + kosmos.fakeAuthenticationRepository.setAuthenticationMethod( + AuthenticationMethodModel.Password + ) + kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus( + SuccessFingerprintAuthenticationStatus(0, true) + ) + runCurrent() + assertThat(deviceUnlockStatus!!.isUnlocked).isTrue() + + sceneInteractor.changeScene(toScene = Scenes.Lockscreen, loggingReason = "reason") + runCurrent() + assertThat(currentScene).isEqualTo(Scenes.Lockscreen) + + kosmos.givenCanShowAlternateBouncer() + alternateBouncerInteractor.forceShow() + runCurrent() + assertThat(alternateBouncerIsVisible).isTrue() + + // Call start to begin hydrating based on the scene framework: + underTest.start() + + sceneInteractor.changeScene(toScene = Scenes.Gone, loggingReason = "reason") + runCurrent() + assertThat(currentScene).isEqualTo(Scenes.Gone) + assertThat(statusBarState).isEqualTo(StatusBarState.KEYGUARD) + } + + @Test + @EnableSceneContainer @EnableFlags(DualShade.FLAG_NAME) fun start_hydratesStatusBarState_dualShade_whileLocked() = testScope.runTest { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/call/domain/interactor/CallChipInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/domain/interactor/CallChipInteractorTest.kt index 8f41caf54ec8..8f41caf54ec8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/call/domain/interactor/CallChipInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/domain/interactor/CallChipInteractorTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt index 7bc6d4ae2816..7bc6d4ae2816 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/domian/interactor/MediaRouterChipInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/casttootherdevice/domian/interactor/MediaRouterChipInteractorTest.kt index ecb1a6d44b22..ecb1a6d44b22 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/domian/interactor/MediaRouterChipInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/casttootherdevice/domian/interactor/MediaRouterChipInteractorTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndCastScreenToOtherDeviceDialogDelegateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndCastScreenToOtherDeviceDialogDelegateTest.kt index 5005d1609113..5005d1609113 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndCastScreenToOtherDeviceDialogDelegateTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndCastScreenToOtherDeviceDialogDelegateTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndGenericCastToOtherDeviceDialogDelegateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndGenericCastToOtherDeviceDialogDelegateTest.kt index e6101f500ad1..e6101f500ad1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndGenericCastToOtherDeviceDialogDelegateTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndGenericCastToOtherDeviceDialogDelegateTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelTest.kt index 77992dbaecc2..77992dbaecc2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractorTest.kt index d0c5e7a102e0..d0c5e7a102e0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractorTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndMediaProjectionDialogHelperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndMediaProjectionDialogHelperTest.kt index c62e40414121..c62e40414121 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndMediaProjectionDialogHelperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndMediaProjectionDialogHelperTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ron/demo/ui/viewmodel/DemoRonChipViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ron/demo/ui/viewmodel/DemoRonChipViewModelTest.kt index 118dea6376bb..118dea6376bb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ron/demo/ui/viewmodel/DemoRonChipViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ron/demo/ui/viewmodel/DemoRonChipViewModelTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/screenrecord/domain/interactor/ScreenRecordChipInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/screenrecord/domain/interactor/ScreenRecordChipInteractorTest.kt index 6bfb40fa17c5..6bfb40fa17c5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/screenrecord/domain/interactor/ScreenRecordChipInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/screenrecord/domain/interactor/ScreenRecordChipInteractorTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/screenrecord/ui/view/EndScreenRecordingDialogDelegateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/screenrecord/ui/view/EndScreenRecordingDialogDelegateTest.kt index bfb63ac66d3d..bfb63ac66d3d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/screenrecord/ui/view/EndScreenRecordingDialogDelegateTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/screenrecord/ui/view/EndScreenRecordingDialogDelegateTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelTest.kt index 16101bfe387c..16101bfe387c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/view/EndShareToAppDialogDelegateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/view/EndShareToAppDialogDelegateTest.kt index 325a42bca7d1..325a42bca7d1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/view/EndShareToAppDialogDelegateTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/view/EndShareToAppDialogDelegateTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelTest.kt index 791a21d0fb63..791a21d0fb63 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/ChipTransitionHelperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/ChipTransitionHelperTest.kt index 4977c548fb92..4977c548fb92 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/ChipTransitionHelperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/ChipTransitionHelperTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModelTest.kt index 6e4d8863fee2..6e4d8863fee2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModelTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt index 26ce7b956fde..26ce7b956fde 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithRonsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithRonsViewModelTest.kt index 631120b39805..631120b39805 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithRonsViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithRonsViewModelTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AboveShelfObserverTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/AboveShelfObserverTest.java index 01a0fd020bda..01a0fd020bda 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AboveShelfObserverTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/AboveShelfObserverTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationTransitionAnimatorControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationTransitionAnimatorControllerTest.kt index cb92b7745961..cb92b7745961 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationTransitionAnimatorControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationTransitionAnimatorControllerTest.kt diff --git a/packages/SystemUI/tests/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 d717fe4c1e04..d717fe4c1e04 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractorTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java index 1c5f37cc60c3..1c5f37cc60c3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java index 027e899e20df..027e899e20df 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt index 48608ebd6de0..48608ebd6de0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationSnoozeTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationSnoozeTest.java index 22f1e4604bbd..22f1e4604bbd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationSnoozeTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationSnoozeTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java index 2340d0289db4..2340d0289db4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapperTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapperTest.java index ec280a1d6d01..ec280a1d6d01 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapperTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapperTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapperTest.kt index 9d990b1d7edf..9d990b1d7edf 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapperTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapperTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapperTest.java index f9a9704334a0..f9a9704334a0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapperTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapperTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMessagingTemplateViewWrapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMessagingTemplateViewWrapperTest.kt index fc829d53a6b4..fc829d53a6b4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMessagingTemplateViewWrapperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMessagingTemplateViewWrapperTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapperTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapperTest.java index d17c8dbcf38d..d17c8dbcf38d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapperTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapperTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java index 14bbd38ece2c..14bbd38ece2c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationTargetsHelperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationTargetsHelperTest.kt index 660eb308fdf3..660eb308fdf3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationTargetsHelperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationTargetsHelperTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java index dd03ab393ce9..dd03ab393ce9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java index 68e17c1b2d73..68e17c1b2d73 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewTest.java index 0932a0c9307c..0932a0c9307c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarTransitionsControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LightBarTransitionsControllerTest.java index 7dfdb9228936..7dfdb9228936 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarTransitionsControllerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LightBarTransitionsControllerTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt index 83600422bda4..83600422bda4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java index 1d74331e429b..1d74331e429b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialogTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialogTest.kt index b560c591af1e..b560c591af1e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialogTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialogTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt index 624c070e95e0..624c070e95e0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt index bd857807851c..bd857807851c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CastDeviceTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/CastDeviceTest.kt index 16061df1fa89..16061df1fa89 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CastDeviceTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/CastDeviceTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateTest.kt index 06b3b57bd133..06b3b57bd133 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateTest.kt diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureMonitorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureMonitorTest.kt index ba9fa926947c..cd18925eb44f 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureMonitorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureMonitorTest.kt @@ -25,6 +25,7 @@ import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.InProgress import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.NotStarted import com.android.systemui.touchpad.tutorial.ui.gesture.MultiFingerGesture.Companion.SWIPE_DISTANCE import com.google.common.truth.Truth.assertThat +import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -34,10 +35,12 @@ class BackGestureMonitorTest : SysuiTestCase() { private var gestureState: GestureState = NotStarted private val gestureMonitor = - BackGestureMonitor( - gestureDistanceThresholdPx = SWIPE_DISTANCE.toInt(), - gestureStateChangedCallback = { gestureState = it }, - ) + BackGestureMonitor(gestureDistanceThresholdPx = SWIPE_DISTANCE.toInt()) + + @Before + fun before() { + gestureMonitor.addGestureStateCallback { gestureState = it } + } @Test fun triggersGestureFinishedForThreeFingerGestureRight() { @@ -82,7 +85,7 @@ class BackGestureMonitorTest : SysuiTestCase() { } private fun assertStateAfterEvents(events: List<MotionEvent>, expectedState: GestureState) { - events.forEach { gestureMonitor.processTouchpadEvent(it) } + events.forEach { gestureMonitor.accept(it) } assertThat(gestureState).isEqualTo(expectedState) } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/EasterEggGestureTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/EasterEggGestureTest.kt index a83ed5649889..3f1633a8972f 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/EasterEggGestureTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/EasterEggGestureTest.kt @@ -36,10 +36,7 @@ class EasterEggGestureTest : SysuiTestCase() { private var triggered = false private val handler = TouchpadGestureHandler( - BackGestureMonitor( - gestureDistanceThresholdPx = SWIPE_DISTANCE.toInt(), - gestureStateChangedCallback = {}, - ), + BackGestureMonitor(gestureDistanceThresholdPx = SWIPE_DISTANCE.toInt()), EasterEggGestureMonitor(callback = { triggered = true }), ) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureMonitorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureMonitorTest.kt index 59cc026e82ee..edf0e5698bf0 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureMonitorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureMonitorTest.kt @@ -25,6 +25,7 @@ import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.InProgress import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.NotStarted import com.android.systemui.touchpad.tutorial.ui.gesture.MultiFingerGesture.Companion.SWIPE_DISTANCE import com.google.common.truth.Truth.assertThat +import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -34,10 +35,12 @@ class HomeGestureMonitorTest : SysuiTestCase() { private var gestureState: GestureState = GestureState.NotStarted private val gestureMonitor = - HomeGestureMonitor( - gestureDistanceThresholdPx = SWIPE_DISTANCE.toInt(), - gestureStateChangedCallback = { gestureState = it }, - ) + HomeGestureMonitor(gestureDistanceThresholdPx = SWIPE_DISTANCE.toInt()) + + @Before + fun before() { + gestureMonitor.addGestureStateCallback { gestureState = it } + } @Test fun triggersGestureFinishedForThreeFingerGestureUp() { @@ -78,7 +81,7 @@ class HomeGestureMonitorTest : SysuiTestCase() { } private fun assertStateAfterEvents(events: List<MotionEvent>, expectedState: GestureState) { - events.forEach { gestureMonitor.processTouchpadEvent(it) } + events.forEach { gestureMonitor.accept(it) } assertThat(gestureState).isEqualTo(expectedState) } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureMonitorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureMonitorTest.kt index 7eac6bb09264..f68e7732d04e 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureMonitorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureMonitorTest.kt @@ -26,6 +26,7 @@ import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.InProgress import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.NotStarted import com.android.systemui.touchpad.tutorial.ui.gesture.MultiFingerGesture.Companion.SWIPE_DISTANCE import com.google.common.truth.Truth.assertThat +import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.kotlin.doReturn @@ -44,7 +45,7 @@ class RecentAppsGestureMonitorTest : SysuiTestCase() { } private var gestureState: GestureState = GestureState.NotStarted - private val velocityTracker = + private val velocityTracker1D = mock<VelocityTracker1D> { // by default return correct speed for the gesture - as if pointer is slowing down on { calculateVelocity() } doReturn SLOW @@ -52,11 +53,15 @@ class RecentAppsGestureMonitorTest : SysuiTestCase() { private val gestureMonitor = RecentAppsGestureMonitor( gestureDistanceThresholdPx = SWIPE_DISTANCE.toInt(), - gestureStateChangedCallback = { gestureState = it }, velocityThresholdPxPerMs = THRESHOLD_VELOCITY_PX_PER_MS, - velocityTracker = velocityTracker, + velocityTracker = VerticalVelocityTracker(velocityTracker1D), ) + @Before + fun before() { + gestureMonitor.addGestureStateCallback { gestureState = it } + } + @Test fun triggersGestureFinishedForThreeFingerGestureUp() { assertStateAfterEvents(events = ThreeFingerGesture.swipeUp(), expectedState = Finished) @@ -64,7 +69,7 @@ class RecentAppsGestureMonitorTest : SysuiTestCase() { @Test fun doesntTriggerGestureFinished_onGestureSpeedTooHigh() { - whenever(velocityTracker.calculateVelocity()).thenReturn(FAST) + whenever(velocityTracker1D.calculateVelocity()).thenReturn(FAST) assertStateAfterEvents(events = ThreeFingerGesture.swipeUp(), expectedState = NotStarted) } @@ -102,7 +107,7 @@ class RecentAppsGestureMonitorTest : SysuiTestCase() { } private fun assertStateAfterEvents(events: List<MotionEvent>, expectedState: GestureState) { - events.forEach { gestureMonitor.processTouchpadEvent(it) } + events.forEach { gestureMonitor.accept(it) } assertThat(gestureState).isEqualTo(expectedState) } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureHandlerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureHandlerTest.kt index 4d26366614f6..9f7ea679b822 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureHandlerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureHandlerTest.kt @@ -27,6 +27,7 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.touchpad.tutorial.ui.gesture.MultiFingerGesture.Companion.SWIPE_DISTANCE import com.google.common.truth.Truth.assertThat +import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -35,14 +36,14 @@ import org.junit.runner.RunWith class TouchpadGestureHandlerTest : SysuiTestCase() { private var gestureState: GestureState = GestureState.NotStarted - private val handler = - TouchpadGestureHandler( - BackGestureMonitor( - gestureDistanceThresholdPx = SWIPE_DISTANCE.toInt(), - gestureStateChangedCallback = { gestureState = it }, - ), - EasterEggGestureMonitor {}, - ) + private val gestureMonitor = + BackGestureMonitor(gestureDistanceThresholdPx = SWIPE_DISTANCE.toInt()) + private val handler = TouchpadGestureHandler(gestureMonitor, EasterEggGestureMonitor {}) + + @Before + fun before() { + gestureMonitor.addGestureStateCallback { gestureState = it } + } @Test fun handlesEventsFromTouchpad() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryImplTest.kt index b8dd334dcad9..b8dd334dcad9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryImplTest.kt diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/VolumeDialogController.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/VolumeDialogController.java index b1736b16875d..c09509d8690a 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/VolumeDialogController.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/VolumeDialogController.java @@ -14,7 +14,6 @@ package com.android.systemui.plugins; -import android.annotation.IntegerRes; import android.content.ComponentName; import android.media.AudioManager; import android.media.AudioSystem; @@ -22,6 +21,8 @@ import android.os.Handler; import android.os.VibrationEffect; import android.util.SparseArray; +import androidx.annotation.StringRes; + import com.android.systemui.plugins.VolumeDialogController.Callbacks; import com.android.systemui.plugins.VolumeDialogController.State; import com.android.systemui.plugins.VolumeDialogController.StreamState; @@ -90,7 +91,7 @@ public interface VolumeDialogController { public int levelMax; public boolean muted; public boolean muteSupported; - public @IntegerRes int name; + public @StringRes int name; public String remoteLabel; public boolean routedToBluetooth; diff --git a/packages/SystemUI/res/drawable/volume_dialog_background.xml b/packages/SystemUI/res/drawable/volume_dialog_background.xml new file mode 100644 index 000000000000..7d7498feeba6 --- /dev/null +++ b/packages/SystemUI/res/drawable/volume_dialog_background.xml @@ -0,0 +1,22 @@ +<?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. +--> + +<shape xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" + android:shape="rectangle"> + <corners android:radius="@dimen/volume_dialog_background_corner_radius" /> + <solid android:color="?androidprv:attr/materialColorSurface" /> +</shape> diff --git a/packages/SystemUI/res/drawable/volume_dialog_floating_slider_background.xml b/packages/SystemUI/res/drawable/volume_dialog_floating_slider_background.xml new file mode 100644 index 000000000000..2694435bcc78 --- /dev/null +++ b/packages/SystemUI/res/drawable/volume_dialog_floating_slider_background.xml @@ -0,0 +1,21 @@ +<!-- + ~ 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. + --> +<shape xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" + android:shape="rectangle"> + <corners android:radius="20dp" /> + <solid android:color="?androidprv:attr/colorSurface" /> +</shape> diff --git a/packages/SystemUI/res/drawable/volume_dialog_floating_sliders_spacer.xml b/packages/SystemUI/res/drawable/volume_dialog_floating_sliders_spacer.xml new file mode 100644 index 000000000000..66a205a57c61 --- /dev/null +++ b/packages/SystemUI/res/drawable/volume_dialog_floating_sliders_spacer.xml @@ -0,0 +1,23 @@ +<?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. +--> + +<shape xmlns:android="http://schemas.android.com/apk/res/android" + android:shape="rectangle"> + <size + android:width="@dimen/volume_dialog_floating_sliders_spacing" + android:height="@dimen/volume_dialog_floating_sliders_spacing" /> + <solid android:color="@color/transparent" /> +</shape> diff --git a/packages/SystemUI/res/drawable/volume_dialog_spacer.xml b/packages/SystemUI/res/drawable/volume_dialog_spacer.xml new file mode 100644 index 000000000000..3c60784cf6b6 --- /dev/null +++ b/packages/SystemUI/res/drawable/volume_dialog_spacer.xml @@ -0,0 +1,23 @@ +<?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. +--> + +<shape xmlns:android="http://schemas.android.com/apk/res/android" + android:shape="rectangle"> + <size + android:width="@dimen/volume_dialog_spacing" + android:height="@dimen/volume_dialog_spacing" /> + <solid android:color="@color/transparent" /> +</shape> diff --git a/packages/SystemUI/res/drawable/volume_row_seekbar_progress.xml b/packages/SystemUI/res/drawable/volume_row_seekbar_progress.xml index 21b177bead11..fa06bd6abeea 100644 --- a/packages/SystemUI/res/drawable/volume_row_seekbar_progress.xml +++ b/packages/SystemUI/res/drawable/volume_row_seekbar_progress.xml @@ -22,7 +22,7 @@ android:autoMirrored="true"> <item android:id="@+id/volume_seekbar_progress_solid"> <shape> - <size android:height="@dimen/volume_dialog_slider_width" /> + <size android:height="@dimen/volume_dialog_slider_width_legacy" /> <solid android:color="?android:attr/colorAccent" /> <corners android:radius="@dimen/volume_dialog_slider_corner_radius"/> </shape> diff --git a/packages/SystemUI/res/layout-land-television/volume_dialog.xml b/packages/SystemUI/res/layout-land-television/volume_dialog.xml index 0fbc519ca8dd..f77db956a493 100644 --- a/packages/SystemUI/res/layout-land-television/volume_dialog.xml +++ b/packages/SystemUI/res/layout-land-television/volume_dialog.xml @@ -1,92 +1,67 @@ <!-- - ~ Copyright (C) 2020 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 - --> -<FrameLayout - xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:sysui="http://schemas.android.com/apk/res-auto" + 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. +--> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" android:id="@+id/volume_dialog_container" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:background="@android:color/transparent" + android:layout_gravity="right" + android:divider="@drawable/volume_dialog_floating_sliders_spacer" + android:orientation="horizontal" + android:showDividers="middle|end|beginning" android:theme="@style/volume_dialog_theme"> - <FrameLayout - android:id="@+id/volume_dialog" + <LinearLayout + android:id="@+id/volume_dialog_floating_sliders_container" android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="right" - android:background="@android:color/transparent" - android:padding="@dimen/volume_dialog_panel_transparent_padding" - android:clipToPadding="false"> - - <LinearLayout - android:id="@+id/volume_dialog_rows_container" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="right" - android:orientation="vertical" - android:translationZ="@dimen/volume_dialog_elevation" - android:clipChildren="false" - android:clipToPadding="false" - android:background="@android:color/transparent"> + android:layout_height="match_parent" + android:background="@drawable/volume_dialog_background" + android:divider="@drawable/volume_dialog_floating_sliders_spacer" + android:gravity="bottom" + android:orientation="horizontal" + android:paddingBottom="@dimen/volume_dialog_floating_sliders_bottom_padding" + android:showDividers="middle" /> - <LinearLayout - android:id="@+id/volume_dialog_rows" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:gravity="center" - android:orientation="horizontal" - android:background="@drawable/tv_volume_dialog_background"> - <!-- volume rows added and removed here! :-) --> - </LinearLayout> - - </LinearLayout> + <LinearLayout + android:layout_width="@dimen/volume_dialog_width" + android:layout_height="wrap_content" + android:background="@drawable/volume_dialog_background" + android:divider="@drawable/volume_dialog_spacer" + android:gravity="center_horizontal" + android:orientation="vertical" + android:paddingVertical="@dimen/volume_dialog_vertical_padding" + android:showDividers="middle"> <FrameLayout - android:id="@+id/odi_captions" - android:layout_width="@dimen/volume_dialog_caption_size" - android:layout_height="@dimen/volume_dialog_caption_size" - android:layout_marginRight="68dp" - android:layout_gravity="right" - android:clipToPadding="false" - android:translationZ="@dimen/volume_dialog_elevation" - android:background="@drawable/rounded_bg_full"> - - <com.android.systemui.volume.CaptionsToggleImageButton - android:id="@+id/odi_captions_icon" - android:src="@drawable/ic_volume_odi_captions_disabled" - style="@style/VolumeButtons" - android:background="@drawable/rounded_ripple" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:tint="@color/caption_tint_color_selector" - android:layout_gravity="center" - android:soundEffectsEnabled="false"/> - - </FrameLayout> - - <ViewStub - android:id="@+id/odi_captions_tooltip_stub" - android:inflatedId="@+id/odi_captions_tooltip_view" - android:layout="@layout/volume_tool_tip_view" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginRight="@dimen/volume_tool_tip_right_margin" - android:layout_marginTop="@dimen/volume_tool_tip_top_margin" - android:layout_gravity="right"/> + android:id="@+id/volume_dialog_ringer_button" + android:layout_width="@dimen/volume_dialog_button_size" + android:layout_height="@dimen/volume_dialog_button_size" /> - </FrameLayout> + <include + android:id="@+id/volume_dialog_slider" + layout="@layout/volume_dialog_slider" /> -</FrameLayout> + <Button + android:id="@+id/volume_dialog_settings" + android:layout_width="@dimen/volume_dialog_button_size" + android:layout_height="@dimen/volume_dialog_button_size" + android:background="@drawable/ripple_drawable_20dp" + android:contentDescription="@string/accessibility_volume_settings" + android:soundEffectsEnabled="false" + android:src="@drawable/horizontal_ellipsis" + android:tint="?androidprv:attr/materialColorPrimary" /> + </LinearLayout> +</LinearLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout-land-television/volume_dialog_legacy.xml b/packages/SystemUI/res/layout-land-television/volume_dialog_legacy.xml new file mode 100644 index 000000000000..0fbc519ca8dd --- /dev/null +++ b/packages/SystemUI/res/layout-land-television/volume_dialog_legacy.xml @@ -0,0 +1,92 @@ +<!-- + ~ Copyright (C) 2020 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 + --> +<FrameLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:sysui="http://schemas.android.com/apk/res-auto" + android:id="@+id/volume_dialog_container" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:background="@android:color/transparent" + android:theme="@style/volume_dialog_theme"> + + <FrameLayout + android:id="@+id/volume_dialog" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="right" + android:background="@android:color/transparent" + android:padding="@dimen/volume_dialog_panel_transparent_padding" + android:clipToPadding="false"> + + <LinearLayout + android:id="@+id/volume_dialog_rows_container" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="right" + android:orientation="vertical" + android:translationZ="@dimen/volume_dialog_elevation" + android:clipChildren="false" + android:clipToPadding="false" + android:background="@android:color/transparent"> + + <LinearLayout + android:id="@+id/volume_dialog_rows" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:gravity="center" + android:orientation="horizontal" + android:background="@drawable/tv_volume_dialog_background"> + <!-- volume rows added and removed here! :-) --> + </LinearLayout> + + </LinearLayout> + + <FrameLayout + android:id="@+id/odi_captions" + android:layout_width="@dimen/volume_dialog_caption_size" + android:layout_height="@dimen/volume_dialog_caption_size" + android:layout_marginRight="68dp" + android:layout_gravity="right" + android:clipToPadding="false" + android:translationZ="@dimen/volume_dialog_elevation" + android:background="@drawable/rounded_bg_full"> + + <com.android.systemui.volume.CaptionsToggleImageButton + android:id="@+id/odi_captions_icon" + android:src="@drawable/ic_volume_odi_captions_disabled" + style="@style/VolumeButtons" + android:background="@drawable/rounded_ripple" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:tint="@color/caption_tint_color_selector" + android:layout_gravity="center" + android:soundEffectsEnabled="false"/> + + </FrameLayout> + + <ViewStub + android:id="@+id/odi_captions_tooltip_stub" + android:inflatedId="@+id/odi_captions_tooltip_view" + android:layout="@layout/volume_tool_tip_view" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginRight="@dimen/volume_tool_tip_right_margin" + android:layout_marginTop="@dimen/volume_tool_tip_top_margin" + android:layout_gravity="right"/> + + </FrameLayout> + +</FrameLayout> diff --git a/packages/SystemUI/res/layout-land-television/volume_dialog_row.xml b/packages/SystemUI/res/layout-land-television/volume_dialog_row.xml index cf301c96a9f8..eb89489bdd83 100644 --- a/packages/SystemUI/res/layout-land-television/volume_dialog_row.xml +++ b/packages/SystemUI/res/layout-land-television/volume_dialog_row.xml @@ -63,8 +63,8 @@ android:layout_height="match_parent" android:layout_gravity="center" android:layoutDirection="ltr" - android:maxHeight="@dimen/volume_dialog_slider_width" - android:minHeight="@dimen/volume_dialog_slider_width" + android:maxHeight="@dimen/volume_dialog_slider_width_legacy" + android:minHeight="@dimen/volume_dialog_slider_width_legacy" android:progressDrawable="@drawable/volume_row_seekbar" android:thumb="@drawable/tv_volume_row_seek_thumb" android:splitTrack="false" diff --git a/packages/SystemUI/res/layout-land/volume_dialog.xml b/packages/SystemUI/res/layout-land/volume_dialog.xml index 08edf59000b8..f77db956a493 100644 --- a/packages/SystemUI/res/layout-land/volume_dialog.xml +++ b/packages/SystemUI/res/layout-land/volume_dialog.xml @@ -1,146 +1,67 @@ <!-- - ~ 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 - --> -<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + 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. +--> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" android:id="@+id/volume_dialog_container" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:gravity="right" android:layout_gravity="right" - android:background="@android:color/transparent" + android:divider="@drawable/volume_dialog_floating_sliders_spacer" + android:orientation="horizontal" + android:showDividers="middle|end|beginning" android:theme="@style/volume_dialog_theme"> - <!-- right-aligned to be physically near volume button --> <LinearLayout - android:id="@+id/volume_dialog" + android:id="@+id/volume_dialog_floating_sliders_container" android:layout_width="wrap_content" + android:layout_height="match_parent" + android:background="@drawable/volume_dialog_background" + android:divider="@drawable/volume_dialog_floating_sliders_spacer" + android:gravity="bottom" + android:orientation="horizontal" + android:paddingBottom="@dimen/volume_dialog_floating_sliders_bottom_padding" + android:showDividers="middle" /> + + <LinearLayout + android:layout_width="@dimen/volume_dialog_width" android:layout_height="wrap_content" - android:gravity="right" - android:layout_gravity="right" - android:layout_marginRight="@dimen/volume_dialog_panel_transparent_padding_right" + android:background="@drawable/volume_dialog_background" + android:divider="@drawable/volume_dialog_spacer" + android:gravity="center_horizontal" android:orientation="vertical" - android:clipToPadding="false" - android:clipChildren="false"> - - - <LinearLayout - android:id="@+id/volume_dialog_top_container" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:orientation="vertical" - android:clipChildren="false" - android:gravity="right"> - - <include layout="@layout/volume_ringer_drawer" /> - - <FrameLayout - android:visibility="gone" - android:id="@+id/ringer" - android:layout_width="@dimen/volume_dialog_ringer_size" - android:layout_height="@dimen/volume_dialog_ringer_size" - android:layout_marginBottom="@dimen/volume_dialog_spacer" - android:gravity="right" - android:layout_gravity="right" - android:translationZ="@dimen/volume_dialog_elevation" - android:clipToPadding="false" - android:background="@drawable/rounded_bg_full"> - <com.android.keyguard.AlphaOptimizedImageButton - android:id="@+id/ringer_icon" - style="@style/VolumeButtons" - android:background="@drawable/rounded_ripple" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:scaleType="fitCenter" - android:padding="@dimen/volume_dialog_ringer_icon_padding" - android:tint="?android:attr/textColorPrimary" - android:layout_gravity="center" - android:soundEffectsEnabled="false" /> - </FrameLayout> - - <LinearLayout - android:id="@+id/volume_dialog_rows_container" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:gravity="right" - android:layout_gravity="right" - android:orientation="vertical" - android:clipChildren="false" - android:clipToPadding="false" > - <LinearLayout - android:id="@+id/volume_dialog_rows" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:gravity="center" - android:orientation="horizontal"> - <!-- volume rows added and removed here! :-) --> - </LinearLayout> - <FrameLayout - android:id="@+id/settings_container" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:background="@drawable/volume_background_bottom" - android:paddingLeft="@dimen/volume_dialog_ringer_rows_padding" - android:paddingBottom="@dimen/volume_dialog_ringer_rows_padding" - android:paddingRight="@dimen/volume_dialog_ringer_rows_padding"> - - <com.android.keyguard.AlphaOptimizedImageButton - android:id="@+id/settings" - android:layout_width="@dimen/volume_dialog_tap_target_size" - android:layout_height="@dimen/volume_dialog_tap_target_size" - android:layout_gravity="center" - android:background="@drawable/ripple_drawable_20dp" - android:contentDescription="@string/accessibility_volume_settings" - android:scaleType="centerInside" - android:soundEffectsEnabled="false" - android:src="@drawable/horizontal_ellipsis" - android:tint="?androidprv:attr/colorAccent" /> - </FrameLayout> - </LinearLayout> - - </LinearLayout> + android:paddingVertical="@dimen/volume_dialog_vertical_padding" + android:showDividers="middle"> <FrameLayout - android:id="@+id/odi_captions" - android:layout_width="@dimen/volume_dialog_caption_size" - android:layout_height="@dimen/volume_dialog_caption_size" - android:layout_marginTop="@dimen/volume_dialog_row_margin_bottom" - android:gravity="right" - android:layout_gravity="right" - android:clipToPadding="false" - android:clipToOutline="true" - android:background="@drawable/volume_row_rounded_background"> - <com.android.systemui.volume.CaptionsToggleImageButton - android:id="@+id/odi_captions_icon" - android:src="@drawable/ic_volume_odi_captions_disabled" - style="@style/VolumeButtons" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:tint="?android:attr/colorAccent" - android:layout_gravity="center" - android:soundEffectsEnabled="false" /> - </FrameLayout> - </LinearLayout> + android:id="@+id/volume_dialog_ringer_button" + android:layout_width="@dimen/volume_dialog_button_size" + android:layout_height="@dimen/volume_dialog_button_size" /> - <ViewStub - android:id="@+id/odi_captions_tooltip_stub" - android:inflatedId="@+id/odi_captions_tooltip_view" - android:layout="@layout/volume_tool_tip_view" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="bottom | right" - android:layout_marginRight="@dimen/volume_tool_tip_right_margin"/> + <include + android:id="@+id/volume_dialog_slider" + layout="@layout/volume_dialog_slider" /> -</FrameLayout>
\ No newline at end of file + <Button + android:id="@+id/volume_dialog_settings" + android:layout_width="@dimen/volume_dialog_button_size" + android:layout_height="@dimen/volume_dialog_button_size" + android:background="@drawable/ripple_drawable_20dp" + android:contentDescription="@string/accessibility_volume_settings" + android:soundEffectsEnabled="false" + android:src="@drawable/horizontal_ellipsis" + android:tint="?androidprv:attr/materialColorPrimary" /> + </LinearLayout> +</LinearLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout-land/volume_dialog_legacy.xml b/packages/SystemUI/res/layout-land/volume_dialog_legacy.xml new file mode 100644 index 000000000000..08edf59000b8 --- /dev/null +++ b/packages/SystemUI/res/layout-land/volume_dialog_legacy.xml @@ -0,0 +1,146 @@ +<!-- + ~ 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 + --> +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" + android:id="@+id/volume_dialog_container" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:gravity="right" + android:layout_gravity="right" + android:background="@android:color/transparent" + android:theme="@style/volume_dialog_theme"> + + <!-- right-aligned to be physically near volume button --> + <LinearLayout + android:id="@+id/volume_dialog" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:gravity="right" + android:layout_gravity="right" + android:layout_marginRight="@dimen/volume_dialog_panel_transparent_padding_right" + android:orientation="vertical" + android:clipToPadding="false" + android:clipChildren="false"> + + + <LinearLayout + android:id="@+id/volume_dialog_top_container" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="vertical" + android:clipChildren="false" + android:gravity="right"> + + <include layout="@layout/volume_ringer_drawer" /> + + <FrameLayout + android:visibility="gone" + android:id="@+id/ringer" + android:layout_width="@dimen/volume_dialog_ringer_size" + android:layout_height="@dimen/volume_dialog_ringer_size" + android:layout_marginBottom="@dimen/volume_dialog_spacer" + android:gravity="right" + android:layout_gravity="right" + android:translationZ="@dimen/volume_dialog_elevation" + android:clipToPadding="false" + android:background="@drawable/rounded_bg_full"> + <com.android.keyguard.AlphaOptimizedImageButton + android:id="@+id/ringer_icon" + style="@style/VolumeButtons" + android:background="@drawable/rounded_ripple" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:scaleType="fitCenter" + android:padding="@dimen/volume_dialog_ringer_icon_padding" + android:tint="?android:attr/textColorPrimary" + android:layout_gravity="center" + android:soundEffectsEnabled="false" /> + </FrameLayout> + + <LinearLayout + android:id="@+id/volume_dialog_rows_container" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:gravity="right" + android:layout_gravity="right" + android:orientation="vertical" + android:clipChildren="false" + android:clipToPadding="false" > + <LinearLayout + android:id="@+id/volume_dialog_rows" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:gravity="center" + android:orientation="horizontal"> + <!-- volume rows added and removed here! :-) --> + </LinearLayout> + <FrameLayout + android:id="@+id/settings_container" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:background="@drawable/volume_background_bottom" + android:paddingLeft="@dimen/volume_dialog_ringer_rows_padding" + android:paddingBottom="@dimen/volume_dialog_ringer_rows_padding" + android:paddingRight="@dimen/volume_dialog_ringer_rows_padding"> + + <com.android.keyguard.AlphaOptimizedImageButton + android:id="@+id/settings" + android:layout_width="@dimen/volume_dialog_tap_target_size" + android:layout_height="@dimen/volume_dialog_tap_target_size" + android:layout_gravity="center" + android:background="@drawable/ripple_drawable_20dp" + android:contentDescription="@string/accessibility_volume_settings" + android:scaleType="centerInside" + android:soundEffectsEnabled="false" + android:src="@drawable/horizontal_ellipsis" + android:tint="?androidprv:attr/colorAccent" /> + </FrameLayout> + </LinearLayout> + + </LinearLayout> + + <FrameLayout + android:id="@+id/odi_captions" + android:layout_width="@dimen/volume_dialog_caption_size" + android:layout_height="@dimen/volume_dialog_caption_size" + android:layout_marginTop="@dimen/volume_dialog_row_margin_bottom" + android:gravity="right" + android:layout_gravity="right" + android:clipToPadding="false" + android:clipToOutline="true" + android:background="@drawable/volume_row_rounded_background"> + <com.android.systemui.volume.CaptionsToggleImageButton + android:id="@+id/odi_captions_icon" + android:src="@drawable/ic_volume_odi_captions_disabled" + style="@style/VolumeButtons" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:tint="?android:attr/colorAccent" + android:layout_gravity="center" + android:soundEffectsEnabled="false" /> + </FrameLayout> + </LinearLayout> + + <ViewStub + android:id="@+id/odi_captions_tooltip_stub" + android:inflatedId="@+id/odi_captions_tooltip_view" + android:layout="@layout/volume_tool_tip_view" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="bottom | right" + android:layout_marginRight="@dimen/volume_tool_tip_right_margin"/> + +</FrameLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/volume_dialog.xml b/packages/SystemUI/res/layout/volume_dialog.xml index 39a1f1f9b85d..f77db956a493 100644 --- a/packages/SystemUI/res/layout/volume_dialog.xml +++ b/packages/SystemUI/res/layout/volume_dialog.xml @@ -1,5 +1,5 @@ <!-- - Copyright (C) 2015 The Android Open Source Project + 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. @@ -13,133 +13,55 @@ See the License for the specific language governing permissions and limitations under the License. --> -<FrameLayout - xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:sysui="http://schemas.android.com/apk/res-auto" +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" android:id="@+id/volume_dialog_container" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:gravity="right" android:layout_gravity="right" - android:clipToPadding="false" + android:divider="@drawable/volume_dialog_floating_sliders_spacer" + android:orientation="horizontal" + android:showDividers="middle|end|beginning" android:theme="@style/volume_dialog_theme"> - <!-- right-aligned to be physically near volume button --> <LinearLayout - android:id="@+id/volume_dialog" + android:id="@+id/volume_dialog_floating_sliders_container" android:layout_width="wrap_content" + android:layout_height="match_parent" + android:background="@drawable/volume_dialog_background" + android:divider="@drawable/volume_dialog_floating_sliders_spacer" + android:gravity="bottom" + android:orientation="horizontal" + android:paddingBottom="@dimen/volume_dialog_floating_sliders_bottom_padding" + android:showDividers="middle" /> + + <LinearLayout + android:layout_width="@dimen/volume_dialog_width" android:layout_height="wrap_content" - android:gravity="right" - android:layout_gravity="right" - android:layout_marginRight="@dimen/volume_dialog_panel_transparent_padding_right" + android:background="@drawable/volume_dialog_background" + android:divider="@drawable/volume_dialog_spacer" + android:gravity="center_horizontal" android:orientation="vertical" - android:clipToPadding="false" - android:clipChildren="false"> - - <LinearLayout - android:id="@+id/volume_dialog_top_container" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:clipChildren="false" - android:orientation="vertical" - android:gravity="right"> - - <include layout="@layout/volume_ringer_drawer" /> - - <FrameLayout - android:visibility="gone" - android:id="@+id/ringer" - android:layout_width="@dimen/volume_dialog_ringer_size" - android:layout_height="@dimen/volume_dialog_ringer_size" - android:layout_marginBottom="@dimen/volume_dialog_spacer" - android:gravity="right" - android:layout_gravity="right" - android:translationZ="@dimen/volume_dialog_elevation" - android:clipToPadding="false" - android:background="@drawable/rounded_bg_full"> - <com.android.keyguard.AlphaOptimizedImageButton - android:id="@+id/ringer_icon" - style="@style/VolumeButtons" - android:background="@drawable/rounded_ripple" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:scaleType="fitCenter" - android:padding="@dimen/volume_dialog_ringer_icon_padding" - android:tint="?android:attr/textColorPrimary" - android:layout_gravity="center" - android:soundEffectsEnabled="false" /> - </FrameLayout> - - <LinearLayout - android:id="@+id/volume_dialog_rows_container" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:gravity="right" - android:layout_gravity="right" - android:orientation="vertical" - android:clipChildren="false" - android:clipToPadding="false" > - <LinearLayout - android:id="@+id/volume_dialog_rows" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:gravity="center" - android:orientation="horizontal"> - <!-- volume rows added and removed here! :-) --> - </LinearLayout> - <FrameLayout - android:id="@+id/settings_container" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:background="@drawable/volume_background_bottom" - android:paddingLeft="@dimen/volume_dialog_ringer_rows_padding" - android:paddingBottom="@dimen/volume_dialog_ringer_rows_padding" - android:paddingRight="@dimen/volume_dialog_ringer_rows_padding"> - <com.android.keyguard.AlphaOptimizedImageButton - android:id="@+id/settings" - android:src="@drawable/horizontal_ellipsis" - android:layout_width="@dimen/volume_dialog_tap_target_size" - android:layout_height="@dimen/volume_dialog_tap_target_size" - android:layout_gravity="center" - android:contentDescription="@string/accessibility_volume_settings" - android:background="@drawable/ripple_drawable_20dp" - android:tint="?androidprv:attr/colorAccent" - android:soundEffectsEnabled="false" /> - </FrameLayout> - </LinearLayout> - - </LinearLayout> + android:paddingVertical="@dimen/volume_dialog_vertical_padding" + android:showDividers="middle"> <FrameLayout - android:id="@+id/odi_captions" - android:layout_width="@dimen/volume_dialog_caption_size" - android:layout_height="@dimen/volume_dialog_caption_size" - android:layout_marginTop="@dimen/volume_dialog_row_margin_bottom" - android:gravity="right" - android:layout_gravity="right" - android:clipToPadding="false" - android:clipToOutline="true" - android:background="@drawable/volume_row_rounded_background"> - <com.android.systemui.volume.CaptionsToggleImageButton - android:id="@+id/odi_captions_icon" - android:src="@drawable/ic_volume_odi_captions_disabled" - style="@style/VolumeButtons" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:tint="?android:attr/colorAccent" - android:layout_gravity="center" - android:soundEffectsEnabled="false"/> - </FrameLayout> - </LinearLayout> + android:id="@+id/volume_dialog_ringer_button" + android:layout_width="@dimen/volume_dialog_button_size" + android:layout_height="@dimen/volume_dialog_button_size" /> - <ViewStub - android:id="@+id/odi_captions_tooltip_stub" - android:inflatedId="@+id/odi_captions_tooltip_view" - android:layout="@layout/volume_tool_tip_view" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="bottom | right" - android:layout_marginRight="@dimen/volume_tool_tip_right_margin"/> + <include + android:id="@+id/volume_dialog_slider" + layout="@layout/volume_dialog_slider" /> -</FrameLayout>
\ No newline at end of file + <Button + android:id="@+id/volume_dialog_settings" + android:layout_width="@dimen/volume_dialog_button_size" + android:layout_height="@dimen/volume_dialog_button_size" + android:background="@drawable/ripple_drawable_20dp" + android:contentDescription="@string/accessibility_volume_settings" + android:soundEffectsEnabled="false" + android:src="@drawable/horizontal_ellipsis" + android:tint="?androidprv:attr/materialColorPrimary" /> + </LinearLayout> +</LinearLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/volume_dialog_legacy.xml b/packages/SystemUI/res/layout/volume_dialog_legacy.xml new file mode 100644 index 000000000000..39a1f1f9b85d --- /dev/null +++ b/packages/SystemUI/res/layout/volume_dialog_legacy.xml @@ -0,0 +1,145 @@ +<!-- + 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. +--> +<FrameLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:sysui="http://schemas.android.com/apk/res-auto" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" + android:id="@+id/volume_dialog_container" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:gravity="right" + android:layout_gravity="right" + android:clipToPadding="false" + android:theme="@style/volume_dialog_theme"> + + <!-- right-aligned to be physically near volume button --> + <LinearLayout + android:id="@+id/volume_dialog" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:gravity="right" + android:layout_gravity="right" + android:layout_marginRight="@dimen/volume_dialog_panel_transparent_padding_right" + android:orientation="vertical" + android:clipToPadding="false" + android:clipChildren="false"> + + <LinearLayout + android:id="@+id/volume_dialog_top_container" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:clipChildren="false" + android:orientation="vertical" + android:gravity="right"> + + <include layout="@layout/volume_ringer_drawer" /> + + <FrameLayout + android:visibility="gone" + android:id="@+id/ringer" + android:layout_width="@dimen/volume_dialog_ringer_size" + android:layout_height="@dimen/volume_dialog_ringer_size" + android:layout_marginBottom="@dimen/volume_dialog_spacer" + android:gravity="right" + android:layout_gravity="right" + android:translationZ="@dimen/volume_dialog_elevation" + android:clipToPadding="false" + android:background="@drawable/rounded_bg_full"> + <com.android.keyguard.AlphaOptimizedImageButton + android:id="@+id/ringer_icon" + style="@style/VolumeButtons" + android:background="@drawable/rounded_ripple" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:scaleType="fitCenter" + android:padding="@dimen/volume_dialog_ringer_icon_padding" + android:tint="?android:attr/textColorPrimary" + android:layout_gravity="center" + android:soundEffectsEnabled="false" /> + </FrameLayout> + + <LinearLayout + android:id="@+id/volume_dialog_rows_container" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:gravity="right" + android:layout_gravity="right" + android:orientation="vertical" + android:clipChildren="false" + android:clipToPadding="false" > + <LinearLayout + android:id="@+id/volume_dialog_rows" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:gravity="center" + android:orientation="horizontal"> + <!-- volume rows added and removed here! :-) --> + </LinearLayout> + <FrameLayout + android:id="@+id/settings_container" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:background="@drawable/volume_background_bottom" + android:paddingLeft="@dimen/volume_dialog_ringer_rows_padding" + android:paddingBottom="@dimen/volume_dialog_ringer_rows_padding" + android:paddingRight="@dimen/volume_dialog_ringer_rows_padding"> + <com.android.keyguard.AlphaOptimizedImageButton + android:id="@+id/settings" + android:src="@drawable/horizontal_ellipsis" + android:layout_width="@dimen/volume_dialog_tap_target_size" + android:layout_height="@dimen/volume_dialog_tap_target_size" + android:layout_gravity="center" + android:contentDescription="@string/accessibility_volume_settings" + android:background="@drawable/ripple_drawable_20dp" + android:tint="?androidprv:attr/colorAccent" + android:soundEffectsEnabled="false" /> + </FrameLayout> + </LinearLayout> + + </LinearLayout> + + <FrameLayout + android:id="@+id/odi_captions" + android:layout_width="@dimen/volume_dialog_caption_size" + android:layout_height="@dimen/volume_dialog_caption_size" + android:layout_marginTop="@dimen/volume_dialog_row_margin_bottom" + android:gravity="right" + android:layout_gravity="right" + android:clipToPadding="false" + android:clipToOutline="true" + android:background="@drawable/volume_row_rounded_background"> + <com.android.systemui.volume.CaptionsToggleImageButton + android:id="@+id/odi_captions_icon" + android:src="@drawable/ic_volume_odi_captions_disabled" + style="@style/VolumeButtons" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:tint="?android:attr/colorAccent" + android:layout_gravity="center" + android:soundEffectsEnabled="false"/> + </FrameLayout> + </LinearLayout> + + <ViewStub + android:id="@+id/odi_captions_tooltip_stub" + android:inflatedId="@+id/odi_captions_tooltip_view" + android:layout="@layout/volume_tool_tip_view" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="bottom | right" + android:layout_marginRight="@dimen/volume_tool_tip_right_margin"/> + +</FrameLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/volume_dialog_slider.xml b/packages/SystemUI/res/layout/volume_dialog_slider.xml new file mode 100644 index 000000000000..8acdd39faaa2 --- /dev/null +++ b/packages/SystemUI/res/layout/volume_dialog_slider.xml @@ -0,0 +1,27 @@ +<!-- + 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. +--> +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="@dimen/volume_dialog_slider_width" + android:layout_height="@dimen/volume_dialog_slider_height"> + + <com.google.android.material.slider.Slider + android:id="@+id/volume_dialog_slider" + android:layout_width="@dimen/volume_dialog_slider_height" + android:layout_height="match_parent" + android:layout_gravity="center" + android:rotation="270" + android:theme="@style/Theme.MaterialComponents.DayNight" /> +</FrameLayout> diff --git a/packages/SystemUI/res/layout/volume_dialog_slider_floating.xml b/packages/SystemUI/res/layout/volume_dialog_slider_floating.xml new file mode 100644 index 000000000000..db800aa4a873 --- /dev/null +++ b/packages/SystemUI/res/layout/volume_dialog_slider_floating.xml @@ -0,0 +1,24 @@ +<!-- + Copyright (C) 2024 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:background="@drawable/volume_dialog_floating_slider_background" + android:paddingHorizontal="@dimen/volume_dialog_floating_sliders_horizontal_padding" + android:paddingVertical="@dimen/volume_dialog_floating_sliders_vertical_padding"> + + <include layout="@layout/volume_dialog_slider" /> +</FrameLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 1727a5fec0a2..6c8a7403953e 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -591,7 +591,7 @@ <dimen name="volume_dialog_panel_width_half">28dp</dimen> - <dimen name="volume_dialog_slider_width">42dp</dimen> + <dimen name="volume_dialog_slider_width_legacy">42dp</dimen> <dimen name="volume_dialog_slider_corner_radius">21dp</dimen> @@ -622,10 +622,6 @@ <dimen name="volume_tool_tip_arrow_corner_radius">2dp</dimen> - <!-- Volume panel slices dimensions --> - <dimen name="volume_panel_slice_vertical_padding">8dp</dimen> - <dimen name="volume_panel_slice_horizontal_padding">24dp</dimen> - <dimen name="bottom_sheet_corner_radius">28dp</dimen> <!-- Size of each item in the ringer selector drawer. --> @@ -2050,4 +2046,22 @@ <dimen name="contextual_edu_dialog_bottom_margin">80dp</dimen> <dimen name="contextual_edu_dialog_elevation">2dp</dimen> + + <!-- Volume start --> + <dimen name="volume_dialog_background_corner_radius">30dp</dimen> + <dimen name="volume_dialog_width">60dp</dimen> + <dimen name="volume_dialog_vertical_padding">6dp</dimen> + <dimen name="volume_dialog_components_spacing">8dp</dimen> + <dimen name="volume_dialog_floating_sliders_spacing">8dp</dimen> + <dimen name="volume_dialog_floating_sliders_vertical_padding">10dp</dimen> + <dimen name="volume_dialog_floating_sliders_horizontal_padding">4dp</dimen> + <dimen name="volume_dialog_spacing">4dp</dimen> + <dimen name="volume_dialog_button_size">48dp</dimen> + <dimen name="volume_dialog_floating_sliders_bottom_padding">48dp</dimen> + <dimen name="volume_dialog_slider_width">52dp</dimen> + <dimen name="volume_dialog_slider_height">254dp</dimen> + + <dimen name="volume_panel_slice_vertical_padding">8dp</dimen> + <dimen name="volume_panel_slice_horizontal_padding">24dp</dimen> + <!-- Volume end --> </resources> diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index 431f04882c8d..83ab5245bc31 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -1875,7 +1875,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab if (posture == DEVICE_POSTURE_OPENED) { mLogger.d("Posture changed to open - attempting to request active" + " unlock and run face auth"); - getFaceAuthInteractor().onDeviceUnfolded(); + if (getFaceAuthInteractor() != null) { + getFaceAuthInteractor().onDeviceUnfolded(); + } requestActiveUnlockFromWakeReason(PowerManager.WAKE_REASON_UNFOLD_DEVICE, false); } diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java b/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java index 831543da3237..ef172a1b24f6 100644 --- a/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java +++ b/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java @@ -69,7 +69,9 @@ public abstract class ClockRegistryModule { layoutInflater, resources, featureFlags.isEnabled(Flags.STEP_CLOCK_ANIMATION), - MigrateClocksToBlueprint.isEnabled()), + MigrateClocksToBlueprint.isEnabled(), + com.android.systemui.Flags.clockReactiveVariants() + ), context.getString(R.string.lockscreen_clock_id_fallback), clockBuffers, /* keepAllLoaded = */ false, diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt index 373671d01395..0949ea4d7797 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt @@ -16,6 +16,7 @@ package com.android.systemui.bouncer.domain.interactor +import android.util.Log import com.android.keyguard.KeyguardUpdateMonitor import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository @@ -46,6 +47,7 @@ import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.stateIn /** Encapsulates business logic for interacting with the lock-screen alternate bouncer. */ @@ -137,6 +139,8 @@ constructor( flowOf(false) } } + .distinctUntilChanged() + .onEach { Log.d(TAG, "canShowAlternateBouncer changed to $it") } .stateIn( scope = scope, started = WhileSubscribed(), @@ -234,5 +238,7 @@ constructor( companion object { private const val MIN_VISIBILITY_DURATION_UNTIL_TOUCHES_DISMISS_ALTERNATE_BOUNCER_MS = 200L + + private const val TAG = "AlternateBouncerInteractor" } } 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 3ae9250b2938..6508e4b574a3 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 @@ -27,7 +27,6 @@ import android.os.UserHandle import android.util.Log import android.view.accessibility.AccessibilityEvent import android.view.accessibility.AccessibilityManager -import androidx.activity.result.ActivityResultLauncher import com.android.internal.logging.UiEventLogger import com.android.systemui.communal.dagger.CommunalModule.Companion.LAUNCHER_PACKAGE import com.android.systemui.communal.data.model.CommunalWidgetCategories @@ -184,10 +183,10 @@ constructor( val isIdleOnCommunal: StateFlow<Boolean> = communalInteractor.isIdleOnCommunal - /** Launch the widget picker activity using the given {@link ActivityResultLauncher}. */ + /** Launch the widget picker activity using the given startActivity method. */ suspend fun onOpenWidgetPicker( resources: Resources, - activityLauncher: ActivityResultLauncher<Intent>, + startActivity: (intent: Intent) -> Unit, ): Boolean = withContext(backgroundDispatcher) { val widgets = communalInteractor.widgetContent.first() @@ -199,7 +198,7 @@ constructor( } getWidgetPickerActivityIntent(resources, excludeList)?.let { try { - activityLauncher.launch(it) + startActivity(it) return@withContext true } catch (e: Exception) { Log.e(TAG, "Failed to launch widget picker activity", e) diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt index 6228ac5f84ae..8c14d63c0e84 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt @@ -27,8 +27,6 @@ import android.view.IWindowManager import android.view.WindowInsets import androidx.activity.ComponentActivity import androidx.activity.compose.setContent -import androidx.activity.result.ActivityResultLauncher -import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize @@ -51,6 +49,7 @@ import com.android.systemui.log.LogBuffer import com.android.systemui.log.core.Logger import com.android.systemui.log.dagger.CommunalLog import com.android.systemui.scene.shared.flag.SceneContainerFlag +import com.android.systemui.settings.UserTracker import javax.inject.Inject import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch @@ -64,12 +63,15 @@ constructor( private val uiEventLogger: UiEventLogger, private val widgetConfiguratorFactory: WidgetConfigurationController.Factory, private val widgetSection: CommunalAppWidgetSection, + private val userTracker: UserTracker, @CommunalLog logBuffer: LogBuffer, ) : ComponentActivity() { companion object { private const val TAG = "EditWidgetsActivity" private const val EXTRA_IS_PENDING_WIDGET_DRAG = "is_pending_widget_drag" const val EXTRA_OPEN_WIDGET_PICKER_ON_START = "open_widget_picker_on_start" + + private const val REQUEST_CODE_WIDGET_PICKER = 200 } /** @@ -110,7 +112,7 @@ constructor( object : ActivityLifecycleCallbacks { override fun onActivityCreated( activity: Activity, - savedInstanceState: Bundle? + savedInstanceState: Bundle?, ) { waitingForResult = savedInstanceState?.getBoolean(STATE_EXTRA_IS_WAITING_FOR_RESULT) @@ -172,41 +174,6 @@ constructor( if (communalEditWidgetsActivityFinishFix()) ActivityControllerImpl(this) else NopActivityController() - private val addWidgetActivityLauncher: ActivityResultLauncher<Intent> = - registerForActivityResult(StartActivityForResult()) { result -> - when (result.resultCode) { - RESULT_OK -> { - uiEventLogger.log(CommunalUiEvent.COMMUNAL_HUB_WIDGET_PICKER_SHOWN) - - result.data?.let { intent -> - val isPendingWidgetDrag = - intent.getBooleanExtra(EXTRA_IS_PENDING_WIDGET_DRAG, false) - // Nothing to do when a widget is being dragged & dropped. The drop - // target in the communal grid will receive the widget to be added (if - // the user drops it over). - if (!isPendingWidgetDrag) { - val (componentName, user) = getWidgetExtraFromIntent(intent) - if (componentName != null && user != null) { - // Add widget at the end. - communalViewModel.onAddWidget( - componentName, - user, - configurator = widgetConfigurator, - ) - } else { - run { Log.w(TAG, "No AppWidgetProviderInfo found in result.") } - } - } - } ?: run { Log.w(TAG, "No data in result.") } - } - else -> - Log.w( - TAG, - "Failed to receive result from widget picker, code=${result.resultCode}" - ) - } - } - override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -226,8 +193,7 @@ constructor( PlatformTheme { Box( modifier = - Modifier.fillMaxSize() - .background(MaterialTheme.colorScheme.surfaceDim), + Modifier.fillMaxSize().background(MaterialTheme.colorScheme.surfaceDim) ) { CommunalHub( viewModel = communalViewModel, @@ -274,7 +240,13 @@ constructor( private fun onOpenWidgetPicker() { lifecycleScope.launch { - communalViewModel.onOpenWidgetPicker(resources, addWidgetActivityLauncher) + communalViewModel.onOpenWidgetPicker(resources) { intent: Intent -> + startActivityForResultAsUser( + intent, + REQUEST_CODE_WIDGET_PICKER, + userTracker.userHandle, + ) + } } } @@ -285,7 +257,7 @@ constructor( communalViewModel.changeScene( scene = CommunalScenes.Communal, loggingReason = "edit mode closing", - transitionKey = CommunalTransitionKeys.FromEditMode + transitionKey = CommunalTransitionKeys.FromEditMode, ) // Wait for the current scene to be idle on communal. @@ -309,7 +281,7 @@ constructor( flagsMask: Int, flagsValues: Int, extraFlags: Int, - options: Bundle? + options: Bundle?, ) { activityController.onWaitingForResult(true) super.startIntentSenderForResult( @@ -319,15 +291,46 @@ constructor( flagsMask, flagsValues, extraFlags, - options + options, ) } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { activityController.onWaitingForResult(false) super.onActivityResult(requestCode, resultCode, data) - if (requestCode == WidgetConfigurationController.REQUEST_CODE) { - widgetConfigurator.setConfigurationResult(resultCode) + + when (requestCode) { + WidgetConfigurationController.REQUEST_CODE -> + widgetConfigurator.setConfigurationResult(resultCode) + REQUEST_CODE_WIDGET_PICKER -> { + if (resultCode != RESULT_OK) { + Log.w(TAG, "Failed to receive result from widget picker, code=$resultCode") + return + } + + uiEventLogger.log(CommunalUiEvent.COMMUNAL_HUB_WIDGET_PICKER_SHOWN) + + data?.let { intent -> + val isPendingWidgetDrag = + intent.getBooleanExtra(EXTRA_IS_PENDING_WIDGET_DRAG, false) + // Nothing to do when a widget is being dragged & dropped. The drop + // target in the communal grid will receive the widget to be added (if + // the user drops it over). + if (!isPendingWidgetDrag) { + val (componentName, user) = getWidgetExtraFromIntent(intent) + if (componentName != null && user != null) { + // Add widget at the end. + communalViewModel.onAddWidget( + componentName, + user, + configurator = widgetConfigurator, + ) + } else { + run { Log.w(TAG, "No AppWidgetProviderInfo found in result.") } + } + } + } ?: run { Log.w(TAG, "No data in result.") } + } } } diff --git a/packages/SystemUI/src/com/android/systemui/display/DisplayModule.kt b/packages/SystemUI/src/com/android/systemui/display/DisplayModule.kt index b56ed8c4c090..589dbf92de38 100644 --- a/packages/SystemUI/src/com/android/systemui/display/DisplayModule.kt +++ b/packages/SystemUI/src/com/android/systemui/display/DisplayModule.kt @@ -24,6 +24,8 @@ import com.android.systemui.display.data.repository.DisplayRepository import com.android.systemui.display.data.repository.DisplayRepositoryImpl import com.android.systemui.display.data.repository.DisplayScopeRepository import com.android.systemui.display.data.repository.DisplayScopeRepositoryImpl +import com.android.systemui.display.data.repository.DisplayWindowPropertiesRepository +import com.android.systemui.display.data.repository.DisplayWindowPropertiesRepositoryImpl import com.android.systemui.display.data.repository.FocusedDisplayRepository import com.android.systemui.display.data.repository.FocusedDisplayRepositoryImpl import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor @@ -58,6 +60,11 @@ interface DisplayModule { @Binds fun displayScopeRepository(impl: DisplayScopeRepositoryImpl): DisplayScopeRepository + @Binds + fun displayWindowPropertiesRepository( + impl: DisplayWindowPropertiesRepositoryImpl + ): DisplayWindowPropertiesRepository + companion object { @Provides @SysUISingleton @@ -72,5 +79,19 @@ interface DisplayModule { CoreStartable.NOP } } + + @Provides + @SysUISingleton + @IntoMap + @ClassKey(DisplayWindowPropertiesRepository::class) + fun displayWindowPropertiesRepoAsCoreStartable( + repoLazy: Lazy<DisplayWindowPropertiesRepositoryImpl> + ): CoreStartable { + return if (StatusBarConnectedDisplays.isEnabled) { + return repoLazy.get() + } else { + CoreStartable.NOP + } + } } } diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayWindowPropertiesRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayWindowPropertiesRepository.kt new file mode 100644 index 000000000000..88d3a28669df --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayWindowPropertiesRepository.kt @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.display.data.repository + +import android.annotation.SuppressLint +import android.content.Context +import android.view.Display +import android.view.WindowManager +import com.android.systemui.CoreStartable +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.display.shared.model.DisplayWindowProperties +import com.android.systemui.res.R +import com.android.systemui.statusbar.core.StatusBarConnectedDisplays +import com.google.common.collect.HashBasedTable +import com.google.common.collect.Table +import java.io.PrintWriter +import javax.inject.Inject +import kotlinx.coroutines.CoroutineName +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch + +/** Provides per display instances of [DisplayWindowProperties]. */ +interface DisplayWindowPropertiesRepository { + + /** + * Returns a [DisplayWindowProperties] instance for a given display id and window type. + * + * @throws IllegalArgumentException if no display with the given display id exists. + */ + fun get( + displayId: Int, + @WindowManager.LayoutParams.WindowType windowType: Int, + ): DisplayWindowProperties +} + +@SysUISingleton +class DisplayWindowPropertiesRepositoryImpl +@Inject +constructor( + @Background private val backgroundApplicationScope: CoroutineScope, + private val globalContext: Context, + private val globalWindowManager: WindowManager, + private val displayRepository: DisplayRepository, +) : DisplayWindowPropertiesRepository, CoreStartable { + + init { + StatusBarConnectedDisplays.assertInNewMode() + } + + private val properties: Table<Int, Int, DisplayWindowProperties> = HashBasedTable.create() + + override fun get( + displayId: Int, + @WindowManager.LayoutParams.WindowType windowType: Int, + ): DisplayWindowProperties { + val display = + displayRepository.getDisplay(displayId) + ?: throw IllegalArgumentException("Display with id $displayId doesn't exist") + return properties.get(displayId, windowType) + ?: create(display, windowType).also { properties.put(displayId, windowType, it) } + } + + override fun start() { + backgroundApplicationScope.launch( + CoroutineName("DisplayWindowPropertiesRepositoryImpl#start") + ) { + displayRepository.displayRemovalEvent.collect { removedDisplayId -> + properties.row(removedDisplayId).clear() + } + } + } + + private fun create(display: Display, windowType: Int): DisplayWindowProperties { + val displayId = display.displayId + return if (displayId == Display.DEFAULT_DISPLAY) { + // For the default display, we can just reuse the global/application properties. + // Creating a window context is expensive, therefore we avoid it. + DisplayWindowProperties( + displayId = displayId, + windowType = windowType, + context = globalContext, + windowManager = globalWindowManager, + ) + } else { + val context = createWindowContext(display, windowType) + @SuppressLint("NonInjectedService") // Need to manually get the service + val windowManager = context.getSystemService(WindowManager::class.java) as WindowManager + DisplayWindowProperties(displayId, windowType, context, windowManager) + } + } + + private fun createWindowContext(display: Display, windowType: Int): Context = + globalContext.createWindowContext(display, windowType, /* options= */ null).also { + it.setTheme(R.style.Theme_SystemUI) + } + + override fun dump(pw: PrintWriter, args: Array<out String>) { + pw.write("perDisplayContexts: $properties") + } +} diff --git a/packages/SystemUI/src/com/android/systemui/display/shared/model/DisplayWindowProperties.kt b/packages/SystemUI/src/com/android/systemui/display/shared/model/DisplayWindowProperties.kt new file mode 100644 index 000000000000..6acc296367a9 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/display/shared/model/DisplayWindowProperties.kt @@ -0,0 +1,43 @@ +/* + * 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.display.shared.model + +import android.content.Context +import android.view.WindowManager + +/** Represents a display specific group of window related properties. */ +data class DisplayWindowProperties( + /** The id of the display associated with this instance. */ + val displayId: Int, + /** + * The window type that was used to create the [Context] in this instance, using + * [Context.createWindowContext]. This is the window type that can be used when adding views to + * the [WindowManager] associated with this instance. + */ + @WindowManager.LayoutParams.WindowType val windowType: Int, + /** + * The display specific [Context] created using [Context.createWindowContext] with window type + * associated with this instance. + */ + val context: Context, + + /** + * The display specific [WindowManager] instance to be used when adding windows of the type + * associated with this instance. + */ + val windowManager: WindowManager, +) diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt index 65c29b829429..9c5231d716da 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt @@ -92,6 +92,7 @@ import com.android.systemui.plugins.qs.QSContainerController import com.android.systemui.qs.composefragment.SceneKeys.QuickQuickSettings import com.android.systemui.qs.composefragment.SceneKeys.QuickSettings import com.android.systemui.qs.composefragment.SceneKeys.toIdleSceneKey +import com.android.systemui.qs.composefragment.ui.NotificationScrimClipParams import com.android.systemui.qs.composefragment.ui.notificationScrimClip import com.android.systemui.qs.composefragment.ui.quickQuickSettingsToQuickSettings import com.android.systemui.qs.composefragment.viewmodel.QSFragmentComposeViewModel @@ -149,20 +150,12 @@ constructor( private val notificationScrimClippingParams = object { var isEnabled by mutableStateOf(false) - var leftInset by mutableStateOf(0) - var rightInset by mutableStateOf(0) - var top by mutableStateOf(0) - var bottom by mutableStateOf(0) - var radius by mutableStateOf(0) + var params by mutableStateOf(NotificationScrimClipParams()) fun dump(pw: IndentingPrintWriter) { pw.printSection("NotificationScrimClippingParams") { pw.println("isEnabled", isEnabled) - pw.println("leftInset", "${leftInset}px") - pw.println("rightInset", "${rightInset}px") - pw.println("top", "${top}px") - pw.println("bottom", "${bottom}px") - pw.println("radius", "${radius}px") + pw.println("params", params) } } } @@ -216,7 +209,7 @@ constructor( FrameLayoutTouchPassthrough( context, { notificationScrimClippingParams.isEnabled }, - { notificationScrimClippingParams.top }, + { notificationScrimClippingParams.params.top }, ) frame.addView( composeView, @@ -237,13 +230,7 @@ constructor( Modifier.windowInsetsPadding(WindowInsets.navigationBars).thenIf( notificationScrimClippingParams.isEnabled ) { - Modifier.notificationScrimClip( - notificationScrimClippingParams.leftInset, - notificationScrimClippingParams.top, - notificationScrimClippingParams.rightInset, - notificationScrimClippingParams.bottom, - notificationScrimClippingParams.radius, - ) + Modifier.notificationScrimClip { notificationScrimClippingParams.params } }, ) { val isEditing by @@ -445,13 +432,14 @@ constructor( fullWidth: Boolean, ) { notificationScrimClippingParams.isEnabled = visible - notificationScrimClippingParams.top = top - notificationScrimClippingParams.bottom = bottom - // Full width means that QS will show in the entire width allocated to it (for example - // phone) vs. showing in a narrower column (for example, tablet portrait). - notificationScrimClippingParams.leftInset = if (fullWidth) 0 else leftInset - notificationScrimClippingParams.rightInset = if (fullWidth) 0 else rightInset - notificationScrimClippingParams.radius = cornerRadius + notificationScrimClippingParams.params = + NotificationScrimClipParams( + top, + bottom, + if (fullWidth) 0 else leftInset, + if (fullWidth) 0 else rightInset, + cornerRadius, + ) } override fun isFullyCollapsed(): Boolean { diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/ui/NotificationScrimClip.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/ui/NotificationScrimClip.kt index 93c6445b78ef..c912bd59c19f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/ui/NotificationScrimClip.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/ui/NotificationScrimClip.kt @@ -31,87 +31,73 @@ import androidx.compose.ui.platform.InspectorInfo * ([ClipOp.Difference]) a `RoundRect(-leftInset, top, width + rightInset, bottom, radius, radius)` * from the QS container. */ -fun Modifier.notificationScrimClip( - leftInset: Int, - top: Int, - rightInset: Int, - bottom: Int, - radius: Int -): Modifier { - return this then NotificationScrimClipElement(leftInset, top, rightInset, bottom, radius) +fun Modifier.notificationScrimClip(clipParams: () -> NotificationScrimClipParams): Modifier { + return this then NotificationScrimClipElement(clipParams) } -private class NotificationScrimClipNode( - var leftInset: Float, - var top: Float, - var rightInset: Float, - var bottom: Float, - var radius: Float, -) : DrawModifierNode, Modifier.Node() { +private class NotificationScrimClipNode(var clipParams: () -> NotificationScrimClipParams) : + DrawModifierNode, Modifier.Node() { private val path = Path() - var invalidated = true + private var lastClipParams = NotificationScrimClipParams() override fun ContentDrawScope.draw() { - if (invalidated) { + val newClipParams = clipParams() + if (newClipParams != lastClipParams) { + lastClipParams = newClipParams + applyClipParams(path, lastClipParams) + } + clipPath(path, ClipOp.Difference) { this@draw.drawContent() } + } + + private fun ContentDrawScope.applyClipParams( + path: Path, + clipParams: NotificationScrimClipParams, + ) { + with(clipParams) { path.rewind() path .asAndroidPath() .addRoundRect( - -leftInset, - top, + -leftInset.toFloat(), + top.toFloat(), size.width + rightInset, - bottom, - radius, - radius, - android.graphics.Path.Direction.CW + bottom.toFloat(), + radius.toFloat(), + radius.toFloat(), + android.graphics.Path.Direction.CW, ) - invalidated = false } - clipPath(path, ClipOp.Difference) { this@draw.drawContent() } } } -private data class NotificationScrimClipElement( - val leftInset: Int, - val top: Int, - val rightInset: Int, - val bottom: Int, - val radius: Int, -) : ModifierNodeElement<NotificationScrimClipNode>() { +private data class NotificationScrimClipElement(val clipParams: () -> NotificationScrimClipParams) : + ModifierNodeElement<NotificationScrimClipNode>() { override fun create(): NotificationScrimClipNode { - return NotificationScrimClipNode( - leftInset.toFloat(), - top.toFloat(), - rightInset.toFloat(), - bottom.toFloat(), - radius.toFloat(), - ) + return NotificationScrimClipNode(clipParams) } override fun update(node: NotificationScrimClipNode) { - val changed = - node.leftInset != leftInset.toFloat() || - node.top != top.toFloat() || - node.rightInset != rightInset.toFloat() || - node.bottom != bottom.toFloat() || - node.radius != radius.toFloat() - if (changed) { - node.leftInset = leftInset.toFloat() - node.top = top.toFloat() - node.rightInset = rightInset.toFloat() - node.bottom = bottom.toFloat() - node.radius = radius.toFloat() - node.invalidated = true - } + node.clipParams = clipParams } override fun InspectorInfo.inspectableProperties() { name = "notificationScrimClip" - properties["leftInset"] = leftInset - properties["top"] = top - properties["rightInset"] = rightInset - properties["bottom"] = bottom - properties["radius"] = radius + with(clipParams()) { + properties["leftInset"] = leftInset + properties["top"] = top + properties["rightInset"] = rightInset + properties["bottom"] = bottom + properties["radius"] = radius + } } } + +/** Params for [notificationScrimClip]. */ +data class NotificationScrimClipParams( + val top: Int = 0, + val bottom: Int = 0, + val leftInset: Int = 0, + val rightInset: Int = 0, + val radius: Int = 0, +) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java index 73ad0e50793a..da04f6edf9e2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java @@ -46,6 +46,7 @@ import com.android.internal.jank.InteractionJankMonitor.Configuration; import com.android.internal.logging.UiEventLogger; import com.android.keyguard.KeyguardClockSwitch; import com.android.systemui.DejankUtils; +import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.deviceentry.domain.interactor.DeviceUnlockedInteractor; import com.android.systemui.deviceentry.shared.model.DeviceUnlockStatus; @@ -123,6 +124,7 @@ public class StatusBarStateControllerImpl implements private final Lazy<SceneContainerOcclusionInteractor> mSceneContainerOcclusionInteractorLazy; private final Lazy<KeyguardClockInteractor> mKeyguardClockInteractorLazy; private final Lazy<SceneBackInteractor> mSceneBackInteractorLazy; + private final Lazy<AlternateBouncerInteractor> mAlternateBouncerInteractorLazy; private int mState; private int mLastState; private int mUpcomingState; @@ -193,7 +195,8 @@ public class StatusBarStateControllerImpl implements Lazy<SceneInteractor> sceneInteractorLazy, Lazy<SceneContainerOcclusionInteractor> sceneContainerOcclusionInteractor, Lazy<KeyguardClockInteractor> keyguardClockInteractorLazy, - Lazy<SceneBackInteractor> sceneBackInteractorLazy) { + Lazy<SceneBackInteractor> sceneBackInteractorLazy, + Lazy<AlternateBouncerInteractor> alternateBouncerInteractorLazy) { mUiEventLogger = uiEventLogger; mInteractionJankMonitorLazy = interactionJankMonitorLazy; mJavaAdapter = javaAdapter; @@ -205,6 +208,7 @@ public class StatusBarStateControllerImpl implements mSceneContainerOcclusionInteractorLazy = sceneContainerOcclusionInteractor; mKeyguardClockInteractorLazy = keyguardClockInteractorLazy; mSceneBackInteractorLazy = sceneBackInteractorLazy; + mAlternateBouncerInteractorLazy = alternateBouncerInteractorLazy; for (int i = 0; i < HISTORY_SIZE; i++) { mHistoricalRecords[i] = new HistoricalState(); } @@ -233,6 +237,7 @@ public class StatusBarStateControllerImpl implements mSceneInteractorLazy.get().getCurrentOverlays(), mSceneBackInteractorLazy.get().getBackStack(), mSceneContainerOcclusionInteractorLazy.get().getInvisibleDueToOcclusion(), + mAlternateBouncerInteractorLazy.get().isVisible(), this::calculateStateFromSceneFramework), this::onStatusBarStateChanged); @@ -693,7 +698,8 @@ public class StatusBarStateControllerImpl implements SceneKey currentScene, Set<OverlayKey> currentOverlays, SceneStack backStack, - boolean isOccluded) { + boolean isOccluded, + boolean alternateBouncerIsVisible) { SceneContainerFlag.isUnexpectedlyInLegacyMode(); final boolean onBouncer = currentScene.equals(Scenes.Bouncer); @@ -714,7 +720,8 @@ public class StatusBarStateControllerImpl implements final String inputLogString = "currentScene=" + currentScene.getTestTag() + " currentOverlays=" + currentOverlays + " backStack=" + backStack - + " isUnlocked=" + isUnlocked + " isOccluded=" + isOccluded; + + " isUnlocked=" + isUnlocked + " isOccluded=" + isOccluded + + " alternateBouncerIsVisible=" + alternateBouncerIsVisible; int newState; @@ -722,6 +729,7 @@ public class StatusBarStateControllerImpl implements // 1. deviceUnlockStatus.isUnlocked changes from false to true. // 2. Lockscreen changes to Gone, either in currentScene or in backStack. // 3. Bouncer is removed from currentScene or backStack, if it was present. + // 4. the alternate bouncer is hidden, if it was visible. // // From this function's perspective, though, deviceUnlockStatus, currentScene, and backStack // each update separately, and the relative order of those updates is not well-defined. This @@ -733,6 +741,7 @@ public class StatusBarStateControllerImpl implements // 1. deviceUnlockStatus.isUnlocked is false. // 2. currentScene is a keyguardish scene (Lockscreen, Bouncer, or Communal). // 3. backStack contains a keyguardish scene (Lockscreen or Communal). + // 4. the alternate bouncer is visible. final boolean onKeyguardish = onLockscreen || onBouncer || onCommunal; final boolean overKeyguardish = overLockscreen || overCommunal; @@ -741,7 +750,7 @@ public class StatusBarStateControllerImpl implements // Occlusion is special; even though the device is still technically on the lockscreen, // the UI behaves as if it is unlocked. newState = StatusBarState.SHADE; - } else if (onKeyguardish || overKeyguardish) { + } else if (onKeyguardish || overKeyguardish || alternateBouncerIsVisible) { // We get here if we are on or over a keyguardish scene, even if isUnlocked is true; we // want to return SHADE_LOCKED or KEYGUARD until we are also neither on nor over a // keyguardish scene. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt index cf238d553225..cd1642eee4b4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt @@ -22,15 +22,20 @@ import com.android.systemui.CoreStartable import com.android.systemui.dagger.SysUISingleton import com.android.systemui.log.LogBuffer import com.android.systemui.log.LogBufferFactory +import com.android.systemui.statusbar.core.StatusBarConnectedDisplays import com.android.systemui.statusbar.data.StatusBarDataLayerModule import com.android.systemui.statusbar.phone.LightBarController import com.android.systemui.statusbar.phone.StatusBarSignalPolicy import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallLog import com.android.systemui.statusbar.ui.SystemBarUtilsProxyImpl +import com.android.systemui.statusbar.window.MultiDisplayStatusBarWindowControllerStore +import com.android.systemui.statusbar.window.SingleDisplayStatusBarWindowControllerStore import com.android.systemui.statusbar.window.StatusBarWindowController import com.android.systemui.statusbar.window.StatusBarWindowControllerImpl +import com.android.systemui.statusbar.window.StatusBarWindowControllerStore import dagger.Binds +import dagger.Lazy import dagger.Module import dagger.Provides import dagger.multibindings.ClassKey @@ -62,13 +67,19 @@ abstract class StatusBarModule { @ClassKey(StatusBarSignalPolicy::class) abstract fun bindStatusBarSignalPolicy(impl: StatusBarSignalPolicy): CoreStartable + @Binds + @SysUISingleton + abstract fun statusBarWindowControllerFactory( + implFactory: StatusBarWindowControllerImpl.Factory + ): StatusBarWindowController.Factory + companion object { @Provides @SysUISingleton - fun statusBarWindowController( - context: Context?, - viewCaptureAwareWindowManager: ViewCaptureAwareWindowManager?, + fun defaultStatusBarWindowController( + context: Context, + viewCaptureAwareWindowManager: ViewCaptureAwareWindowManager, factory: StatusBarWindowControllerImpl.Factory, ): StatusBarWindowController { return factory.create(context, viewCaptureAwareWindowManager) @@ -76,6 +87,33 @@ abstract class StatusBarModule { @Provides @SysUISingleton + fun windowControllerStore( + multiDisplayImplLazy: Lazy<MultiDisplayStatusBarWindowControllerStore>, + singleDisplayImplLazy: Lazy<SingleDisplayStatusBarWindowControllerStore>, + ): StatusBarWindowControllerStore { + return if (StatusBarConnectedDisplays.isEnabled) { + multiDisplayImplLazy.get() + } else { + singleDisplayImplLazy.get() + } + } + + @Provides + @SysUISingleton + @IntoMap + @ClassKey(MultiDisplayStatusBarWindowControllerStore::class) + fun multiDisplayControllerStoreAsCoreStartable( + storeLazy: Lazy<MultiDisplayStatusBarWindowControllerStore> + ): CoreStartable { + return if (StatusBarConnectedDisplays.isEnabled) { + storeLazy.get() + } else { + CoreStartable.NOP + } + } + + @Provides + @SysUISingleton @OngoingCallLog fun provideOngoingCallLogBuffer(factory: LogBufferFactory): LogBuffer { return factory.create("OngoingCall", 75) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java index 0474344ee390..7e5b45543e9e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -33,7 +33,6 @@ import static com.android.systemui.Flags.relockWithPowerButtonImmediately; import static com.android.systemui.Flags.statusBarSignalPolicyRefactor; import static com.android.systemui.charging.WirelessChargingAnimation.UNKNOWN_BATTERY_LEVEL; import static com.android.systemui.flags.Flags.SHORTCUT_LIST_SEARCH_LAYOUT; -import static com.android.systemui.statusbar.NotificationLockscreenUserManager.PERMISSION_SELF; import static com.android.systemui.statusbar.StatusBarState.SHADE; import android.annotation.Nullable; @@ -41,7 +40,6 @@ import android.app.ActivityOptions; import android.app.IWallpaperManager; import android.app.KeyguardManager; import android.app.Notification; -import android.app.NotificationManager; import android.app.PendingIntent; import android.app.StatusBarManager; import android.app.TaskInfo; @@ -275,11 +273,6 @@ import javax.inject.Provider; @SysUISingleton public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { - private static final String BANNER_ACTION_CANCEL = - "com.android.systemui.statusbar.banner_action_cancel"; - private static final String BANNER_ACTION_SETUP = - "com.android.systemui.statusbar.banner_action_setup"; - private static final int MSG_LAUNCH_TRANSITION_TIMEOUT = 1003; // 1020-1040 reserved for BaseStatusBar @@ -963,12 +956,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { } } - IntentFilter internalFilter = new IntentFilter(); - internalFilter.addAction(BANNER_ACTION_CANCEL); - internalFilter.addAction(BANNER_ACTION_SETUP); - mContext.registerReceiver(mBannerActionBroadcastReceiver, internalFilter, PERMISSION_SELF, - null, Context.RECEIVER_EXPORTED_UNAUDITED); - if (mWallpaperSupported) { IWallpaperManager wallpaperManager = IWallpaperManager.Stub.asInterface( ServiceManager.getService(Context.WALLPAPER_SERVICE)); @@ -2948,29 +2935,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { return mDeviceInteractive; } - private final BroadcastReceiver mBannerActionBroadcastReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - String action = intent.getAction(); - if (BANNER_ACTION_CANCEL.equals(action) || BANNER_ACTION_SETUP.equals(action)) { - NotificationManager noMan = (NotificationManager) - mContext.getSystemService(Context.NOTIFICATION_SERVICE); - noMan.cancel(com.android.internal.messages.nano.SystemMessageProto.SystemMessage. - NOTE_HIDDEN_NOTIFICATIONS); - - Settings.Secure.putInt(mContext.getContentResolver(), - Settings.Secure.SHOW_NOTE_ABOUT_NOTIFICATION_HIDING, 0); - if (BANNER_ACTION_SETUP.equals(action)) { - mShadeController.animateCollapseShadeForced(); - mContext.startActivity(new Intent(Settings.ACTION_APP_NOTIFICATION_REDACTION) - .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - - ); - } - } - } - }; - @Override public void handleExternalShadeWindowTouch(MotionEvent event) { getNotificationShadeWindowViewController().handleExternalTouch(event); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java index 479ffb728eb2..17bd53869ee5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -530,6 +530,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb this::consumeKeyguardAuthenticatedBiometricsHandled ); } else { + // Collector that keeps the AlternateBouncerInteractor#canShowAlternateBouncer flow hot. mListenForCanShowAlternateBouncer = mJavaAdapter.alwaysCollectFlow( mAlternateBouncerInteractor.getCanShowAlternateBouncer(), this::consumeCanShowAlternateBouncer @@ -578,8 +579,17 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb } private void consumeCanShowAlternateBouncer(boolean canShow) { - // do nothing, we only are registering for the flow to ensure that there's at least - // one subscriber that will update AlternateBouncerInteractor.canShowAlternateBouncer.value + // Hack: this is required to fix issues where + // KeyguardBouncerRepository#alternateBouncerVisible state is incorrectly set and then never + // reset. This is caused by usages of show()/forceShow() that only read this flow to set the + // alternate bouncer visible state, if there is a race condition between when that flow + // changes to false and when the read happens, the flow will be set to an incorrect value + // and not reset on time. + if (!canShow) { + Log.d(TAG, "canShowAlternateBouncer turned false, maybe try hiding the alternate " + + "bouncer if it is already visible"); + mAlternateBouncerInteractor.maybeHide(); + } } /** Register a callback, to be invoked by the Predictive Back system. */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepository.kt index f5cfc8c5b307..e0bf00fbe431 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepository.kt @@ -26,6 +26,7 @@ import android.net.NetworkCapabilities.TRANSPORT_CELLULAR import android.net.NetworkCapabilities.TRANSPORT_ETHERNET import android.net.NetworkCapabilities.TRANSPORT_WIFI import android.net.vcn.VcnTransportInfo +import android.net.vcn.VcnUtils import android.net.wifi.WifiInfo import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID import androidx.annotation.ArrayRes @@ -161,7 +162,9 @@ constructor( defaultNetworkCapabilities .map { networkCapabilities -> networkCapabilities?.run { - val subId = (transportInfo as? VcnTransportInfo)?.subId + val subId = + VcnUtils.getSubIdFromVcnCaps(connectivityManager, networkCapabilities) + // Never return an INVALID_SUBSCRIPTION_ID (-1) if (subId != INVALID_SUBSCRIPTION_ID) { subId @@ -245,9 +248,9 @@ constructor( * info. */ fun NetworkCapabilities.getMainOrUnderlyingWifiInfo( - connectivityManager: ConnectivityManager, + connectivityManager: ConnectivityManager ): WifiInfo? { - val mainWifiInfo = this.getMainWifiInfo() + val mainWifiInfo = this.getMainWifiInfo(connectivityManager) if (mainWifiInfo != null) { return mainWifiInfo } @@ -264,7 +267,9 @@ constructor( // eventually traced to a wifi or carrier merged connection. So, check those underlying // networks for possible wifi information as well. See b/225902574. return this.underlyingNetworks?.firstNotNullOfOrNull { underlyingNetwork -> - connectivityManager.getNetworkCapabilities(underlyingNetwork)?.getMainWifiInfo() + connectivityManager + .getNetworkCapabilities(underlyingNetwork) + ?.getMainWifiInfo(connectivityManager) } } @@ -272,7 +277,9 @@ constructor( * Checks the network capabilities for wifi info, but does *not* check the underlying * networks. See [getMainOrUnderlyingWifiInfo]. */ - private fun NetworkCapabilities.getMainWifiInfo(): WifiInfo? { + private fun NetworkCapabilities.getMainWifiInfo( + connectivityManager: ConnectivityManager + ): WifiInfo? { // Wifi info can either come from a WIFI Transport, or from a CELLULAR transport for // virtual networks like VCN. val canHaveWifiInfo = @@ -286,7 +293,7 @@ constructor( // [com.android.settingslib.Utils.tryGetWifiInfoForVcn]. It's copied instead of // re-used because it makes the logic here clearer, and because the method will be // removed once this pipeline is fully launched. - is VcnTransportInfo -> currentTransportInfo.wifiInfo + is VcnTransportInfo -> VcnUtils.getWifiInfoFromVcnCaps(connectivityManager, this) is WifiInfo -> currentTransportInfo else -> null } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.kt index 421e5c45bbfe..e8dc93465685 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.kt @@ -16,8 +16,10 @@ package com.android.systemui.statusbar.window +import android.content.Context import android.view.View import android.view.ViewGroup +import com.android.app.viewcapture.ViewCaptureAwareWindowManager import com.android.systemui.animation.ActivityTransitionAnimator import com.android.systemui.fragments.FragmentHostManager import java.util.Optional @@ -73,4 +75,11 @@ interface StatusBarWindowController { * this#setForceStatusBarVisible} together and use some sort of ranking system instead. */ fun setOngoingProcessRequiresStatusBarVisible(visible: Boolean) + + interface Factory { + fun create( + context: Context, + viewCaptureAwareWindowManager: ViewCaptureAwareWindowManager, + ): StatusBarWindowController + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerImpl.java index 1ee7cf3490f4..d709e5a0cd6c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerImpl.java @@ -354,11 +354,13 @@ public class StatusBarWindowControllerImpl implements StatusBarWindowController } @AssistedFactory - public interface Factory { + public interface Factory extends StatusBarWindowController.Factory { /** Creates a new instance. */ + @NonNull + @Override StatusBarWindowControllerImpl create( - Context context, - ViewCaptureAwareWindowManager viewCaptureAwareWindowManager); + @NonNull Context context, + @NonNull ViewCaptureAwareWindowManager viewCaptureAwareWindowManager); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerStore.kt new file mode 100644 index 000000000000..5f30b3719aa7 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerStore.kt @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.window + +import android.view.Display +import android.view.WindowManager +import com.android.app.viewcapture.ViewCaptureAwareWindowManager +import com.android.systemui.CoreStartable +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.display.data.repository.DisplayRepository +import com.android.systemui.display.data.repository.DisplayWindowPropertiesRepository +import com.android.systemui.statusbar.core.StatusBarConnectedDisplays +import java.util.concurrent.ConcurrentHashMap +import javax.inject.Inject +import kotlinx.coroutines.CoroutineName +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch + +/** Store that allows to retrieve per display instances of [StatusBarWindowController]. */ +interface StatusBarWindowControllerStore { + /** + * The instance for the default/main display of the device. For example, on a phone or a tablet, + * the default display is the internal/built-in display of the device. + * + * Note that the id of the default display is [Display.DEFAULT_DISPLAY]. + */ + val defaultDisplay: StatusBarWindowController + + /** + * Returns an instance for a specific display id. + * + * @throws IllegalArgumentException if [displayId] doesn't match the id of any existing + * displays. + */ + fun forDisplay(displayId: Int): StatusBarWindowController +} + +@SysUISingleton +class MultiDisplayStatusBarWindowControllerStore +@Inject +constructor( + @Background private val backgroundApplicationScope: CoroutineScope, + private val controllerFactory: StatusBarWindowController.Factory, + private val displayWindowPropertiesRepository: DisplayWindowPropertiesRepository, + private val viewCaptureAwareWindowManagerFactory: ViewCaptureAwareWindowManager.Factory, + private val displayRepository: DisplayRepository, +) : StatusBarWindowControllerStore, CoreStartable { + + init { + StatusBarConnectedDisplays.assertInNewMode() + } + + private val perDisplayControllers = ConcurrentHashMap<Int, StatusBarWindowController>() + + override fun start() { + backgroundApplicationScope.launch(CoroutineName("StatusBarWindowController#start")) { + displayRepository.displayRemovalEvent.collect { displayId -> + perDisplayControllers.remove(displayId) + } + } + } + + override val defaultDisplay: StatusBarWindowController + get() = forDisplay(Display.DEFAULT_DISPLAY) + + override fun forDisplay(displayId: Int): StatusBarWindowController { + if (displayRepository.getDisplay(displayId) == null) { + throw IllegalArgumentException("Display with id $displayId doesn't exist.") + } + return perDisplayControllers.computeIfAbsent(displayId) { + createControllerForDisplay(displayId) + } + } + + private fun createControllerForDisplay(displayId: Int): StatusBarWindowController { + val statusBarDisplayContext = + displayWindowPropertiesRepository.get( + displayId = displayId, + windowType = WindowManager.LayoutParams.TYPE_STATUS_BAR, + ) + val viewCaptureAwareWindowManager = + viewCaptureAwareWindowManagerFactory.create(statusBarDisplayContext.windowManager) + return controllerFactory.create( + statusBarDisplayContext.context, + viewCaptureAwareWindowManager, + ) + } +} + +@SysUISingleton +class SingleDisplayStatusBarWindowControllerStore +@Inject +constructor(private val controller: StatusBarWindowController) : StatusBarWindowControllerStore { + + init { + StatusBarConnectedDisplays.assertInLegacyMode() + } + + override val defaultDisplay = controller + + override fun forDisplay(displayId: Int) = controller +} diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/BackGestureTutorialScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/BackGestureTutorialScreen.kt index bfc5429b59d4..6879a3415238 100644 --- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/BackGestureTutorialScreen.kt +++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/BackGestureTutorialScreen.kt @@ -27,10 +27,7 @@ import com.android.systemui.res.R import com.android.systemui.touchpad.tutorial.ui.gesture.BackGestureMonitor @Composable -fun BackGestureTutorialScreen( - onDoneButtonClicked: () -> Unit, - onBack: () -> Unit, -) { +fun BackGestureTutorialScreen(onDoneButtonClicked: () -> Unit, onBack: () -> Unit) { val screenConfig = TutorialScreenConfig( colors = rememberScreenColors(), @@ -39,18 +36,20 @@ fun BackGestureTutorialScreen( titleResId = R.string.touchpad_back_gesture_action_title, bodyResId = R.string.touchpad_back_gesture_guidance, titleSuccessResId = R.string.touchpad_back_gesture_success_title, - bodySuccessResId = R.string.touchpad_back_gesture_success_body + bodySuccessResId = R.string.touchpad_back_gesture_success_body, ), animations = TutorialScreenConfig.Animations( educationResId = R.raw.trackpad_back_edu, - successResId = R.raw.trackpad_back_success - ) + successResId = R.raw.trackpad_back_success, + ), ) val gestureMonitorProvider = DistanceBasedGestureMonitorProvider( monitorFactory = { distanceThresholdPx, gestureStateCallback -> - BackGestureMonitor(distanceThresholdPx, gestureStateCallback) + BackGestureMonitor(distanceThresholdPx).also { + it.addGestureStateCallback(gestureStateCallback) + } } ) GestureTutorialScreen(screenConfig, gestureMonitorProvider, onDoneButtonClicked, onBack) @@ -67,7 +66,7 @@ private fun rememberScreenColors(): TutorialScreenConfig.Colors { rememberColorFilterProperty(".tertiaryFixedDim", tertiaryFixedDim), rememberColorFilterProperty(".onTertiaryFixed", onTertiaryFixed), rememberColorFilterProperty(".onTertiary", onTertiary), - rememberColorFilterProperty(".onTertiaryFixedVariant", onTertiaryFixedVariant) + rememberColorFilterProperty(".onTertiaryFixedVariant", onTertiaryFixedVariant), ) val screenColors = remember(dynamicProperties) { diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/HomeGestureTutorialScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/HomeGestureTutorialScreen.kt index f2fec5f5d9b1..a55fa442cd96 100644 --- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/HomeGestureTutorialScreen.kt +++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/HomeGestureTutorialScreen.kt @@ -26,10 +26,7 @@ import com.android.systemui.res.R import com.android.systemui.touchpad.tutorial.ui.gesture.HomeGestureMonitor @Composable -fun HomeGestureTutorialScreen( - onDoneButtonClicked: () -> Unit, - onBack: () -> Unit, -) { +fun HomeGestureTutorialScreen(onDoneButtonClicked: () -> Unit, onBack: () -> Unit) { val screenConfig = TutorialScreenConfig( colors = rememberScreenColors(), @@ -38,18 +35,20 @@ fun HomeGestureTutorialScreen( titleResId = R.string.touchpad_home_gesture_action_title, bodyResId = R.string.touchpad_home_gesture_guidance, titleSuccessResId = R.string.touchpad_home_gesture_success_title, - bodySuccessResId = R.string.touchpad_home_gesture_success_body + bodySuccessResId = R.string.touchpad_home_gesture_success_body, ), animations = TutorialScreenConfig.Animations( educationResId = R.raw.trackpad_home_edu, - successResId = R.raw.trackpad_home_success - ) + successResId = R.raw.trackpad_home_success, + ), ) val gestureMonitorProvider = DistanceBasedGestureMonitorProvider( monitorFactory = { distanceThresholdPx, gestureStateCallback -> - HomeGestureMonitor(distanceThresholdPx, gestureStateCallback) + HomeGestureMonitor(distanceThresholdPx).also { + it.addGestureStateCallback(gestureStateCallback) + } } ) GestureTutorialScreen(screenConfig, gestureMonitorProvider, onDoneButtonClicked, onBack) @@ -64,7 +63,7 @@ private fun rememberScreenColors(): TutorialScreenConfig.Colors { rememberLottieDynamicProperties( rememberColorFilterProperty(".primaryFixedDim", primaryFixedDim), rememberColorFilterProperty(".onPrimaryFixed", onPrimaryFixed), - rememberColorFilterProperty(".onPrimaryFixedVariant", onPrimaryFixedVariant) + rememberColorFilterProperty(".onPrimaryFixedVariant", onPrimaryFixedVariant), ) val screenColors = remember(dynamicProperties) { diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/RecentAppsGestureTutorialScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/RecentAppsGestureTutorialScreen.kt index b2fb6cdfcee5..6ee15aa952f4 100644 --- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/RecentAppsGestureTutorialScreen.kt +++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/RecentAppsGestureTutorialScreen.kt @@ -29,10 +29,7 @@ import com.android.systemui.touchpad.tutorial.ui.gesture.RecentAppsGestureMonito import com.android.systemui.touchpad.tutorial.ui.gesture.TouchpadGestureMonitor @Composable -fun RecentAppsGestureTutorialScreen( - onDoneButtonClicked: () -> Unit, - onBack: () -> Unit, -) { +fun RecentAppsGestureTutorialScreen(onDoneButtonClicked: () -> Unit, onBack: () -> Unit) { val screenConfig = TutorialScreenConfig( colors = rememberScreenColors(), @@ -41,20 +38,20 @@ fun RecentAppsGestureTutorialScreen( titleResId = R.string.touchpad_recent_apps_gesture_action_title, bodyResId = R.string.touchpad_recent_apps_gesture_guidance, titleSuccessResId = R.string.touchpad_recent_apps_gesture_success_title, - bodySuccessResId = R.string.touchpad_recent_apps_gesture_success_body + bodySuccessResId = R.string.touchpad_recent_apps_gesture_success_body, ), animations = TutorialScreenConfig.Animations( educationResId = R.raw.trackpad_recent_apps_edu, - successResId = R.raw.trackpad_recent_apps_success - ) + successResId = R.raw.trackpad_recent_apps_success, + ), ) val gestureMonitorProvider = object : GestureMonitorProvider { @Composable override fun rememberGestureMonitor( resources: Resources, - gestureStateChangedCallback: (GestureState) -> Unit + gestureStateChangedCallback: (GestureState) -> Unit, ): TouchpadGestureMonitor { val distanceThresholdPx = resources.getDimensionPixelSize( @@ -63,11 +60,9 @@ fun RecentAppsGestureTutorialScreen( val velocityThresholdPxPerMs = resources.getDimension(R.dimen.touchpad_recent_apps_gesture_velocity_threshold) return remember(distanceThresholdPx, velocityThresholdPxPerMs) { - RecentAppsGestureMonitor( - distanceThresholdPx, - gestureStateChangedCallback, - velocityThresholdPxPerMs - ) + RecentAppsGestureMonitor(distanceThresholdPx, velocityThresholdPxPerMs).also { + it.addGestureStateCallback(gestureStateChangedCallback) + } } } } @@ -83,7 +78,7 @@ private fun rememberScreenColors(): TutorialScreenConfig.Colors { rememberLottieDynamicProperties( rememberColorFilterProperty(".secondaryFixedDim", secondaryFixedDim), rememberColorFilterProperty(".onSecondaryFixed", onSecondaryFixed), - rememberColorFilterProperty(".onSecondaryFixedVariant", onSecondaryFixedVariant) + rememberColorFilterProperty(".onSecondaryFixedVariant", onSecondaryFixedVariant), ) val screenColors = remember(dynamicProperties) { diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialSelectionScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialSelectionScreen.kt index 94e19deb0006..3c31efa6265a 100644 --- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialSelectionScreen.kt +++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialSelectionScreen.kt @@ -84,7 +84,7 @@ private fun TutorialSelectionButtons( ) { TutorialButton( text = stringResource(R.string.touchpad_tutorial_home_gesture_button), - icon = Icons.AutoMirrored.Outlined.ArrowBack, + icon = ImageVector.vectorResource(id = R.drawable.touchpad_tutorial_home_icon), iconColor = MaterialTheme.colorScheme.onPrimary, onClick = onHomeTutorialClicked, backgroundColor = MaterialTheme.colorScheme.primary, @@ -92,7 +92,7 @@ private fun TutorialSelectionButtons( ) TutorialButton( text = stringResource(R.string.touchpad_tutorial_back_gesture_button), - icon = ImageVector.vectorResource(id = R.drawable.touchpad_tutorial_home_icon), + icon = Icons.AutoMirrored.Outlined.ArrowBack, iconColor = MaterialTheme.colorScheme.onTertiary, onClick = onBackTutorialClicked, backgroundColor = MaterialTheme.colorScheme.tertiary, diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureMonitor.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureMonitor.kt index ecb5574ba5df..490f04d55802 100644 --- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureMonitor.kt +++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureMonitor.kt @@ -20,18 +20,21 @@ import android.view.MotionEvent import kotlin.math.abs /** Monitors for touchpad back gesture, that is three fingers swiping left or right */ -class BackGestureMonitor( - private val gestureDistanceThresholdPx: Int, - override val gestureStateChangedCallback: (GestureState) -> Unit, -) : TouchpadGestureMonitor { +class BackGestureMonitor(private val gestureDistanceThresholdPx: Int) : TouchpadGestureMonitor { + private val distanceTracker = DistanceTracker() + private var gestureStateChangedCallback: (GestureState) -> Unit = {} + + override fun addGestureStateCallback(callback: (GestureState) -> Unit) { + gestureStateChangedCallback = callback + } - override fun processTouchpadEvent(event: MotionEvent) { + override fun accept(event: MotionEvent) { if (!isThreeFingerTouchpadSwipe(event)) return - val distanceState = distanceTracker.processEvent(event) - updateGestureStateBasedOnDistance( + val gestureState = distanceTracker.processEvent(event) + updateGestureState( gestureStateChangedCallback, - distanceState, + gestureState, isFinished = { abs(it.deltaX) >= gestureDistanceThresholdPx }, progress = { 0f }, ) diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/DistanceTracker.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/DistanceTracker.kt index 70d93668c6b3..d48235892d69 100644 --- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/DistanceTracker.kt +++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/DistanceTracker.kt @@ -38,10 +38,10 @@ class DistanceTracker(var startX: Float = 0f, var startY: Float = 0f) { } } -sealed interface DistanceGestureState +sealed class DistanceGestureState(val deltaX: Float, val deltaY: Float) -class Started(val deltaX: Float, val deltaY: Float) : DistanceGestureState +class Started(deltaX: Float, deltaY: Float) : DistanceGestureState(deltaX, deltaY) -class Moving(val deltaX: Float, val deltaY: Float) : DistanceGestureState +class Moving(deltaX: Float, deltaY: Float) : DistanceGestureState(deltaX, deltaY) -class Finished(val deltaX: Float, val deltaY: Float) : DistanceGestureState +class Finished(deltaX: Float, deltaY: Float) : DistanceGestureState(deltaX, deltaY) diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/GestureStateUpdates.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/GestureStateUpdates.kt index c1caeb3cbf9e..f19467726def 100644 --- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/GestureStateUpdates.kt +++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/GestureStateUpdates.kt @@ -16,11 +16,8 @@ package com.android.systemui.touchpad.tutorial.ui.gesture -/** - * Helper function for gesture recognizers to have common state triggering logic based on distance - * only. - */ -inline fun updateGestureStateBasedOnDistance( +/** Helper function for gesture recognizers to have common state triggering logic */ +inline fun updateGestureState( gestureStateChangedCallback: (GestureState) -> Unit, gestureState: DistanceGestureState?, isFinished: (Finished) -> Boolean, diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureMonitor.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureMonitor.kt index fdcf9deec923..83d4f566257b 100644 --- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureMonitor.kt +++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureMonitor.kt @@ -19,18 +19,21 @@ package com.android.systemui.touchpad.tutorial.ui.gesture import android.view.MotionEvent /** Monitors for touchpad home gesture, that is three fingers swiping up */ -class HomeGestureMonitor( - private val gestureDistanceThresholdPx: Int, - override val gestureStateChangedCallback: (GestureState) -> Unit, -) : TouchpadGestureMonitor { +class HomeGestureMonitor(private val gestureDistanceThresholdPx: Int) : TouchpadGestureMonitor { + private val distanceTracker = DistanceTracker() + private var gestureStateChangedCallback: (GestureState) -> Unit = {} + + override fun addGestureStateCallback(callback: (GestureState) -> Unit) { + gestureStateChangedCallback = callback + } - override fun processTouchpadEvent(event: MotionEvent) { + override fun accept(event: MotionEvent) { if (!isThreeFingerTouchpadSwipe(event)) return - val distanceState = distanceTracker.processEvent(event) - updateGestureStateBasedOnDistance( + val gestureState = distanceTracker.processEvent(event) + updateGestureState( gestureStateChangedCallback, - distanceState, + gestureState, isFinished = { -it.deltaY >= gestureDistanceThresholdPx }, progress = { 0f }, ) diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureMonitor.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureMonitor.kt index dd31ce309cce..1731bb85fba4 100644 --- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureMonitor.kt +++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureMonitor.kt @@ -17,7 +17,6 @@ package com.android.systemui.touchpad.tutorial.ui.gesture import android.view.MotionEvent -import androidx.compose.ui.input.pointer.util.VelocityTracker1D import kotlin.math.abs /** @@ -27,45 +26,30 @@ import kotlin.math.abs */ class RecentAppsGestureMonitor( private val gestureDistanceThresholdPx: Int, - override val gestureStateChangedCallback: (GestureState) -> Unit, private val velocityThresholdPxPerMs: Float, - private val velocityTracker: VelocityTracker1D = VelocityTracker1D(isDataDifferential = false), + private val distanceTracker: DistanceTracker = DistanceTracker(), + private val velocityTracker: VerticalVelocityTracker = VerticalVelocityTracker(), ) : TouchpadGestureMonitor { - private var xStart = 0f - private var yStart = 0f + private var gestureStateChangedCallback: (GestureState) -> Unit = {} - override fun processTouchpadEvent(event: MotionEvent) { - val action = event.actionMasked - velocityTracker.addDataPoint(event.eventTime, event.y) - when (action) { - MotionEvent.ACTION_DOWN -> { - if (isThreeFingerTouchpadSwipe(event)) { - xStart = event.x - yStart = event.y - gestureStateChangedCallback(GestureState.InProgress()) - } - } - MotionEvent.ACTION_UP -> { - if (isThreeFingerTouchpadSwipe(event) && isRecentAppsGesture(event)) { - gestureStateChangedCallback(GestureState.Finished) - } else { - gestureStateChangedCallback(GestureState.NotStarted) - } - velocityTracker.resetTracking() - } - MotionEvent.ACTION_CANCEL -> { - velocityTracker.resetTracking() - } - } + override fun addGestureStateCallback(callback: (GestureState) -> Unit) { + gestureStateChangedCallback = callback } - private fun isRecentAppsGesture(event: MotionEvent): Boolean { - // below is trying to mirror behavior of TriggerSwipeUpTouchTracker#onGestureEnd. - // We're diving velocity by 1000, to have the same unit of measure: pixels/ms. - val swipeDistance = yStart - event.y - val velocity = velocityTracker.calculateVelocity() / 1000 - return swipeDistance >= gestureDistanceThresholdPx && - abs(velocity) <= velocityThresholdPxPerMs + override fun accept(event: MotionEvent) { + if (!isThreeFingerTouchpadSwipe(event)) return + val gestureState = distanceTracker.processEvent(event) + velocityTracker.accept(event) + + updateGestureState( + gestureStateChangedCallback, + gestureState, + isFinished = { state -> + -state.deltaY >= gestureDistanceThresholdPx && + abs(velocityTracker.calculateVelocity().value) <= velocityThresholdPxPerMs + }, + progress = { 0f }, + ) } } diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureHandler.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureHandler.kt index 88671d41f1cd..4b82ba1c0dda 100644 --- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureHandler.kt +++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureHandler.kt @@ -18,13 +18,14 @@ package com.android.systemui.touchpad.tutorial.ui.gesture import android.view.InputDevice import android.view.MotionEvent +import java.util.function.Consumer /** * Allows listening to touchpadGesture and calling onDone when gesture was triggered. Can have all * motion events passed to [onMotionEvent] and will filter touchpad events accordingly */ class TouchpadGestureHandler( - private val gestureMonitor: TouchpadGestureMonitor, + private val gestureMonitor: Consumer<MotionEvent>, private val easterEggGestureMonitor: EasterEggGestureMonitor, ) { @@ -40,7 +41,7 @@ class TouchpadGestureHandler( if (isTwoFingerSwipe(event)) { easterEggGestureMonitor.processTouchpadEvent(event) } else { - gestureMonitor.processTouchpadEvent(event) + gestureMonitor.accept(event) } true } else { diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureMonitor.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureMonitor.kt index 4655c98b65ac..9216821272ff 100644 --- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureMonitor.kt +++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureMonitor.kt @@ -17,15 +17,11 @@ package com.android.systemui.touchpad.tutorial.ui.gesture import android.view.MotionEvent +import java.util.function.Consumer -/** - * Monitor for touchpad gestures that calls [gestureStateChangedCallback] when [GestureState] - * changes. All tracked motion events should be passed to [processTouchpadEvent] - */ -interface TouchpadGestureMonitor { - val gestureStateChangedCallback: (GestureState) -> Unit - - fun processTouchpadEvent(event: MotionEvent) +/** Monitor for touchpad gestures that can notify callback when [GestureState] changes. */ +interface TouchpadGestureMonitor : Consumer<MotionEvent> { + fun addGestureStateCallback(callback: (GestureState) -> Unit) } fun isThreeFingerTouchpadSwipe(event: MotionEvent) = isNFingerTouchpadSwipe(event, fingerCount = 3) diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/VelocityTracker.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/VelocityTracker.kt new file mode 100644 index 000000000000..9b38eca89f15 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/VelocityTracker.kt @@ -0,0 +1,51 @@ +/* + * 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.touchpad.tutorial.ui.gesture + +import android.view.MotionEvent +import androidx.compose.ui.input.pointer.util.VelocityTracker1D +import java.util.function.Consumer + +/** Velocity in pixels/ms. */ +@JvmInline value class Velocity(val value: Float) + +/** + * Tracks velocity for processed MotionEvents. Useful for recognizing gestures based on velocity. + */ +interface VelocityTracker : Consumer<MotionEvent> { + + fun calculateVelocity(): Velocity +} + +class VerticalVelocityTracker( + private val velocityTracker: VelocityTracker1D = VelocityTracker1D(isDataDifferential = false) +) : VelocityTracker { + + override fun accept(event: MotionEvent) { + val action = event.actionMasked + if (action == MotionEvent.ACTION_DOWN) { + velocityTracker.resetTracking() + } + velocityTracker.addDataPoint(event.eventTime, event.y) + } + + /** + * Calculates velocity on demand - this calculation can be expensive so shouldn't be called + * after every event. + */ + override fun calculateVelocity() = Velocity(velocityTracker.calculateVelocity() / 1000) +} diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java index 1f92bc1df9c8..bbd8f3dc7e82 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java @@ -59,7 +59,6 @@ import android.view.accessibility.CaptioningManager; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.annotation.VisibleForTesting; import androidx.lifecycle.Observer; import com.android.internal.annotations.GuardedBy; @@ -110,8 +109,8 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa // It is safe to use 99 as the broadcast stream now. There are only 10+ default audio // streams defined in AudioSystem for now and audio team is in the middle of restructure, // no new default stream is preferred. - @VisibleForTesting static final int DYNAMIC_STREAM_BROADCAST = 99; - private static final int DYNAMIC_STREAM_REMOTE_START_INDEX = 100; + public static final int DYNAMIC_STREAM_BROADCAST = 99; + public static final int DYNAMIC_STREAM_REMOTE_START_INDEX = 100; private static final AudioAttributes SONIFICIATION_VIBRATION_ATTRIBUTES = new AudioAttributes.Builder() .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java index 7166428d863f..7c5116dbf72c 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java @@ -537,7 +537,7 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, mWindow.setAttributes(lp); mWindow.setLayout(WRAP_CONTENT, WRAP_CONTENT); - mDialog.setContentView(R.layout.volume_dialog); + mDialog.setContentView(R.layout.volume_dialog_legacy); mDialogView = mDialog.findViewById(R.id.volume_dialog); mDialogView.setAlpha(0); mDialogTimeoutMillis = mSecureSettings.get().getInt( diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/model/VolumeDialogStateModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/model/VolumeDialogStateModel.kt index f1443e36d019..500cc0bf748c 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/model/VolumeDialogStateModel.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/model/VolumeDialogStateModel.kt @@ -23,7 +23,7 @@ import com.android.systemui.plugins.VolumeDialogController /** Models a state of the Volume Dialog. */ data class VolumeDialogStateModel( - val states: Map<Int, VolumeDialogStreamStateModel>, + val states: Map<Int, VolumeDialogStreamModel>, val ringerModeInternal: Int = 0, val ringerModeExternal: Int = 0, val zenMode: Int = 0, @@ -39,7 +39,7 @@ data class VolumeDialogStateModel( constructor( legacyState: VolumeDialogController.State ) : this( - states = legacyState.states.mapToMap { VolumeDialogStreamStateModel(it) }, + states = legacyState.states.mapToMap { VolumeDialogStreamModel(it) }, ringerModeInternal = legacyState.ringerModeInternal, ringerModeExternal = legacyState.ringerModeExternal, zenMode = legacyState.zenMode, diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/model/VolumeDialogStreamStateModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/model/VolumeDialogStreamModel.kt index a9d367da41e4..26c96eabc65f 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/model/VolumeDialogStreamStateModel.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/model/VolumeDialogStreamModel.kt @@ -16,18 +16,18 @@ package com.android.systemui.volume.dialog.domain.model -import android.annotation.IntegerRes +import androidx.annotation.StringRes import com.android.systemui.plugins.VolumeDialogController /** Models a state of an audio stream of the Volume Dialog. */ -data class VolumeDialogStreamStateModel( +data class VolumeDialogStreamModel( val isDynamic: Boolean = false, val level: Int = 0, val levelMin: Int = 0, val levelMax: Int = 0, val muted: Boolean = false, val muteSupported: Boolean = false, - @IntegerRes val name: Int = 0, + @StringRes val name: Int = 0, val remoteLabel: String? = null, val routedToBluetooth: Boolean = false, ) { diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/settings/ui/binder/VolumeDialogSettingsButtonViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/settings/ui/binder/VolumeDialogSettingsButtonViewBinder.kt index ba08876609ae..b2f6cb332e74 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/settings/ui/binder/VolumeDialogSettingsButtonViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/settings/ui/binder/VolumeDialogSettingsButtonViewBinder.kt @@ -34,7 +34,7 @@ constructor(private val viewModelFactory: VolumeDialogSettingsButtonViewModel.Fa fun bind(view: View) { with(view) { - val button = requireViewById<View>(R.id.settings) + val button = requireViewById<View>(R.id.volume_dialog_settings) repeatWhenAttached { viewModel( traceName = "VolumeDialogViewBinder", diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInteractor.kt new file mode 100644 index 000000000000..81507ba7dc60 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInteractor.kt @@ -0,0 +1,54 @@ +/* + * 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.volume.dialog.sliders.domain.interactor + +import com.android.systemui.plugins.VolumeDialogController +import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope +import com.android.systemui.volume.dialog.domain.interactor.VolumeDialogStateInteractor +import com.android.systemui.volume.dialog.domain.model.VolumeDialogStreamModel +import com.android.systemui.volume.dialog.sliders.domain.model.VolumeDialogSliderType +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.mapNotNull + +/** Operates a state of particular slider of the Volume Dialog. */ +class VolumeDialogSliderInteractor +@AssistedInject +constructor( + @Assisted private val sliderType: VolumeDialogSliderType, + volumeDialogStateInteractor: VolumeDialogStateInteractor, + private val volumeDialogController: VolumeDialogController, +) { + + val slider: Flow<VolumeDialogStreamModel> = + volumeDialogStateInteractor.volumeDialogState.mapNotNull { + it.states[sliderType.audioStream] + } + + fun setStreamVolume(userLevel: Int) { + volumeDialogController.setStreamVolume(sliderType.audioStream, userLevel) + } + + @VolumeDialogScope + @AssistedFactory + interface Factory { + + fun create(sliderType: VolumeDialogSliderType): VolumeDialogSliderInteractor + } +} diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSlidersInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSlidersInteractor.kt new file mode 100644 index 000000000000..325e4c9514cf --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSlidersInteractor.kt @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.volume.dialog.sliders.domain.interactor + +import com.android.systemui.volume.VolumeDialogControllerImpl +import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope +import com.android.systemui.volume.dialog.domain.interactor.VolumeDialogStateInteractor +import com.android.systemui.volume.dialog.sliders.domain.model.VolumeDialogSliderType +import com.android.systemui.volume.dialog.sliders.domain.model.VolumeDialogSlidersModel +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map + +/** Provides a state for the Sliders section of the Volume Dialog. */ +@VolumeDialogScope +class VolumeDialogSlidersInteractor +@Inject +constructor(volumeDialogStateInteractor: VolumeDialogStateInteractor) { + + val sliders: Flow<VolumeDialogSlidersModel> = + volumeDialogStateInteractor.volumeDialogState.map { + val sliderTypes: List<VolumeDialogSliderType> = + it.states.keys.sortedWith(StreamsSorter).map { audioStream -> + when { + audioStream == VolumeDialogControllerImpl.DYNAMIC_STREAM_BROADCAST -> + VolumeDialogSliderType.AudioSharingStream(audioStream) + audioStream >= + VolumeDialogControllerImpl.DYNAMIC_STREAM_REMOTE_START_INDEX -> + VolumeDialogSliderType.RemoteMediaStream(audioStream) + else -> VolumeDialogSliderType.Stream(audioStream) + } + } + VolumeDialogSlidersModel( + slider = sliderTypes.first(), + floatingSliders = sliderTypes.drop(1), + ) + } + + private object StreamsSorter : Comparator<Int> { + + // TODO(b/369992924) order the streams + override fun compare(lhs: Int, rhs: Int): Int { + return lhs - rhs + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/model/VolumeDialogSliderType.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/model/VolumeDialogSliderType.kt new file mode 100644 index 000000000000..18a26891e904 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/model/VolumeDialogSliderType.kt @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.volume.dialog.sliders.domain.model + +/** Models different possible audio sliders shown in the Volume Dialog. */ +sealed interface VolumeDialogSliderType { + + // VolumeDialogController uses the same model for every slider type. We need to follow the same + // logic until we refactor and decouple data and domain layers from the VolumeDialogController + // into separated interactors. + val audioStream: Int + + class Stream(override val audioStream: Int) : VolumeDialogSliderType + + class RemoteMediaStream(override val audioStream: Int) : VolumeDialogSliderType + + class AudioSharingStream(override val audioStream: Int) : VolumeDialogSliderType +} diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/model/VolumeDialogSlidersModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/model/VolumeDialogSlidersModel.kt new file mode 100644 index 000000000000..91a332830b75 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/model/VolumeDialogSlidersModel.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.volume.dialog.sliders.domain.model + +/** Models a state of the sliders section of the Volume Dialog. */ +data class VolumeDialogSlidersModel( + val slider: VolumeDialogSliderType, + val floatingSliders: List<VolumeDialogSliderType>, +) diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt new file mode 100644 index 000000000000..25a5f287c21f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.volume.dialog.sliders.ui + +import android.view.View +import androidx.lifecycle.viewmodel.compose.viewModel +import com.android.systemui.lifecycle.WindowLifecycleState +import com.android.systemui.lifecycle.repeatWhenAttached +import com.android.systemui.lifecycle.setSnapshotBinding +import com.android.systemui.lifecycle.viewModel +import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope +import com.android.systemui.volume.dialog.sliders.ui.viewmodel.VolumeDialogSliderViewModel +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import kotlinx.coroutines.awaitCancellation + +class VolumeDialogSliderViewBinder +@AssistedInject +constructor(@Assisted private val viewModelProvider: () -> VolumeDialogSliderViewModel) { + + fun bind(view: View) { + with(view) { + repeatWhenAttached { + viewModel( + traceName = "VolumeDialogSliderViewBinder", + minWindowLifecycleState = WindowLifecycleState.ATTACHED, + factory = { viewModelProvider() }, + ) { viewModel -> + setSnapshotBinding {} + + awaitCancellation() + } + } + } + } + + @AssistedFactory + @VolumeDialogScope + interface Factory { + + fun create( + viewModelProvider: () -> VolumeDialogSliderViewModel + ): VolumeDialogSliderViewBinder + } +} diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinder.kt new file mode 100644 index 000000000000..0a00f70b54f1 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinder.kt @@ -0,0 +1,49 @@ +/* + * 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.volume.dialog.sliders.ui + +import android.view.View +import com.android.systemui.lifecycle.WindowLifecycleState +import com.android.systemui.lifecycle.repeatWhenAttached +import com.android.systemui.lifecycle.setSnapshotBinding +import com.android.systemui.lifecycle.viewModel +import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope +import com.android.systemui.volume.dialog.sliders.ui.viewmodel.VolumeDialogSlidersViewModel +import javax.inject.Inject +import kotlinx.coroutines.awaitCancellation + +@VolumeDialogScope +class VolumeDialogSlidersViewBinder +@Inject +constructor(private val viewModelFactory: VolumeDialogSlidersViewModel.Factory) { + + fun bind(view: View) { + with(view) { + repeatWhenAttached { + viewModel( + traceName = "VolumeDialogSlidersViewBinder", + minWindowLifecycleState = WindowLifecycleState.ATTACHED, + factory = { viewModelFactory.create() }, + ) { viewModel -> + setSnapshotBinding {} + + awaitCancellation() + } + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt new file mode 100644 index 000000000000..27b8f2f5adb7 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.volume.dialog.sliders.ui.viewmodel + +import com.android.systemui.volume.dialog.domain.model.VolumeDialogStreamModel +import com.android.systemui.volume.dialog.sliders.domain.interactor.VolumeDialogSliderInteractor +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import kotlinx.coroutines.flow.Flow + +class VolumeDialogSliderViewModel +@AssistedInject +constructor(@Assisted private val interactor: VolumeDialogSliderInteractor) { + + val model: Flow<VolumeDialogStreamModel> = interactor.slider + + fun setStreamVolume(volume: Int) { + interactor.setStreamVolume(volume) + } + + @AssistedFactory + interface Factory { + + fun create(interactor: VolumeDialogSliderInteractor): VolumeDialogSliderViewModel + } +} diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSlidersViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSlidersViewModel.kt new file mode 100644 index 000000000000..b5b292fa4a66 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSlidersViewModel.kt @@ -0,0 +1,73 @@ +/* + * 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.volume.dialog.sliders.ui.viewmodel + +import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog +import com.android.systemui.volume.dialog.sliders.domain.interactor.VolumeDialogSliderInteractor +import com.android.systemui.volume.dialog.sliders.domain.interactor.VolumeDialogSlidersInteractor +import com.android.systemui.volume.dialog.sliders.domain.model.VolumeDialogSliderType +import com.android.systemui.volume.dialog.sliders.ui.VolumeDialogSliderViewBinder +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn + +class VolumeDialogSlidersViewModel +@AssistedInject +constructor( + @VolumeDialog coroutineScope: CoroutineScope, + private val slidersInteractor: VolumeDialogSlidersInteractor, + private val sliderInteractorFactory: VolumeDialogSliderInteractor.Factory, + private val sliderViewModelFactory: VolumeDialogSliderViewModel.Factory, + private val sliderViewBinderFactory: VolumeDialogSliderViewBinder.Factory, +) { + + val sliders: Flow<VolumeDialogSliderUiModel> = + slidersInteractor.sliders + .distinctUntilChanged() + .map { slidersModel -> + VolumeDialogSliderUiModel( + sliderViewBinder = createSliderViewBinder(slidersModel.slider), + floatingSliderViewBinders = + slidersModel.floatingSliders.map(::createSliderViewBinder), + ) + } + .stateIn(coroutineScope, SharingStarted.Eagerly, null) + .filterNotNull() + + private fun createSliderViewBinder(type: VolumeDialogSliderType): VolumeDialogSliderViewBinder = + sliderViewBinderFactory.create { + sliderViewModelFactory.create(sliderInteractorFactory.create(type)) + } + + @AssistedFactory + interface Factory { + + fun create(): VolumeDialogSlidersViewModel + } +} + +/** Models slider ui */ +data class VolumeDialogSliderUiModel( + val sliderViewBinder: VolumeDialogSliderViewBinder, + val floatingSliderViewBinders: List<VolumeDialogSliderViewBinder>, +) diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogBinder.kt index 9452d8c0dcd4..77733fe33275 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogBinder.kt @@ -50,7 +50,7 @@ constructor( dialog.setContentView(R.layout.volume_dialog) dialog.setCanceledOnTouchOutside(true) - settingsButtonViewBinder.bind(dialog.requireViewById(R.id.settings_container)) + settingsButtonViewBinder.bind(dialog.requireViewById(R.id.volume_dialog_settings)) volumeDialogViewBinder.bind( dialog, dialog.requireViewById(R.id.volume_dialog_container), diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt index 7889b3cd6cc3..7889b3cd6cc3 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt index 9fbe09619ff1..9fbe09619ff1 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardModelTest.kt index 85e8ab43b2ee..5741d64c4d32 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardModelTest.kt @@ -122,6 +122,7 @@ class ClipboardModelTest : SysuiTestCase() { @Test @Throws(IOException::class) + @DisableFlags(FLAG_CLIPBOARD_USE_DESCRIPTION_MIMETYPE) fun test_imageClipData_loadFailure() { whenever(mMockContext.contentResolver).thenReturn(mMockContentResolver) whenever(mMockContext.resources).thenReturn(mContext.resources) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/backup/CommunalBackupUtilsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/data/backup/CommunalBackupUtilsTest.kt index edc8c837bf78..edc8c837bf78 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/backup/CommunalBackupUtilsTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/communal/data/backup/CommunalBackupUtilsTest.kt diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt index 2312bbd2d7f8..2312bbd2d7f8 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayWindowPropertiesRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayWindowPropertiesRepositoryImplTest.kt new file mode 100644 index 000000000000..ff3186abecdc --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayWindowPropertiesRepositoryImplTest.kt @@ -0,0 +1,158 @@ +/* + * 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.display.data.repository + +import android.content.testableContext +import android.platform.test.annotations.EnableFlags +import android.view.Display +import android.view.mockWindowManager +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.display.shared.model.DisplayWindowProperties +import com.android.systemui.kosmos.applicationCoroutineScope +import com.android.systemui.kosmos.testDispatcher +import com.android.systemui.kosmos.testScope +import com.android.systemui.kosmos.unconfinedTestDispatcher +import com.android.systemui.statusbar.core.StatusBarConnectedDisplays +import com.android.systemui.testKosmos +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock + +@EnableFlags(StatusBarConnectedDisplays.FLAG_NAME) +@RunWith(AndroidJUnit4::class) +@SmallTest +class DisplayWindowPropertiesRepositoryImplTest : SysuiTestCase() { + + private val kosmos = testKosmos().also { it.testDispatcher = it.unconfinedTestDispatcher } + private val fakeDisplayRepository = kosmos.displayRepository + private val testScope = kosmos.testScope + + private val applicationContext = kosmos.testableContext + private val applicationWindowManager = kosmos.mockWindowManager + + private val repo = + DisplayWindowPropertiesRepositoryImpl( + kosmos.applicationCoroutineScope, + applicationContext, + applicationWindowManager, + fakeDisplayRepository, + ) + + @Before + fun start() { + repo.start() + } + + @Before + fun addDisplays() = runBlocking { + fakeDisplayRepository.addDisplay(createDisplay(DEFAULT_DISPLAY_ID)) + fakeDisplayRepository.addDisplay(createDisplay(NON_DEFAULT_DISPLAY_ID)) + } + + @Test + fun get_defaultDisplayId_returnsDefaultProperties() = + testScope.runTest { + val displayContext = repo.get(DEFAULT_DISPLAY_ID, WINDOW_TYPE_FOO) + + assertThat(displayContext) + .isEqualTo( + DisplayWindowProperties( + displayId = DEFAULT_DISPLAY_ID, + windowType = WINDOW_TYPE_FOO, + context = applicationContext, + windowManager = applicationWindowManager, + ) + ) + } + + @Test + fun get_nonDefaultDisplayId_returnsNewStatusBarContext() = + testScope.runTest { + val displayContext = repo.get(NON_DEFAULT_DISPLAY_ID, WINDOW_TYPE_FOO) + + assertThat(displayContext.context).isNotSameInstanceAs(applicationContext) + } + + @Test + fun get_nonDefaultDisplayId_returnsNewWindowManager() = + testScope.runTest { + val displayContext = repo.get(NON_DEFAULT_DISPLAY_ID, WINDOW_TYPE_FOO) + + assertThat(displayContext.windowManager).isNotSameInstanceAs(applicationWindowManager) + } + + @Test + fun get_multipleCallsForDefaultDisplay_returnsSameInstance() = + testScope.runTest { + val displayContext = repo.get(DEFAULT_DISPLAY_ID, WINDOW_TYPE_FOO) + + assertThat(repo.get(DEFAULT_DISPLAY_ID, WINDOW_TYPE_FOO)) + .isSameInstanceAs(displayContext) + } + + @Test + fun get_multipleCallsForNonDefaultDisplay_returnsSameInstance() = + testScope.runTest { + val displayContext = repo.get(NON_DEFAULT_DISPLAY_ID, WINDOW_TYPE_FOO) + + assertThat(repo.get(NON_DEFAULT_DISPLAY_ID, WINDOW_TYPE_FOO)) + .isSameInstanceAs(displayContext) + } + + @Test + fun get_multipleCalls_differentType_returnsNewInstance() = + testScope.runTest { + val displayContext = repo.get(NON_DEFAULT_DISPLAY_ID, WINDOW_TYPE_FOO) + + assertThat(repo.get(NON_DEFAULT_DISPLAY_ID, WINDOW_TYPE_BAR)) + .isNotSameInstanceAs(displayContext) + } + + @Test + fun get_afterDisplayRemoved_returnsNewInstance() = + testScope.runTest { + val displayContext = repo.get(NON_DEFAULT_DISPLAY_ID, WINDOW_TYPE_FOO) + + fakeDisplayRepository.removeDisplay(NON_DEFAULT_DISPLAY_ID) + fakeDisplayRepository.addDisplay(createDisplay(NON_DEFAULT_DISPLAY_ID)) + + assertThat(repo.get(NON_DEFAULT_DISPLAY_ID, WINDOW_TYPE_FOO)) + .isNotSameInstanceAs(displayContext) + } + + @Test(expected = IllegalArgumentException::class) + fun get_nonExistingDisplayId_throws() = + testScope.runTest { repo.get(NON_EXISTING_DISPLAY_ID, WINDOW_TYPE_FOO) } + + private fun createDisplay(displayId: Int) = + mock<Display> { on { getDisplayId() } doReturn displayId } + + companion object { + private const val DEFAULT_DISPLAY_ID = Display.DEFAULT_DISPLAY + private const val NON_DEFAULT_DISPLAY_ID = DEFAULT_DISPLAY_ID + 1 + private const val NON_EXISTING_DISPLAY_ID = DEFAULT_DISPLAY_ID + 2 + private const val WINDOW_TYPE_FOO = 123 + private const val WINDOW_TYPE_BAR = 321 + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/data/repository/KeyboardRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/data/repository/KeyboardRepositoryTest.kt index 8b1341114c68..8b1341114c68 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/data/repository/KeyboardRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/data/repository/KeyboardRepositoryTest.kt diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt index e1845a17a767..e1845a17a767 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt index 5b216620ec2b..5b216620ec2b 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/ActivatableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/lifecycle/ActivatableTest.kt index 2ba670ceb76a..2ba670ceb76a 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/ActivatableTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/lifecycle/ActivatableTest.kt diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/SysUiViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/lifecycle/SysUiViewModelTest.kt index 73f724e7daef..73f724e7daef 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/SysUiViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/lifecycle/SysUiViewModelTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaSwitchingControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaSwitchingControllerTest.java index 07e48b9da153..bf4ef509ac80 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaSwitchingControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaSwitchingControllerTest.java @@ -125,6 +125,8 @@ public class MediaSwitchingControllerTest extends SysuiTestCase { private static final boolean VOLUME_FIXED_TRUE = true; private static final int LATCH_COUNT_DOWN_TIME_IN_SECOND = 5; private static final int LATCH_TIME_OUT_TIME_IN_SECOND = 10; + private static final String PRODUCT_NAME_BUILTIN_MIC = "Built-in Mic"; + private static final String PRODUCT_NAME_WIRED_HEADSET = "My Wired Headset"; @Mock private DialogTransitionAnimator mDialogTransitionAnimator; @@ -568,7 +570,8 @@ public class MediaSwitchingControllerTest extends SysuiTestCase { AudioDeviceInfo.TYPE_BUILTIN_MIC, MAX_VOLUME, CURRENT_VOLUME, - VOLUME_FIXED_TRUE); + VOLUME_FIXED_TRUE, + PRODUCT_NAME_BUILTIN_MIC); final MediaDevice mediaDevice4 = InputMediaDevice.create( mContext, @@ -576,7 +579,8 @@ public class MediaSwitchingControllerTest extends SysuiTestCase { AudioDeviceInfo.TYPE_WIRED_HEADSET, MAX_VOLUME, CURRENT_VOLUME, - VOLUME_FIXED_TRUE); + VOLUME_FIXED_TRUE, + PRODUCT_NAME_WIRED_HEADSET); final List<MediaDevice> inputDevices = new ArrayList<>(); inputDevices.add(mediaDevice3); inputDevices.add(mediaDevice4); @@ -1355,7 +1359,8 @@ public class MediaSwitchingControllerTest extends SysuiTestCase { AudioDeviceInfo.TYPE_BUILTIN_MIC, MAX_VOLUME, CURRENT_VOLUME, - VOLUME_FIXED_TRUE); + VOLUME_FIXED_TRUE, + PRODUCT_NAME_BUILTIN_MIC); mMediaSwitchingController.connectDevice(inputMediaDevice); CountDownLatch latch = new CountDownLatch(LATCH_COUNT_DOWN_TIME_IN_SECOND); diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSImplTest.java index fe1b963f6801..fe1b963f6801 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSImplTest.java diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt index 8d060e936cd9..8d060e936cd9 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/ResizingTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/ResizingTest.kt index ee1c0e99d6ac..ee1c0e99d6ac 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/ResizingTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/ResizingTest.kt diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt index 963973588236..963973588236 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/ScreenshotDetectionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotDetectionControllerTest.kt index c50702868025..c50702868025 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/ScreenshotDetectionControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotDetectionControllerTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerWifiTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerWifiTest.java index 6febb91db992..7a579bacc86d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerWifiTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerWifiTest.java @@ -58,7 +58,7 @@ public class NetworkControllerWifiTest extends NetworkControllerBaseTest { private static final int MIN_RSSI = -100; private static final int MAX_RSSI = -55; private WifiInfo mWifiInfo = mock(WifiInfo.class); - private VcnTransportInfo mVcnTransportInfo = mock(VcnTransportInfo.class); + private VcnTransportInfo mVcnTransportInfo = new VcnTransportInfo.Builder().build(); @Before public void setUp() throws Exception { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java index 59fc0d157d54..87cda64ed8e5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java @@ -591,8 +591,8 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { ArgumentCaptor<FooterView> captor = ArgumentCaptor.forClass(FooterView.class); verify(mStackScroller).setFooterView(captor.capture()); - assertNotNull(captor.getValue().findViewById(R.id.manage_text).hasOnClickListeners()); - assertNotNull(captor.getValue().findViewById(R.id.dismiss_text).hasOnClickListeners()); + assertNotNull(captor.getValue().findViewById(R.id.manage_text)); + assertNotNull(captor.getValue().findViewById(R.id.dismiss_text)); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt index 328d31014ccc..c48898aad087 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt @@ -136,6 +136,7 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { private val wifiLogBuffer = LogBuffer("wifi", maxSize = 100, logcatEchoTracker = mock()) private val wifiPickerTrackerCallback = argumentCaptor<WifiPickerTracker.WifiPickerTrackerCallback>() + private val vcnTransportInfo = VcnTransportInfo.Builder().build() private val testDispatcher = StandardTestDispatcher() private val testScope = TestScope(testDispatcher) @@ -1003,6 +1004,18 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { assertThat(latest).isTrue() } + private fun newWifiNetwork(wifiInfo: WifiInfo): Network { + val network = mock<Network>() + val capabilities = + mock<NetworkCapabilities>().also { + whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true) + whenever(it.transportInfo).thenReturn(wifiInfo) + } + whenever(connectivityManager.getNetworkCapabilities(network)).thenReturn(capabilities) + + return network + } + /** Regression test for b/272586234. */ @Test fun hasCarrierMergedConnection_carrierMergedViaWifiWithVcnTransport_isTrue() = @@ -1012,10 +1025,12 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { whenever(this.isCarrierMerged).thenReturn(true) whenever(this.isPrimary).thenReturn(true) } + val underlyingWifi = newWifiNetwork(carrierMergedInfo) val caps = mock<NetworkCapabilities>().also { whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true) - whenever(it.transportInfo).thenReturn(VcnTransportInfo(carrierMergedInfo)) + whenever(it.transportInfo).thenReturn(vcnTransportInfo) + whenever(it.underlyingNetworks).thenReturn(listOf(underlyingWifi)) } val latest by collectLastValue(underTest.hasCarrierMergedConnection) @@ -1034,10 +1049,12 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { whenever(this.isCarrierMerged).thenReturn(true) whenever(this.isPrimary).thenReturn(true) } + val underlyingWifi = newWifiNetwork(carrierMergedInfo) val caps = mock<NetworkCapabilities>().also { whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) - whenever(it.transportInfo).thenReturn(VcnTransportInfo(carrierMergedInfo)) + whenever(it.transportInfo).thenReturn(vcnTransportInfo) + whenever(it.underlyingNetworks).thenReturn(listOf(underlyingWifi)) } val latest by collectLastValue(underTest.hasCarrierMergedConnection) @@ -1094,10 +1111,15 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { whenever(this.isCarrierMerged).thenReturn(true) whenever(this.isPrimary).thenReturn(true) } + + // The Wifi network that is under the VCN network + val physicalWifiNetwork = newWifiNetwork(carrierMergedInfo) + val underlyingCapabilities = mock<NetworkCapabilities>().also { whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) - whenever(it.transportInfo).thenReturn(VcnTransportInfo(carrierMergedInfo)) + whenever(it.transportInfo).thenReturn(vcnTransportInfo) + whenever(it.underlyingNetworks).thenReturn(listOf(physicalWifiNetwork)) } whenever(connectivityManager.getNetworkCapabilities(underlyingCarrierMergedNetwork)) .thenReturn(underlyingCapabilities) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepositoryImplTest.kt index 0945742fb325..88f262bec123 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepositoryImplTest.kt @@ -23,6 +23,7 @@ import android.net.NetworkCapabilities.TRANSPORT_CELLULAR import android.net.NetworkCapabilities.TRANSPORT_ETHERNET import android.net.NetworkCapabilities.TRANSPORT_VPN import android.net.NetworkCapabilities.TRANSPORT_WIFI +import android.net.TelephonyNetworkSpecifier import android.net.VpnTransportInfo import android.net.vcn.VcnTransportInfo import android.net.wifi.WifiInfo @@ -74,6 +75,8 @@ class ConnectivityRepositoryImplTest : SysuiTestCase() { private val testScope = kosmos.testScope private val tunerService = mock<TunerService>() + private val vcnTransportInfo = VcnTransportInfo.Builder().build() + @Before fun setUp() { createAndSetRepo() @@ -343,6 +346,30 @@ class ConnectivityRepositoryImplTest : SysuiTestCase() { assertThat(latest!!.wifi.isDefault).isTrue() } + private fun newWifiNetwork(wifiInfo: WifiInfo): Network { + val network = mock<Network>() + val capabilities = + mock<NetworkCapabilities>().also { + whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true) + whenever(it.transportInfo).thenReturn(wifiInfo) + } + whenever(connectivityManager.getNetworkCapabilities(network)).thenReturn(capabilities) + + return network + } + + private fun newCellNetwork(subId: Int): Network { + val network = mock<Network>() + val capabilities = + mock<NetworkCapabilities>().also { + whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) + whenever(it.networkSpecifier).thenReturn(TelephonyNetworkSpecifier(subId)) + } + whenever(connectivityManager.getNetworkCapabilities(network)).thenReturn(capabilities) + + return network + } + @Test fun defaultConnections_carrierMergedViaWifiWithVcnTransport_wifiAndCarrierMergedDefault() = testScope.runTest { @@ -350,10 +377,12 @@ class ConnectivityRepositoryImplTest : SysuiTestCase() { val carrierMergedInfo = mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) } + val underlyingWifi = newWifiNetwork(carrierMergedInfo) val capabilities = mock<NetworkCapabilities>().also { whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true) - whenever(it.transportInfo).thenReturn(VcnTransportInfo(carrierMergedInfo)) + whenever(it.transportInfo).thenReturn(vcnTransportInfo) + whenever(it.underlyingNetworks).thenReturn(listOf(underlyingWifi)) whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(false) whenever(it.hasTransport(TRANSPORT_ETHERNET)).thenReturn(false) } @@ -373,10 +402,12 @@ class ConnectivityRepositoryImplTest : SysuiTestCase() { val carrierMergedInfo = mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) } + val underlyingWifi = newWifiNetwork(carrierMergedInfo) val capabilities = mock<NetworkCapabilities>().also { whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) - whenever(it.transportInfo).thenReturn(VcnTransportInfo(carrierMergedInfo)) + whenever(it.transportInfo).thenReturn(vcnTransportInfo) + whenever(it.underlyingNetworks).thenReturn(listOf(underlyingWifi)) whenever(it.hasTransport(TRANSPORT_ETHERNET)).thenReturn(false) whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(false) } @@ -561,10 +592,12 @@ class ConnectivityRepositoryImplTest : SysuiTestCase() { val underlyingCarrierMergedNetwork = mock<Network>() val carrierMergedInfo = mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) } + val underlyingWifi = newWifiNetwork(carrierMergedInfo) val underlyingCapabilities = mock<NetworkCapabilities>().also { whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) - whenever(it.transportInfo).thenReturn(VcnTransportInfo(carrierMergedInfo)) + whenever(it.transportInfo).thenReturn(vcnTransportInfo) + whenever(it.underlyingNetworks).thenReturn(listOf(underlyingWifi)) } whenever(connectivityManager.getNetworkCapabilities(underlyingCarrierMergedNetwork)) .thenReturn(underlyingCapabilities) @@ -645,14 +678,15 @@ class ConnectivityRepositoryImplTest : SysuiTestCase() { @Test fun vcnSubId_tracksVcnTransportInfo() = testScope.runTest { - val vcnInfo = VcnTransportInfo(SUB_1_ID) + val underlyingCell = newCellNetwork(SUB_1_ID) val latest by collectLastValue(underTest.vcnSubId) val capabilities = mock<NetworkCapabilities>().also { whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) - whenever(it.transportInfo).thenReturn(vcnInfo) + whenever(it.transportInfo).thenReturn(vcnTransportInfo) + whenever(it.underlyingNetworks).thenReturn(listOf(underlyingCell)) } getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities) @@ -663,14 +697,15 @@ class ConnectivityRepositoryImplTest : SysuiTestCase() { @Test fun vcnSubId_filersOutInvalid() = testScope.runTest { - val vcnInfo = VcnTransportInfo(INVALID_SUBSCRIPTION_ID) + val underlyingCell = newCellNetwork(INVALID_SUBSCRIPTION_ID) val latest by collectLastValue(underTest.vcnSubId) val capabilities = mock<NetworkCapabilities>().also { whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) - whenever(it.transportInfo).thenReturn(vcnInfo) + whenever(it.transportInfo).thenReturn(vcnTransportInfo) + whenever(it.underlyingNetworks).thenReturn(listOf(underlyingCell)) } getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities) @@ -703,11 +738,12 @@ class ConnectivityRepositoryImplTest : SysuiTestCase() { val latest by collectLastValue(underTest.vcnSubId) val wifiInfo = mock<WifiInfo>() - val vcnInfo = VcnTransportInfo(wifiInfo) + val underlyingWifi = newWifiNetwork(wifiInfo) val capabilities = mock<NetworkCapabilities>().also { whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) - whenever(it.transportInfo).thenReturn(vcnInfo) + whenever(it.transportInfo).thenReturn(vcnTransportInfo) + whenever(it.underlyingNetworks).thenReturn(listOf(underlyingWifi)) } getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities) @@ -721,14 +757,15 @@ class ConnectivityRepositoryImplTest : SysuiTestCase() { val latest by collectLastValue(underTest.vcnSubId) val wifiInfo = mock<WifiInfo>() - val wifiVcnInfo = VcnTransportInfo(wifiInfo) - val sub1VcnInfo = VcnTransportInfo(SUB_1_ID) - val sub2VcnInfo = VcnTransportInfo(SUB_2_ID) + val underlyingWifi = newWifiNetwork(wifiInfo) + val underlyingCell1 = newCellNetwork(SUB_1_ID) + val underlyingCell2 = newCellNetwork(SUB_2_ID) val capabilities = mock<NetworkCapabilities>().also { whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true) - whenever(it.transportInfo).thenReturn(wifiVcnInfo) + whenever(it.transportInfo).thenReturn(vcnTransportInfo) + whenever(it.underlyingNetworks).thenReturn(listOf(underlyingWifi)) } // WIFI VCN info @@ -738,14 +775,16 @@ class ConnectivityRepositoryImplTest : SysuiTestCase() { // Cellular VCN info with subId 1 whenever(capabilities.hasTransport(eq(TRANSPORT_CELLULAR))).thenReturn(true) - whenever(capabilities.transportInfo).thenReturn(sub1VcnInfo) + whenever(capabilities.transportInfo).thenReturn(vcnTransportInfo) + whenever(capabilities.underlyingNetworks).thenReturn(listOf(underlyingCell1)) getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities) assertThat(latest).isEqualTo(SUB_1_ID) // Cellular VCN info with subId 2 - whenever(capabilities.transportInfo).thenReturn(sub2VcnInfo) + whenever(capabilities.transportInfo).thenReturn(vcnTransportInfo) + whenever(capabilities.underlyingNetworks).thenReturn(listOf(underlyingCell2)) getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities) @@ -776,11 +815,12 @@ class ConnectivityRepositoryImplTest : SysuiTestCase() { @Test fun getMainOrUnderlyingWifiInfo_vcnWithWifi_hasInfo() { val wifiInfo = mock<WifiInfo>() - val vcnInfo = VcnTransportInfo(wifiInfo) + val underlyingWifi = newWifiNetwork(wifiInfo) val capabilities = mock<NetworkCapabilities>().also { whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true) - whenever(it.transportInfo).thenReturn(vcnInfo) + whenever(it.transportInfo).thenReturn(vcnTransportInfo) + whenever(it.underlyingNetworks).thenReturn(listOf(underlyingWifi)) } val result = capabilities.getMainOrUnderlyingWifiInfo(connectivityManager) @@ -860,11 +900,15 @@ class ConnectivityRepositoryImplTest : SysuiTestCase() { fun getMainOrUnderlyingWifiInfo_cellular_underlyingVcnWithWifi_hasInfo() { val wifiInfo = mock<WifiInfo>() val underlyingNetwork = mock<Network>() - val underlyingVcnInfo = VcnTransportInfo(wifiInfo) + + // The Wifi network that is under the VCN network + val physicalWifiNetwork = newWifiNetwork(wifiInfo) + val underlyingWifiCapabilities = mock<NetworkCapabilities>().also { whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true) - whenever(it.transportInfo).thenReturn(underlyingVcnInfo) + whenever(it.transportInfo).thenReturn(vcnTransportInfo) + whenever(it.underlyingNetworks).thenReturn(listOf(physicalWifiNetwork)) } whenever(connectivityManager.getNetworkCapabilities(underlyingNetwork)) .thenReturn(underlyingWifiCapabilities) @@ -887,11 +931,15 @@ class ConnectivityRepositoryImplTest : SysuiTestCase() { @DisableFlags(FLAG_STATUS_BAR_ALWAYS_CHECK_UNDERLYING_NETWORKS) fun getMainOrUnderlyingWifiInfo_notCellular_underlyingVcnWithWifi_noInfo() { val underlyingNetwork = mock<Network>() - val underlyingVcnInfo = VcnTransportInfo(mock<WifiInfo>()) + + // The Wifi network that is under the VCN network + val physicalWifiNetwork = newWifiNetwork(mock<WifiInfo>()) + val underlyingWifiCapabilities = mock<NetworkCapabilities>().also { whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true) - whenever(it.transportInfo).thenReturn(underlyingVcnInfo) + whenever(it.transportInfo).thenReturn(vcnTransportInfo) + whenever(it.underlyingNetworks).thenReturn(listOf(physicalWifiNetwork)) } whenever(connectivityManager.getNetworkCapabilities(underlyingNetwork)) .thenReturn(underlyingWifiCapabilities) @@ -917,10 +965,15 @@ class ConnectivityRepositoryImplTest : SysuiTestCase() { val underlyingCarrierMergedNetwork = mock<Network>() val carrierMergedInfo = mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) } + + // The Wifi network that is under the VCN network + val physicalWifiNetwork = newWifiNetwork(carrierMergedInfo) + val underlyingCapabilities = mock<NetworkCapabilities>().also { whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) - whenever(it.transportInfo).thenReturn(VcnTransportInfo(carrierMergedInfo)) + whenever(it.transportInfo).thenReturn(vcnTransportInfo) + whenever(it.underlyingNetworks).thenReturn(listOf(physicalWifiNetwork)) } whenever(connectivityManager.getNetworkCapabilities(underlyingCarrierMergedNetwork)) .thenReturn(underlyingCapabilities) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/window/MultiDisplayStatusBarWindowControllerStoreTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/window/MultiDisplayStatusBarWindowControllerStoreTest.kt new file mode 100644 index 000000000000..faaa4c415d28 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/window/MultiDisplayStatusBarWindowControllerStoreTest.kt @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.window + +import android.platform.test.annotations.EnableFlags +import android.view.Display +import android.view.WindowManager +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.app.viewcapture.ViewCaptureAwareWindowManager +import com.android.app.viewcapture.mockViewCaptureAwareWindowManager +import com.android.systemui.SysuiTestCase +import com.android.systemui.display.data.repository.displayRepository +import com.android.systemui.display.data.repository.fakeDisplayWindowPropertiesRepository +import com.android.systemui.kosmos.applicationCoroutineScope +import com.android.systemui.kosmos.testDispatcher +import com.android.systemui.kosmos.testScope +import com.android.systemui.kosmos.unconfinedTestDispatcher +import com.android.systemui.statusbar.core.StatusBarConnectedDisplays +import com.android.systemui.testKosmos +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock + +@RunWith(AndroidJUnit4::class) +@SmallTest +@EnableFlags(StatusBarConnectedDisplays.FLAG_NAME) +class MultiDisplayStatusBarWindowControllerStoreTest : SysuiTestCase() { + + private val kosmos = testKosmos().also { it.testDispatcher = it.unconfinedTestDispatcher } + private val testScope = kosmos.testScope + private val fakeDisplayRepository = kosmos.displayRepository + + private val store = + MultiDisplayStatusBarWindowControllerStore( + backgroundApplicationScope = kosmos.applicationCoroutineScope, + controllerFactory = kosmos.fakeStatusBarWindowControllerFactory, + displayWindowPropertiesRepository = kosmos.fakeDisplayWindowPropertiesRepository, + viewCaptureAwareWindowManagerFactory = + object : ViewCaptureAwareWindowManager.Factory { + override fun create( + windowManager: WindowManager + ): ViewCaptureAwareWindowManager { + return kosmos.mockViewCaptureAwareWindowManager + } + }, + displayRepository = fakeDisplayRepository, + ) + + @Before + fun start() { + store.start() + } + + @Before + fun addDisplays() = runBlocking { + fakeDisplayRepository.addDisplay(createDisplay(DEFAULT_DISPLAY_ID)) + fakeDisplayRepository.addDisplay(createDisplay(NON_DEFAULT_DISPLAY_ID)) + } + + @Test + fun forDisplay_defaultDisplay_multipleCalls_returnsSameInstance() = + testScope.runTest { + val controller = store.defaultDisplay + + assertThat(store.defaultDisplay).isSameInstanceAs(controller) + } + + @Test + fun forDisplay_nonDefaultDisplay_multipleCalls_returnsSameInstance() = + testScope.runTest { + val controller = store.forDisplay(NON_DEFAULT_DISPLAY_ID) + + assertThat(store.forDisplay(NON_DEFAULT_DISPLAY_ID)).isSameInstanceAs(controller) + } + + @Test + fun forDisplay_nonDefaultDisplay_afterDisplayRemoved_returnsNewInstance() = + testScope.runTest { + val controller = store.forDisplay(NON_DEFAULT_DISPLAY_ID) + + fakeDisplayRepository.removeDisplay(NON_DEFAULT_DISPLAY_ID) + fakeDisplayRepository.addDisplay(createDisplay(NON_DEFAULT_DISPLAY_ID)) + + assertThat(store.forDisplay(NON_DEFAULT_DISPLAY_ID)).isNotSameInstanceAs(controller) + } + + @Test(expected = IllegalArgumentException::class) + fun forDisplay_nonExistingDisplayId_throws() = + testScope.runTest { store.forDisplay(NON_EXISTING_DISPLAY_ID) } + + private fun createDisplay(displayId: Int): Display = mock { + on { getDisplayId() } doReturn displayId + } + + companion object { + private const val DEFAULT_DISPLAY_ID = Display.DEFAULT_DISPLAY + private const val NON_DEFAULT_DISPLAY_ID = DEFAULT_DISPLAY_ID + 1 + private const val NON_EXISTING_DISPLAY_ID = DEFAULT_DISPLAY_ID + 2 + } +} diff --git a/packages/SystemUI/tests/utils/src/com/android/app/viewcapture/ViewCaptureAwareWindowManagerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/app/viewcapture/ViewCaptureAwareWindowManagerKosmos.kt new file mode 100644 index 000000000000..e1c6699348a9 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/app/viewcapture/ViewCaptureAwareWindowManagerKosmos.kt @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.app.viewcapture + +import com.android.systemui.kosmos.Kosmos +import org.mockito.kotlin.mock + +val Kosmos.mockViewCaptureAwareWindowManager by + Kosmos.Fixture { mock<ViewCaptureAwareWindowManager>() } + +var Kosmos.viewCaptureAwareWindowManager: ViewCaptureAwareWindowManager by + Kosmos.Fixture { mockViewCaptureAwareWindowManager } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/DisplayScopeRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/DisplayScopeRepositoryKosmos.kt new file mode 100644 index 000000000000..ff4ba61b6965 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/DisplayScopeRepositoryKosmos.kt @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.display.data.repository + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.testDispatcher + +val Kosmos.fakeDisplayScopeRepository by + Kosmos.Fixture { FakeDisplayScopeRepository(testDispatcher) } + +var Kosmos.displayScopeRepository: DisplayScopeRepository by + Kosmos.Fixture { fakeDisplayScopeRepository } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/DisplayWindowPropertiesRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/DisplayWindowPropertiesRepositoryKosmos.kt new file mode 100644 index 000000000000..65b18c102a16 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/DisplayWindowPropertiesRepositoryKosmos.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.display.data.repository + +import com.android.systemui.kosmos.Kosmos + +val Kosmos.fakeDisplayWindowPropertiesRepository by + Kosmos.Fixture { FakeDisplayWindowPropertiesRepository() } + +var Kosmos.displayWindowPropertiesRepository: DisplayWindowPropertiesRepository by + Kosmos.Fixture { fakeDisplayWindowPropertiesRepository } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayScopeRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayScopeRepository.kt new file mode 100644 index 000000000000..3c2592471694 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayScopeRepository.kt @@ -0,0 +1,30 @@ +/* + * 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.display.data.repository + +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope + +class FakeDisplayScopeRepository(private val dispatcher: CoroutineDispatcher) : + DisplayScopeRepository { + + private val perDisplayScopes = mutableMapOf<Int, CoroutineScope>() + + override fun scopeForDisplay(displayId: Int): CoroutineScope { + return perDisplayScopes.computeIfAbsent(displayId) { CoroutineScope(dispatcher) } + } +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayWindowPropertiesRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayWindowPropertiesRepository.kt new file mode 100644 index 000000000000..9282f275b20b --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayWindowPropertiesRepository.kt @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.display.data.repository + +import com.android.systemui.display.shared.model.DisplayWindowProperties +import com.google.common.collect.HashBasedTable +import org.mockito.kotlin.mock + +class FakeDisplayWindowPropertiesRepository : DisplayWindowPropertiesRepository { + + private val properties = HashBasedTable.create<Int, Int, DisplayWindowProperties>() + + override fun get(displayId: Int, windowType: Int): DisplayWindowProperties { + return properties.get(displayId, windowType) + ?: DisplayWindowProperties( + displayId = displayId, + windowType = windowType, + context = mock(), + windowManager = mock(), + ) + .also { properties.put(displayId, windowType, it) } + } +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt index f97f30383398..522c387a0b08 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt @@ -23,6 +23,7 @@ import android.os.fakeExecutorHandler import com.android.systemui.SysuiTestCase import com.android.systemui.bouncer.data.repository.bouncerRepository import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository +import com.android.systemui.bouncer.domain.interactor.alternateBouncerInteractor import com.android.systemui.bouncer.domain.interactor.simBouncerInteractor import com.android.systemui.classifier.falsingCollector import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository @@ -152,6 +153,7 @@ class KosmosJavaAdapter() { val wifiInteractor by lazy { kosmos.wifiInteractor } val fakeWifiRepository by lazy { kosmos.fakeWifiRepository } val volumeDialogInteractor by lazy { kosmos.volumeDialogInteractor } + val alternateBouncerInteractor by lazy { kosmos.alternateBouncerInteractor } val ongoingActivityChipsViewModel by lazy { kosmos.ongoingActivityChipsViewModel } val scrimController by lazy { kosmos.scrimController } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/statusbar/StatusBarStateControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/statusbar/StatusBarStateControllerKosmos.kt index cfc31c7f301c..10b073e8f331 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/statusbar/StatusBarStateControllerKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/statusbar/StatusBarStateControllerKosmos.kt @@ -17,6 +17,7 @@ package com.android.systemui.plugins.statusbar import com.android.internal.logging.uiEventLogger +import com.android.systemui.bouncer.domain.interactor.alternateBouncerInteractor import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor import com.android.systemui.jank.interactionJankMonitor import com.android.systemui.keyguard.domain.interactor.keyguardClockInteractor @@ -45,5 +46,6 @@ var Kosmos.statusBarStateController: SysuiStatusBarStateController by { sceneContainerOcclusionInteractor }, { keyguardClockInteractor }, { sceneBackInteractor }, + { alternateBouncerInteractor }, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/FakeStatusBarWindowControllerFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/FakeStatusBarWindowControllerFactory.kt new file mode 100644 index 000000000000..10f328be12d2 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/FakeStatusBarWindowControllerFactory.kt @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.window + +import android.content.Context +import com.android.app.viewcapture.ViewCaptureAwareWindowManager + +class FakeStatusBarWindowControllerFactory : StatusBarWindowController.Factory { + override fun create( + context: Context, + viewCaptureAwareWindowManager: ViewCaptureAwareWindowManager, + ) = FakeStatusBarWindowController() +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/FakeStatusBarWindowControllerStore.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/FakeStatusBarWindowControllerStore.kt new file mode 100644 index 000000000000..d19e3227027c --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/FakeStatusBarWindowControllerStore.kt @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.window + +import android.view.Display + +class FakeStatusBarWindowControllerStore : StatusBarWindowControllerStore { + + private val perDisplayControllers = mutableMapOf<Int, FakeStatusBarWindowController>() + + override val defaultDisplay + get() = forDisplay(Display.DEFAULT_DISPLAY) + + override fun forDisplay(displayId: Int): StatusBarWindowController { + return perDisplayControllers.computeIfAbsent(displayId) { FakeStatusBarWindowController() } + } +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/StatusBarWindowControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/StatusBarWindowControllerKosmos.kt index c198b35be289..6c6f243f3953 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/StatusBarWindowControllerKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/StatusBarWindowControllerKosmos.kt @@ -21,3 +21,15 @@ import com.android.systemui.kosmos.Kosmos val Kosmos.fakeStatusBarWindowController by Kosmos.Fixture { FakeStatusBarWindowController() } var Kosmos.statusBarWindowController by Kosmos.Fixture { fakeStatusBarWindowController } + +val Kosmos.fakeStatusBarWindowControllerStore by + Kosmos.Fixture { FakeStatusBarWindowControllerStore() } + +var Kosmos.statusBarWindowControllerStore: StatusBarWindowControllerStore by + Kosmos.Fixture { fakeStatusBarWindowControllerStore } + +val Kosmos.fakeStatusBarWindowControllerFactory by + Kosmos.Fixture { FakeStatusBarWindowControllerFactory() } + +var Kosmos.statusBarWindowControllerFactory: StatusBarWindowController.Factory by + Kosmos.Fixture { fakeStatusBarWindowControllerFactory } diff --git a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionDumpHelper.java b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionDumpHelper.java index 9fc413f6224b..a832545475bf 100644 --- a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionDumpHelper.java +++ b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionDumpHelper.java @@ -84,15 +84,17 @@ public final class AppFunctionDumpHelper { new FutureGlobalSearchSession(appSearchManager, Runnable::run)) { pw.println(); - FutureSearchResults futureSearchResults = - searchSession.search("", buildAppFunctionMetadataSearchSpec()).get(); - List<SearchResult> searchResultsList; - do { - searchResultsList = futureSearchResults.getNextPage().get(); - for (SearchResult searchResult : searchResultsList) { - dumpAppFunctionMetadata(pw, searchResult); - } - } while (!searchResultsList.isEmpty()); + try (FutureSearchResults futureSearchResults = + searchSession.search("", buildAppFunctionMetadataSearchSpec()).get(); ) { + List<SearchResult> searchResultsList; + do { + searchResultsList = futureSearchResults.getNextPage().get(); + for (SearchResult searchResult : searchResultsList) { + dumpAppFunctionMetadata(pw, searchResult); + } + } while (!searchResultsList.isEmpty()); + } + } catch (Exception e) { pw.println("Failed to dump AppFunction state: " + e); } diff --git a/services/appfunctions/java/com/android/server/appfunctions/FutureSearchResults.java b/services/appfunctions/java/com/android/server/appfunctions/FutureSearchResults.java index 45cbdb4021e5..c38ff143178f 100644 --- a/services/appfunctions/java/com/android/server/appfunctions/FutureSearchResults.java +++ b/services/appfunctions/java/com/android/server/appfunctions/FutureSearchResults.java @@ -24,11 +24,12 @@ import android.app.appsearch.SearchResults; import com.android.internal.infra.AndroidFuture; +import java.io.Closeable; import java.io.IOException; import java.util.List; /** A future API wrapper of {@link android.app.appsearch.SearchResults}. */ -public interface FutureSearchResults { +public interface FutureSearchResults extends Closeable { /** Converts a failed app search result codes into an exception. */ @NonNull @@ -52,4 +53,7 @@ public interface FutureSearchResults { * there are no more results. */ AndroidFuture<List<SearchResult>> getNextPage(); + + @Override + void close(); } diff --git a/services/appfunctions/java/com/android/server/appfunctions/FutureSearchResultsImpl.java b/services/appfunctions/java/com/android/server/appfunctions/FutureSearchResultsImpl.java index c3be342043ba..c8bc538f7226 100644 --- a/services/appfunctions/java/com/android/server/appfunctions/FutureSearchResultsImpl.java +++ b/services/appfunctions/java/com/android/server/appfunctions/FutureSearchResultsImpl.java @@ -54,4 +54,9 @@ public class FutureSearchResultsImpl implements FutureSearchResults { } }); } + + @Override + public void close() { + mSearchResults.close(); + } } diff --git a/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java b/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java index bbf6c0beb163..96be76975e9d 100644 --- a/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java +++ b/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java @@ -420,26 +420,29 @@ public class MetadataSyncAdapter { Objects.requireNonNull(propertyPackageName); ArrayMap<String, ArraySet<String>> packageToFunctionIds = new ArrayMap<>(); - FutureSearchResults futureSearchResults = + try (FutureSearchResults futureSearchResults = searchSession .search( "", buildMetadataSearchSpec( schemaType, propertyFunctionId, propertyPackageName)) - .get(); - List<SearchResult> searchResultsList = futureSearchResults.getNextPage().get(); - // TODO(b/357551503): This could be expensive if we have more functions - while (!searchResultsList.isEmpty()) { - for (SearchResult searchResult : searchResultsList) { - String packageName = - searchResult.getGenericDocument().getPropertyString(propertyPackageName); - String functionId = - searchResult.getGenericDocument().getPropertyString(propertyFunctionId); - packageToFunctionIds - .computeIfAbsent(packageName, k -> new ArraySet<>()) - .add(functionId); + .get(); ) { + List<SearchResult> searchResultsList = futureSearchResults.getNextPage().get(); + // TODO(b/357551503): This could be expensive if we have more functions + while (!searchResultsList.isEmpty()) { + for (SearchResult searchResult : searchResultsList) { + String packageName = + searchResult + .getGenericDocument() + .getPropertyString(propertyPackageName); + String functionId = + searchResult.getGenericDocument().getPropertyString(propertyFunctionId); + packageToFunctionIds + .computeIfAbsent(packageName, k -> new ArraySet<>()) + .add(functionId); + } + searchResultsList = futureSearchResults.getNextPage().get(); } - searchResultsList = futureSearchResults.getNextPage().get(); } return packageToFunctionIds; } diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java index 3bcca1c22c89..2968ff3d0df6 100644 --- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java +++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java @@ -16,10 +16,13 @@ package com.android.server.companion.virtual; +import static android.Manifest.permission.ADD_ALWAYS_UNLOCKED_DISPLAY; +import static android.Manifest.permission.ADD_TRUSTED_DISPLAY; import static android.app.admin.DevicePolicyManager.NEARBY_STREAMING_ENABLED; import static android.app.admin.DevicePolicyManager.NEARBY_STREAMING_NOT_CONTROLLED_BY_POLICY; import static android.app.admin.DevicePolicyManager.NEARBY_STREAMING_SAME_MANAGED_ACCOUNT_ONLY; import static android.companion.virtual.VirtualDeviceParams.ACTIVITY_POLICY_DEFAULT_ALLOWED; +import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_CUSTOM; import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT; import static android.companion.virtual.VirtualDeviceParams.NAVIGATION_POLICY_DEFAULT_ALLOWED; import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_ACTIVITY; @@ -425,6 +428,27 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub mDisplayManager = displayManager; mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class); mPowerManager = context.getSystemService(PowerManager.class); + + if (mDevicePolicies.get(POLICY_TYPE_CLIPBOARD, DEVICE_POLICY_DEFAULT) + != DEVICE_POLICY_DEFAULT) { + if (mContext.checkCallingOrSelfPermission(ADD_TRUSTED_DISPLAY) + != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException("Requires ADD_TRUSTED_DISPLAY permission to " + + "set a custom clipboard policy."); + } + } + + int flags = DEFAULT_VIRTUAL_DISPLAY_FLAGS; + if (mParams.getLockState() == VirtualDeviceParams.LOCK_STATE_ALWAYS_UNLOCKED) { + if (mContext.checkCallingOrSelfPermission(ADD_ALWAYS_UNLOCKED_DISPLAY) + != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException("Requires ADD_ALWAYS_UNLOCKED_DISPLAY permission to " + + "create an always unlocked virtual device."); + } + flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED; + } + mBaseVirtualDisplayFlags = flags; + if (inputController == null) { mInputController = new InputController( context.getMainThreadHandler(), @@ -467,12 +491,6 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub : mParams.getAllowedActivities(); } - int flags = DEFAULT_VIRTUAL_DISPLAY_FLAGS; - if (mParams.getLockState() == VirtualDeviceParams.LOCK_STATE_ALWAYS_UNLOCKED) { - flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED; - } - mBaseVirtualDisplayFlags = flags; - if (Flags.vdmCustomIme() && mParams.getInputMethodComponent() != null) { final String imeId = mParams.getInputMethodComponent().flattenToShortString(); Slog.d(TAG, "Setting custom input method " + imeId + " as default for virtual device " @@ -884,8 +902,12 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub synchronized (mVirtualDeviceLock) { mDevicePolicies.put(policyType, devicePolicy); for (int i = 0; i < mVirtualDisplays.size(); i++) { - mVirtualDisplays.valueAt(i).getWindowPolicyController() - .setShowInHostDeviceRecents(devicePolicy == DEVICE_POLICY_DEFAULT); + VirtualDisplayWrapper wrapper = mVirtualDisplays.valueAt(i); + if (wrapper.isTrusted()) { + wrapper.getWindowPolicyController() + .setShowInHostDeviceRecents( + devicePolicy == DEVICE_POLICY_DEFAULT); + } } } break; @@ -905,7 +927,20 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub break; case POLICY_TYPE_CLIPBOARD: if (Flags.crossDeviceClipboard()) { + if (policyType == DEVICE_POLICY_CUSTOM + && mContext.checkCallingOrSelfPermission(ADD_TRUSTED_DISPLAY) + != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException("Requires ADD_TRUSTED_DISPLAY permission to " + + "set a custom clipboard policy."); + } synchronized (mVirtualDeviceLock) { + for (int i = 0; i < mVirtualDisplays.size(); i++) { + VirtualDisplayWrapper wrapper = mVirtualDisplays.valueAt(i); + if (!wrapper.isTrusted() && !wrapper.isMirror()) { + throw new SecurityException("All displays must be trusted for " + + "devices with custom clipboard policy."); + } + } mDevicePolicies.put(policyType, devicePolicy); } } @@ -936,8 +971,11 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub checkDisplayOwnedByVirtualDeviceLocked(displayId); switch (policyType) { case POLICY_TYPE_RECENTS: - mVirtualDisplays.get(displayId).getWindowPolicyController() - .setShowInHostDeviceRecents(devicePolicy == DEVICE_POLICY_DEFAULT); + VirtualDisplayWrapper wrapper = mVirtualDisplays.get(displayId); + if (wrapper.isTrusted()) { + wrapper.getWindowPolicyController() + .setShowInHostDeviceRecents(devicePolicy == DEVICE_POLICY_DEFAULT); + } break; case POLICY_TYPE_ACTIVITY: mVirtualDisplays.get(displayId).getWindowPolicyController() @@ -1247,10 +1285,13 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub try { synchronized (mVirtualDeviceLock) { mDefaultShowPointerIcon = showPointerIcon; - } - final int[] displayIds = getDisplayIds(); - for (int i = 0; i < displayIds.length; ++i) { - mInputController.setShowPointerIcon(showPointerIcon, displayIds[i]); + for (int i = 0; i < mVirtualDisplays.size(); i++) { + VirtualDisplayWrapper wrapper = mVirtualDisplays.valueAt(i); + if (wrapper.isTrusted() || wrapper.isMirror()) { + mInputController.setShowPointerIcon( + mDefaultShowPointerIcon, mVirtualDisplays.keyAt(i)); + } + } } } finally { Binder.restoreCallingIdentity(ident); @@ -1491,6 +1532,12 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub boolean isTrustedDisplay = (mDisplayManagerInternal.getDisplayInfo(displayId).flags & Display.FLAG_TRUSTED) == Display.FLAG_TRUSTED; + if (!isTrustedDisplay) { + if (getDevicePolicy(POLICY_TYPE_CLIPBOARD) != DEVICE_POLICY_DEFAULT) { + throw new SecurityException("All displays must be trusted for devices with custom" + + "clipboard policy."); + } + } boolean showPointer; synchronized (mVirtualDeviceLock) { @@ -1500,7 +1547,8 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub "Virtual device already has a virtual display with ID " + displayId); } - PowerManager.WakeLock wakeLock = createAndAcquireWakeLockForDisplay(displayId); + PowerManager.WakeLock wakeLock = + isTrustedDisplay ? createAndAcquireWakeLockForDisplay(displayId) : null; mVirtualDisplays.put(displayId, new VirtualDisplayWrapper(callback, gwpc, wakeLock, isTrustedDisplay, isMirrorDisplay)); showPointer = mDefaultShowPointerIcon; @@ -1508,14 +1556,15 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub final long token = Binder.clearCallingIdentity(); try { - mInputController.setShowPointerIcon(showPointer, displayId); mInputController.setMousePointerAccelerationEnabled(false, displayId); mInputController.setDisplayEligibilityForPointerCapture(/* isEligible= */ false, displayId); - // WM throws a SecurityException if the display is untrusted. if (isTrustedDisplay) { + mInputController.setShowPointerIcon(showPointer, displayId); mInputController.setDisplayImePolicy(displayId, WindowManager.DISPLAY_IME_POLICY_LOCAL); + } else { + gwpc.setShowInHostDeviceRecents(true); } } finally { Binder.restoreCallingIdentity(token); @@ -1616,6 +1665,11 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub != PackageManager.PERMISSION_GRANTED) { synchronized (mVirtualDeviceLock) { checkDisplayOwnedByVirtualDeviceLocked(displayId); + VirtualDisplayWrapper wrapper = mVirtualDisplays.get(displayId); + if (!wrapper.isTrusted() && !wrapper.isMirror()) { + throw new SecurityException( + "Cannot create input device associated with an untrusted display"); + } } } } @@ -1665,7 +1719,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub * @param virtualDisplayWrapper - VirtualDisplayWrapper to release resources for. */ private void releaseOwnedVirtualDisplayResources(VirtualDisplayWrapper virtualDisplayWrapper) { - virtualDisplayWrapper.getWakeLock().release(); + virtualDisplayWrapper.releaseWakeLock(); virtualDisplayWrapper.getWindowPolicyController().unregisterRunningAppsChangedListener( this); } @@ -1833,10 +1887,10 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub VirtualDisplayWrapper(@NonNull IVirtualDisplayCallback token, @NonNull GenericWindowPolicyController windowPolicyController, - @NonNull PowerManager.WakeLock wakeLock, boolean isTrusted, boolean isMirror) { + @Nullable PowerManager.WakeLock wakeLock, boolean isTrusted, boolean isMirror) { mToken = Objects.requireNonNull(token); mWindowPolicyController = Objects.requireNonNull(windowPolicyController); - mWakeLock = Objects.requireNonNull(wakeLock); + mWakeLock = wakeLock; mIsTrusted = isTrusted; mIsMirror = isMirror; } @@ -1845,8 +1899,10 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub return mWindowPolicyController; } - PowerManager.WakeLock getWakeLock() { - return mWakeLock; + void releaseWakeLock() { + if (mWakeLock != null && mWakeLock.isHeld()) { + mWakeLock.release(); + } } boolean isTrusted() { diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java index 2485626a8f42..5236b0399f25 100644 --- a/services/core/java/com/android/server/am/ProcessList.java +++ b/services/core/java/com/android/server/am/ProcessList.java @@ -3629,42 +3629,68 @@ public final class ProcessList { } @GuardedBy({"mService", "mProcLock"}) - private int updateLruProcessInternalLSP(ProcessRecord app, long now, int index, - int lruSeq, String what, Object obj, ProcessRecord srcApp) { + private int offerLruProcessInternalLSP(ProcessRecord app, long now, String what, Object obj, + ProcessRecord srcApp) { app.setLastActivityTime(now); if (app.hasActivitiesOrRecentTasks()) { // Don't want to touch dependent processes that are hosting activities. - return index; + return -1; } - int lrui = mLruProcesses.lastIndexOf(app); + final int lrui = mLruProcesses.lastIndexOf(app); if (lrui < 0) { Slog.wtf(TAG, "Adding dependent process " + app + " not on LRU list: " + what + " " + obj + " from " + srcApp); - return index; } + return lrui; + } - if (lrui >= index) { - // Don't want to cause this to move dependent processes *back* in the - // list as if they were less frequently used. - return index; - } + /** + * This method is called after the indices array is populated by the indices offered by + * {@link #offerLruProcessInternalLSP} to actually move the processes to the desired locations + * in the LRU list. Since the indices array is a SparseBooleanArray, the indices are sorted + * and this allows us to preserve the previous order of the processes relative to each other. + * Key of the indices array holds the current index of the process in the LRU list and the value + * is a boolean indicating whether the process is an activity process or not. Activity processes + * are moved to the nextActivityIndex and non-activity processes are moved to the nextIndex + * positions, which are provided by the caller. + * + * @param indices The indices of the processes to move. + * @param nextActivityIndex The next index to insert an activity process. + * @param nextIndex The next index to insert a non-activity process. + */ + @GuardedBy({"mService", "mProcLock"}) + private void completeLruProcessInternalLSP(SparseBooleanArray indices, int nextActivityIndex, + int nextIndex) { + for (int i = indices.size() - 1; i >= 0; i--) { + final int lrui = indices.keyAt(i); + if (lrui < 0) { + // Rest of the indices are invalid, we can return early. + return; + } + final boolean isActivity = indices.valueAt(i); + int index = isActivity ? nextActivityIndex : nextIndex; - if (lrui >= mLruProcessActivityStart && index < mLruProcessActivityStart) { - // Don't want to touch dependent processes that are hosting activities. - return index; - } + if (lrui >= index) { + // Don't want to cause this to move dependent processes *back* in the + // list as if they were less frequently used. + continue; + } - mLruProcesses.remove(lrui); - if (index > 0) { + final ProcessRecord app = mLruProcesses.remove(lrui); index--; + if (DEBUG_LRU) Slog.d(TAG_LRU, "Moving dep from " + lrui + " to " + index + + " in LRU list: " + app); + mLruProcesses.add(index, app); + app.setLruSeq(mLruSeq); + + if (isActivity) { + nextActivityIndex = index; + } else { + nextIndex = index; + } } - if (DEBUG_LRU) Slog.d(TAG_LRU, "Moving dep from " + lrui + " to " + index - + " in LRU list: " + app); - mLruProcesses.add(index, app); - app.setLruSeq(lruSeq); - return index; } /** @@ -4058,6 +4084,15 @@ public final class ProcessList { app.setLruSeq(mLruSeq); + // Key of the indices array holds the current index of the process in the LRU list and the + // value is a boolean indicating whether the process is an activity process or not. + // Activity processes will be moved to the nextActivityIndex and non-activity processes will + // be moved to the nextIndex positions when completeLruProcessInternalLSP is called. + // Since SparseBooleanArray's keys are sorted, we'll be able to keep the existing order of + // the processes relative to each other after the move. + final SparseBooleanArray indices = new SparseBooleanArray(psr.numberOfConnections() + + app.mProviders.numberOfProviderConnections()); + // If the app is currently using a content provider or service, // bump those processes as well. for (int j = psr.numberOfConnections() - 1; j >= 0; j--) { @@ -4069,16 +4104,12 @@ public final class ProcessList { && !cr.binding.service.app.isPersistent()) { if (cr.binding.service.app.mServices.hasClientActivities()) { if (nextActivityIndex >= 0) { - nextActivityIndex = updateLruProcessInternalLSP(cr.binding.service.app, - now, - nextActivityIndex, mLruSeq, - "service connection", cr, app); + indices.append(offerLruProcessInternalLSP(cr.binding.service.app, now, + "service connection", cr, app), true); } } else { - nextIndex = updateLruProcessInternalLSP(cr.binding.service.app, - now, - nextIndex, mLruSeq, - "service connection", cr, app); + indices.append(offerLruProcessInternalLSP(cr.binding.service.app, now, + "service connection", cr, app), false); } } } @@ -4086,10 +4117,11 @@ public final class ProcessList { for (int j = ppr.numberOfProviderConnections() - 1; j >= 0; j--) { ContentProviderRecord cpr = ppr.getProviderConnectionAt(j).provider; if (cpr.proc != null && cpr.proc.getLruSeq() != mLruSeq && !cpr.proc.isPersistent()) { - nextIndex = updateLruProcessInternalLSP(cpr.proc, now, nextIndex, mLruSeq, - "provider reference", cpr, app); + indices.append(offerLruProcessInternalLSP(cpr.proc, now, + "provider reference", cpr, app), false); } } + completeLruProcessInternalLSP(indices, nextActivityIndex, nextIndex); } @GuardedBy(anyOf = {"mService", "mProcLock"}) diff --git a/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java b/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java index 03c81560be89..ed41f2e881f8 100644 --- a/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java +++ b/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java @@ -36,6 +36,7 @@ import static android.app.AppOpsManager.UID_STATE_FOREGROUND_SERVICE; import static android.app.AppOpsManager.UID_STATE_MAX_LAST_NON_RESTRICTED; import static android.app.AppOpsManager.UID_STATE_NONEXISTENT; import static android.app.AppOpsManager.UID_STATE_TOP; +import static android.permission.flags.Flags.delayUidStateChangesFromCapabilityUpdates; import static android.permission.flags.Flags.finishRunningOpsForKilledPackages; import static com.android.server.appop.AppOpsUidStateTracker.processStateToUidState; @@ -236,20 +237,26 @@ class AppOpsUidStateTrackerImpl implements AppOpsUidStateTracker { mPendingUidStates.put(uid, uidState); mPendingCapability.put(uid, capability); + boolean hasLostCapability = (prevCapability & ~capability) != 0; + if (procState == PROCESS_STATE_NONEXISTENT) { mPendingGone.put(uid, true); commitUidPendingState(uid); - } else if (uidState < prevUidState - || (uidState <= UID_STATE_MAX_LAST_NON_RESTRICTED - && prevUidState > UID_STATE_MAX_LAST_NON_RESTRICTED)) { + } else if (uidState < prevUidState) { // We are moving to a more important state, or the new state may be in the // foreground and the old state is in the background, then always do it // immediately. commitUidPendingState(uid); - } else if (uidState == prevUidState && capability != prevCapability) { + } else if (delayUidStateChangesFromCapabilityUpdates() + && uidState == prevUidState && !hasLostCapability) { + // No change on process state, but process capability hasn't decreased. + commitUidPendingState(uid); + } else if (!delayUidStateChangesFromCapabilityUpdates() + && uidState == prevUidState && capability != prevCapability) { // No change on process state, but process capability has changed. commitUidPendingState(uid); - } else if (uidState <= UID_STATE_MAX_LAST_NON_RESTRICTED) { + } else if (uidState <= UID_STATE_MAX_LAST_NON_RESTRICTED + && (!delayUidStateChangesFromCapabilityUpdates() || !hasLostCapability)) { // We are moving to a less important state, but it doesn't cross the restriction // threshold. commitUidPendingState(uid); diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index d206b20f5b25..fdf7dec31cad 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -286,7 +286,6 @@ import java.util.Objects; import java.util.Set; import java.util.TreeSet; import java.util.concurrent.CancellationException; -import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; import java.util.concurrent.Executors; @@ -8049,7 +8048,14 @@ public class AudioService extends IAudioService.Stub } synchronized (mAbsoluteVolumeDeviceInfoMapLock) { if (mAbsoluteVolumeDeviceInfoMap.containsKey(audioSystemDeviceOut)) { - return mAbsoluteVolumeDeviceInfoMap.get(audioSystemDeviceOut).mDeviceVolumeBehavior; + final AbsoluteVolumeDeviceInfo deviceInfo = mAbsoluteVolumeDeviceInfoMap.get( + audioSystemDeviceOut); + if (deviceInfo != null) { + return deviceInfo.mDeviceVolumeBehavior; + } + + Log.e(TAG, + "Null absolute volume device info stored for key " + audioSystemDeviceOut); } } @@ -15043,6 +15049,11 @@ public class AudioService extends IAudioService.Stub private void addAudioSystemDeviceOutToAbsVolumeDevices(int audioSystemDeviceOut, AbsoluteVolumeDeviceInfo info) { + if (info == null) { + Log.e(TAG, "Cannot add null absolute volume info for audioSystemDeviceOut " + + audioSystemDeviceOut); + return; + } if (DEBUG_VOL) { Log.d(TAG, "Adding DeviceType: 0x" + Integer.toHexString(audioSystemDeviceOut) + " to mAbsoluteVolumeDeviceInfoMap with behavior " diff --git a/services/core/java/com/android/server/compat/PlatformCompat.java b/services/core/java/com/android/server/compat/PlatformCompat.java index a9fe8cb01b3a..8d64383b32b9 100644 --- a/services/core/java/com/android/server/compat/PlatformCompat.java +++ b/services/core/java/com/android/server/compat/PlatformCompat.java @@ -242,7 +242,8 @@ public class PlatformCompat extends IPlatformCompat.Stub { boolean enabled = true; final int userId = UserHandle.getUserId(uid); for (String packageName : packages) { - final var appInfo = getApplicationInfo(packageName, userId); + final var appInfo = + fixTargetSdk(getApplicationInfo(packageName, userId), uid); enabled &= isChangeEnabledInternal(changeId, appInfo); } return enabled; @@ -261,7 +262,8 @@ public class PlatformCompat extends IPlatformCompat.Stub { boolean enabled = true; final int userId = UserHandle.getUserId(uid); for (String packageName : packages) { - final var appInfo = getApplicationInfo(packageName, userId); + final var appInfo = + fixTargetSdk(getApplicationInfo(packageName, userId), uid); enabled &= isChangeEnabledInternalNoLogging(changeId, appInfo); } return enabled; @@ -504,6 +506,15 @@ public class PlatformCompat extends IPlatformCompat.Stub { packageName, 0, Process.myUid(), userId); } + private ApplicationInfo fixTargetSdk(ApplicationInfo appInfo, int uid) { + // b/282922910 - we don't want apps sharing system uid and targeting + // older target sdk to impact all system uid apps + if (Flags.systemUidTargetSystemSdk() && uid == Process.SYSTEM_UID) { + appInfo.targetSdkVersion = Build.VERSION.SDK_INT; + } + return appInfo; + } + private void killPackage(String packageName) { int uid = LocalServices.getService(PackageManagerInternal.class).getPackageUid(packageName, 0, UserHandle.myUserId()); diff --git a/services/core/java/com/android/server/compat/platform_compat_flags.aconfig b/services/core/java/com/android/server/compat/platform_compat_flags.aconfig new file mode 100644 index 000000000000..fb323238c38e --- /dev/null +++ b/services/core/java/com/android/server/compat/platform_compat_flags.aconfig @@ -0,0 +1,10 @@ +package: "com.android.server.compat" +container: "system" + +flag { + name: "system_uid_target_system_sdk" + namespace: "app_compat" + description: "Compat framework feature flag for forcing all system uid apps to target system sdk" + bug: "29702703" + is_fixed_read_only: true +} diff --git a/services/core/java/com/android/server/crashrecovery/CrashRecoveryUtils.java b/services/core/java/com/android/server/crashrecovery/CrashRecoveryUtils.java index 3eb3380a57a0..2e2a93776f9d 100644 --- a/services/core/java/com/android/server/crashrecovery/CrashRecoveryUtils.java +++ b/services/core/java/com/android/server/crashrecovery/CrashRecoveryUtils.java @@ -18,7 +18,7 @@ package com.android.server.crashrecovery; import android.os.Environment; import android.util.IndentingPrintWriter; -import android.util.Slog; +import android.util.Log; import java.io.BufferedReader; import java.io.File; @@ -41,7 +41,7 @@ public class CrashRecoveryUtils { /** Persist recovery related events in crashrecovery events file.**/ public static void logCrashRecoveryEvent(int priority, String msg) { - Slog.println(priority, TAG, msg); + Log.println(priority, TAG, msg); try { File fname = getCrashRecoveryEventsFile(); synchronized (sFileLock) { @@ -52,7 +52,7 @@ public class CrashRecoveryUtils { pw.close(); } } catch (IOException e) { - Slog.e(TAG, "Unable to log CrashRecoveryEvents " + e.getMessage()); + Log.e(TAG, "Unable to log CrashRecoveryEvents " + e.getMessage()); } } @@ -72,7 +72,7 @@ public class CrashRecoveryUtils { pw.println(line); } } catch (IOException e) { - Slog.e(TAG, "Unable to dump CrashRecoveryEvents " + e.getMessage()); + Log.e(TAG, "Unable to dump CrashRecoveryEvents " + e.getMessage()); } } pw.decreaseIndent(); diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java index 8d96ba93530b..c4e10360a7cb 100644 --- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java +++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java @@ -150,7 +150,7 @@ import javax.xml.datatype.DatatypeConfigurationException; * <screenBrightnessDefault>0.65</screenBrightnessDefault> * <powerThrottlingConfig> * <brightnessLowestCapAllowed>0.1</brightnessLowestCapAllowed> - * <customAnimationRateSec>0.004</customAnimationRateSec> + * <customAnimationRate>0.004</customAnimationRate> * <pollingWindowMaxMillis>30000</pollingWindowMaxMillis> * <pollingWindowMinMillis>10000</pollingWindowMinMillis> * <powerThrottlingMap> @@ -2193,11 +2193,11 @@ public class DisplayDeviceConfig { return; } float lowestBrightnessCap = powerThrottlingCfg.getBrightnessLowestCapAllowed().floatValue(); - float customAnimationRateSec = powerThrottlingCfg.getCustomAnimationRateSec().floatValue(); + float customAnimationRate = powerThrottlingCfg.getCustomAnimationRate().floatValue(); int pollingWindowMaxMillis = powerThrottlingCfg.getPollingWindowMaxMillis().intValue(); int pollingWindowMinMillis = powerThrottlingCfg.getPollingWindowMinMillis().intValue(); mPowerThrottlingConfigData = new PowerThrottlingConfigData(lowestBrightnessCap, - customAnimationRateSec, + customAnimationRate, pollingWindowMaxMillis, pollingWindowMinMillis); } @@ -3012,16 +3012,16 @@ public class DisplayDeviceConfig { /** Lowest brightness cap allowed for this device. */ public final float brightnessLowestCapAllowed; /** Time take to animate brightness in seconds. */ - public final float customAnimationRateSec; + public final float customAnimationRate; /** Time window for maximum polling power in milliseconds. */ public final int pollingWindowMaxMillis; /** Time window for minimum polling power in milliseconds. */ public final int pollingWindowMinMillis; public PowerThrottlingConfigData(float brightnessLowestCapAllowed, - float customAnimationRateSec, int pollingWindowMaxMillis, + float customAnimationRate, int pollingWindowMaxMillis, int pollingWindowMinMillis) { this.brightnessLowestCapAllowed = brightnessLowestCapAllowed; - this.customAnimationRateSec = customAnimationRateSec; + this.customAnimationRate = customAnimationRate; this.pollingWindowMaxMillis = pollingWindowMaxMillis; this.pollingWindowMinMillis = pollingWindowMinMillis; } @@ -3031,7 +3031,7 @@ public class DisplayDeviceConfig { return "PowerThrottlingConfigData{" + "brightnessLowestCapAllowed: " + brightnessLowestCapAllowed - + ", customAnimationRateSec: " + customAnimationRateSec + + ", customAnimationRate: " + customAnimationRate + ", pollingWindowMaxMillis: " + pollingWindowMaxMillis + ", pollingWindowMinMillis: " + pollingWindowMinMillis + "} "; diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index 179ec63458dd..c7a70fafbf26 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -1769,10 +1769,11 @@ public final class DisplayManagerService extends SystemService { flags &= ~VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP; } // Put the display in the virtual device's display group only if it's not a mirror display, - // and if it doesn't need its own display group. So effectively, mirror displays go into the - // default display group. + // it is a trusted display, and it doesn't need its own display group. So effectively, + // mirror and untrusted displays go into the default display group. if ((flags & VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP) == 0 && (flags & VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR) == 0 + && (flags & VIRTUAL_DISPLAY_FLAG_TRUSTED) == VIRTUAL_DISPLAY_FLAG_TRUSTED && virtualDevice != null) { flags |= VIRTUAL_DISPLAY_FLAG_DEVICE_DISPLAY_GROUP; } @@ -1848,9 +1849,7 @@ public final class DisplayManagerService extends SystemService { if (callingUid != Process.SYSTEM_UID && (flags & VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP) != 0) { - // The virtualDevice instance has been validated above using isValidVirtualDevice - if (virtualDevice == null - && !checkCallingPermission(ADD_TRUSTED_DISPLAY, "createVirtualDisplay()")) { + if (!checkCallingPermission(ADD_TRUSTED_DISPLAY, "createVirtualDisplay()")) { throw new SecurityException("Requires ADD_TRUSTED_DISPLAY permission to " + "create a virtual display which is not in the default DisplayGroup."); } @@ -5667,6 +5666,11 @@ public final class DisplayManagerService extends SystemService { displayPowerController.stylusGestureStarted(eventTime); } } + + @Override + public boolean isDisplayReadyForMirroring(int displayId) { + return mExternalDisplayPolicy.isDisplayReadyForMirroring(displayId); + } } class DesiredDisplayModeSpecsObserver diff --git a/services/core/java/com/android/server/display/ExternalDisplayPolicy.java b/services/core/java/com/android/server/display/ExternalDisplayPolicy.java index 28a0b28a0167..f34d2cc6e684 100644 --- a/services/core/java/com/android/server/display/ExternalDisplayPolicy.java +++ b/services/core/java/com/android/server/display/ExternalDisplayPolicy.java @@ -375,6 +375,54 @@ class ExternalDisplayPolicy { } } + boolean isDisplayReadyForMirroring(int displayId) { + if (!mFlags.isWaitingConfirmationBeforeMirroringEnabled()) { + if (DEBUG) { + Slog.d(TAG, "isDisplayReadyForMirroring: mirroring CONFIRMED - " + + " flag 'waiting for confirmation before mirroring' is disabled"); + } + return true; + } + + synchronized (mSyncRoot) { + if (!mIsBootCompleted) { + if (DEBUG) { + Slog.d(TAG, "isDisplayReadyForMirroring: mirroring is not confirmed - " + + "boot is in progress"); + } + return false; + } + + var logicalDisplay = mLogicalDisplayMapper.getDisplayLocked(displayId); + if (logicalDisplay == null) { + if (DEBUG) { + Slog.d(TAG, "isDisplayReadyForMirroring: mirroring is not confirmed - " + + "logicalDisplay is null"); + } + return false; + } + + if (!isExternalDisplayLocked(logicalDisplay)) { + if (DEBUG) { + Slog.d(TAG, "isDisplayReadyForMirroring: mirroring is not confirmed - " + + "logicalDisplay" + logicalDisplay.getDisplayIdLocked() + + " type is " + logicalDisplay.getDisplayInfoLocked().type); + } + return false; + } + + if (!logicalDisplay.isEnabledLocked()) { + if (DEBUG) { + Slog.d(TAG, "isDisplayReadyForMirroring: mirroring is not confirmed - " + + "logicalDisplay is disabled"); + } + return false; + } + } + + return true; + } + private final class SkinThermalStatusObserver extends IThermalEventListener.Stub { @Override public void notifyThrottling(@NonNull final Temperature temp) { diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java index 06a910396d6c..09fa4e6aa628 100644 --- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java +++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java @@ -16,6 +16,7 @@ package com.android.server.display; +import static android.hardware.devicestate.DeviceState.PROPERTY_EMULATED_ONLY; import static android.hardware.devicestate.DeviceState.PROPERTY_POWER_CONFIGURATION_TRIGGER_SLEEP; import static android.hardware.devicestate.DeviceState.PROPERTY_POWER_CONFIGURATION_TRIGGER_WAKE; import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE; @@ -594,6 +595,13 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { boolean shouldDeviceBeWoken(DeviceState pendingState, DeviceState currentState, boolean isInteractive, boolean isBootCompleted) { if (mDeviceStateManagerFlags.deviceStatePropertyMigration()) { + if (currentState.hasProperties(PROPERTY_EMULATED_ONLY) + && !pendingState.hasProperties(PROPERTY_EMULATED_ONLY)) { + // Do not wake the device, since this transition may occur due to the user pressing + // the power button to exit an emulated state. + return false; + } + return pendingState.hasProperty(PROPERTY_POWER_CONFIGURATION_TRIGGER_WAKE) && !currentState.equals(INVALID_DEVICE_STATE) && !currentState.hasProperty(PROPERTY_POWER_CONFIGURATION_TRIGGER_WAKE) diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessPowerClamper.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessPowerClamper.java index 85e81f989845..1a18b004a3e2 100644 --- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessPowerClamper.java +++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessPowerClamper.java @@ -85,7 +85,7 @@ class BrightnessPowerClamper extends private String mDataId = null; private float mCurrentBrightness = PowerManager.BRIGHTNESS_INVALID; private float mCustomAnimationRateSec = DisplayBrightnessState.CUSTOM_ANIMATION_RATE_NOT_SET; - private float mCustomAnimationRateSecDeviceConfig = + private float mCustomAnimationRateDeviceConfig = DisplayBrightnessState.CUSTOM_ANIMATION_RATE_NOT_SET; private final BiFunction<String, String, ThrottlingLevel> mDataPointMapper = (key, value) -> { try { @@ -117,7 +117,7 @@ class BrightnessPowerClamper extends }; mPowerThrottlingConfigData = powerData.getPowerThrottlingConfigData(); if (mPowerThrottlingConfigData != null) { - mCustomAnimationRateSecDeviceConfig = mPowerThrottlingConfigData.customAnimationRateSec; + mCustomAnimationRateDeviceConfig = mPowerThrottlingConfigData.customAnimationRate; } mThermalLevelListener = new ThermalLevelListener(handler); mPmicMonitor = @@ -228,10 +228,6 @@ class BrightnessPowerClamper extends } mPowerThrottlingConfigData = data.getPowerThrottlingConfigData(); - if (mPowerThrottlingConfigData == null) { - Slog.d(TAG, - "Power throttling data is missing for configuration data."); - } } private void recalculateBrightnessCap() { @@ -282,13 +278,13 @@ class BrightnessPowerClamper extends mIsActive = isActive; Slog.i(TAG, "Power clamper changing current brightness cap mBrightnessCap: " + mBrightnessCap + " to target brightness cap:" + targetBrightnessCap - + " for current screen brightness: " + mCurrentBrightness); - mBrightnessCap = targetBrightnessCap; - Slog.i(TAG, "Power clamper changed state: thermalStatus:" + mCurrentThermalLevel + + " for current screen brightness: " + mCurrentBrightness + "\n" + + "Power clamper changed state: thermalStatus:" + mCurrentThermalLevel + " mCurrentThermalLevelChanged:" + mCurrentThermalLevelChanged + " mCurrentAvgPowerConsumed:" + mCurrentAvgPowerConsumed - + " mCustomAnimationRateSec:" + mCustomAnimationRateSecDeviceConfig); - mCustomAnimationRateSec = mCustomAnimationRateSecDeviceConfig; + + " mCustomAnimationRateSec:" + mCustomAnimationRateDeviceConfig); + mBrightnessCap = targetBrightnessCap; + mCustomAnimationRateSec = mCustomAnimationRateDeviceConfig; mChangeListener.onChanged(); } else { mCustomAnimationRateSec = DisplayBrightnessState.CUSTOM_ANIMATION_RATE_NOT_SET; @@ -344,7 +340,7 @@ class BrightnessPowerClamper extends + mPowerThrottlingConfigData.pollingWindowMinMillis + " msec."); return; } - mCustomAnimationRateSecDeviceConfig = mPowerThrottlingConfigData.customAnimationRateSec; + mCustomAnimationRateDeviceConfig = mPowerThrottlingConfigData.customAnimationRate; mThermalLevelListener.start(); } diff --git a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java index 99ced7f8bb00..b2e98bc05e75 100644 --- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java +++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java @@ -217,6 +217,11 @@ public class DisplayManagerFlags { Flags::enableUserRefreshRateForExternalDisplay ); + private final FlagState mEnableWaitingConfirmationBeforeMirroring = new FlagState( + Flags.FLAG_ENABLE_WAITING_CONFIRMATION_BEFORE_MIRRORING, + Flags::enableWaitingConfirmationBeforeMirroring + ); + private final FlagState mEnableBatteryStatsForAllDisplays = new FlagState( Flags.FLAG_ENABLE_BATTERY_STATS_FOR_ALL_DISPLAYS, Flags::enableBatteryStatsForAllDisplays @@ -445,6 +450,14 @@ public class DisplayManagerFlags { } /** + * @return {@code true} if mirroring won't be enabled until boot completes and the user enables + * the display. + */ + public boolean isWaitingConfirmationBeforeMirroringEnabled() { + return mEnableWaitingConfirmationBeforeMirroring.isEnabled(); + } + + /** * @return {@code true} if battery stats is enabled for all displays, not just the primary * display. */ @@ -511,6 +524,7 @@ public class DisplayManagerFlags { pw.println(" " + mVirtualDisplayLimit); pw.println(" " + mNormalBrightnessForDozeParameter); pw.println(" " + mIdleScreenConfigInSubscribingLightSensor); + pw.println(" " + mEnableWaitingConfirmationBeforeMirroring); pw.println(" " + mEnableBatteryStatsForAllDisplays); pw.println(" " + mBlockAutobrightnessChangesOnStylusUsage); pw.println(" " + mIsUserRefreshRateForExternalDisplayEnabled); diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig index 2f04d9e5fdbb..df626385c5cc 100644 --- a/services/core/java/com/android/server/display/feature/display_flags.aconfig +++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig @@ -367,6 +367,17 @@ flag { } flag { + name: "enable_waiting_confirmation_before_mirroring" + namespace: "display_manager" + description: "Allow ContentRecorder checking whether user confirmed mirroring after boot" + bug: "361698995" + is_fixed_read_only: true + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "enable_battery_stats_for_all_displays" namespace: "display_manager" description: "Flag to enable battery stats for all displays." diff --git a/services/core/java/com/android/server/input/InputSettingsObserver.java b/services/core/java/com/android/server/input/InputSettingsObserver.java index d70bd8b17ddf..d1a6d3b9bb00 100644 --- a/services/core/java/com/android/server/input/InputSettingsObserver.java +++ b/services/core/java/com/android/server/input/InputSettingsObserver.java @@ -63,6 +63,12 @@ class InputSettingsObserver extends ContentObserver { mObservers = Map.ofEntries( Map.entry(Settings.System.getUriFor(Settings.System.POINTER_SPEED), (reason) -> updateMousePointerSpeed()), + Map.entry(Settings.System.getUriFor( + Settings.System.MOUSE_REVERSE_VERTICAL_SCROLLING), + (reason) -> updateMouseReverseVerticalScrolling()), + Map.entry(Settings.System.getUriFor( + Settings.System.MOUSE_SWAP_PRIMARY_BUTTON), + (reason) -> updateMouseSwapPrimaryButton()), Map.entry(Settings.System.getUriFor(Settings.System.TOUCHPAD_POINTER_SPEED), (reason) -> updateTouchpadPointerSpeed()), Map.entry(Settings.System.getUriFor(Settings.System.TOUCHPAD_NATURAL_SCROLLING), @@ -163,6 +169,16 @@ class InputSettingsObserver extends ContentObserver { mNative.setPointerSpeed(constrainPointerSpeedValue(speed)); } + private void updateMouseReverseVerticalScrolling() { + mNative.setMouseReverseVerticalScrollingEnabled( + InputSettings.isMouseReverseVerticalScrollingEnabled(mContext)); + } + + private void updateMouseSwapPrimaryButton() { + mNative.setMouseSwapPrimaryButtonEnabled( + InputSettings.isMouseSwapPrimaryButtonEnabled(mContext)); + } + private void updateTouchpadPointerSpeed() { mNative.setTouchpadPointerSpeed( constrainPointerSpeedValue(InputSettings.getTouchpadPointerSpeed(mContext))); diff --git a/services/core/java/com/android/server/input/NativeInputManagerService.java b/services/core/java/com/android/server/input/NativeInputManagerService.java index 4404d63e02fc..21e8bccd2883 100644 --- a/services/core/java/com/android/server/input/NativeInputManagerService.java +++ b/services/core/java/com/android/server/input/NativeInputManagerService.java @@ -127,6 +127,10 @@ interface NativeInputManagerService { void setMousePointerAccelerationEnabled(int displayId, boolean enabled); + void setMouseReverseVerticalScrollingEnabled(boolean enabled); + + void setMouseSwapPrimaryButtonEnabled(boolean enabled); + void setTouchpadPointerSpeed(int speed); void setTouchpadNaturalScrollingEnabled(boolean enabled); @@ -388,6 +392,12 @@ interface NativeInputManagerService { public native void setMousePointerAccelerationEnabled(int displayId, boolean enabled); @Override + public native void setMouseReverseVerticalScrollingEnabled(boolean enabled); + + @Override + public native void setMouseSwapPrimaryButtonEnabled(boolean enabled); + + @Override public native void setTouchpadPointerSpeed(int speed); @Override diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index f0fb33eaaee7..35b517118aab 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -84,6 +84,7 @@ import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.content.pm.UserInfo; import android.content.res.Resources; +import android.hardware.display.DisplayManagerInternal; import android.hardware.input.InputManager; import android.inputmethodservice.InputMethodService; import android.inputmethodservice.InputMethodService.BackDispositionMode; @@ -119,6 +120,7 @@ import android.util.Printer; import android.util.Slog; import android.util.SparseArray; import android.util.proto.ProtoOutputStream; +import android.view.Display; import android.view.InputChannel; import android.view.InputDevice; import android.view.MotionEvent; @@ -448,6 +450,8 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. private AudioManagerInternal mAudioManagerInternal = null; @Nullable private VirtualDeviceManagerInternal mVdmInternal = null; + @Nullable + private DisplayManagerInternal mDisplayManagerInternal = null; // Mapping from deviceId to the device-specific imeId for that device. @GuardedBy("ImfLock.class") @@ -2165,7 +2169,18 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. final var bindingController = getInputMethodBindingController(userId); final int oldDeviceId = bindingController.getDeviceIdToShowIme(); final int displayIdToShowIme = bindingController.getDisplayIdToShowIme(); - final int newDeviceId = mVdmInternal.getDeviceIdForDisplayId(displayIdToShowIme); + int newDeviceId = mVdmInternal.getDeviceIdForDisplayId(displayIdToShowIme); + if (newDeviceId != DEVICE_ID_DEFAULT) { + // Only show custom IME on trusted displays. + if (mDisplayManagerInternal == null) { + mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class); + } + int displayFlags = mDisplayManagerInternal.getDisplayInfo(displayIdToShowIme).flags; + if ((displayFlags & Display.FLAG_TRUSTED) != Display.FLAG_TRUSTED) { + // If the display is not trusted, fallback to the default device IME. + newDeviceId = DEVICE_ID_DEFAULT; + } + } bindingController.setDeviceIdToShowIme(newDeviceId); if (newDeviceId == DEVICE_ID_DEFAULT) { if (oldDeviceId == DEVICE_ID_DEFAULT) { diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverySnapshotStorage.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverySnapshotStorage.java index c02b103f1d33..404c8411f750 100644 --- a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverySnapshotStorage.java +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverySnapshotStorage.java @@ -19,7 +19,6 @@ package com.android.server.locksettings.recoverablekeystore.storage; import android.annotation.Nullable; import android.os.Environment; import android.security.keystore.recovery.KeyChainSnapshot; -import android.util.Log; import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; @@ -29,9 +28,11 @@ import com.android.server.locksettings.recoverablekeystore.serialization import com.android.server.locksettings.recoverablekeystore.serialization .KeyChainSnapshotParserException; import com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSerializer; +import com.android.server.utils.Slogf; import java.io.File; import java.io.FileInputStream; +import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.security.cert.CertificateEncodingException; @@ -81,12 +82,14 @@ public class RecoverySnapshotStorage { public synchronized void put(int uid, KeyChainSnapshot snapshot) { mSnapshotByUid.put(uid, snapshot); - try { - writeToDisk(uid, snapshot); + File snapshotFile = getSnapshotFile(uid); + try (FileOutputStream fileOutputStream = new FileOutputStream(snapshotFile)) { + KeyChainSnapshotSerializer.serialize(snapshot, fileOutputStream); } catch (IOException | CertificateEncodingException e) { - Log.e(TAG, - String.format(Locale.US, "Error persisting snapshot for %d to disk", uid), - e); + // If we fail to write the latest snapshot, we should delete any older snapshot that + // happens to be around. Otherwise snapshot syncs might end up going 'back in time'. + snapshotFile.delete(); + Slogf.e(TAG, e, "Error persisting snapshot for %d to disk", uid); } } @@ -100,10 +103,17 @@ public class RecoverySnapshotStorage { return snapshot; } - try { - return readFromDisk(uid); + File snapshotFile = getSnapshotFile(uid); + try (FileInputStream fileInputStream = new FileInputStream(snapshotFile)) { + return KeyChainSnapshotDeserializer.deserialize(fileInputStream); + } catch (FileNotFoundException e) { + Slogf.i(TAG, "Snapshot for uid %d not found", uid); + return null; } catch (IOException | KeyChainSnapshotParserException e) { - Log.e(TAG, String.format(Locale.US, "Error reading snapshot for %d from disk", uid), e); + // If we fail to read the latest snapshot, we should delete it in case it is in some way + // corrupted. We can regenerate snapshots anyway. + snapshotFile.delete(); + Slogf.e(TAG, e, "Error reading snapshot for %d from disk", uid); return null; } } @@ -116,50 +126,6 @@ public class RecoverySnapshotStorage { getSnapshotFile(uid).delete(); } - /** - * Writes the snapshot for recovery agent {@code uid} to disk. - * - * @throws IOException if an IO error occurs writing to disk. - */ - private void writeToDisk(int uid, KeyChainSnapshot snapshot) - throws IOException, CertificateEncodingException { - File snapshotFile = getSnapshotFile(uid); - - try ( - FileOutputStream fileOutputStream = new FileOutputStream(snapshotFile) - ) { - KeyChainSnapshotSerializer.serialize(snapshot, fileOutputStream); - } catch (IOException | CertificateEncodingException e) { - // If we fail to write the latest snapshot, we should delete any older snapshot that - // happens to be around. Otherwise snapshot syncs might end up going 'back in time'. - snapshotFile.delete(); - throw e; - } - } - - /** - * Reads the last snapshot for recovery agent {@code uid} from disk. - * - * @return The snapshot, or null if none existed. - * @throws IOException if an IO error occurs reading from disk. - */ - @Nullable - private KeyChainSnapshot readFromDisk(int uid) - throws IOException, KeyChainSnapshotParserException { - File snapshotFile = getSnapshotFile(uid); - - try ( - FileInputStream fileInputStream = new FileInputStream(snapshotFile) - ) { - return KeyChainSnapshotDeserializer.deserialize(fileInputStream); - } catch (IOException | KeyChainSnapshotParserException e) { - // If we fail to read the latest snapshot, we should delete it in case it is in some way - // corrupted. We can regenerate snapshots anyway. - snapshotFile.delete(); - throw e; - } - } - private File getSnapshotFile(int uid) { File folder = getStorageFolder(); String fileName = getSnapshotFileName(uid); diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java index 4665a72b0b06..b228bb9a89c3 100644 --- a/services/core/java/com/android/server/pm/ComputerEngine.java +++ b/services/core/java/com/android/server/pm/ComputerEngine.java @@ -5144,10 +5144,28 @@ public class ComputerEngine implements Computer { } updateOwnerPackageName = installSource.mUpdateOwnerPackageName; + + if (DEBUG_INSTALL) { + Log.d(TAG, "ComputerEngine getInstallSourceInfo updateOwnerPackageName = " + + updateOwnerPackageName + ", callingUid = " + callingUid + ", packageName = " + + packageName + ", userId = " + userId); + } + if (updateOwnerPackageName != null) { final PackageStateInternal ps = mSettings.getPackage(updateOwnerPackageName); final boolean isCallerSystemOrUpdateOwner = callingUid == Process.SYSTEM_UID || isCallerSameApp(updateOwnerPackageName, callingUid); + + if (DEBUG_INSTALL) { + Log.d(TAG, "ComputerEngine getInstallSourceInfo ps = " + + ps + ", isCallerSystemOrUpdateOwner =" + isCallerSystemOrUpdateOwner + + ", isCallerSameApp = " + + isCallerSameApp(updateOwnerPackageName, callingUid) + ", filter = " + + shouldFilterApplicationIncludingUninstalled(ps, callingUid, userId) + + ", FromManagedUserOrProfile = " + + isCallerFromManagedUserOrProfile(userId)); + } + // Except for package visibility filtering, we also hide update owner if the installer // is in the managed user or profile. As we don't enforce the update ownership for the // managed user and profile, knowing there's an update owner is meaningless in that @@ -5159,6 +5177,11 @@ public class ComputerEngine implements Computer { } } + if (DEBUG_INSTALL) { + Log.d(TAG, "ComputerEngine getInstallSourceInfo updateOwnerPackageName = " + + updateOwnerPackageName); + } + if (installSource.mIsInitiatingPackageUninstalled) { // We can't check visibility in the usual way, since the initiating package is no // longer present. So we apply simpler rules to whether to expose the info: diff --git a/services/core/java/com/android/server/pm/InstallRequest.java b/services/core/java/com/android/server/pm/InstallRequest.java index 1f79ac0afdac..089bbb7b0022 100644 --- a/services/core/java/com/android/server/pm/InstallRequest.java +++ b/services/core/java/com/android/server/pm/InstallRequest.java @@ -16,7 +16,6 @@ package com.android.server.pm; -import static android.content.pm.Flags.improveInstallFreeze; import static android.content.pm.PackageInstaller.SessionParams.USER_ACTION_UNSPECIFIED; import static android.content.pm.PackageManager.INSTALL_REASON_UNKNOWN; import static android.content.pm.PackageManager.INSTALL_SCENARIO_DEFAULT; @@ -1050,13 +1049,13 @@ final class InstallRequest { } public void onFreezeStarted() { - if (mPackageMetrics != null && improveInstallFreeze()) { + if (mPackageMetrics != null) { mPackageMetrics.onStepStarted(PackageMetrics.STEP_FREEZE_INSTALL); } } public void onFreezeCompleted() { - if (mPackageMetrics != null && improveInstallFreeze()) { + if (mPackageMetrics != null) { mPackageMetrics.onStepFinished(PackageMetrics.STEP_FREEZE_INSTALL); } } diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index 8bab9de903ba..708e0679d6d4 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -1101,7 +1101,7 @@ public class UserManagerService extends IUserManager.Stub { if (android.multiuser.Flags.cachesNotInvalidatedAtStartReadOnly()) { UserManager.invalidateIsUserUnlockedCache(); UserManager.invalidateQuietModeEnabledCache(); - UserManager.invalidateUserSerialNumberCache(); + UserManager.invalidateCacheOnUserListChange(); } } @@ -4448,7 +4448,7 @@ public class UserManagerService extends IUserManager.Stub { if (userData != null) { synchronized (mUsersLock) { - mUsers.put(userData.info.id, userData); + addUserDataLU(userData); if (mNextSerialNumber < 0 || mNextSerialNumber <= userData.info.id) { mNextSerialNumber = userData.info.id + 1; @@ -5724,7 +5724,7 @@ public class UserManagerService extends IUserManager.Stub { userData.info = userInfo; userData.userProperties = new UserProperties( userTypeDetails.getDefaultUserPropertiesReference()); - mUsers.put(userId, userData); + addUserDataLU(userData); } writeUserLP(userData); writeUserListLP(); @@ -6138,7 +6138,7 @@ public class UserManagerService extends IUserManager.Stub { final UserData userData = new UserData(); userData.info = userInfo; synchronized (mUsersLock) { - mUsers.put(userInfo.id, userData); + addUserDataLU(userData); } updateUserIds(); return userData; @@ -6148,8 +6148,7 @@ public class UserManagerService extends IUserManager.Stub { @VisibleForTesting void removeUserInfo(@UserIdInt int userId) { synchronized (mUsersLock) { - UserManager.invalidateUserSerialNumberCache(); - mUsers.remove(userId); + removeUserDataLU(userId); } } @@ -6579,8 +6578,7 @@ public class UserManagerService extends IUserManager.Stub { // Remove this user from the list synchronized (mUsersLock) { - UserManager.invalidateUserSerialNumberCache(); - mUsers.remove(userId); + removeUserDataLU(userId); mIsUserManaged.delete(userId); } synchronized (mUserStates) { @@ -6969,6 +6967,26 @@ public class UserManagerService extends IUserManager.Stub { } /** + * Adding user data to mUsers list in one place to invalidate related caches. + */ + @GuardedBy("mUsersLock") + private void addUserDataLU(UserData userData) { + if (android.multiuser.Flags.invalidateCacheOnUsersChangedReadOnly()) { + UserManager.invalidateCacheOnUserListChange(); + } + mUsers.put(userData.info.id, userData); + } + + /** + * Removing user data to mUsers list in one place to invalidate related caches. + */ + @GuardedBy("mUsersLock") + private void removeUserDataLU(@UserIdInt int userId) { + UserManager.invalidateCacheOnUserListChange(); + mUsers.remove(userId); + } + + /** * Caches the list of user ids in an array, adjusting the array size when necessary. */ private void updateUserIds() { diff --git a/services/core/java/com/android/server/wm/ContentRecorder.java b/services/core/java/com/android/server/wm/ContentRecorder.java index bc33946ef618..0b5872b3e601 100644 --- a/services/core/java/com/android/server/wm/ContentRecorder.java +++ b/services/core/java/com/android/server/wm/ContentRecorder.java @@ -285,6 +285,11 @@ final class ContentRecorder implements WindowContainerListener { } } + private boolean isDisplayReadyForMirroring() { + return mDisplayContent.getDisplayInfo().type != Display.TYPE_EXTERNAL + || mDisplayContent.mWmService.mDisplayManagerInternal.isDisplayReadyForMirroring( + mDisplayContent.getDisplayId()); + } /** * Ensure recording does not fall back to the display stack; ensure the recording is stopped @@ -335,7 +340,7 @@ final class ContentRecorder implements WindowContainerListener { return; } - if (mContentRecordingSession.isWaitingForConsent()) { + if (mContentRecordingSession.isWaitingForConsent() || !isDisplayReadyForMirroring()) { ProtoLog.v(WM_DEBUG_CONTENT_RECORDING, "Content Recording: waiting to record, so do " + "nothing"); return; diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp index efca90217e83..248ed1a58b75 100644 --- a/services/core/jni/com_android_server_input_InputManagerService.cpp +++ b/services/core/jni/com_android_server_input_InputManagerService.cpp @@ -337,6 +337,8 @@ public: int32_t getMousePointerSpeed(); void setPointerSpeed(int32_t speed); void setMousePointerAccelerationEnabled(ui::LogicalDisplayId displayId, bool enabled); + void setMouseReverseVerticalScrollingEnabled(bool enabled); + void setMouseSwapPrimaryButtonEnabled(bool enabled); void setTouchpadPointerSpeed(int32_t speed); void setTouchpadNaturalScrollingEnabled(bool enabled); void setTouchpadTapToClickEnabled(bool enabled); @@ -482,6 +484,12 @@ private: // True if stylus button reporting through motion events is enabled. bool stylusButtonMotionEventsEnabled{true}; + // True if mouse vertical scrolling is reversed. + bool mouseReverseVerticalScrollingEnabled{false}; + + // True if the mouse primary button is swapped (left/right buttons). + bool mouseSwapPrimaryButtonEnabled{false}; + // The touchpad pointer speed, as a number from -7 (slowest) to 7 (fastest). int32_t touchpadPointerSpeed{0}; @@ -762,6 +770,10 @@ void NativeInputManager::getReaderConfiguration(InputReaderConfiguration* outCon outConfig->defaultPointerDisplayId = mLocked.pointerDisplayId; + outConfig->mouseReverseVerticalScrollingEnabled = + mLocked.mouseReverseVerticalScrollingEnabled; + outConfig->mouseSwapPrimaryButtonEnabled = mLocked.mouseSwapPrimaryButtonEnabled; + outConfig->touchpadPointerSpeed = mLocked.touchpadPointerSpeed; outConfig->touchpadNaturalScrollingEnabled = mLocked.touchpadNaturalScrollingEnabled; outConfig->touchpadTapToClickEnabled = mLocked.touchpadTapToClickEnabled; @@ -1317,6 +1329,36 @@ int32_t NativeInputManager::getMousePointerSpeed() { return mLocked.pointerSpeed; } +void NativeInputManager::setMouseReverseVerticalScrollingEnabled(bool enabled) { + { // acquire lock + std::scoped_lock _l(mLock); + + if (mLocked.mouseReverseVerticalScrollingEnabled == enabled) { + return; + } + + mLocked.mouseReverseVerticalScrollingEnabled = enabled; + } // release lock + + mInputManager->getReader().requestRefreshConfiguration( + InputReaderConfiguration::Change::MOUSE_SETTINGS); +} + +void NativeInputManager::setMouseSwapPrimaryButtonEnabled(bool enabled) { + { // acquire lock + std::scoped_lock _l(mLock); + + if (mLocked.mouseSwapPrimaryButtonEnabled == enabled) { + return; + } + + mLocked.mouseSwapPrimaryButtonEnabled = enabled; + } // release lock + + mInputManager->getReader().requestRefreshConfiguration( + InputReaderConfiguration::Change::MOUSE_SETTINGS); +} + void NativeInputManager::setPointerSpeed(int32_t speed) { { // acquire lock std::scoped_lock _l(mLock); @@ -3002,6 +3044,18 @@ static jint nativeGetLastUsedInputDeviceId(JNIEnv* env, jobject nativeImplObj) { return static_cast<jint>(im->getInputManager()->getReader().getLastUsedInputDeviceId()); } +static void nativeSetMouseReverseVerticalScrollingEnabled(JNIEnv* env, jobject nativeImplObj, + bool enabled) { + NativeInputManager* im = getNativeInputManager(env, nativeImplObj); + im->setMouseReverseVerticalScrollingEnabled(enabled); +} + +static void nativeSetMouseSwapPrimaryButtonEnabled(JNIEnv* env, jobject nativeImplObj, + bool enabled) { + NativeInputManager* im = getNativeInputManager(env, nativeImplObj); + im->setMouseSwapPrimaryButtonEnabled(enabled); +} + // ---------------------------------------------------------------------------- static const JNINativeMethod gInputManagerMethods[] = { @@ -3048,6 +3102,9 @@ static const JNINativeMethod gInputManagerMethods[] = { {"setPointerSpeed", "(I)V", (void*)nativeSetPointerSpeed}, {"setMousePointerAccelerationEnabled", "(IZ)V", (void*)nativeSetMousePointerAccelerationEnabled}, + {"setMouseReverseVerticalScrollingEnabled", "(Z)V", + (void*)nativeSetMouseReverseVerticalScrollingEnabled}, + {"setMouseSwapPrimaryButtonEnabled", "(Z)V", (void*)nativeSetMouseSwapPrimaryButtonEnabled}, {"setTouchpadPointerSpeed", "(I)V", (void*)nativeSetTouchpadPointerSpeed}, {"setTouchpadNaturalScrollingEnabled", "(Z)V", (void*)nativeSetTouchpadNaturalScrollingEnabled}, diff --git a/services/core/jni/com_android_server_utils_AnrTimer.cpp b/services/core/jni/com_android_server_utils_AnrTimer.cpp index 2836d46b0f1a..2add5b09f15b 100644 --- a/services/core/jni/com_android_server_utils_AnrTimer.cpp +++ b/services/core/jni/com_android_server_utils_AnrTimer.cpp @@ -349,7 +349,7 @@ class AnrTimerTracer { return nullptr; } - // Return the currently watched pids. The lock must be held. + // Return the currently watched pids as a comma-separated list. The lock must be held. std::string watchedPidsLocked() const { if (watched_.size() == 0) return "none"; bool first = true; @@ -357,6 +357,7 @@ class AnrTimerTracer { for (auto i = watched_.cbegin(); i != watched_.cend(); i++) { if (first) { result += StringPrintf("%d", *i); + first = false; } else { result += StringPrintf(",%d", *i); } diff --git a/services/core/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd index 776de2e52061..20c69ac93f63 100644 --- a/services/core/xsd/display-device-config/display-device-config.xsd +++ b/services/core/xsd/display-device-config/display-device-config.xsd @@ -464,7 +464,7 @@ <xs:annotation name="nonnull"/> <xs:annotation name="final"/> </xs:element> - <xs:element name="customAnimationRateSec" type="nonNegativeDecimal" minOccurs="0" maxOccurs="1"> + <xs:element name="customAnimationRate" type="nonNegativeDecimal" minOccurs="0" maxOccurs="1"> <xs:annotation name="nonnull"/> <xs:annotation name="final"/> </xs:element> diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt index 110a5a20da6a..a8f18b3d2eee 100644 --- a/services/core/xsd/display-device-config/schema/current.txt +++ b/services/core/xsd/display-device-config/schema/current.txt @@ -345,12 +345,12 @@ package com.android.server.display.config { public class PowerThrottlingConfig { ctor public PowerThrottlingConfig(); method @NonNull public final java.math.BigDecimal getBrightnessLowestCapAllowed(); - method @NonNull public final java.math.BigDecimal getCustomAnimationRateSec(); + method @NonNull public final java.math.BigDecimal getCustomAnimationRate(); method @NonNull public final java.math.BigInteger getPollingWindowMaxMillis(); method @NonNull public final java.math.BigInteger getPollingWindowMinMillis(); method public final java.util.List<com.android.server.display.config.PowerThrottlingMap> getPowerThrottlingMap(); method public final void setBrightnessLowestCapAllowed(@NonNull java.math.BigDecimal); - method public final void setCustomAnimationRateSec(@NonNull java.math.BigDecimal); + method public final void setCustomAnimationRate(@NonNull java.math.BigDecimal); method public final void setPollingWindowMaxMillis(@NonNull java.math.BigInteger); method public final void setPollingWindowMinMillis(@NonNull java.math.BigInteger); } diff --git a/services/tests/appfunctions/src/com/android/server/appfunctions/MetadataSyncAdapterTest.kt b/services/tests/appfunctions/src/com/android/server/appfunctions/MetadataSyncAdapterTest.kt index 5758da858423..96fb4535b992 100644 --- a/services/tests/appfunctions/src/com/android/server/appfunctions/MetadataSyncAdapterTest.kt +++ b/services/tests/appfunctions/src/com/android/server/appfunctions/MetadataSyncAdapterTest.kt @@ -391,6 +391,10 @@ class MetadataSyncAdapterTest { return AndroidFuture.completedFuture(mutableListOf()) } } + + override fun close() { + Log.d("FakeRuntimeMetadataSearchSession", "Closing session") + } } return AndroidFuture.completedFuture(futureSearchResults) } diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java index 3976ea4fc86e..2220f439f6c8 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java @@ -265,7 +265,7 @@ public final class DisplayDeviceConfigTest { mDisplayDeviceConfig.getPowerThrottlingConfigData(); assertNotNull(powerThrottlingConfigData); assertEquals(0.1f, powerThrottlingConfigData.brightnessLowestCapAllowed, SMALL_DELTA); - assertEquals(15f, powerThrottlingConfigData.customAnimationRateSec, SMALL_DELTA); + assertEquals(15f, powerThrottlingConfigData.customAnimationRate, SMALL_DELTA); assertEquals(20000, powerThrottlingConfigData.pollingWindowMaxMillis); assertEquals(10000, powerThrottlingConfigData.pollingWindowMinMillis); } @@ -1299,7 +1299,7 @@ public final class DisplayDeviceConfigTest { private String getPowerThrottlingConfig() { return "<powerThrottlingConfig >\n" + "<brightnessLowestCapAllowed>0.1</brightnessLowestCapAllowed>\n" - + "<customAnimationRateSec>15</customAnimationRateSec>\n" + + "<customAnimationRate>15</customAnimationRate>\n" + "<pollingWindowMaxMillis>20000</pollingWindowMaxMillis>\n" + "<pollingWindowMinMillis>10000</pollingWindowMinMillis>\n" + "<powerThrottlingMap>\n" diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java index 1a1c8e50ba0a..94eab9cda968 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java @@ -25,6 +25,7 @@ import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_ import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY; import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP; import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION; +import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_TRUSTED; import static android.view.ContentRecordingSession.RECORD_CONTENT_DISPLAY; import static android.view.ContentRecordingSession.RECORD_CONTENT_TASK; import static android.view.Display.HdrCapabilities.HDR_TYPE_INVALID; @@ -1068,9 +1069,9 @@ public class DisplayManagerServiceTest { firstDisplayId); } - /** Tests that the virtual device is created in a device display group. */ + /** Tests that a trusted virtual display is created in a device display group. */ @Test - public void createVirtualDisplay_addsDisplaysToDeviceDisplayGroups() throws Exception { + public void createVirtualDisplay_addsTrustedDisplaysToDeviceDisplayGroups() throws Exception { DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector); DisplayManagerInternal localService = displayManager.new LocalService(); @@ -1081,12 +1082,16 @@ public class DisplayManagerServiceTest { IVirtualDevice virtualDevice = mock(IVirtualDevice.class); when(virtualDevice.getDeviceId()).thenReturn(1); when(mIVirtualDeviceManager.isValidVirtualDeviceId(1)).thenReturn(true); + + when(mContext.checkCallingPermission(ADD_TRUSTED_DISPLAY)) + .thenReturn(PackageManager.PERMISSION_GRANTED); + // Create a first virtual display. A display group should be created for this display on the // virtual device. final VirtualDisplayConfig.Builder builder1 = new VirtualDisplayConfig.Builder(VIRTUAL_DISPLAY_NAME, 600, 800, 320) - .setUniqueId("uniqueId --- device display group 1"); - + .setUniqueId("uniqueId --- device display group") + .setFlags(VIRTUAL_DISPLAY_FLAG_TRUSTED); int displayId1 = localService.createVirtualDisplay( builder1.build(), @@ -1097,12 +1102,14 @@ public class DisplayManagerServiceTest { verify(mMockProjectionService, never()).setContentRecordingSession(any(), nullable(IMediaProjection.class)); int displayGroupId1 = localService.getDisplayInfo(displayId1).displayGroupId; + assertNotEquals(displayGroupId1, Display.DEFAULT_DISPLAY_GROUP); // Create a second virtual display. This should be added to the previously created display // group. final VirtualDisplayConfig.Builder builder2 = new VirtualDisplayConfig.Builder(VIRTUAL_DISPLAY_NAME, 600, 800, 320) - .setUniqueId("uniqueId --- device display group 1"); + .setUniqueId("uniqueId --- device display group") + .setFlags(VIRTUAL_DISPLAY_FLAG_TRUSTED); int displayId2 = localService.createVirtualDisplay( @@ -1121,6 +1128,36 @@ public class DisplayManagerServiceTest { displayGroupId2); } + /** Tests that an untrusted virtual display is created in the default display group. */ + @Test + public void createVirtualDisplay_addsUntrustedDisplayToDefaultDisplayGroups() throws Exception { + DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector); + DisplayManagerInternal localService = displayManager.new LocalService(); + + registerDefaultDisplays(displayManager); + when(mMockAppToken.asBinder()).thenReturn(mMockAppToken); + + IVirtualDevice virtualDevice = mock(IVirtualDevice.class); + when(virtualDevice.getDeviceId()).thenReturn(1); + when(mIVirtualDeviceManager.isValidVirtualDeviceId(1)).thenReturn(true); + // Create the virtual display. It is untrusted, so it should go into the default group. + final VirtualDisplayConfig.Builder builder = + new VirtualDisplayConfig.Builder(VIRTUAL_DISPLAY_NAME, 600, 800, 320) + .setUniqueId("uniqueId --- device display group"); + + int displayId = + localService.createVirtualDisplay( + builder.build(), + mMockAppToken /* callback */, + virtualDevice /* virtualDeviceToken */, + mock(DisplayWindowPolicyController.class), + PACKAGE_NAME); + verify(mMockProjectionService, never()).setContentRecordingSession(any(), + nullable(IMediaProjection.class)); + int displayGroupId = localService.getDisplayInfo(displayId).displayGroupId; + assertEquals(displayGroupId, Display.DEFAULT_DISPLAY_GROUP); + } + /** * Tests that the virtual display is not added to the device display group when * VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP is set. @@ -1138,11 +1175,15 @@ public class DisplayManagerServiceTest { when(virtualDevice.getDeviceId()).thenReturn(1); when(mIVirtualDeviceManager.isValidVirtualDeviceId(1)).thenReturn(true); + when(mContext.checkCallingPermission(ADD_TRUSTED_DISPLAY)) + .thenReturn(PackageManager.PERMISSION_GRANTED); + // Create a first virtual display. A display group should be created for this display on the // virtual device. final VirtualDisplayConfig.Builder builder1 = new VirtualDisplayConfig.Builder(VIRTUAL_DISPLAY_NAME, 600, 800, 320) - .setUniqueId("uniqueId --- device display group"); + .setUniqueId("uniqueId --- device display group") + .setFlags(VIRTUAL_DISPLAY_FLAG_TRUSTED); int displayId1 = localService.createVirtualDisplay( @@ -1154,12 +1195,14 @@ public class DisplayManagerServiceTest { verify(mMockProjectionService, never()).setContentRecordingSession(any(), nullable(IMediaProjection.class)); int displayGroupId1 = localService.getDisplayInfo(displayId1).displayGroupId; + assertNotEquals(displayGroupId1, Display.DEFAULT_DISPLAY_GROUP); // Create a second virtual display. With the flag VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP, // the display should not be added to the previously created display group. final VirtualDisplayConfig.Builder builder2 = new VirtualDisplayConfig.Builder(VIRTUAL_DISPLAY_NAME, 600, 800, 320) - .setFlags(VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP) + .setFlags(VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP + | VIRTUAL_DISPLAY_FLAG_TRUSTED) .setUniqueId("uniqueId --- own display group"); when(mIVirtualDeviceManager.isValidVirtualDeviceId(1)).thenReturn(true); @@ -1174,6 +1217,7 @@ public class DisplayManagerServiceTest { verify(mMockProjectionService, never()).setContentRecordingSession(any(), nullable(IMediaProjection.class)); int displayGroupId2 = localService.getDisplayInfo(displayId2).displayGroupId; + assertNotEquals(displayGroupId2, Display.DEFAULT_DISPLAY_GROUP); assertNotEquals( "Display 1 should be in the device display group and display 2 in its own display" @@ -1208,7 +1252,8 @@ public class DisplayManagerServiceTest { final VirtualDisplayConfig deviceDisplayGroupDisplayConfig = new VirtualDisplayConfig.Builder(VIRTUAL_DISPLAY_NAME, 600, 800, 320) .setUniqueId("uniqueId --- device display group 1") - .setFlags(VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED) + .setFlags(VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED + | VIRTUAL_DISPLAY_FLAG_TRUSTED) .build(); int deviceDisplayGroupDisplayId = @@ -1235,6 +1280,7 @@ public class DisplayManagerServiceTest { .setUniqueId("uniqueId --- own display group 1") .setFlags( VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED + | VIRTUAL_DISPLAY_FLAG_TRUSTED | VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP) .build(); @@ -1852,7 +1898,7 @@ public class DisplayManagerServiceTest { /** * Tests that specifying VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP is allowed when the permission - * ADD_TRUSTED_DISPLAY is granted. + * ADD_TRUSTED_DISPLAY is granted and that display is not in the default display group. */ @Test public void testOwnDisplayGroup_allowCreationWithAddTrustedDisplayPermission() @@ -1881,6 +1927,9 @@ public class DisplayManagerServiceTest { DisplayDeviceInfo ddi = displayManager.getDisplayDeviceInfoInternal(displayId); assertNotNull(ddi); assertNotEquals(0, ddi.flags & DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP); + + int displayGroupId = bs.getDisplayInfo(displayId).displayGroupId; + assertNotEquals(displayGroupId, Display.DEFAULT_DISPLAY_GROUP); } /** @@ -1915,11 +1964,11 @@ public class DisplayManagerServiceTest { } /** - * Tests that specifying VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP is allowed when called with - * a virtual device, even if ADD_TRUSTED_DISPLAY is not granted. + * Tests that specifying VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP is not allowed when called with + * a virtual device, if ADD_TRUSTED_DISPLAY is not granted. */ @Test - public void testOwnDisplayGroup_allowCreationWithVirtualDevice() throws Exception { + public void testOwnDisplayGroup_disallowCreationWithVirtualDevice() throws Exception { DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector); DisplayManagerInternal localService = displayManager.new LocalService(); @@ -1940,16 +1989,16 @@ public class DisplayManagerServiceTest { when(virtualDevice.getDeviceId()).thenReturn(1); when(mIVirtualDeviceManager.isValidVirtualDeviceId(1)).thenReturn(true); - int displayId = localService.createVirtualDisplay(builder.build(), - mMockAppToken /* callback */, virtualDevice /* virtualDeviceToken */, - mock(DisplayWindowPolicyController.class), PACKAGE_NAME); - verify(mMockProjectionService, never()).setContentRecordingSession(any(), - nullable(IMediaProjection.class)); - performTraversalInternal(displayManager); - displayManager.getDisplayHandler().runWithScissors(() -> {}, 0 /* now */); - DisplayDeviceInfo ddi = displayManager.getDisplayDeviceInfoInternal(displayId); - assertNotNull(ddi); - assertNotEquals(0, ddi.flags & DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP); + try { + localService.createVirtualDisplay(builder.build(), + mMockAppToken /* callback */, virtualDevice /* virtualDeviceToken */, + mock(DisplayWindowPolicyController.class), PACKAGE_NAME); + fail("Creating virtual display with VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP without " + + "ADD_TRUSTED_DISPLAY permission should throw SecurityException even if " + + "called with a virtual device."); + } catch (SecurityException e) { + // SecurityException is expected + } } /** diff --git a/services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayPolicyTest.java b/services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayPolicyTest.java index f72816889c3b..782262d3f7c9 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayPolicyTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayPolicyTest.java @@ -18,6 +18,7 @@ package com.android.server.display; import static android.hardware.display.DisplayManagerGlobal.EVENT_DISPLAY_CONNECTED; import static android.view.Display.TYPE_EXTERNAL; +import static android.view.Display.TYPE_INTERNAL; import static com.google.common.truth.Truth.assertThat; @@ -36,6 +37,7 @@ import android.os.IThermalEventListener; import android.os.IThermalService; import android.os.RemoteException; import android.os.Temperature; +import android.view.Display; import android.view.DisplayInfo; import androidx.test.filters.SmallTest; @@ -97,6 +99,8 @@ public class ExternalDisplayPolicyTest { @Mock private LogicalDisplay mMockedLogicalDisplay; @Mock + private LogicalDisplay mMockedDefaultDisplay; + @Mock private DisplayNotificationManager mMockedDisplayNotificationManager; @Mock private ExternalDisplayStatsService mMockedExternalDisplayStatsService; @@ -141,6 +145,15 @@ public class ExternalDisplayPolicyTest { when(mMockedLogicalDisplay.getDisplayInfoLocked()).thenReturn(mockedLogicalDisplayInfo); when(mMockedLogicalDisplayMapper.getDisplayLocked(EXTERNAL_DISPLAY_ID)).thenReturn( mMockedLogicalDisplay); + + // Initialize default logical display + when(mMockedDefaultDisplay.getDisplayIdLocked()).thenReturn(Display.DEFAULT_DISPLAY); + when(mMockedDefaultDisplay.isEnabledLocked()).thenReturn(true); + final var mockedDefaultDisplayInfo = new DisplayInfo(); + mockedDefaultDisplayInfo.type = TYPE_INTERNAL; + when(mMockedDefaultDisplay.getDisplayInfoLocked()).thenReturn(mockedDefaultDisplayInfo); + when(mMockedLogicalDisplayMapper.getDisplayLocked(Display.DEFAULT_DISPLAY)).thenReturn( + mMockedDefaultDisplay); } @Test @@ -293,6 +306,52 @@ public class ExternalDisplayPolicyTest { verify(mMockedLogicalDisplayMapper, never()).forEachLocked(any()); } + @Test + public void testMirroringAlwaysConfirmedByUser_flagDisabled() { + when(mMockedFlags.isWaitingConfirmationBeforeMirroringEnabled()).thenReturn(false); + assertThat(mExternalDisplayPolicy.isDisplayReadyForMirroring(EXTERNAL_DISPLAY_ID)).isTrue(); + } + + @Test + public void testMirroringConfirmed_afterBootForEnabledDisplay() { + when(mMockedFlags.isWaitingConfirmationBeforeMirroringEnabled()).thenReturn(true); + mExternalDisplayPolicy.onBootCompleted(); + assertThat(mExternalDisplayPolicy.isDisplayReadyForMirroring(EXTERNAL_DISPLAY_ID)) + .isTrue(); + } + + @Test + public void testMirroringNotConfirmed_afterBootForDisabledDisplay() { + when(mMockedFlags.isWaitingConfirmationBeforeMirroringEnabled()).thenReturn(true); + mExternalDisplayPolicy.onBootCompleted(); + when(mMockedLogicalDisplay.isEnabledLocked()).thenReturn(false); + assertThat(mExternalDisplayPolicy.isDisplayReadyForMirroring(EXTERNAL_DISPLAY_ID)) + .isFalse(); + } + + @Test + public void testMirroringNeverConfirmed_forNonExternalDisplays() { + when(mMockedFlags.isWaitingConfirmationBeforeMirroringEnabled()).thenReturn(true); + mExternalDisplayPolicy.onBootCompleted(); + assertThat(mExternalDisplayPolicy.isDisplayReadyForMirroring(Display.DEFAULT_DISPLAY)) + .isFalse(); + } + + @Test + public void testMirroringNeverConfirmed_forNonExistingDisplays() { + when(mMockedFlags.isWaitingConfirmationBeforeMirroringEnabled()).thenReturn(true); + mExternalDisplayPolicy.onBootCompleted(); + assertThat(mExternalDisplayPolicy.isDisplayReadyForMirroring(Display.INVALID_DISPLAY)) + .isFalse(); + } + + @Test + public void testMirroringNeverConfirmed_duringBoot() { + when(mMockedFlags.isWaitingConfirmationBeforeMirroringEnabled()).thenReturn(true); + assertThat(mExternalDisplayPolicy.isDisplayReadyForMirroring(EXTERNAL_DISPLAY_ID)) + .isFalse(); + } + private void setTemperature(final IThermalEventListener thermalEventListener, final List<Temperature> temperature) throws RemoteException { for (var t : temperature) { diff --git a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java index 1729ad5ff19f..d831cf8a3643 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java @@ -121,6 +121,8 @@ public class LogicalDisplayMapperTest { Set.of(DeviceState.PROPERTY_POWER_CONFIGURATION_TRIGGER_WAKE), Collections.emptySet()); private static final DeviceState DEVICE_STATE_OPEN = createDeviceState(2, "Two", Set.of(DeviceState.PROPERTY_POWER_CONFIGURATION_TRIGGER_WAKE), Collections.emptySet()); + private static final DeviceState DEVICE_STATE_EMULATED = createDeviceState(3, "Three", + Set.of(DeviceState.PROPERTY_EMULATED_ONLY), Collections.emptySet()); private static final int FLAG_GO_TO_SLEEP_ON_FOLD = 0; private static final int FLAG_GO_TO_SLEEP_FLAG_SOFT_SLEEP = 2; private static int sNextNonDefaultDisplayId = DEFAULT_DISPLAY + 1; @@ -686,6 +688,14 @@ public class LogicalDisplayMapperTest { } @Test + public void testDeviceShouldNotBeWokenWhenExitingEmulatedState() { + assertFalse(mLogicalDisplayMapper.shouldDeviceBeWoken(DEVICE_STATE_OPEN, + DEVICE_STATE_EMULATED, + /* isInteractive= */false, + /* isBootCompleted= */true)); + } + + @Test public void testDeviceShouldBePutToSleep() { assertTrue(mLogicalDisplayMapper.shouldDeviceBePutToSleep(DEVICE_STATE_CLOSED, DEVICE_STATE_OPEN, diff --git a/services/tests/displayservicetests/src/com/android/server/display/PersistentDataStoreTest.java b/services/tests/displayservicetests/src/com/android/server/display/PersistentDataStoreTest.java index 5676a388acff..6d14065b6248 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/PersistentDataStoreTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/PersistentDataStoreTest.java @@ -459,7 +459,6 @@ public class PersistentDataStoreTest { ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); newInjector.setReadStream(bais); newDataStore.loadIfNeeded(); - assertNotNull(newDataStore.getUserPreferredRefreshRate(testDisplayDevice)); assertEquals(85.3f, mDataStore.getUserPreferredRefreshRate(testDisplayDevice), 01.f); assertEquals(85.3f, newDataStore.getUserPreferredRefreshRate(testDisplayDevice), 0.1f); } diff --git a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java index 1731590be3c9..026e72f117b4 100644 --- a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java @@ -31,12 +31,14 @@ import static android.app.AppOpsManager.UID_STATE_FOREGROUND; import static android.app.AppOpsManager.UID_STATE_FOREGROUND_SERVICE; import static android.app.AppOpsManager.UID_STATE_MAX_LAST_NON_RESTRICTED; import static android.app.AppOpsManager.UID_STATE_TOP; +import static android.permission.flags.Flags.delayUidStateChangesFromCapabilityUpdates; import static com.android.server.appop.AppOpsUidStateTracker.processStateToUidState; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import static org.junit.Assume.assumeTrue; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; @@ -325,6 +327,10 @@ public class AppOpsUidStateTrackerTest { .backgroundState() .update(); + assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_RECORD_AUDIO, MODE_FOREGROUND)); + assertEquals(MODE_IGNORED, + mIntf.evalMode(UID, OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO, MODE_FOREGROUND)); + procStateBuilder(UID) .backgroundState() .microphoneCapability() @@ -342,10 +348,23 @@ public class AppOpsUidStateTrackerTest { .microphoneCapability() .update(); + assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_RECORD_AUDIO, MODE_FOREGROUND)); + assertEquals(MODE_ALLOWED, + mIntf.evalMode(UID, OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO, MODE_FOREGROUND)); + procStateBuilder(UID) .backgroundState() .update(); + if (delayUidStateChangesFromCapabilityUpdates()) { + mClock.advanceTime(mConstants.BG_STATE_SETTLE_TIME - 1); + assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_RECORD_AUDIO, MODE_FOREGROUND)); + assertEquals(MODE_ALLOWED, + mIntf.evalMode(UID, OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO, + MODE_FOREGROUND)); + + mClock.advanceTime(1); + } assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_RECORD_AUDIO, MODE_FOREGROUND)); assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO, MODE_FOREGROUND)); @@ -357,6 +376,8 @@ public class AppOpsUidStateTrackerTest { .backgroundState() .update(); + assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_CAMERA, MODE_FOREGROUND)); + procStateBuilder(UID) .backgroundState() .cameraCapability() @@ -372,10 +393,18 @@ public class AppOpsUidStateTrackerTest { .cameraCapability() .update(); + assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_CAMERA, MODE_FOREGROUND)); + procStateBuilder(UID) .backgroundState() .update(); + if (delayUidStateChangesFromCapabilityUpdates()) { + mClock.advanceTime(mConstants.BG_STATE_SETTLE_TIME - 1); + assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_CAMERA, MODE_FOREGROUND)); + + mClock.advanceTime(1); + } assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_CAMERA, MODE_FOREGROUND)); } @@ -385,6 +414,9 @@ public class AppOpsUidStateTrackerTest { .backgroundState() .update(); + assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_COARSE_LOCATION, MODE_FOREGROUND)); + assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_FINE_LOCATION, MODE_FOREGROUND)); + procStateBuilder(UID) .backgroundState() .locationCapability() @@ -401,15 +433,55 @@ public class AppOpsUidStateTrackerTest { .locationCapability() .update(); + assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_COARSE_LOCATION, MODE_FOREGROUND)); + assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_FINE_LOCATION, MODE_FOREGROUND)); + procStateBuilder(UID) .backgroundState() .update(); + if (delayUidStateChangesFromCapabilityUpdates()) { + mClock.advanceTime(mConstants.BG_STATE_SETTLE_TIME - 1); + assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_COARSE_LOCATION, MODE_FOREGROUND)); + assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_FINE_LOCATION, MODE_FOREGROUND)); + + mClock.advanceTime(1); + } assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_COARSE_LOCATION, MODE_FOREGROUND)); assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_FINE_LOCATION, MODE_FOREGROUND)); } @Test + public void testProcStateChangesAndStaysUnrestrictedAndCapabilityRemoved() { + assumeTrue(delayUidStateChangesFromCapabilityUpdates()); + + procStateBuilder(UID) + .topState() + .microphoneCapability() + .cameraCapability() + .locationCapability() + .update(); + + assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_RECORD_AUDIO, MODE_FOREGROUND)); + assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_CAMERA, MODE_FOREGROUND)); + assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_COARSE_LOCATION, MODE_FOREGROUND)); + + procStateBuilder(UID) + .foregroundState() + .update(); + + mClock.advanceTime(mConstants.TOP_STATE_SETTLE_TIME - 1); + assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_RECORD_AUDIO, MODE_FOREGROUND)); + assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_CAMERA, MODE_FOREGROUND)); + assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_COARSE_LOCATION, MODE_FOREGROUND)); + + mClock.advanceTime(1); + assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_RECORD_AUDIO, MODE_FOREGROUND)); + assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_CAMERA, MODE_FOREGROUND)); + assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_COARSE_LOCATION, MODE_FOREGROUND)); + } + + @Test public void testVisibleAppWidget() { procStateBuilder(UID) .backgroundState() diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java index 584fd6270c69..40b9c61a0597 100644 --- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java @@ -25,6 +25,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSess import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.when; import static com.android.server.job.Flags.FLAG_COUNT_QUOTA_FIX; +import static com.android.server.job.Flags.FLAG_ENFORCE_QUOTA_POLICY_TO_FGS_JOBS; import static com.android.server.job.JobSchedulerService.ACTIVE_INDEX; import static com.android.server.job.JobSchedulerService.EXEMPTED_INDEX; import static com.android.server.job.JobSchedulerService.FREQUENT_INDEX; @@ -303,6 +304,12 @@ public class QuotaControllerTest { } } + private int getProcessStateQuotaFreeThreshold() { + synchronized (mQuotaController.mLock) { + return mQuotaController.getProcessStateQuotaFreeThreshold(); + } + } + private void setProcessState(int procState) { setProcessState(procState, mSourceUid); } @@ -315,7 +322,7 @@ public class QuotaControllerTest { final boolean contained = foregroundUids.get(uid); mUidObserver.onUidStateChanged(uid, procState, 0, ActivityManager.PROCESS_CAPABILITY_NONE); - if (procState <= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) { + if (procState <= getProcessStateQuotaFreeThreshold()) { if (!contained) { verify(foregroundUids, timeout(2 * SECOND_IN_MILLIS).times(1)) .put(eq(uid), eq(true)); @@ -1371,7 +1378,7 @@ public class QuotaControllerTest { } setDischarging(); - setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE); + setProcessState(getProcessStateQuotaFreeThreshold()); synchronized (mQuotaController.mLock) { assertEquals(timeUntilQuotaConsumedMs, mQuotaController.getMaxJobExecutionTimeMsLocked((job))); @@ -1473,7 +1480,7 @@ public class QuotaControllerTest { } setDischarging(); - setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE); + setProcessState(getProcessStateQuotaFreeThreshold()); synchronized (mQuotaController.mLock) { assertEquals(mQcConstants.EJ_LIMIT_WORKING_MS / 2, mQuotaController.getMaxJobExecutionTimeMsLocked(job)); @@ -1505,7 +1512,7 @@ public class QuotaControllerTest { createTimingSession(sElapsedRealtimeClock.millis() - mQcConstants.EJ_WINDOW_SIZE_MS, timeUsedMs, 5), true); - setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE); + setProcessState(getProcessStateQuotaFreeThreshold()); synchronized (mQuotaController.mLock) { assertEquals(mQcConstants.EJ_LIMIT_WORKING_MS / 2, mQuotaController.getMaxJobExecutionTimeMsLocked(job)); @@ -4126,7 +4133,7 @@ public class QuotaControllerTest { } advanceElapsedClock(5 * SECOND_IN_MILLIS); // Change to a state that should still be considered foreground. - setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE); + setProcessState(getProcessStateQuotaFreeThreshold()); advanceElapsedClock(5 * SECOND_IN_MILLIS); synchronized (mQuotaController.mLock) { mQuotaController.maybeStopTrackingJobLocked(jobStatus, null); @@ -4134,6 +4141,36 @@ public class QuotaControllerTest { assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); } + /** Tests that Timers count FOREGROUND_SERVICE jobs. */ + @Test + @RequiresFlagsEnabled(FLAG_ENFORCE_QUOTA_POLICY_TO_FGS_JOBS) + public void testTimerTracking_Fgs() { + setDischarging(); + + JobStatus jobStatus = createJobStatus("testTimerTracking_Fgs", 1); + setProcessState(ActivityManager.PROCESS_STATE_BOUND_TOP); + synchronized (mQuotaController.mLock) { + mQuotaController.maybeStartTrackingJobLocked(jobStatus, null); + } + + assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); + + synchronized (mQuotaController.mLock) { + mQuotaController.prepareForExecutionLocked(jobStatus); + } + advanceElapsedClock(5 * SECOND_IN_MILLIS); + // Change to FOREGROUND_SERVICE state that should count. + setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE); + long start = JobSchedulerService.sElapsedRealtimeClock.millis(); + advanceElapsedClock(5 * SECOND_IN_MILLIS); + synchronized (mQuotaController.mLock) { + mQuotaController.maybeStopTrackingJobLocked(jobStatus, null); + } + List<TimingSession> expected = new ArrayList<>(); + expected.add(createTimingSession(start, 5 * SECOND_IN_MILLIS, 1)); + assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); + } + /** * Tests that Timers properly track sessions when switching between foreground and background * states. @@ -4180,7 +4217,7 @@ public class QuotaControllerTest { } advanceElapsedClock(10 * SECOND_IN_MILLIS); expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1)); - setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE); + setProcessState(getProcessStateQuotaFreeThreshold()); synchronized (mQuotaController.mLock) { mQuotaController.prepareForExecutionLocked(jobFg3); } @@ -4213,7 +4250,7 @@ public class QuotaControllerTest { } advanceElapsedClock(10 * SECOND_IN_MILLIS); expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1)); - setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE); + setProcessState(getProcessStateQuotaFreeThreshold()); synchronized (mQuotaController.mLock) { mQuotaController.prepareForExecutionLocked(jobFg3); } @@ -4262,7 +4299,7 @@ public class QuotaControllerTest { } assertEquals(0, stats.jobCountInRateLimitingWindow); - setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE); + setProcessState(getProcessStateQuotaFreeThreshold()); synchronized (mQuotaController.mLock) { mQuotaController.prepareForExecutionLocked(jobFg1); } @@ -4412,7 +4449,7 @@ public class QuotaControllerTest { mQuotaController.maybeStopTrackingJobLocked(jobBg1, jobBg1); } advanceElapsedClock(5 * SECOND_IN_MILLIS); - setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE); + setProcessState(getProcessStateQuotaFreeThreshold()); synchronized (mQuotaController.mLock) { mQuotaController.prepareForExecutionLocked(jobFg1); } @@ -4625,7 +4662,7 @@ public class QuotaControllerTest { // App still in foreground so everything should be in quota. advanceElapsedClock(20 * SECOND_IN_MILLIS); - setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE); + setProcessState(getProcessStateQuotaFreeThreshold()); assertTrue(jobTop.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)); assertTrue(jobFg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)); assertTrue(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)); @@ -5901,7 +5938,7 @@ public class QuotaControllerTest { } advanceElapsedClock(10 * SECOND_IN_MILLIS); expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1)); - setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE); + setProcessState(getProcessStateQuotaFreeThreshold()); synchronized (mQuotaController.mLock) { mQuotaController.prepareForExecutionLocked(jobFg3); } @@ -5935,7 +5972,7 @@ public class QuotaControllerTest { } advanceElapsedClock(10 * SECOND_IN_MILLIS); expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1)); - setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE); + setProcessState(getProcessStateQuotaFreeThreshold()); synchronized (mQuotaController.mLock) { mQuotaController.prepareForExecutionLocked(jobFg3); } @@ -6056,7 +6093,7 @@ public class QuotaControllerTest { mQuotaController.maybeStopTrackingJobLocked(jobBg1, jobBg1); } advanceElapsedClock(5 * SECOND_IN_MILLIS); - setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE); + setProcessState(getProcessStateQuotaFreeThreshold()); synchronized (mQuotaController.mLock) { mQuotaController.prepareForExecutionLocked(jobFg1); } @@ -6534,7 +6571,7 @@ public class QuotaControllerTest { // App still in foreground so everything should be in quota. advanceElapsedClock(20 * SECOND_IN_MILLIS); - setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE); + setProcessState(getProcessStateQuotaFreeThreshold()); assertTrue(jobTop2.isExpeditedQuotaApproved()); assertTrue(jobFg.isExpeditedQuotaApproved()); assertTrue(jobBg.isExpeditedQuotaApproved()); diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java index dad45b3048b9..e09933a55fb9 100644 --- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java @@ -245,6 +245,8 @@ public class VirtualDeviceManagerServiceTest { @Mock private IDisplayManager mIDisplayManager; @Mock + private WindowManager mWindowManager; + @Mock private VirtualDeviceImpl.PendingTrampolineCallback mPendingTrampolineCallback; @Mock private DevicePolicyManager mDevicePolicyManagerMock; @@ -383,8 +385,7 @@ public class VirtualDeviceManagerServiceTest { // Allow virtual devices to be created on the looper thread for testing. final InputController.DeviceCreationThreadVerifier threadVerifier = () -> true; mInputController = new InputController(mNativeWrapperMock, - new Handler(TestableLooper.get(this).getLooper()), - mContext.getSystemService(WindowManager.class), + new Handler(TestableLooper.get(this).getLooper()), mWindowManager, AttributionSource.myAttributionSource(), threadVerifier); mCameraAccessController = new CameraAccessController(mContext, mLocalService, mCameraAccessBlockedCallback); @@ -535,7 +536,7 @@ public class VirtualDeviceManagerServiceTest { .build(); mDeviceImpl.close(); mDeviceImpl = createVirtualDevice(VIRTUAL_DEVICE_ID_1, DEVICE_OWNER_UID_1, params); - addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1); + addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1, Display.FLAG_TRUSTED); GenericWindowPolicyController gwpc = mDeviceImpl.getDisplayWindowPolicyControllerForTest(DISPLAY_ID_1); @@ -543,6 +544,21 @@ public class VirtualDeviceManagerServiceTest { } @Test + public void getDevicePolicy_customRecentsPolicy_untrustedDisplaygwpcShowsRecentsOnHostDevice() { + VirtualDeviceParams params = new VirtualDeviceParams + .Builder() + .setDevicePolicy(POLICY_TYPE_RECENTS, DEVICE_POLICY_CUSTOM) + .build(); + mDeviceImpl.close(); + mDeviceImpl = createVirtualDevice(VIRTUAL_DEVICE_ID_1, DEVICE_OWNER_UID_1, params); + addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1); + + GenericWindowPolicyController gwpc = + mDeviceImpl.getDisplayWindowPolicyControllerForTest(DISPLAY_ID_1); + assertThat(gwpc.canShowTasksInHostDeviceRecents()).isTrue(); + } + + @Test public void getDeviceOwnerUid_oneDevice_returnsCorrectId() { int ownerUid = mLocalService.getDeviceOwnerUid(mDeviceImpl.getDeviceId()); assertThat(ownerUid).isEqualTo(mDeviceImpl.getOwnerUid()); @@ -692,7 +708,7 @@ public class VirtualDeviceManagerServiceTest { @Test public void getPreferredLocaleListForApp_keyboardAttached_returnLocaleHints() { - addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1); + addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1, Display.FLAG_TRUSTED); mDeviceImpl.createVirtualKeyboard(KEYBOARD_CONFIG, BINDER); mVdms.notifyRunningAppsChanged(mDeviceImpl.getDeviceId(), Sets.newArraySet(UID_1)); @@ -732,8 +748,8 @@ public class VirtualDeviceManagerServiceTest { .setLanguageTag("fr-FR") .build(); - addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1); - addVirtualDisplay(secondDevice, DISPLAY_ID_2); + addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1, Display.FLAG_TRUSTED); + addVirtualDisplay(secondDevice, DISPLAY_ID_2, Display.FLAG_TRUSTED); mDeviceImpl.createVirtualKeyboard(firstKeyboardConfig, BINDER); secondDevice.createVirtualKeyboard(secondKeyboardConfig, secondBinder); @@ -910,11 +926,24 @@ public class VirtualDeviceManagerServiceTest { } @Test - public void onVirtualDisplayCreatedLocked_wakeLockIsAcquired() throws RemoteException { + public void onVirtualDisplayCreatedLocked_notTrustedDisplay_noWakeLockIsAcquired() + throws RemoteException { verify(mIPowerManagerMock, never()).acquireWakeLock(any(Binder.class), anyInt(), nullable(String.class), nullable(String.class), nullable(WorkSource.class), nullable(String.class), anyInt(), eq(null)); addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1); + TestableLooper.get(this).processAllMessages(); + verify(mIPowerManagerMock, never()).acquireWakeLock(any(Binder.class), anyInt(), + nullable(String.class), nullable(String.class), nullable(WorkSource.class), + nullable(String.class), anyInt(), eq(null)); + } + + @Test + public void onVirtualDisplayCreatedLocked_wakeLockIsAcquired() throws RemoteException { + verify(mIPowerManagerMock, never()).acquireWakeLock(any(Binder.class), anyInt(), + nullable(String.class), nullable(String.class), nullable(WorkSource.class), + nullable(String.class), anyInt(), eq(null)); + addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1, Display.FLAG_TRUSTED); verify(mIPowerManagerMock).acquireWakeLock(any(Binder.class), anyInt(), nullable(String.class), nullable(String.class), nullable(WorkSource.class), nullable(String.class), eq(DISPLAY_ID_1), eq(null)); @@ -923,7 +952,7 @@ public class VirtualDeviceManagerServiceTest { @Test public void onVirtualDisplayCreatedLocked_duplicateCalls_onlyOneWakeLockIsAcquired() throws RemoteException { - addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1); + addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1, Display.FLAG_TRUSTED); assertThrows(IllegalStateException.class, () -> addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1)); TestableLooper.get(this).processAllMessages(); @@ -934,7 +963,7 @@ public class VirtualDeviceManagerServiceTest { @Test public void onVirtualDisplayRemovedLocked_wakeLockIsReleased() throws RemoteException { - addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1); + addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1, Display.FLAG_TRUSTED); ArgumentCaptor<IBinder> wakeLockCaptor = ArgumentCaptor.forClass(IBinder.class); TestableLooper.get(this).processAllMessages(); verify(mIPowerManagerMock).acquireWakeLock(wakeLockCaptor.capture(), @@ -949,7 +978,7 @@ public class VirtualDeviceManagerServiceTest { @Test public void addVirtualDisplay_displayNotReleased_wakeLockIsReleased() throws RemoteException { - addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1); + addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1, Display.FLAG_TRUSTED); ArgumentCaptor<IBinder> wakeLockCaptor = ArgumentCaptor.forClass(IBinder.class); TestableLooper.get(this).processAllMessages(); verify(mIPowerManagerMock).acquireWakeLock(wakeLockCaptor.capture(), @@ -970,24 +999,52 @@ public class VirtualDeviceManagerServiceTest { } @Test + public void createVirtualDpad_untrustedDisplay_failsSecurityException() { + addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1); + assertThrows(SecurityException.class, + () -> mDeviceImpl.createVirtualDpad(DPAD_CONFIG, BINDER)); + } + + @Test public void createVirtualKeyboard_noDisplay_failsSecurityException() { assertThrows(SecurityException.class, () -> mDeviceImpl.createVirtualKeyboard(KEYBOARD_CONFIG, BINDER)); } @Test + public void createVirtualKeyboard_untrustedDisplay_failsSecurityException() { + addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1); + assertThrows(SecurityException.class, + () -> mDeviceImpl.createVirtualKeyboard(KEYBOARD_CONFIG, BINDER)); + } + + @Test public void createVirtualMouse_noDisplay_failsSecurityException() { assertThrows(SecurityException.class, () -> mDeviceImpl.createVirtualMouse(MOUSE_CONFIG, BINDER)); } @Test + public void createVirtualMouse_untrustedDisplay_failsSecurityException() { + addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1); + assertThrows(SecurityException.class, + () -> mDeviceImpl.createVirtualMouse(MOUSE_CONFIG, BINDER)); + } + + @Test public void createVirtualTouchscreen_noDisplay_failsSecurityException() { assertThrows(SecurityException.class, () -> mDeviceImpl.createVirtualTouchscreen(TOUCHSCREEN_CONFIG, BINDER)); } @Test + public void createVirtualTouchscreen_untrustedDisplay_failsSecurityException() { + addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1); + assertThrows(SecurityException.class, + () -> mDeviceImpl.createVirtualTouchscreen(TOUCHSCREEN_CONFIG, BINDER)); + } + + @Test public void createVirtualTouchscreen_zeroDisplayDimension_failsIllegalArgumentException() { assertThrows(IllegalArgumentException.class, () -> new VirtualTouchscreenConfig.Builder( @@ -1003,7 +1060,7 @@ public class VirtualDeviceManagerServiceTest { @Test public void createVirtualTouchscreen_positiveDisplayDimension_successful() { - addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1); + addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1, Display.FLAG_TRUSTED); VirtualTouchscreenConfig positiveConfig = new VirtualTouchscreenConfig.Builder( /* touchscrenWidth= */ 600, /* touchscreenHeight= */ 800) @@ -1026,6 +1083,14 @@ public class VirtualDeviceManagerServiceTest { } @Test + public void createVirtualNavigationTouchpad_untrustedDisplay_failsSecurityException() { + addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1); + assertThrows(SecurityException.class, + () -> mDeviceImpl.createVirtualNavigationTouchpad(NAVIGATION_TOUCHPAD_CONFIG, + BINDER)); + } + + @Test public void createVirtualNavigationTouchpad_zeroDisplayDimension_failsWithException() { assertThrows(IllegalArgumentException.class, () -> new VirtualNavigationTouchpadConfig.Builder( @@ -1041,7 +1106,7 @@ public class VirtualDeviceManagerServiceTest { @Test public void createVirtualNavigationTouchpad_positiveDisplayDimension_successful() { - addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1); + addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1, Display.FLAG_TRUSTED); VirtualNavigationTouchpadConfig positiveConfig = new VirtualNavigationTouchpadConfig.Builder( /* touchpadHeight= */ 50, /* touchpadWidth= */ 50) @@ -1130,7 +1195,7 @@ public class VirtualDeviceManagerServiceTest { @Test public void createVirtualDpad_hasDisplay_obtainFileDescriptor() { - addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1); + addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1, Display.FLAG_TRUSTED); mDeviceImpl.createVirtualDpad(DPAD_CONFIG, BINDER); assertWithMessage("Virtual dpad should register fd when the display matches").that( mInputController.getInputDeviceDescriptors()).isNotEmpty(); @@ -1140,7 +1205,7 @@ public class VirtualDeviceManagerServiceTest { @Test public void createVirtualKeyboard_hasDisplay_obtainFileDescriptor() { - addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1); + addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1, Display.FLAG_TRUSTED); mDeviceImpl.createVirtualKeyboard(KEYBOARD_CONFIG, BINDER); assertWithMessage("Virtual keyboard should register fd when the display matches").that( mInputController.getInputDeviceDescriptors()).isNotEmpty(); @@ -1150,7 +1215,7 @@ public class VirtualDeviceManagerServiceTest { @Test public void createVirtualKeyboard_keyboardCreated_localeUpdated() { - addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1); + addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1, Display.FLAG_TRUSTED); mDeviceImpl.createVirtualKeyboard(KEYBOARD_CONFIG, BINDER); assertWithMessage("Virtual keyboard should register fd when the display matches") .that(mInputController.getInputDeviceDescriptors()) @@ -1171,7 +1236,7 @@ public class VirtualDeviceManagerServiceTest { .setAssociatedDisplayId(DISPLAY_ID_1) .build(); - addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1); + addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1, Display.FLAG_TRUSTED); mDeviceImpl.createVirtualKeyboard(configWithoutExplicitLayoutInfo, BINDER); assertWithMessage("Virtual keyboard should register fd when the display matches") .that(mInputController.getInputDeviceDescriptors()) @@ -1192,7 +1257,7 @@ public class VirtualDeviceManagerServiceTest { @Test public void createVirtualMouse_hasDisplay_obtainFileDescriptor() { - addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1); + addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1, Display.FLAG_TRUSTED); mDeviceImpl.createVirtualMouse(MOUSE_CONFIG, BINDER); assertWithMessage("Virtual mouse should register fd when the display matches").that( mInputController.getInputDeviceDescriptors()).isNotEmpty(); @@ -1202,7 +1267,7 @@ public class VirtualDeviceManagerServiceTest { @Test public void createVirtualTouchscreen_hasDisplay_obtainFileDescriptor() { - addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1); + addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1, Display.FLAG_TRUSTED); mDeviceImpl.createVirtualTouchscreen(TOUCHSCREEN_CONFIG, BINDER); assertWithMessage("Virtual touchscreen should register fd when the display matches").that( mInputController.getInputDeviceDescriptors()).isNotEmpty(); @@ -1212,7 +1277,7 @@ public class VirtualDeviceManagerServiceTest { @Test public void createVirtualNavigationTouchpad_hasDisplay_obtainFileDescriptor() { - addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1); + addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1, Display.FLAG_TRUSTED); mDeviceImpl.createVirtualNavigationTouchpad(NAVIGATION_TOUCHPAD_CONFIG, BINDER); assertWithMessage("Virtual navigation touchpad should register fd when the display matches") .that( @@ -1472,9 +1537,9 @@ public class VirtualDeviceManagerServiceTest { @Test public void setShowPointerIcon_setsValueForAllDisplays() { - addVirtualDisplay(mDeviceImpl, 1); - addVirtualDisplay(mDeviceImpl, 2); - addVirtualDisplay(mDeviceImpl, 3); + addVirtualDisplay(mDeviceImpl, 1, Display.FLAG_TRUSTED); + addVirtualDisplay(mDeviceImpl, 2, Display.FLAG_TRUSTED); + addVirtualDisplay(mDeviceImpl, 3, Display.FLAG_TRUSTED); VirtualMouseConfig config1 = new VirtualMouseConfig.Builder() .setAssociatedDisplayId(1) .setInputDeviceName(DEVICE_NAME_1) @@ -1507,6 +1572,14 @@ public class VirtualDeviceManagerServiceTest { } @Test + public void setShowPointerIcon_untrustedDisplay_pointerIconIsAlwaysShown() { + addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1); + clearInvocations(mInputManagerInternalMock); + mDeviceImpl.setShowPointerIcon(false); + verify(mInputManagerInternalMock, times(0)).setPointerIconVisible(eq(false), anyInt()); + } + + @Test public void openNonBlockedAppOnVirtualDisplay_doesNotStartBlockedAlertActivity() { addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1); GenericWindowPolicyController gwpc = mDeviceImpl.getDisplayWindowPolicyControllerForTest( @@ -1968,15 +2041,20 @@ public class VirtualDeviceManagerServiceTest { } private void addVirtualDisplay(VirtualDeviceImpl virtualDevice, int displayId) { + addVirtualDisplay(virtualDevice, displayId, /* flags= */ 0); + } + + private void addVirtualDisplay(VirtualDeviceImpl virtualDevice, int displayId, int flags) { when(mDisplayManagerInternalMock.createVirtualDisplay(any(), eq(mVirtualDisplayCallback), eq(virtualDevice), any(), any())).thenReturn(displayId); - virtualDevice.createVirtualDisplay(VIRTUAL_DISPLAY_CONFIG, mVirtualDisplayCallback); final String uniqueId = UNIQUE_ID + displayId; doAnswer(inv -> { final DisplayInfo displayInfo = new DisplayInfo(); displayInfo.uniqueId = uniqueId; + displayInfo.flags = flags; return displayInfo; }).when(mDisplayManagerInternalMock).getDisplayInfo(eq(displayId)); + virtualDevice.createVirtualDisplay(VIRTUAL_DISPLAY_CONFIG, mVirtualDisplayCallback); mInputManagerMockHelper.addDisplayIdMapping(uniqueId, displayId); } diff --git a/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java b/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java index 9df7a3612a92..1d075401832d 100644 --- a/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java +++ b/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java @@ -20,6 +20,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.anyLong; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; @@ -33,6 +34,11 @@ import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.os.Build; +import android.os.Process; + +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; +import android.platform.test.flag.junit.SetFlagsRule; import androidx.test.runner.AndroidJUnit4; @@ -43,6 +49,7 @@ import com.android.internal.compat.CompatibilityChangeInfo; import com.android.server.LocalServices; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -55,6 +62,8 @@ import java.util.Set; public class PlatformCompatTest { private static final String PACKAGE_NAME = "my.package"; + @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + @Mock private Context mContext; @Mock @@ -441,4 +450,79 @@ public class PlatformCompatTest { assertThat(mPlatformCompat.isChangeEnabled(3L, systemAppInfo)).isTrue(); verify(mChangeReporter).reportChange(123, 3L, ChangeReporter.STATE_ENABLED, true, false); } + + @DisableFlags(Flags.FLAG_SYSTEM_UID_TARGET_SYSTEM_SDK) + @Test + public void testSharedSystemUidFlagOff() throws Exception { + testSharedSystemUid(false); + } + + @EnableFlags(Flags.FLAG_SYSTEM_UID_TARGET_SYSTEM_SDK) + @Test + public void testSharedSystemUidFlagOn() throws Exception { + testSharedSystemUid(true); + } + + private void testSharedSystemUid(Boolean expectSystemUidTargetSystemSdk) throws Exception { + final String systemUidPackageNameTargetsR = "systemuid.package1"; + final String systemUidPackageNameTargetsQ = "systemuid.package2"; + final String nonSystemUidPackageNameTargetsR = "nonsystemuid.package1"; + final String nonSystemUidPackageNameTargetsQ = "nonsystemuid.package2"; + final int nonSystemUid = 123; + + mCompatConfig = + CompatConfigBuilder.create(mBuildClassifier, mContext) + .addEnableSinceSdkChangeWithId(Build.VERSION_CODES.R, 1L) + .build(); + mCompatConfig.forceNonDebuggableFinalForTest(true); + mPlatformCompat = + new PlatformCompat(mContext, mCompatConfig, mBuildClassifier, mChangeReporter); + + ApplicationInfo systemUidAppInfo1 = ApplicationInfoBuilder.create() + .withPackageName(systemUidPackageNameTargetsR) + .withUid(Process.SYSTEM_UID) + .withTargetSdk(Build.VERSION_CODES.R) + .build(); + when(mPackageManagerInternal.getApplicationInfo( + eq(systemUidPackageNameTargetsR), anyLong(), anyInt(), anyInt())) + .thenReturn(systemUidAppInfo1); + + ApplicationInfo systemUidAppInfo2 = ApplicationInfoBuilder.create() + .withPackageName(systemUidPackageNameTargetsQ) + .withUid(Process.SYSTEM_UID) + .withTargetSdk(Build.VERSION_CODES.Q) + .build(); + when(mPackageManagerInternal.getApplicationInfo( + eq(systemUidPackageNameTargetsQ), anyLong(), anyInt(), anyInt())) + .thenReturn(systemUidAppInfo2); + + ApplicationInfo nonSystemUidAppInfo1 = ApplicationInfoBuilder.create() + .withPackageName(nonSystemUidPackageNameTargetsR) + .withUid(nonSystemUid) + .withTargetSdk(Build.VERSION_CODES.R) + .build(); + when(mPackageManagerInternal.getApplicationInfo( + eq(nonSystemUidPackageNameTargetsR), anyLong(), anyInt(), anyInt())) + .thenReturn(nonSystemUidAppInfo1); + + ApplicationInfo nonSystemUidAppInfo2 = ApplicationInfoBuilder.create() + .withPackageName(nonSystemUidPackageNameTargetsQ) + .withUid(nonSystemUid) + .withTargetSdk(Build.VERSION_CODES.Q) + .build(); + when(mPackageManagerInternal.getApplicationInfo( + eq(nonSystemUidPackageNameTargetsQ), anyLong(), anyInt(), anyInt())) + .thenReturn(nonSystemUidAppInfo2); + + when(mPackageManager.getPackagesForUid(eq(Process.SYSTEM_UID))) + .thenReturn(new String[] {systemUidPackageNameTargetsR, systemUidPackageNameTargetsQ}); + when(mPackageManager.getPackagesForUid(eq(nonSystemUid))) + .thenReturn(new String[] { + nonSystemUidPackageNameTargetsR, nonSystemUidPackageNameTargetsQ + }); + + assertThat(mPlatformCompat.isChangeEnabledByUid(1L, Process.SYSTEM_UID)) + .isEqualTo(expectSystemUidTargetSystemSdk); + assertThat(mPlatformCompat.isChangeEnabledByUid(1L, nonSystemUid)).isFalse(); + } } diff --git a/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java b/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java index e4436966ae03..c51261f40ed5 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java @@ -52,6 +52,7 @@ import android.graphics.Rect; import android.os.IBinder; import android.platform.test.annotations.Presubmit; import android.view.ContentRecordingSession; +import android.view.Display; import android.view.DisplayInfo; import android.view.Gravity; import android.view.SurfaceControl; @@ -93,9 +94,11 @@ public class ContentRecorderTests extends WindowTestsBase { private boolean mHandleAnisotropicDisplayMirroring = false; @Before public void setUp() { + mDisplayInfo.type = Display.TYPE_VIRTUAL; MockitoAnnotations.initMocks(this); doReturn(INVALID_DISPLAY).when(mWm.mDisplayManagerInternal).getDisplayIdToMirror(anyInt()); + doReturn(false).when(mWm.mDisplayManagerInternal).isDisplayReadyForMirroring(anyInt()); // Skip unnecessary operations of relayout. spyOn(mWm.mWindowPlacerLocked); @@ -163,6 +166,25 @@ public class ContentRecorderTests extends WindowTestsBase { } @Test + public void testUpdateRecording_externalDisplayWithoutUserConfirmation() { + mDisplayInfo.type = Display.TYPE_EXTERNAL; + defaultInit(); + mContentRecorder.setContentRecordingSession(mDisplaySession); + mContentRecorder.updateRecording(); + assertThat(mContentRecorder.isCurrentlyRecording()).isFalse(); + } + + @Test + public void testUpdateRecording_externalDisplayWithUserConfirmation() { + doReturn(true).when(mWm.mDisplayManagerInternal).isDisplayReadyForMirroring(anyInt()); + mDisplayInfo.type = Display.TYPE_EXTERNAL; + defaultInit(); + mContentRecorder.setContentRecordingSession(mDisplaySession); + mContentRecorder.updateRecording(); + assertThat(mContentRecorder.isCurrentlyRecording()).isTrue(); + } + + @Test public void testUpdateRecording_display_invalidDisplayIdToMirror() { defaultInit(); ContentRecordingSession session = ContentRecordingSession.createDisplaySession( diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationImmersiveAppCompatPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationImmersiveAppCompatPolicyTests.java index 5e8f347c0c6e..c8fc4822259e 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationImmersiveAppCompatPolicyTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationImmersiveAppCompatPolicyTests.java @@ -26,7 +26,6 @@ import static android.content.res.Configuration.ORIENTATION_UNDEFINED; 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.spy; import static com.android.dx.mockito.inline.extended.ExtendedMockito.when; import static org.junit.Assert.assertFalse; @@ -73,7 +72,6 @@ public class DisplayRotationImmersiveAppCompatPolicyTests extends WindowTestsBas when(mMockWindowState.getRequestedVisibleTypes()).thenReturn(0); when(mMockActivityRecord.findMainWindow()).thenReturn(mMockWindowState); - spy(mDisplayContent); doReturn(mMockActivityRecord).when(mDisplayContent).topRunningActivity(); when(mDisplayContent.getIgnoreOrientationRequest()).thenReturn(true); diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java index 49ca6f34d2d9..44de65a009ff 100644 --- a/telephony/java/android/telephony/satellite/SatelliteManager.java +++ b/telephony/java/android/telephony/satellite/SatelliteManager.java @@ -1965,13 +1965,14 @@ public final class SatelliteManager { } /** - * Inform whether the device is aligned with the satellite for demo mode. + * Inform whether the device is aligned with the satellite in both real and demo mode. * - * Framework can send datagram to modem only when device is aligned with the satellite. - * This method helps framework to simulate the experience of sending datagram over satellite. + * In demo mode, framework will send datagram to modem only when device is aligned with + * the satellite. This method helps framework to simulate the experience of sending datagram + * over satellite. * - * @param isAligned {@true} Device is aligned with the satellite for demo mode - * {@false} Device is not aligned with the satellite for demo mode + * @param isAligned {code @true} Device is aligned with the satellite + * {code @false} Device is not aligned with the satellite * * @throws SecurityException if the caller doesn't have required permission. * @throws IllegalStateException if the Telephony process is not currently available. diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl index 61f01461232f..231c8f551389 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -2977,10 +2977,10 @@ interface ITelephony { void requestTimeForNextSatelliteVisibility(in ResultReceiver receiver); /** - * Inform whether the device is aligned with the satellite within in margin for demo mode. + * Inform whether the device is aligned with the satellite in both real and demo mode. * - * @param isAligned {@true} Device is aligned with the satellite for demo mode - * {@false} Device is not aligned with the satellite for demo mode + * @param isAligned {@true} Device is aligned with the satellite. + * {@false} Device is not aligned with the satellite. */ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(" + "android.Manifest.permission.SATELLITE_COMMUNICATION)") diff --git a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/LetterboxAppHelper.kt b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/LetterboxAppHelper.kt index 634b6eedd7e6..8d27c1d1dfd1 100644 --- a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/LetterboxAppHelper.kt +++ b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/LetterboxAppHelper.kt @@ -33,9 +33,9 @@ class LetterboxAppHelper @JvmOverloads constructor( instr: Instrumentation, - launcherName: String = ActivityOptions.NonResizeablePortraitActivity.LABEL, + launcherName: String = ActivityOptions.NonResizeableFixedAspectRatioPortraitActivity.LABEL, component: ComponentNameMatcher = - ActivityOptions.NonResizeablePortraitActivity.COMPONENT.toFlickerComponent() + ActivityOptions.NonResizeableFixedAspectRatioPortraitActivity.COMPONENT.toFlickerComponent() ) : StandardAppHelper(instr, launcherName, component) { private val gestureHelper: GestureHelper = GestureHelper(instrumentation) diff --git a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml index f891606f0066..f2e34257ef01 100644 --- a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml +++ b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml @@ -115,6 +115,19 @@ <category android:name="android.intent.category.LAUNCHER"/> </intent-filter> </activity> + <activity android:name=".NonResizeableFixedAspectRatioPortraitActivity" + android:theme="@style/CutoutNever" + android:resizeableActivity="false" + android:screenOrientation="portrait" + android:minAspectRatio="1.77" + android:taskAffinity="com.android.server.wm.flicker.testapp.NonResizeableFixedAspectRatioPortraitActivity" + android:label="NonResizeableFixedAspectRatioPortraitActivity" + android:exported="true"> + <intent-filter> + <action android:name="android.intent.action.MAIN"/> + <category android:name="android.intent.category.LAUNCHER"/> + </intent-filter> + </activity> <activity android:name=".StartMediaProjectionActivity" android:theme="@style/CutoutNever" android:resizeableActivity="false" @@ -143,6 +156,7 @@ <activity android:name=".LaunchTransparentActivity" android:resizeableActivity="false" android:screenOrientation="portrait" + android:minAspectRatio="1.77" android:theme="@style/OptOutEdgeToEdge" android:taskAffinity="com.android.server.wm.flicker.testapp.LaunchTransparentActivity" android:label="LaunchTransparentActivity" diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java index e4de2c574553..73625da9dfa5 100644 --- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java @@ -85,6 +85,12 @@ public class ActivityOptions { FLICKER_APP_PACKAGE + ".NonResizeablePortraitActivity"); } + public static class NonResizeableFixedAspectRatioPortraitActivity { + public static final String LABEL = "NonResizeableFixedAspectRatioPortraitActivity"; + public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE, + FLICKER_APP_PACKAGE + ".NonResizeableFixedAspectRatioPortraitActivity"); + } + public static class StartMediaProjectionActivity { public static final String LABEL = "StartMediaProjectionActivity"; public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE, diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/NonResizeableFixedAspectRatioPortraitActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/NonResizeableFixedAspectRatioPortraitActivity.java new file mode 100644 index 000000000000..be38c259d00d --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/NonResizeableFixedAspectRatioPortraitActivity.java @@ -0,0 +1,28 @@ +/* + * 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.flicker.testapp; + +import android.app.Activity; +import android.os.Bundle; + +public class NonResizeableFixedAspectRatioPortraitActivity extends Activity { + @Override + protected void onCreate(Bundle icicle) { + super.onCreate(icicle); + setContentView(R.layout.activity_non_resizeable); + } +} |