diff options
312 files changed, 6775 insertions, 3106 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp index f249884cb1a0..458d1dfeadb8 100644 --- a/AconfigFlags.bp +++ b/AconfigFlags.bp @@ -115,6 +115,7 @@ aconfig_declarations_group { "framework-jobscheduler-job.flags-aconfig-java", "framework_graphics_flags_java_lib", "hwui_flags_java_lib", + "icu_exported_aconfig_flags_lib", "interaction_jank_monitor_flags_lib", "keystore2_flags_java-framework", "libcore_exported_aconfig_flags_lib", @@ -163,6 +164,14 @@ java_aconfig_library { defaults: ["framework-minus-apex-aconfig-java-defaults"], } +// ICU +java_aconfig_library { + name: "icu_exported_aconfig_flags_lib", + aconfig_declarations: "icu_aconfig_flags", + mode: "exported", + defaults: ["framework-minus-apex-aconfig-java-defaults"], +} + // Camera java_aconfig_library { name: "camera_platform_flags_core_java_lib", @@ -1409,6 +1418,7 @@ java_aconfig_library { // Content Capture aconfig_declarations { name: "android.view.contentcapture.flags-aconfig", + exportable: true, package: "android.view.contentcapture.flags", container: "system", srcs: ["core/java/android/view/contentcapture/flags/*.aconfig"], diff --git a/api/StubLibraries.bp b/api/StubLibraries.bp index a949ff5a331b..787fdee6ee16 100644 --- a/api/StubLibraries.bp +++ b/api/StubLibraries.bp @@ -1023,7 +1023,6 @@ stubs_defaults { api_levels_annotations_enabled: true, api_levels_annotations_dirs: [ "sdk-dir", - "api-versions-jars-dir", ], } diff --git a/cmds/am/am.sh b/cmds/am/am.sh index 54c2d394be2c..76ec214cb446 100755 --- a/cmds/am/am.sh +++ b/cmds/am/am.sh @@ -1,5 +1,8 @@ #!/system/bin/sh +# set to top-app process group +settaskprofile $$ SCHED_SP_TOP_APP >/dev/null 2>&1 || true + if [ "$1" != "instrument" ] ; then cmd activity "$@" else diff --git a/cmds/uinput/README.md b/cmds/uinput/README.md index 6138388b30c7..5734c847be87 100644 --- a/cmds/uinput/README.md +++ b/cmds/uinput/README.md @@ -59,7 +59,7 @@ Register a new uinput device | `name` | string | Device name | | `vid` | 16-bit integer | Vendor ID | | `pid` | 16-bit integer | Product ID | -| `bus` | string | Bus that device should use | +| `bus` | string | The bus to report | | `port` | string | `phys` value to report | | `configuration` | object array | uinput device configuration| | `ff_effects_max` | integer | `ff_effects_max` value | @@ -68,8 +68,11 @@ Register a new uinput device `id` is used for matching the subsequent commands to a specific device to avoid ambiguity when multiple devices are registered. -`bus` is used to determine how the uinput device is connected to the host. The options are `"usb"` -and `"bluetooth"`. +`bus` specifies the bus that the kernel should report the device as being connected to. The most +common values are `"usb"` and `"bluetooth"`, but any bus with a `BUS_…` constant in the [Linux +kernel's input.h][input.h] can be specified using the part of its identifier after `BUS_`. For +example, to specify the SPI bus type (`BUS_SPI` in the kernel header), use `"spi"` (or `"SPI"`, +since it's case-insensitive). Device configuration is used to configure the uinput device. The `type` field provides a `UI_SET_*` control code as an integer value or a string label (e.g. `"UI_SET_EVBIT"`), and data is a vector of @@ -137,6 +140,7 @@ Example: } ``` +[input.h]: https://source.chromium.org/chromiumos/chromiumos/codesearch/+/main:src/third_party/kernel/upstream/include/uapi/linux/input.h?q=BUS_ [struct input_absinfo]: https://cs.android.com/android/platform/superproject/main/+/main:bionic/libc/kernel/uapi/linux/input.h?q=%22struct%20input_absinfo%22 ##### Waiting for registration diff --git a/core/api/system-current.txt b/core/api/system-current.txt index f44448a8c311..15ae79e34061 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -3165,6 +3165,11 @@ package android.app.wallpaper { method @NonNull public android.util.SparseArray<android.graphics.Rect> getCropHints(); } + public static final class WallpaperDescription.Builder { + method @NonNull public android.app.wallpaper.WallpaperDescription.Builder setCropHints(@NonNull java.util.Map<android.graphics.Point,android.graphics.Rect>); + method @NonNull public android.app.wallpaper.WallpaperDescription.Builder setCropHints(@NonNull android.util.SparseArray<android.graphics.Rect>); + } + } package android.app.wallpapereffectsgeneration { @@ -3457,7 +3462,7 @@ package android.companion.virtual { method public void removeSoundEffectListener(@NonNull android.companion.virtual.VirtualDeviceManager.SoundEffectListener); method public void setDevicePolicy(int, int); method @FlaggedApi("android.companion.virtualdevice.flags.activity_control_api") public void setDevicePolicy(int, int, int); - method @FlaggedApi("android.companion.virtual.flags.vdm_custom_ime") public void setDisplayImePolicy(int, int); + method public void setDisplayImePolicy(int, int); method public void setShowPointerIcon(boolean); method public void unregisterIntentInterceptor(@NonNull android.companion.virtual.VirtualDeviceManager.IntentInterceptorCallback); method @FlaggedApi("android.companion.virtualdevice.flags.device_aware_display_power") public void wakeUp(); @@ -3476,7 +3481,7 @@ package android.companion.virtual { method public int getDevicePolicy(int); method @FlaggedApi("android.companion.virtualdevice.flags.device_aware_display_power") @NonNull public java.time.Duration getDimDuration(); method @Nullable public android.content.ComponentName getHomeComponent(); - method @FlaggedApi("android.companion.virtual.flags.vdm_custom_ime") @Nullable public android.content.ComponentName getInputMethodComponent(); + method @Nullable public android.content.ComponentName getInputMethodComponent(); method public int getLockState(); method @Nullable public String getName(); method @FlaggedApi("android.companion.virtualdevice.flags.device_aware_display_power") @NonNull public java.time.Duration getScreenOffTimeout(); @@ -3515,7 +3520,7 @@ package android.companion.virtual { method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setDevicePolicy(int, int); method @FlaggedApi("android.companion.virtualdevice.flags.device_aware_display_power") @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setDimDuration(@NonNull java.time.Duration); method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setHomeComponent(@Nullable android.content.ComponentName); - method @FlaggedApi("android.companion.virtual.flags.vdm_custom_ime") @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setInputMethodComponent(@Nullable android.content.ComponentName); + method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setInputMethodComponent(@Nullable android.content.ComponentName); method @NonNull @RequiresPermission(value=android.Manifest.permission.ADD_ALWAYS_UNLOCKED_DISPLAY, conditional=true) public android.companion.virtual.VirtualDeviceParams.Builder setLockState(int); method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setName(@NonNull String); method @FlaggedApi("android.companion.virtualdevice.flags.device_aware_display_power") @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setScreenOffTimeout(@NonNull java.time.Duration); @@ -19038,7 +19043,7 @@ package android.view.displayhash { package android.view.inputmethod { public final class InputMethodInfo implements android.os.Parcelable { - method @FlaggedApi("android.companion.virtual.flags.vdm_custom_ime") public boolean isVirtualDeviceOnly(); + method public boolean isVirtualDeviceOnly(); } public final class InputMethodManager { diff --git a/core/api/test-current.txt b/core/api/test-current.txt index eb483c82b450..0126db70296c 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -881,15 +881,6 @@ package android.app.usage { } -package android.app.wallpaper { - - public static final class WallpaperDescription.Builder { - method @NonNull public android.app.wallpaper.WallpaperDescription.Builder setCropHints(@NonNull java.util.Map<android.graphics.Point,android.graphics.Rect>); - method @NonNull public android.app.wallpaper.WallpaperDescription.Builder setCropHints(@NonNull android.util.SparseArray<android.graphics.Rect>); - } - -} - package android.appwidget { public class AppWidgetManager { diff --git a/core/java/android/app/DreamManager.java b/core/java/android/app/DreamManager.java index 4ac40a1f77b2..c597a9dcae7e 100644 --- a/core/java/android/app/DreamManager.java +++ b/core/java/android/app/DreamManager.java @@ -234,4 +234,19 @@ public class DreamManager { throw e.rethrowFromSystemServer(); } } + + /** + * Notifies dream manager of device postured state, which may affect dream enablement. + * + * @hide + */ + @FlaggedApi(Flags.FLAG_ALLOW_DREAM_WHEN_POSTURED) + @RequiresPermission(android.Manifest.permission.WRITE_DREAM_STATE) + public void setDevicePostured(boolean isPostured) { + try { + mService.setDevicePostured(isPostured); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } } diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl index 1738a92b7672..4a78d01783f8 100644 --- a/core/java/android/app/INotificationManager.aidl +++ b/core/java/android/app/INotificationManager.aidl @@ -271,6 +271,6 @@ interface INotificationManager int[] getAllowedAdjustmentKeyTypes(); void setAssistantAdjustmentKeyTypeState(int type, boolean enabled); - int[] getAllowedAdjustmentKeyTypesForPackage(String pkg); - void setAssistantAdjustmentKeyTypeStateForPackage(String pkg, int type, boolean enabled); + String[] getTypeAdjustmentDeniedPackages(); + void setTypeAdjustmentForPackageState(String pkg, boolean enabled); } diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java index 24f2495d8f09..e5d80de24f2b 100644 --- a/core/java/android/app/NotificationManager.java +++ b/core/java/android/app/NotificationManager.java @@ -2163,12 +2163,10 @@ public class NotificationManager { * @hide */ @FlaggedApi(android.app.Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI) - public void setAssistantAdjustmentKeyTypeStateForPackage(@NonNull String pkg, - @Adjustment.Types int type, - boolean enabled) { + public void setTypeAdjustmentForPackageState(@NonNull String pkg, boolean enabled) { INotificationManager service = service(); try { - service.setAssistantAdjustmentKeyTypeStateForPackage(pkg, type, enabled); + service.setTypeAdjustmentForPackageState(pkg, enabled); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/app/supervision/ISupervisionAppService.aidl b/core/java/android/app/supervision/ISupervisionAppService.aidl index 033998fc4a5b..207fab90d015 100644 --- a/core/java/android/app/supervision/ISupervisionAppService.aidl +++ b/core/java/android/app/supervision/ISupervisionAppService.aidl @@ -20,4 +20,6 @@ package android.app.supervision; * @hide */ interface ISupervisionAppService { + void onEnabled(); + void onDisabled(); } diff --git a/core/java/android/app/supervision/SupervisionAppService.java b/core/java/android/app/supervision/SupervisionAppService.java index 4468c78cbd34..4530be5c270a 100644 --- a/core/java/android/app/supervision/SupervisionAppService.java +++ b/core/java/android/app/supervision/SupervisionAppService.java @@ -28,10 +28,29 @@ import android.os.IBinder; */ public class SupervisionAppService extends Service { private final ISupervisionAppService mBinder = new ISupervisionAppService.Stub() { + @Override + public void onEnabled() { + SupervisionAppService.this.onEnabled(); + } + + @Override + public void onDisabled() { + SupervisionAppService.this.onDisabled(); + } }; @Override public final IBinder onBind(Intent intent) { return mBinder.asBinder(); } + + /** + * Called when supervision is enabled. + */ + public void onEnabled() {} + + /** + * Called when supervision is disabled. + */ + public void onDisabled() {} } diff --git a/core/java/android/app/supervision/SupervisionManager.java b/core/java/android/app/supervision/SupervisionManager.java index a5b58f968c27..92241f3634e8 100644 --- a/core/java/android/app/supervision/SupervisionManager.java +++ b/core/java/android/app/supervision/SupervisionManager.java @@ -34,6 +34,35 @@ public class SupervisionManager { private final Context mContext; private final ISupervisionManager mService; + /** + * Activity action: ask the human user to enable supervision for this user. Only the app that + * holds the {@code SYSTEM_SUPERVISION} role can launch this intent. + * + * <p>The intent must be invoked via {@link Activity#startActivityForResult} to receive the + * result of whether or not the user approved the action. If approved, the result will be {@link + * Activity#RESULT_OK}. + * + * <p>If supervision is already enabled, the operation will return a failure result. + * + * @hide + */ + public static final String ACTION_ENABLE_SUPERVISION = "android.app.action.ENABLE_SUPERVISION"; + + /** + * Activity action: ask the human user to disable supervision for this user. Only the app that + * holds the {@code SYSTEM_SUPERVISION} role can launch this intent. + * + * <p>The intent must be invoked via {@link Activity#startActivityForResult} to receive the + * result of whether or not the user approved the action. If approved, the result will be {@link + * Activity#RESULT_OK}. + * + * <p>If supervision is not enabled, the operation will return a failure result. + * + * @hide + */ + public static final String ACTION_DISABLE_SUPERVISION = + "android.app.action.DISABLE_SUPERVISION"; + /** @hide */ @UnsupportedAppUsage public SupervisionManager(Context context, ISupervisionManager service) { diff --git a/core/java/android/app/wallpaper/WallpaperDescription.java b/core/java/android/app/wallpaper/WallpaperDescription.java index 999a5da870ad..a13af7f1ddcd 100644 --- a/core/java/android/app/wallpaper/WallpaperDescription.java +++ b/core/java/android/app/wallpaper/WallpaperDescription.java @@ -19,9 +19,7 @@ package android.app.wallpaper; import static android.app.Flags.FLAG_LIVE_WALLPAPER_CONTENT_HANDLING; import android.annotation.FlaggedApi; -import android.annotation.SuppressLint; import android.annotation.SystemApi; -import android.annotation.TestApi; import android.app.WallpaperInfo; import android.app.WallpaperManager; import android.app.WallpaperManager.ScreenOrientation; @@ -514,8 +512,7 @@ public final class WallpaperDescription implements Parcelable { * @hide */ @NonNull - @TestApi - @SuppressLint("MissingGetterMatchingBuilder") + @SystemApi public Builder setCropHints(@NonNull Map<Point, Rect> cropHints) { mCropHints = new SparseArray<>(); cropHints.forEach( @@ -531,8 +528,7 @@ public final class WallpaperDescription implements Parcelable { * @hide */ @NonNull - @TestApi - @SuppressLint("MissingGetterMatchingBuilder") + @SystemApi public Builder setCropHints(@NonNull SparseArray<Rect> cropHints) { mCropHints = cropHints; return this; diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java index 99794d7f49fe..252db824c69f 100644 --- a/core/java/android/companion/virtual/VirtualDeviceManager.java +++ b/core/java/android/companion/virtual/VirtualDeviceManager.java @@ -1116,11 +1116,8 @@ public final class VirtualDeviceManager { * @throws SecurityException if the display is not owned by this device or is not * {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_TRUSTED trusted} */ - @FlaggedApi(Flags.FLAG_VDM_CUSTOM_IME) public void setDisplayImePolicy(int displayId, @WindowManager.DisplayImePolicy int policy) { - if (Flags.vdmCustomIme()) { - mVirtualDeviceInternal.setDisplayImePolicy(displayId, policy); - } + mVirtualDeviceInternal.setDisplayImePolicy(displayId, policy); } /** diff --git a/core/java/android/companion/virtual/VirtualDeviceParams.java b/core/java/android/companion/virtual/VirtualDeviceParams.java index 761e75bd9076..95dee9b72a88 100644 --- a/core/java/android/companion/virtual/VirtualDeviceParams.java +++ b/core/java/android/companion/virtual/VirtualDeviceParams.java @@ -440,7 +440,6 @@ public final class VirtualDeviceParams implements Parcelable { * * @see Builder#setInputMethodComponent */ - @FlaggedApi(Flags.FLAG_VDM_CUSTOM_IME) @Nullable public ComponentName getInputMethodComponent() { return mInputMethodComponent; @@ -945,7 +944,6 @@ public final class VirtualDeviceParams implements Parcelable { * @attr ref android.R.styleable#InputMethod_isVirtualDeviceOnly * @attr ref android.R.styleable#InputMethod_showInInputMethodPicker */ - @FlaggedApi(Flags.FLAG_VDM_CUSTOM_IME) @NonNull public Builder setInputMethodComponent(@Nullable ComponentName inputMethodComponent) { mInputMethodComponent = inputMethodComponent; diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java index bbfae8117b16..7cd2d31ac974 100644 --- a/core/java/android/content/res/AssetManager.java +++ b/core/java/android/content/res/AssetManager.java @@ -62,6 +62,7 @@ import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Objects; +import java.util.concurrent.atomic.AtomicInteger; /** * Provides access to an application's raw asset files; see {@link Resources} @@ -133,7 +134,7 @@ public final class AssetManager implements AutoCloseable { // Debug/reference counting implementation. @GuardedBy("this") private boolean mOpen = true; - @GuardedBy("this") private int mNumRefs = 1; + private AtomicInteger mNumRefs = new AtomicInteger(1); @GuardedBy("this") private HashMap<Long, RuntimeException> mRefStacks; private ResourcesLoader[] mLoaders; @@ -244,7 +245,7 @@ public final class AssetManager implements AutoCloseable { mObject = nativeCreate(); if (DEBUG_REFS) { - mNumRefs = 0; + mNumRefs.set(0); incRefsLocked(hashCode()); } @@ -260,7 +261,7 @@ public final class AssetManager implements AutoCloseable { private AssetManager(boolean sentinel) { mObject = nativeCreate(); if (DEBUG_REFS) { - mNumRefs = 0; + mNumRefs.set(0); incRefsLocked(hashCode()); } } @@ -324,7 +325,7 @@ public final class AssetManager implements AutoCloseable { } mOpen = false; - decRefsLocked(hashCode()); + decRefs(hashCode()); } } @@ -1235,9 +1236,7 @@ public final class AssetManager implements AutoCloseable { } void xmlBlockGone(int id) { - synchronized (this) { - decRefsLocked(id); - } + decRefs(id); } @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) @@ -1308,9 +1307,7 @@ public final class AssetManager implements AutoCloseable { } void releaseTheme(long themePtr) { - synchronized (this) { - decRefsLocked(themePtr); - } + decRefs(themePtr); } static long getThemeFreeFunction() { @@ -1332,7 +1329,7 @@ public final class AssetManager implements AutoCloseable { if (this != newAssetManager) { synchronized (this) { ensureValidLocked(); - decRefsLocked(themePtr); + decRefs(themePtr); } synchronized (newAssetManager) { newAssetManager.ensureValidLocked(); @@ -1364,8 +1361,8 @@ public final class AssetManager implements AutoCloseable { @Override protected void finalize() throws Throwable { - if (DEBUG_REFS && mNumRefs != 0) { - Log.w(TAG, "AssetManager " + this + " finalized with non-zero refs: " + mNumRefs); + if (DEBUG_REFS && mNumRefs.get() != 0) { + Log.w(TAG, "AssetManager " + this + " finalized with non-zero refs: " + mNumRefs.get()); if (mRefStacks != null) { for (RuntimeException e : mRefStacks.values()) { Log.w(TAG, "Reference from here", e); @@ -1473,9 +1470,7 @@ public final class AssetManager implements AutoCloseable { nativeAssetDestroy(mAssetNativePtr); mAssetNativePtr = 0; - synchronized (AssetManager.this) { - decRefsLocked(hashCode()); - } + decRefs(hashCode()); } } @@ -1680,19 +1675,25 @@ public final class AssetManager implements AutoCloseable { RuntimeException ex = new RuntimeException(); mRefStacks.put(id, ex); } - mNumRefs++; + mNumRefs.incrementAndGet(); } - @GuardedBy("this") - private void decRefsLocked(long id) { - if (DEBUG_REFS && mRefStacks != null) { - mRefStacks.remove(id); - } - mNumRefs--; - if (mNumRefs == 0 && mObject != 0) { - nativeDestroy(mObject); - mObject = 0; - mApkAssets = sEmptyApkAssets; + private void decRefs(long id) { + if (DEBUG_REFS) { + synchronized (this) { + if (mRefStacks != null) { + mRefStacks.remove(id); + } + } + } + if (mNumRefs.decrementAndGet() == 0) { + synchronized (this) { + if (mNumRefs.get() == 0 && mObject != 0) { + nativeDestroy(mObject); + mObject = 0; + mApkAssets = sEmptyApkAssets; + } + } } } diff --git a/core/java/android/credentials/flags.aconfig b/core/java/android/credentials/flags.aconfig index 2161e10337c9..430ed2b68342 100644 --- a/core/java/android/credentials/flags.aconfig +++ b/core/java/android/credentials/flags.aconfig @@ -144,3 +144,13 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + namespace: "credential_manager" + name: "fix_metric_duplication_emits" + description: "Fixes duplicate emits in the original metric emit system." + bug: "362994633" + metadata { + purpose: PURPOSE_BUGFIX + } +}
\ No newline at end of file diff --git a/core/java/android/database/sqlite/SQLiteOpenHelper.java b/core/java/android/database/sqlite/SQLiteOpenHelper.java index 88d69b665c87..030c883924a7 100644 --- a/core/java/android/database/sqlite/SQLiteOpenHelper.java +++ b/core/java/android/database/sqlite/SQLiteOpenHelper.java @@ -182,17 +182,10 @@ public abstract class SQLiteOpenHelper implements AutoCloseable { setOpenParamsBuilder(openParamsBuilder); Object lock = null; - if (mName == null || !Flags.concurrentOpenHelper()) { + if (!Flags.concurrentOpenHelper() || mName == null) { lock = new Object(); } else { - try { - final String path = mContext.getDatabasePath(mName).getCanonicalPath(); - lock = sDbLock.computeIfAbsent(path, (String k) -> new Object()); - } catch (IOException e) { - Log.d(TAG, "failed to construct db path for " + mName); - // Ensure the lock is not null. - lock = new Object(); - } + lock = sDbLock.computeIfAbsent(mName, (String k) -> new Object()); } mLock = lock; } diff --git a/core/java/android/hardware/input/KeyGestureEvent.java b/core/java/android/hardware/input/KeyGestureEvent.java index 4025242fd208..1a712d2b3f31 100644 --- a/core/java/android/hardware/input/KeyGestureEvent.java +++ b/core/java/android/hardware/input/KeyGestureEvent.java @@ -122,17 +122,11 @@ public final class KeyGestureEvent { public static final int KEY_GESTURE_TYPE_SNAP_RIGHT_FREEFORM_WINDOW = 69; public static final int KEY_GESTURE_TYPE_MINIMIZE_FREEFORM_WINDOW = 70; public static final int KEY_GESTURE_TYPE_TOGGLE_MAXIMIZE_FREEFORM_WINDOW = 71; - public static final int KEY_GESTURE_TYPE_MAGNIFICATION_ZOOM_IN = 72; - public static final int KEY_GESTURE_TYPE_MAGNIFICATION_ZOOM_OUT = 73; - public static final int KEY_GESTURE_TYPE_TOGGLE_MAGNIFICATION = 74; - public static final int KEY_GESTURE_TYPE_ACTIVATE_SELECT_TO_SPEAK = 75; - public static final int KEY_GESTURE_TYPE_MAXIMIZE_FREEFORM_WINDOW = 76; - public static final int KEY_GESTURE_TYPE_TOGGLE_DO_NOT_DISTURB = 77; - public static final int KEY_GESTURE_TYPE_MAGNIFICATION_PAN_LEFT = 78; - public static final int KEY_GESTURE_TYPE_MAGNIFICATION_PAN_RIGHT = 79; - public static final int KEY_GESTURE_TYPE_MAGNIFICATION_PAN_UP = 80; - public static final int KEY_GESTURE_TYPE_MAGNIFICATION_PAN_DOWN = 81; - public static final int KEY_GESTURE_TYPE_TOGGLE_VOICE_ACCESS = 82; + public static final int KEY_GESTURE_TYPE_TOGGLE_MAGNIFICATION = 72; + public static final int KEY_GESTURE_TYPE_ACTIVATE_SELECT_TO_SPEAK = 73; + public static final int KEY_GESTURE_TYPE_MAXIMIZE_FREEFORM_WINDOW = 74; + public static final int KEY_GESTURE_TYPE_TOGGLE_DO_NOT_DISTURB = 75; + public static final int KEY_GESTURE_TYPE_TOGGLE_VOICE_ACCESS = 76; public static final int FLAG_CANCELLED = 1; @@ -220,16 +214,10 @@ public final class KeyGestureEvent { KEY_GESTURE_TYPE_SNAP_RIGHT_FREEFORM_WINDOW, KEY_GESTURE_TYPE_MINIMIZE_FREEFORM_WINDOW, KEY_GESTURE_TYPE_TOGGLE_MAXIMIZE_FREEFORM_WINDOW, - KEY_GESTURE_TYPE_MAGNIFICATION_ZOOM_IN, - KEY_GESTURE_TYPE_MAGNIFICATION_ZOOM_OUT, KEY_GESTURE_TYPE_TOGGLE_MAGNIFICATION, KEY_GESTURE_TYPE_ACTIVATE_SELECT_TO_SPEAK, KEY_GESTURE_TYPE_MAXIMIZE_FREEFORM_WINDOW, KEY_GESTURE_TYPE_TOGGLE_DO_NOT_DISTURB, - KEY_GESTURE_TYPE_MAGNIFICATION_PAN_LEFT, - KEY_GESTURE_TYPE_MAGNIFICATION_PAN_RIGHT, - KEY_GESTURE_TYPE_MAGNIFICATION_PAN_UP, - KEY_GESTURE_TYPE_MAGNIFICATION_PAN_DOWN, KEY_GESTURE_TYPE_TOGGLE_VOICE_ACCESS, }) @Retention(RetentionPolicy.SOURCE) @@ -815,10 +803,6 @@ public final class KeyGestureEvent { return "KEY_GESTURE_TYPE_MINIMIZE_FREEFORM_WINDOW"; case KEY_GESTURE_TYPE_TOGGLE_MAXIMIZE_FREEFORM_WINDOW: return "KEY_GESTURE_TYPE_TOGGLE_MAXIMIZE_FREEFORM_WINDOW"; - case KEY_GESTURE_TYPE_MAGNIFICATION_ZOOM_IN: - return "KEY_GESTURE_TYPE_MAGNIFICATION_ZOOM_IN"; - case KEY_GESTURE_TYPE_MAGNIFICATION_ZOOM_OUT: - return "KEY_GESTURE_TYPE_MAGNIFICATION_ZOOM_OUT"; case KEY_GESTURE_TYPE_TOGGLE_MAGNIFICATION: return "KEY_GESTURE_TYPE_TOGGLE_MAGNIFICATION"; case KEY_GESTURE_TYPE_ACTIVATE_SELECT_TO_SPEAK: @@ -827,14 +811,6 @@ public final class KeyGestureEvent { return "KEY_GESTURE_TYPE_MAXIMIZE_FREEFORM_WINDOW"; case KEY_GESTURE_TYPE_TOGGLE_DO_NOT_DISTURB: return "KEY_GESTURE_TYPE_TOGGLE_DO_NOT_DISTURB"; - case KEY_GESTURE_TYPE_MAGNIFICATION_PAN_LEFT: - return "KEY_GESTURE_TYPE_MAGNIFICATION_PAN_LEFT"; - case KEY_GESTURE_TYPE_MAGNIFICATION_PAN_RIGHT: - return "KEY_GESTURE_TYPE_MAGNIFICATION_PAN_RIGHT"; - case KEY_GESTURE_TYPE_MAGNIFICATION_PAN_UP: - return "KEY_GESTURE_TYPE_MAGNIFICATION_PAN_UP"; - case KEY_GESTURE_TYPE_MAGNIFICATION_PAN_DOWN: - return "KEY_GESTURE_TYPE_MAGNIFICATION_PAN_DOWN"; case KEY_GESTURE_TYPE_TOGGLE_VOICE_ACCESS: return "KEY_GESTURE_TYPE_TOGGLE_VOICE_ACCESS"; default: diff --git a/core/java/android/os/PowerManagerInternal.java b/core/java/android/os/PowerManagerInternal.java index 9435f4d59a2c..77b6d70339ab 100644 --- a/core/java/android/os/PowerManagerInternal.java +++ b/core/java/android/os/PowerManagerInternal.java @@ -351,4 +351,10 @@ public abstract class PowerManagerInternal { * return false if ambient display is not available. */ public abstract boolean isAmbientDisplaySuppressed(); + + /** + * Notifies PowerManager that the device has entered a postured state (stationary + upright). + * This may affect dream eligibility. + */ + public abstract void setDevicePostured(boolean isPostured); } diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 0cfec2cc7314..73d1e1701eec 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -12869,6 +12869,19 @@ public final class Settings { */ public static final String DISABLE_SECURE_WINDOWS = "disable_secure_windows"; + /** + * Controls if the adaptive authentication feature should be disabled, which + * will attempt to lock the device after a number of consecutive authentication + * attempts fail. + * + * This can only be disabled on debuggable builds. Set to 1 to disable or 0 for the + * normal behavior. + * + * @hide + */ + public static final String DISABLE_ADAPTIVE_AUTH_LIMIT_LOCK = + "disable_adaptive_auth_limit_lock"; + /** @hide */ public static final int PRIVATE_SPACE_AUTO_LOCK_ON_DEVICE_LOCK = 0; /** @hide */ diff --git a/core/java/android/security/flags.aconfig b/core/java/android/security/flags.aconfig index 4a9e945e62a9..a5586227cbb3 100644 --- a/core/java/android/security/flags.aconfig +++ b/core/java/android/security/flags.aconfig @@ -155,4 +155,11 @@ flag { description: "Feature flag to add the privileged flag to the SUBSCRIBE_TO_KEYGUARD_LOCKED_STATE permission" bug: "380120712" is_fixed_read_only: true -}
\ No newline at end of file +} + +flag { + name: "disable_adaptive_auth_counter_lock" + namespace: "biometrics" + description: "Flag to allow an adb secure setting to disable the adaptive auth lock" + bug: "371057865" +} diff --git a/core/java/android/service/dreams/IDreamManager.aidl b/core/java/android/service/dreams/IDreamManager.aidl index 1c0a2c021bca..3ca9d937b0ca 100644 --- a/core/java/android/service/dreams/IDreamManager.aidl +++ b/core/java/android/service/dreams/IDreamManager.aidl @@ -53,6 +53,8 @@ interface IDreamManager { void startDreamActivity(in Intent intent); @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.WRITE_DREAM_STATE)") oneway void setDreamIsObscured(in boolean isObscured); + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.WRITE_DREAM_STATE)") + oneway void setDevicePostured(in boolean isPostured); oneway void startDozingOneway(in IBinder token, int screenState, int reason, float screenBrightnessFloat, int screenBrightnessInt, boolean useNormalBrightnessForDoze); diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java index eeb56d4d6c05..464756842caf 100644 --- a/core/java/android/service/wallpaper/WallpaperService.java +++ b/core/java/android/service/wallpaper/WallpaperService.java @@ -2424,9 +2424,10 @@ public abstract class WallpaperService extends Service { Surface ret = null; if (mBlastBufferQueue == null) { - mBlastBufferQueue = new BLASTBufferQueue("Wallpaper", mBbqSurfaceControl, - width, height, format); + mBlastBufferQueue = new BLASTBufferQueue("Wallpaper", + true /* updateDestinationFrame */); mBlastBufferQueue.setApplyToken(mBbqApplyToken); + mBlastBufferQueue.update(mBbqSurfaceControl, width, height, format); // We only return the Surface the first time, as otherwise // it hasn't changed and there is no need to update. ret = mBlastBufferQueue.createSurface(); diff --git a/core/java/android/timezone/TelephonyNetworkFinder.java b/core/java/android/timezone/TelephonyNetworkFinder.java index c69ddf86d3f8..fb4a19b9dcac 100644 --- a/core/java/android/timezone/TelephonyNetworkFinder.java +++ b/core/java/android/timezone/TelephonyNetworkFinder.java @@ -19,6 +19,9 @@ package android.timezone; import android.annotation.NonNull; import android.annotation.Nullable; +import com.android.i18n.timezone.MobileCountries; +import com.android.icu.Flags; + import java.util.Objects; /** @@ -50,4 +53,17 @@ public final class TelephonyNetworkFinder { return telephonyNetworkDelegate != null ? new TelephonyNetwork(telephonyNetworkDelegate) : null; } + + /** + * Returns the countries where a given MCC is in use. + */ + @Nullable + public MobileCountries findCountriesByMcc(@NonNull String mcc) { + Objects.requireNonNull(mcc); + + if (!Flags.telephonyLookupMccExtension()) { + return null; + } + return mDelegate.findCountriesByMcc(mcc); + } } diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl index 028043340732..06eb0428bfcf 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -227,9 +227,6 @@ interface IWindowManager @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553) void endProlongedAnimations(); - void startFreezingScreen(int exitAnim, int enterAnim); - void stopFreezingScreen(); - // these require DISABLE_KEYGUARD permission /** @deprecated use Activity.setShowWhenLocked instead. */ void disableKeyguard(IBinder token, String tag, int userId); diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java index d7cf3e827695..311fbee2fc4b 100644 --- a/core/java/android/view/SurfaceControl.java +++ b/core/java/android/view/SurfaceControl.java @@ -3026,6 +3026,7 @@ public final class SurfaceControl implements Parcelable { // Only non-null if the SurfaceControlRegistry is enabled. This list tracks the set of calls // made through this transaction object, and is dumped (and cleared) when the transaction is // later applied. + @Nullable ArrayList<String> mCalls; Runnable mFreeNativeResources; @@ -4898,8 +4899,10 @@ public final class SurfaceControl implements Parcelable { SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging( "merge", this, null, "otherTx=" + other.getId()); if (mCalls != null) { - mCalls.addAll(other.mCalls); - other.mCalls.clear(); + if (other.mCalls != null) { + mCalls.addAll(other.mCalls); + other.mCalls.clear(); + } } } mResizedSurfaces.putAll(other.mResizedSurfaces); diff --git a/core/java/android/view/SurfaceControlRegistry.java b/core/java/android/view/SurfaceControlRegistry.java index 121c01be7294..0b528bffe5c5 100644 --- a/core/java/android/view/SurfaceControlRegistry.java +++ b/core/java/android/view/SurfaceControlRegistry.java @@ -334,13 +334,17 @@ public class SurfaceControlRegistry { if (call == APPLY) { // Log the apply and dump the calls on that transaction Log.e(TAG, msg, new Throwable()); - for (int i = 0; i < tx.mCalls.size(); i++) { - Log.d(TAG, " " + tx.mCalls.get(i)); + if (tx.mCalls != null) { + for (int i = 0; i < tx.mCalls.size(); i++) { + Log.d(TAG, " " + tx.mCalls.get(i)); + } } } else if (matchesForCallStackDebugging(sc != null ? sc.getName() : null, call)) { // Otherwise log this call to the transaction if it matches the tracked calls Log.e(TAG, msg, new Throwable()); - tx.mCalls.add(msg); + if (tx.mCalls != null) { + tx.mCalls.add(msg); + } } } else { // Log this call if it matches the tracked calls diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index a657acd261d4..64e7becb1ed4 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -2759,14 +2759,15 @@ public final class ViewRootImpl implements ViewParent, if (mBlastBufferQueue != null) { mBlastBufferQueue.destroy(); } - mBlastBufferQueue = new BLASTBufferQueue(mTag, mSurfaceControl, - mSurfaceSize.x, mSurfaceSize.y, mWindowAttributes.format); - mBlastBufferQueue.setTransactionHangCallback(sTransactionHangCallback); - mBlastBufferQueue.setWaitForBufferReleaseCallback(mChoreographer::onWaitForBufferRelease); + mBlastBufferQueue = new BLASTBufferQueue(mTag, true /* updateDestinationFrame */); // If we create and destroy BBQ without recreating the SurfaceControl, we can end up // queuing buffers on multiple apply tokens causing out of order buffer submissions. We // fix this by setting the same apply token on all BBQs created by this VRI. mBlastBufferQueue.setApplyToken(mBbqApplyToken); + mBlastBufferQueue.update(mSurfaceControl, mSurfaceSize.x, mSurfaceSize.y, + mWindowAttributes.format); + mBlastBufferQueue.setTransactionHangCallback(sTransactionHangCallback); + mBlastBufferQueue.setWaitForBufferReleaseCallback(mChoreographer::onWaitForBufferRelease); Surface blastSurface; if (addSchandleToVriSurface()) { blastSurface = mBlastBufferQueue.createSurfaceWithHandle(); @@ -5551,6 +5552,9 @@ public final class ViewRootImpl implements ViewParent, if (mAttachInfo.mContentCaptureManager != null) { ContentCaptureSession session = mAttachInfo.mContentCaptureManager.getMainContentCaptureSession(); + if (android.view.contentcapture.flags.Flags.postCreateAndroidBgThread()) { + session.performStart(); + } session.notifyWindowBoundsChanged(session.getId(), getConfiguration().windowConfiguration.getBounds()); } diff --git a/core/java/android/view/contentcapture/ChildContentCaptureSession.java b/core/java/android/view/contentcapture/ChildContentCaptureSession.java index 8baa55f8e377..6e2e1009fd40 100644 --- a/core/java/android/view/contentcapture/ChildContentCaptureSession.java +++ b/core/java/android/view/contentcapture/ChildContentCaptureSession.java @@ -74,8 +74,8 @@ final class ChildContentCaptureSession extends ContentCaptureSession { } @Override - void flush(@FlushReason int reason) { - mParent.flush(reason); + void internalFlush(@FlushReason int reason) { + mParent.internalFlush(reason); } @Override diff --git a/core/java/android/view/contentcapture/ContentCaptureManager.java b/core/java/android/view/contentcapture/ContentCaptureManager.java index 724e8fa830af..b7a77d701045 100644 --- a/core/java/android/view/contentcapture/ContentCaptureManager.java +++ b/core/java/android/view/contentcapture/ContentCaptureManager.java @@ -52,7 +52,6 @@ import android.view.contentcapture.ContentCaptureSession.FlushReason; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.os.BackgroundThread; import com.android.internal.util.RingBuffer; import com.android.internal.util.SyncResultReceiver; @@ -605,7 +604,6 @@ public final class ContentCaptureManager { mContext, this, prepareUiHandler(), - prepareContentCaptureHandler(), mService ); if (sVerbose) Log.v(TAG, "getMainContentCaptureSession(): created " + mMainSession); @@ -616,15 +614,6 @@ public final class ContentCaptureManager { @NonNull @GuardedBy("mLock") - private Handler prepareContentCaptureHandler() { - if (mContentCaptureHandler == null) { - mContentCaptureHandler = BackgroundThread.getHandler(); - } - return mContentCaptureHandler; - } - - @NonNull - @GuardedBy("mLock") private Handler prepareUiHandler() { if (mUiHandler == null) { mUiHandler = Handler.createAsync(Looper.getMainLooper()); @@ -674,7 +663,7 @@ public final class ContentCaptureManager { @UiThread public void flush(@FlushReason int reason) { if (mOptions.lite) return; - getMainContentCaptureSession().flush(reason); + getMainContentCaptureSession().internalFlush(reason); } /** diff --git a/core/java/android/view/contentcapture/ContentCaptureSession.java b/core/java/android/view/contentcapture/ContentCaptureSession.java index 9aeec20ec9b7..791a6f4254ec 100644 --- a/core/java/android/view/contentcapture/ContentCaptureSession.java +++ b/core/java/android/view/contentcapture/ContentCaptureSession.java @@ -286,6 +286,9 @@ public abstract class ContentCaptureSession implements AutoCloseable { abstract void start(@NonNull IBinder token, @NonNull IBinder shareableActivityToken, @NonNull ComponentName component, int flags); + /** @hide */ + public void performStart() {} + abstract boolean isDisabled(); /** @@ -339,7 +342,7 @@ public abstract class ContentCaptureSession implements AutoCloseable { /** * Flushes the buffered events to the service. */ - abstract void flush(@FlushReason int reason); + abstract void internalFlush(@FlushReason int reason); /** * Sets the {@link ContentCaptureContext} associated with the session. diff --git a/core/java/android/view/contentcapture/MainContentCaptureSession.java b/core/java/android/view/contentcapture/MainContentCaptureSession.java index 2fb78c038ca2..29cae857098d 100644 --- a/core/java/android/view/contentcapture/MainContentCaptureSession.java +++ b/core/java/android/view/contentcapture/MainContentCaptureSession.java @@ -57,10 +57,12 @@ import android.view.View; import android.view.ViewStructure; import android.view.autofill.AutofillId; import android.view.contentcapture.ViewNode.ViewStructureImpl; +import android.view.contentcapture.flags.Flags; import android.view.contentprotection.ContentProtectionEventProcessor; import android.view.inputmethod.BaseInputConnection; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.os.BackgroundThread; import com.android.internal.os.IResultReceiver; import com.android.modules.expresslog.Counter; @@ -107,8 +109,10 @@ public final class MainContentCaptureSession extends ContentCaptureSession { @NonNull private final Handler mUiHandler; - @NonNull - private final Handler mContentCaptureHandler; + /** @hide */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) + @Nullable + public Handler mContentCaptureHandler; /** * Interface to the system_server binder object - it's only used to start the session (and @@ -187,6 +191,12 @@ public final class MainContentCaptureSession extends ContentCaptureSession { @Nullable public ContentProtectionEventProcessor mContentProtectionEventProcessor; + /** + * A runnable object to perform the start of this session. + */ + @Nullable + private Runnable mStartRunnable = null; + private static class SessionStateReceiver extends IResultReceiver.Stub { private final WeakReference<MainContentCaptureSession> mMainSession; @@ -198,7 +208,7 @@ public final class MainContentCaptureSession extends ContentCaptureSession { public void send(int resultCode, Bundle resultData) { final MainContentCaptureSession mainSession = mMainSession.get(); if (mainSession == null) { - Log.w(TAG, "received result after mina session released"); + Log.w(TAG, "received result after main session released"); return; } final IBinder binder; @@ -213,6 +223,8 @@ public final class MainContentCaptureSession extends ContentCaptureSession { binder = resultData.getBinder(EXTRA_BINDER); if (binder == null) { Log.wtf(TAG, "No " + EXTRA_BINDER + " extra result"); + // explicitly init the bg thread + mainSession.mContentCaptureHandler = mainSession.prepareContentCaptureHandler(); mainSession.runOnContentCaptureThread(() -> mainSession.resetSession( STATE_DISABLED | STATE_INTERNAL_ERROR)); return; @@ -220,23 +232,45 @@ public final class MainContentCaptureSession extends ContentCaptureSession { } else { binder = null; } + // explicitly init the bg thread + mainSession.mContentCaptureHandler = mainSession.prepareContentCaptureHandler(); mainSession.runOnContentCaptureThread(() -> mainSession.onSessionStarted(resultCode, binder)); } } + /** + * Prepares the content capture handler(i.e. the background thread). + * + * This is expected to be called from the {@link SessionStateReceiver#send} callback, after the + * session {@link performStart}. This is expected to be executed in a binder thread, instead + * of the UI thread. + */ + @NonNull + private Handler prepareContentCaptureHandler() { + if (mContentCaptureHandler == null) { + try { + if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { + Trace.traceBegin(Trace.TRACE_TAG_VIEW, "prepareContentCaptureHandler"); + } + mContentCaptureHandler = BackgroundThread.getHandler(); + } finally { + Trace.traceEnd(Trace.TRACE_TAG_VIEW); + } + } + return mContentCaptureHandler; + } + /** @hide */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PROTECTED) public MainContentCaptureSession( @NonNull ContentCaptureManager.StrippedContext context, @NonNull ContentCaptureManager manager, @NonNull Handler uiHandler, - @NonNull Handler contentCaptureHandler, @NonNull IContentCaptureManager systemServerInterface) { mContext = context; mManager = manager; mUiHandler = uiHandler; - mContentCaptureHandler = contentCaptureHandler; mSystemServerInterface = systemServerInterface; final int logHistorySize = mManager.mOptions.logHistorySize; @@ -260,18 +294,49 @@ public final class MainContentCaptureSession extends ContentCaptureSession { } /** - * Starts this session. + * Performs the start of the session. + * + * This is expected to be called from the UI thread, when the activity finishes its first frame. + * This is a no-op if the session has already been started. + * + * See {@link #start(IBinder, IBinder, ComponentName, int)} for more details. + * + * @hide */ + @Override + public void performStart() { + if (!hasStarted() && mStartRunnable != null) { + mStartRunnable.run(); + } + } + + /** + * Creates a runnable to start this session. + * + * For performance reasons, it is better to only create a task to start the session + * during the creation of the activity and perform the actual start when the activity + * finishes it's first frame. */ @Override void start(@NonNull IBinder token, @NonNull IBinder shareableActivityToken, @NonNull ComponentName component, int flags) { - runOnContentCaptureThread( - () -> startImpl(token, shareableActivityToken, component, flags)); + if (Flags.postCreateAndroidBgThread()) { + mStartRunnable = () -> { + try { + if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { + Trace.traceBegin(Trace.TRACE_TAG_VIEW, "cc session startImpl"); + } + startImpl(token, shareableActivityToken, component, flags); + } finally { + Trace.traceEnd(Trace.TRACE_TAG_VIEW); + } + }; + } else { + startImpl(token, shareableActivityToken, component, flags); + } } private void startImpl(@NonNull IBinder token, @NonNull IBinder shareableActivityToken, @NonNull ComponentName component, int flags) { - checkOnContentCaptureThread(); if (!isContentCaptureEnabled()) return; if (sVerbose) { @@ -305,11 +370,12 @@ public final class MainContentCaptureSession extends ContentCaptureSession { Log.w(TAG, "Error starting session for " + component.flattenToShortString() + ": " + e); } } + @Override void onDestroy() { clearAndRunOnContentCaptureThread(() -> { try { - flush(FLUSH_REASON_SESSION_FINISHED); + internalFlush(FLUSH_REASON_SESSION_FINISHED); } finally { destroySession(); } @@ -557,11 +623,10 @@ public final class MainContentCaptureSession extends ContentCaptureSession { flushReason = forceFlush ? FLUSH_REASON_FORCE_FLUSH : FLUSH_REASON_FULL; } - flush(flushReason); + internalFlush(flushReason); } private boolean hasStarted() { - checkOnContentCaptureThread(); return mState != UNKNOWN_STATE; } @@ -575,6 +640,11 @@ public final class MainContentCaptureSession extends ContentCaptureSession { if (sVerbose) Log.v(TAG, "handleScheduleFlush(): session not started yet"); return; } + if (mContentCaptureHandler == null) { + Log.w(TAG, "handleScheduleFlush(" + getDebugState(reason) + "): content capture " + + "thread not ready"); + return; + } if (mDisabled.get()) { // Should not be called on this state, as handleSendEvent checks. @@ -617,15 +687,18 @@ public final class MainContentCaptureSession extends ContentCaptureSession { if (sVerbose) Log.v(TAG, "Nothing to flush"); return; } - flush(reason); + internalFlush(reason); } - /** @hide */ + /** + * Internal API to flush the buffered events to the service. + * + * Do not confuse this with the public API {@link #flush()}. + * + * @hide */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) @Override - public void flush(@FlushReason int reason) { - // TODO: b/380381249 renaming the internal APIs to prevent confusions between this and the - // public API. + public void internalFlush(@FlushReason int reason) { runOnContentCaptureThread(() -> flushImpl(reason)); } @@ -647,6 +720,11 @@ public final class MainContentCaptureSession extends ContentCaptureSession { if (!isContentCaptureReceiverEnabled()) { return; } + if (mContentCaptureHandler == null) { + Log.w(TAG, "handleForceFlush(" + getDebugState(reason) + "): content capture thread" + + "not ready"); + return; + } if (mDirectServiceInterface == null) { if (sVerbose) { @@ -763,7 +841,9 @@ public final class MainContentCaptureSession extends ContentCaptureSession { } mDirectServiceInterface = null; mContentProtectionEventProcessor = null; - mContentCaptureHandler.removeMessages(MSG_FLUSH); + if (mContentCaptureHandler != null) { + mContentCaptureHandler.removeMessages(MSG_FLUSH); + } } @Override @@ -917,6 +997,10 @@ public final class MainContentCaptureSession extends ContentCaptureSession { * clear the buffer events then starting sending out current event. */ private void enqueueEvent(@NonNull final ContentCaptureEvent event, boolean forceFlush) { + if (mContentCaptureHandler == null) { + mEventProcessQueue.offer(event); + return; + } if (forceFlush || mEventProcessQueue.size() >= mManager.mOptions.maxBufferSize - 1) { // The buffer events are cleared in the same thread first to prevent new events // being added during the time of context switch. This would disrupt the sequence @@ -1119,6 +1203,10 @@ public final class MainContentCaptureSession extends ContentCaptureSession { * always delegate to the assigned thread from {@code mHandler} for synchronization.</p> */ private void checkOnContentCaptureThread() { + if (mContentCaptureHandler == null) { + Log.e(TAG, "content capture thread is not initiallized!"); + return; + } final boolean onContentCaptureThread = mContentCaptureHandler.getLooper().isCurrentThread(); if (!onContentCaptureThread) { mWrongThreadCount.incrementAndGet(); @@ -1139,6 +1227,12 @@ public final class MainContentCaptureSession extends ContentCaptureSession { * </p> */ private void runOnContentCaptureThread(@NonNull Runnable r) { + if (mContentCaptureHandler == null) { + Log.e(TAG, "content capture thread is not initiallized!"); + // fall back to UI thread + runOnUiThread(r); + return; + } if (!mContentCaptureHandler.getLooper().isCurrentThread()) { mContentCaptureHandler.post(r); } else { @@ -1147,6 +1241,12 @@ public final class MainContentCaptureSession extends ContentCaptureSession { } private void clearAndRunOnContentCaptureThread(@NonNull Runnable r, int what) { + if (mContentCaptureHandler == null) { + Log.e(TAG, "content capture thread is not initiallized!"); + // fall back to UI thread + runOnUiThread(r); + return; + } if (!mContentCaptureHandler.getLooper().isCurrentThread()) { mContentCaptureHandler.removeMessages(what); mContentCaptureHandler.post(r); diff --git a/core/java/android/view/contentcapture/flags/content_capture_flags.aconfig b/core/java/android/view/contentcapture/flags/content_capture_flags.aconfig index f709ed7f57cd..9df835098268 100644 --- a/core/java/android/view/contentcapture/flags/content_capture_flags.aconfig +++ b/core/java/android/view/contentcapture/flags/content_capture_flags.aconfig @@ -13,4 +13,16 @@ flag { namespace: "machine_learning" description: "Feature flag for baklava content capture API" bug: "380381249" + is_exported: true +} + +flag { + name: "post_create_android_bg_thread" + namespace: "pixel_state_server" + description: "Feature flag to post create the bg thread when an app is in the allowlist" + bug: "376468525" + is_fixed_read_only: true + metadata { + purpose: PURPOSE_BUGFIX + } } diff --git a/core/java/android/view/inputmethod/InputMethodInfo.java b/core/java/android/view/inputmethod/InputMethodInfo.java index 0a83bdc35b1f..8fef2d726b2c 100644 --- a/core/java/android/view/inputmethod/InputMethodInfo.java +++ b/core/java/android/view/inputmethod/InputMethodInfo.java @@ -766,7 +766,6 @@ public final class InputMethodInfo implements Parcelable { * Returns true if IME supports only virtual devices. * @hide */ - @FlaggedApi(android.companion.virtual.flags.Flags.FLAG_VDM_CUSTOM_IME) @SystemApi public boolean isVirtualDeviceOnly() { return mIsVirtualDeviceOnly; diff --git a/core/java/android/widget/Magnifier.java b/core/java/android/widget/Magnifier.java index 33890b80869d..f70bf9737636 100644 --- a/core/java/android/widget/Magnifier.java +++ b/core/java/android/widget/Magnifier.java @@ -1021,8 +1021,9 @@ public final class Magnifier { .setCallsite("InternalPopupWindow") .build(); - mBBQ = new BLASTBufferQueue("magnifier surface", mBbqSurfaceControl, - surfaceWidth, surfaceHeight, PixelFormat.TRANSLUCENT); + mBBQ = new BLASTBufferQueue("magnifier surface", /*updateDestinationFrame*/ true); + mBBQ.update(mBbqSurfaceControl, + surfaceWidth, surfaceHeight, PixelFormat.TRANSLUCENT); mSurface = mBBQ.createSurface(); // Setup the RenderNode tree. The root has two children, one containing the bitmap diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java index 2c21417fb790..ddbf9e49bb8d 100644 --- a/core/java/android/window/TransitionInfo.java +++ b/core/java/android/window/TransitionInfo.java @@ -211,7 +211,7 @@ public final class TransitionInfo implements Parcelable { FLAG_CONFIG_AT_END, FLAG_IS_TASK_DISPLAY_AREA, FLAG_FIRST_CUSTOM - }, flag = true) + }) public @interface ChangeFlags {} private final @TransitionType int mType; diff --git a/core/java/com/android/internal/app/IntentForwarderActivity.java b/core/java/com/android/internal/app/IntentForwarderActivity.java index 644d69919998..9d7bedc4d0c3 100644 --- a/core/java/com/android/internal/app/IntentForwarderActivity.java +++ b/core/java/com/android/internal/app/IntentForwarderActivity.java @@ -689,14 +689,15 @@ public class IntentForwarderActivity extends Activity { } private void setMiniresolverPadding() { - Insets systemWindowInsets = - getWindowManager().getCurrentWindowMetrics().getWindowInsets().getInsets( - WindowInsets.Type.systemBars()); - View buttonContainer = findViewById(R.id.button_bar_container); - buttonContainer.setPadding(0, 0, 0, - systemWindowInsets.bottom + getResources().getDimensionPixelOffset( - R.dimen.resolver_button_bar_spacing)); + if (buttonContainer != null) { + Insets systemWindowInsets = + getWindowManager().getCurrentWindowMetrics().getWindowInsets().getInsets( + WindowInsets.Type.systemBars()); + buttonContainer.setPadding(0, 0, 0, + systemWindowInsets.bottom + getResources().getDimensionPixelOffset( + R.dimen.resolver_button_bar_spacing)); + } } @VisibleForTesting diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java index a1945352ae09..db65d31f59da 100644 --- a/core/java/com/android/internal/app/ResolverActivity.java +++ b/core/java/com/android/internal/app/ResolverActivity.java @@ -929,8 +929,11 @@ public class ResolverActivity extends Activity implements if (shouldUseMiniResolver()) { View buttonContainer = findViewById(R.id.button_bar_container); - buttonContainer.setPadding(0, 0, 0, mSystemWindowInsets.bottom - + getResources().getDimensionPixelOffset(R.dimen.resolver_button_bar_spacing)); + if (buttonContainer != null) { + buttonContainer.setPadding(0, 0, 0, mSystemWindowInsets.bottom + + getResources().getDimensionPixelOffset( + R.dimen.resolver_button_bar_spacing)); + } } // Need extra padding so the list can fully scroll up diff --git a/core/java/com/android/internal/content/NativeLibraryHelper.java b/core/java/com/android/internal/content/NativeLibraryHelper.java index 6ad7fef95357..e170d6652863 100644 --- a/core/java/com/android/internal/content/NativeLibraryHelper.java +++ b/core/java/com/android/internal/content/NativeLibraryHelper.java @@ -172,8 +172,7 @@ public class NativeLibraryHelper { private static native long nativeOpenApkFd(FileDescriptor fd, String debugPath); private static native void nativeClose(long handle); - private static native long nativeSumNativeBinaries(long handle, String cpuAbi, - boolean debuggable); + private static native long nativeSumNativeBinaries(long handle, String cpuAbi); private native static int nativeCopyNativeBinaries(long handle, String sharedLibraryPath, String abiToCopy, boolean extractNativeLibs, boolean debuggable); @@ -188,7 +187,7 @@ public class NativeLibraryHelper { private static long sumNativeBinaries(Handle handle, String abi) { long sum = 0; for (long apkHandle : handle.apkHandles) { - sum += nativeSumNativeBinaries(apkHandle, abi, handle.debuggable); + sum += nativeSumNativeBinaries(apkHandle, abi); } return sum; } @@ -222,7 +221,7 @@ public class NativeLibraryHelper { public static int findSupportedAbi(Handle handle, String[] supportedAbis) { int finalRes = NO_NATIVE_LIBRARIES; for (long apkHandle : handle.apkHandles) { - final int res = nativeFindSupportedAbi(apkHandle, supportedAbis, handle.debuggable); + final int res = nativeFindSupportedAbi(apkHandle, supportedAbis); if (res == NO_NATIVE_LIBRARIES) { // No native code, keep looking through all APKs. } else if (res == INSTALL_FAILED_NO_MATCHING_ABIS) { @@ -244,8 +243,7 @@ public class NativeLibraryHelper { return finalRes; } - private native static int nativeFindSupportedAbi(long handle, String[] supportedAbis, - boolean debuggable); + private native static int nativeFindSupportedAbi(long handle, String[] supportedAbis); // Convenience method to call removeNativeBinariesFromDirLI(File) public static void removeNativeBinariesLI(String nativeLibraryPath) { diff --git a/core/java/com/android/internal/widget/NotificationProgressBar.java b/core/java/com/android/internal/widget/NotificationProgressBar.java index 1b770207f2cb..0d3c470b0e8a 100644 --- a/core/java/com/android/internal/widget/NotificationProgressBar.java +++ b/core/java/com/android/internal/widget/NotificationProgressBar.java @@ -817,12 +817,13 @@ public final class NotificationProgressBar extends ProgressBar implements if (part instanceof Segment segment) { final float segWidth = segment.mFraction * totalWidth; // Advance the start position to account for a point immediately prior. - final float startOffset = getSegStartOffset(prevPart, pointRadius, segPointGap, x); + final float startOffset = getSegStartOffset(prevPart, pointRadius, segPointGap, + iPart == 1); final float start = x + startOffset; // Retract the end position to account for the padding and a point immediately // after. final float endOffset = getSegEndOffset(segment, nextPart, pointRadius, segPointGap, - segSegGap, x + segWidth, totalWidth, hasTrackerIcon); + segSegGap, iPart == nParts - 2, totalWidth, hasTrackerIcon); final float end = x + segWidth - endOffset; drawableParts.add(new DrawableSegment(start, end, segment.mColor, segment.mFaded)); @@ -840,10 +841,10 @@ public final class NotificationProgressBar extends ProgressBar implements // Only shift the points right at the start/end. // For the points close to the start/end, the segment minimum width requirement // would take care of shifting them to be within the bounds. - if (x == 0) { + if (iPart == 0) { start = 0; end = pointWidth; - } else if (x == totalWidth) { + } else if (iPart == nParts - 1) { start = totalWidth - pointWidth; end = totalWidth; } @@ -856,14 +857,14 @@ public final class NotificationProgressBar extends ProgressBar implements } private static float getSegStartOffset(Part prevPart, float pointRadius, float segPointGap, - float startX) { + boolean isSecondPart) { if (!(prevPart instanceof Point)) return 0F; - final float pointOffset = (startX == 0) ? pointRadius : 0; + final float pointOffset = isSecondPart ? pointRadius : 0; return pointOffset + pointRadius + segPointGap; } private static float getSegEndOffset(Segment seg, Part nextPart, float pointRadius, - float segPointGap, float segSegGap, float endX, float totalWidth, + float segPointGap, float segSegGap, boolean isSecondToLastPart, float totalWidth, boolean hasTrackerIcon) { if (nextPart == null) return 0F; if (nextPart instanceof Segment nextSeg) { @@ -874,7 +875,7 @@ public final class NotificationProgressBar extends ProgressBar implements return segSegGap; } - final float pointOffset = (endX == totalWidth) ? pointRadius : 0; + final float pointOffset = isSecondToLastPart ? pointRadius : 0; return segPointGap + pointRadius + pointOffset; } diff --git a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp index 3108f1f2c7c5..e78c5247d8a7 100644 --- a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp +++ b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp @@ -349,21 +349,19 @@ static install_status_t copyFileIfChanged(JNIEnv* env, void* arg, ZipFileRO* zip * satisfied : * * - The entry is under the lib/ directory. - * - The entry name ends with ".so" and the entry name starts with "lib", - * an exception is made for debuggable apps. * - The entry filename is "safe" (as determined by isFilenameSafe). * */ class NativeLibrariesIterator { private: - NativeLibrariesIterator(ZipFileRO* zipFile, bool debuggable, void* cookie) - : mZipFile(zipFile), mDebuggable(debuggable), mCookie(cookie), mLastSlash(nullptr) { + NativeLibrariesIterator(ZipFileRO* zipFile, void* cookie) + : mZipFile(zipFile), mCookie(cookie), mLastSlash(nullptr) { fileName[0] = '\0'; } public: static base::expected<std::unique_ptr<NativeLibrariesIterator>, int32_t> create( - ZipFileRO* zipFile, bool debuggable) { + ZipFileRO* zipFile) { // Do not specify a suffix to find both .so files and gdbserver. auto result = zipFile->startIterationOrError(APK_LIB.data(), nullptr /* suffix */); if (!result.ok()) { @@ -371,7 +369,7 @@ public: } return std::unique_ptr<NativeLibrariesIterator>( - new NativeLibrariesIterator(zipFile, debuggable, result.value())); + new NativeLibrariesIterator(zipFile, result.value())); } base::expected<ZipEntryRO, int32_t> next() { @@ -390,7 +388,7 @@ public: continue; } - const char* lastSlash = util::ValidLibraryPathLastSlash(fileName, false, mDebuggable); + const char* lastSlash = util::ValidLibraryPathLastSlash(fileName, false); if (lastSlash) { mLastSlash = lastSlash; break; @@ -415,20 +413,19 @@ private: char fileName[PATH_MAX]; ZipFileRO* const mZipFile; - const bool mDebuggable; void* mCookie; const char* mLastSlash; }; static install_status_t iterateOverNativeFiles(JNIEnv *env, jlong apkHandle, jstring javaCpuAbi, - jboolean debuggable, iterFunc callFunc, void* callArg) { + iterFunc callFunc, void* callArg) { ZipFileRO* zipFile = reinterpret_cast<ZipFileRO*>(apkHandle); if (zipFile == nullptr) { return INSTALL_FAILED_INVALID_APK; } - auto result = NativeLibrariesIterator::create(zipFile, debuggable); + auto result = NativeLibrariesIterator::create(zipFile); if (!result.ok()) { return INSTALL_FAILED_INVALID_APK; } @@ -470,14 +467,13 @@ iterateOverNativeFiles(JNIEnv *env, jlong apkHandle, jstring javaCpuAbi, return INSTALL_SUCCEEDED; } -static int findSupportedAbi(JNIEnv* env, jlong apkHandle, jobjectArray supportedAbisArray, - jboolean debuggable) { +static int findSupportedAbi(JNIEnv* env, jlong apkHandle, jobjectArray supportedAbisArray) { ZipFileRO* zipFile = reinterpret_cast<ZipFileRO*>(apkHandle); if (zipFile == nullptr) { return INSTALL_FAILED_INVALID_APK; } - auto result = NativeLibrariesIterator::create(zipFile, debuggable); + auto result = NativeLibrariesIterator::create(zipFile); if (!result.ok()) { return INSTALL_FAILED_INVALID_APK; } @@ -548,26 +544,26 @@ com_android_internal_content_NativeLibraryHelper_copyNativeBinaries(JNIEnv *env, { jboolean app_compat_16kb = app_compat_16kb_enabled(); void* args[] = { &javaNativeLibPath, &extractNativeLibs, &debuggable, &app_compat_16kb }; - return (jint) iterateOverNativeFiles(env, apkHandle, javaCpuAbi, debuggable, + return (jint) iterateOverNativeFiles(env, apkHandle, javaCpuAbi, copyFileIfChanged, reinterpret_cast<void*>(args)); } static jlong com_android_internal_content_NativeLibraryHelper_sumNativeBinaries(JNIEnv *env, jclass clazz, - jlong apkHandle, jstring javaCpuAbi, jboolean debuggable) + jlong apkHandle, jstring javaCpuAbi) { size_t totalSize = 0; - iterateOverNativeFiles(env, apkHandle, javaCpuAbi, debuggable, sumFiles, &totalSize); + iterateOverNativeFiles(env, apkHandle, javaCpuAbi, sumFiles, &totalSize); return totalSize; } static jint com_android_internal_content_NativeLibraryHelper_findSupportedAbi(JNIEnv *env, jclass clazz, - jlong apkHandle, jobjectArray javaCpuAbisToSearch, jboolean debuggable) + jlong apkHandle, jobjectArray javaCpuAbisToSearch) { - return (jint) findSupportedAbi(env, apkHandle, javaCpuAbisToSearch, debuggable); + return (jint) findSupportedAbi(env, apkHandle, javaCpuAbisToSearch); } enum bitcode_scan_result_t { @@ -748,7 +744,7 @@ static jint com_android_internal_content_NativeLibraryHelper_checkApkAlignment( return PAGE_SIZE_APP_COMPAT_FLAG_ERROR; } - auto result = NativeLibrariesIterator::create(zipFile, debuggable); + auto result = NativeLibrariesIterator::create(zipFile); if (!result.ok()) { ALOGE("Can't iterate over native libs for file:%s", zipFile->getZipFileName()); return PAGE_SIZE_APP_COMPAT_FLAG_ERROR; @@ -810,9 +806,9 @@ static const JNINativeMethod gMethods[] = { {"nativeClose", "(J)V", (void*)com_android_internal_content_NativeLibraryHelper_close}, {"nativeCopyNativeBinaries", "(JLjava/lang/String;Ljava/lang/String;ZZ)I", (void*)com_android_internal_content_NativeLibraryHelper_copyNativeBinaries}, - {"nativeSumNativeBinaries", "(JLjava/lang/String;Z)J", + {"nativeSumNativeBinaries", "(JLjava/lang/String;)J", (void*)com_android_internal_content_NativeLibraryHelper_sumNativeBinaries}, - {"nativeFindSupportedAbi", "(J[Ljava/lang/String;Z)I", + {"nativeFindSupportedAbi", "(J[Ljava/lang/String;)I", (void*)com_android_internal_content_NativeLibraryHelper_findSupportedAbi}, {"hasRenderscriptBitcode", "(J)I", (void*)com_android_internal_content_NativeLibraryHelper_hasRenderscriptBitcode}, diff --git a/core/jni/fd_utils.cpp b/core/jni/fd_utils.cpp index 0eb7c4aee287..5225ce878310 100644 --- a/core/jni/fd_utils.cpp +++ b/core/jni/fd_utils.cpp @@ -51,7 +51,8 @@ static const char* kPathAllowlist[] = { "/dev/blkio/tasks", "/metadata/aconfig/maps/system.package.map", "/metadata/aconfig/maps/system.flag.map", - "/metadata/aconfig/boot/system.val" + "/metadata/aconfig/boot/system.val", + "/metadata/libprocessgroup/memcg_v2_max_activation_depth" // TODO Revert after go/android-memcgv2-exp b/386797433 }; static const char kFdPath[] = "/proc/self/fd"; diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 445080215017..73279700ecb1 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -180,6 +180,7 @@ <protected-broadcast android:name="android.bluetooth.device.action.CONNECTION_ACCESS_CANCEL" /> <protected-broadcast android:name="android.bluetooth.device.action.CONNECTION_ACCESS_REQUEST" /> <protected-broadcast android:name="android.bluetooth.device.action.KEY_MISSING" /> + <protected-broadcast android:name="android.bluetooth.device.action.ENCRYPTION_CHANGE" /> <protected-broadcast android:name="android.bluetooth.device.action.SDP_RECORD" /> <protected-broadcast android:name="android.bluetooth.device.action.BATTERY_LEVEL_CHANGED" /> <protected-broadcast android:name="android.bluetooth.device.action.REMOTE_ISSUE_OCCURRED" /> diff --git a/core/res/res/layout/preference_list_fragment.xml b/core/res/res/layout/preference_list_fragment.xml index 44a5df9b60be..c43975e4ad3c 100644 --- a/core/res/res/layout/preference_list_fragment.xml +++ b/core/res/res/layout/preference_list_fragment.xml @@ -19,7 +19,6 @@ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" - android:fitsSystemWindows="true" android:layout_height="match_parent" android:layout_width="match_parent" android:background="@android:color/transparent" diff --git a/core/res/res/layout/preference_list_fragment_material.xml b/core/res/res/layout/preference_list_fragment_material.xml index 4df76029e606..db2fe7d038e0 100644 --- a/core/res/res/layout/preference_list_fragment_material.xml +++ b/core/res/res/layout/preference_list_fragment_material.xml @@ -19,7 +19,6 @@ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" - android:fitsSystemWindows="true" android:layout_height="match_parent" android:layout_width="match_parent" android:background="@android:color/transparent" diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 416e0aeb776c..ec1be83dcae6 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -5235,10 +5235,6 @@ <!-- Whether or not swipe up gesture's opt-in setting is available on this device --> <bool name="config_swipe_up_gesture_setting_available">true</bool> - <!-- Applications which are disabled unless matching a particular sku --> - <string-array name="config_disableApksUnlessMatchedSku_apk_list" translatable="false" /> - <string-array name="config_disableApkUnlessMatchedSku_skus_list" translatable="false" /> - <!-- Whether or not we should show the option to show battery percentage --> <bool name="config_battery_percentage_setting_available">true</bool> @@ -7323,4 +7319,8 @@ <!-- Whether the device supports Wi-Fi USD feature. --> <bool name="config_deviceSupportsWifiUsd">false</bool> + + <!-- Array containing the notification assistant service adjustments that are not supported by + default on this device--> + <string-array translatable="false" name="config_notificationDefaultUnsupportedAdjustments" /> </resources> diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml index 666f1cf39fe3..965c69d16d79 100644 --- a/core/res/res/values/config_telephony.xml +++ b/core/res/res/values/config_telephony.xml @@ -84,7 +84,7 @@ CarrierConfigManager#KEY_AUTO_DATA_SWITCH_RAT_SIGNAL_SCORE_STRING_ARRAY. If 0, the device always switch to the higher score SIM. If < 0, the network type and signal strength based auto switch is disabled. --> - <integer name="auto_data_switch_score_tolerance">-1</integer> + <integer name="auto_data_switch_score_tolerance">4000</integer> <java-symbol type="integer" name="auto_data_switch_score_tolerance" /> <!-- Boolean indicating whether the Iwlan data service supports persistence of iwlan ipsec diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml index 3f657541eb28..7baaa6d590f2 100644 --- a/core/res/res/values/public-staging.xml +++ b/core/res/res/values/public-staging.xml @@ -116,7 +116,7 @@ <public name="adServiceTypes" /> <!-- @FlaggedApi("android.view.inputmethod.ime_switcher_revamp_api") --> <public name="languageSettingsActivity"/> - <!-- @FlaggedApi("android.service.controls.flags.Flags.FLAG_HOME_PANEL_DREAM") --> + <!-- @FlaggedApi(android.service.controls.flags.Flags.FLAG_HOME_PANEL_DREAM) --> <public name="dreamCategory"/> <!-- @FlaggedApi("android.permission.flags.replace_body_sensor_permission_enabled") @hide @SystemApi --> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 84d51f0b8ad8..77cc6868bd58 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -4366,10 +4366,6 @@ <java-symbol type="integer" name="config_unfoldTransitionHalfFoldedTimeout" /> <java-symbol type="array" name="config_perDeviceStateRotationLockDefaults" /> - - <java-symbol type="array" name="config_disableApksUnlessMatchedSku_apk_list" /> - <java-symbol type="array" name="config_disableApkUnlessMatchedSku_skus_list" /> - <java-symbol type="string" name="config_misprovisionedDeviceModel" /> <java-symbol type="string" name="config_misprovisionedBrandValue" /> @@ -5842,4 +5838,6 @@ <!-- Whether the device supports Wi-Fi USD feature. --> <java-symbol type="bool" name="config_deviceSupportsWifiUsd" /> + <java-symbol type="array" name="config_notificationDefaultUnsupportedAdjustments" /> + </resources> diff --git a/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodInfoTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodInfoTest.java index 44b2d90decf2..dfe7d0306905 100644 --- a/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodInfoTest.java +++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodInfoTest.java @@ -26,7 +26,6 @@ import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.os.Bundle; import android.os.Parcel; -import android.platform.test.annotations.EnableFlags; import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.platform.test.flag.junit.SetFlagsRule; @@ -124,7 +123,6 @@ public class InputMethodInfoTest { } @Test - @EnableFlags(android.companion.virtual.flags.Flags.FLAG_VDM_CUSTOM_IME) public void testIsVirtualDeviceOnly() throws Exception { final InputMethodInfo imi = buildInputMethodForTest(R.xml.ime_meta_virtual_device_only); diff --git a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java index f87b6994900f..ee8d428d8370 100644 --- a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java +++ b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java @@ -224,7 +224,7 @@ public class ContentCaptureSessionTest { } @Override - void flush(int reason) { + void internalFlush(int reason) { throw new UnsupportedOperationException("should not have been called"); } diff --git a/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java b/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java index b42bcee77c67..a1d7f87614e4 100644 --- a/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java +++ b/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java @@ -263,7 +263,7 @@ public class MainContentCaptureSessionTest { session.mEvents = new ArrayList<>(Arrays.asList(EVENT)); session.mDirectServiceInterface = mMockContentCaptureDirectManager; - session.flush(REASON); + session.internalFlush(REASON); mTestableLooper.processAllMessages(); verifyZeroInteractions(mMockContentProtectionEventProcessor); @@ -280,7 +280,7 @@ public class MainContentCaptureSessionTest { session.mEvents = new ArrayList<>(Arrays.asList(EVENT)); session.mDirectServiceInterface = mMockContentCaptureDirectManager; - session.flush(REASON); + session.internalFlush(REASON); mTestableLooper.processAllMessages(); verifyZeroInteractions(mMockContentProtectionEventProcessor); @@ -298,7 +298,7 @@ public class MainContentCaptureSessionTest { session.mEvents = new ArrayList<>(Arrays.asList(EVENT)); session.mDirectServiceInterface = mMockContentCaptureDirectManager; - session.flush(REASON); + session.internalFlush(REASON); mTestableLooper.processAllMessages(); verifyZeroInteractions(mMockContentProtectionEventProcessor); @@ -316,7 +316,7 @@ public class MainContentCaptureSessionTest { session.mEvents = new ArrayList<>(Arrays.asList(EVENT)); session.mDirectServiceInterface = mMockContentCaptureDirectManager; - session.flush(REASON); + session.internalFlush(REASON); mTestableLooper.processAllMessages(); verifyZeroInteractions(mMockContentProtectionEventProcessor); @@ -499,6 +499,57 @@ public class MainContentCaptureSessionTest { assertThat(session.mEventProcessQueue).hasSize(1); } + @Test + public void notifyContentCaptureEvents_beforeSessionPerformStart() throws RemoteException { + ContentCaptureOptions options = + createOptions( + /* enableContentCaptureReceiver= */ true, + /* enableContentProtectionReceiver= */ true); + MainContentCaptureSession session = createSession(options); + session.mContentCaptureHandler = null; + session.mDirectServiceInterface = null; + + notifyContentCaptureEvents(session); + mTestableLooper.processAllMessages(); + + assertThat(session.mEvents).isNull(); + assertThat(session.mEventProcessQueue).hasSize(7); // 5 view events + 2 view tree events + } + + @Test + public void notifyViewAppeared_beforeSessionPerformStart() throws RemoteException { + ContentCaptureOptions options = + createOptions( + /* enableContentCaptureReceiver= */ true, + /* enableContentProtectionReceiver= */ true); + MainContentCaptureSession session = createSession(options); + session.mContentCaptureHandler = null; + session.mDirectServiceInterface = null; + + View view = prepareView(session); + session.notifyViewAppeared(session.newViewStructure(view)); + + assertThat(session.mEvents).isNull(); + assertThat(session.mEventProcessQueue).hasSize(1); + } + + @Test + public void flush_beforeSessionPerformStart() throws Exception { + ContentCaptureOptions options = + createOptions( + /* enableContentCaptureReceiver= */ true, + /* enableContentProtectionReceiver= */ true); + MainContentCaptureSession session = createSession(options); + session.mEvents = new ArrayList<>(Arrays.asList(EVENT)); + session.mContentCaptureHandler = null; + session.mDirectServiceInterface = null; + + session.internalFlush(REASON); + + assertThat(session.mEvents).hasSize(1); + assertThat(session.mEventProcessQueue).isEmpty(); + } + /** Simulates the regular content capture events sequence. */ private void notifyContentCaptureEvents(final MainContentCaptureSession session) { final ArrayList<Object> events = new ArrayList<>( @@ -561,8 +612,8 @@ public class MainContentCaptureSessionTest { sStrippedContext, manager, testHandler, - testHandler, mMockSystemServerInterface); + session.mContentCaptureHandler = testHandler; session.mComponentName = COMPONENT_NAME; return session; } diff --git a/graphics/java/android/graphics/BLASTBufferQueue.java b/graphics/java/android/graphics/BLASTBufferQueue.java index 906c71d9caca..1c34e0d54908 100644 --- a/graphics/java/android/graphics/BLASTBufferQueue.java +++ b/graphics/java/android/graphics/BLASTBufferQueue.java @@ -66,12 +66,6 @@ public final class BLASTBufferQueue { } /** Create a new connection with the surface flinger. */ - public BLASTBufferQueue(String name, SurfaceControl sc, int width, int height, - @PixelFormat.Format int format) { - this(name, true /* updateDestinationFrame */); - update(sc, width, height, format); - } - public BLASTBufferQueue(String name, boolean updateDestinationFrame) { mNativeObject = nativeCreate(name, updateDestinationFrame); } diff --git a/graphics/java/android/graphics/OWNERS b/graphics/java/android/graphics/OWNERS index ef8d26cc65b9..1ea197658f93 100644 --- a/graphics/java/android/graphics/OWNERS +++ b/graphics/java/android/graphics/OWNERS @@ -2,10 +2,10 @@ romainguy@google.com jreck@google.com -njawad@google.com sumir@google.com djsollen@google.com -scroggo@google.com +alecmouri@google.com +sallyqi@google.com per-file BLASTBufferQueue.java = file:/services/core/java/com/android/server/wm/OWNERS per-file FontFamily.java = file:fonts/OWNERS diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleControllerBubbleBarTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleControllerBubbleBarTest.kt index c62d2a06bad5..90ea7d35015e 100644 --- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleControllerBubbleBarTest.kt +++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleControllerBubbleBarTest.kt @@ -34,7 +34,6 @@ import com.android.internal.protolog.ProtoLog import com.android.internal.statusbar.IStatusBarService import com.android.wm.shell.Flags import com.android.wm.shell.ShellTaskOrganizer -import com.android.wm.shell.TestShellExecutor import com.android.wm.shell.bubbles.Bubbles.SysuiProxy import com.android.wm.shell.bubbles.properties.ProdBubbleProperties import com.android.wm.shell.bubbles.storage.BubblePersistentRepository @@ -44,6 +43,7 @@ import com.android.wm.shell.common.DisplayInsetsController import com.android.wm.shell.common.FloatingContentCoordinator import com.android.wm.shell.common.SyncTransactionQueue import com.android.wm.shell.common.TaskStackListenerImpl +import com.android.wm.shell.common.TestShellExecutor import com.android.wm.shell.draganddrop.DragAndDropController import com.android.wm.shell.shared.TransactionPool import com.android.wm.shell.shared.bubbles.BubbleBarLocation diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt index ab2e552c7a3d..a7eebd6159e4 100644 --- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt +++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt @@ -36,10 +36,10 @@ import com.android.internal.protolog.ProtoLog import com.android.launcher3.icons.BubbleIconFactory import com.android.wm.shell.Flags import com.android.wm.shell.R -import com.android.wm.shell.TestShellExecutor import com.android.wm.shell.bubbles.Bubbles.SysuiProxy import com.android.wm.shell.bubbles.animation.AnimatableScaleMatrix import com.android.wm.shell.common.FloatingContentCoordinator +import com.android.wm.shell.common.TestShellExecutor import com.android.wm.shell.shared.animation.PhysicsAnimatorTestUtils import com.google.common.truth.Truth.assertThat import com.google.common.util.concurrent.MoreExecutors.directExecutor diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleViewInfoTaskTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleViewInfoTaskTest.kt index 3043e2bcb0be..a83327bbadee 100644 --- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleViewInfoTaskTest.kt +++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleViewInfoTaskTest.kt @@ -35,7 +35,6 @@ import com.android.internal.protolog.ProtoLog import com.android.internal.statusbar.IStatusBarService import com.android.launcher3.icons.BubbleIconFactory import com.android.wm.shell.ShellTaskOrganizer -import com.android.wm.shell.TestShellExecutor import com.android.wm.shell.bubbles.properties.BubbleProperties import com.android.wm.shell.bubbles.storage.BubblePersistentRepository import com.android.wm.shell.common.DisplayController @@ -44,6 +43,7 @@ import com.android.wm.shell.common.DisplayInsetsController import com.android.wm.shell.common.FloatingContentCoordinator import com.android.wm.shell.common.SyncTransactionQueue import com.android.wm.shell.common.TaskStackListenerImpl +import com.android.wm.shell.common.TestShellExecutor import com.android.wm.shell.shared.TransactionPool import com.android.wm.shell.sysui.ShellCommandHandler import com.android.wm.shell.sysui.ShellController diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/FakeBubbleFactory.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/FakeBubbleFactory.kt index bcaa63bfad36..750178678785 100644 --- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/FakeBubbleFactory.kt +++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/FakeBubbleFactory.kt @@ -22,9 +22,9 @@ import android.content.res.Resources import android.view.LayoutInflater import com.android.internal.logging.testing.UiEventLoggerFake import com.android.wm.shell.R -import com.android.wm.shell.TestShellExecutor import com.android.wm.shell.bubbles.BubbleViewInfoTask.BubbleViewInfo import com.android.wm.shell.bubbles.bar.BubbleBarExpandedView +import com.android.wm.shell.common.TestShellExecutor import com.google.common.util.concurrent.MoreExecutors.directExecutor /** Helper to create a [Bubble] instance */ diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelperTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelperTest.kt index 117ede0d0ac8..9e58b5be9d0d 100644 --- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelperTest.kt +++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelperTest.kt @@ -36,7 +36,6 @@ import androidx.test.filters.SmallTest import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation import com.android.internal.logging.testing.UiEventLoggerFake import com.android.internal.protolog.ProtoLog -import com.android.wm.shell.TestShellExecutor import com.android.wm.shell.bubbles.Bubble import com.android.wm.shell.bubbles.BubbleExpandedViewManager import com.android.wm.shell.bubbles.BubbleLogger @@ -46,6 +45,7 @@ import com.android.wm.shell.bubbles.BubbleTaskView import com.android.wm.shell.bubbles.DeviceConfig import com.android.wm.shell.bubbles.FakeBubbleExpandedViewManager import com.android.wm.shell.bubbles.FakeBubbleFactory +import com.android.wm.shell.common.TestShellExecutor import com.android.wm.shell.taskview.TaskView import com.android.wm.shell.taskview.TaskViewTaskController import com.google.common.truth.Truth.assertThat diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewTest.kt index bfc798bb9c79..fbbcff2dee92 100644 --- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewTest.kt +++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewTest.kt @@ -33,7 +33,6 @@ import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation import com.android.internal.logging.testing.UiEventLoggerFake import com.android.internal.protolog.ProtoLog import com.android.wm.shell.R -import com.android.wm.shell.TestShellExecutor import com.android.wm.shell.bubbles.Bubble import com.android.wm.shell.bubbles.BubbleExpandedViewManager import com.android.wm.shell.bubbles.BubbleLogger @@ -44,6 +43,7 @@ import com.android.wm.shell.bubbles.DeviceConfig import com.android.wm.shell.bubbles.FakeBubbleExpandedViewManager import com.android.wm.shell.bubbles.RegionSamplingProvider import com.android.wm.shell.bubbles.UiEventSubject.Companion.assertThat +import com.android.wm.shell.common.TestShellExecutor import com.android.wm.shell.shared.handles.RegionSamplingHelper import com.android.wm.shell.taskview.TaskView import com.android.wm.shell.taskview.TaskViewTaskController diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt index 9b1645e9534c..5c5dde7da351 100644 --- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt +++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt @@ -36,7 +36,6 @@ import com.android.internal.protolog.ProtoLog import com.android.internal.statusbar.IStatusBarService import com.android.wm.shell.R import com.android.wm.shell.ShellTaskOrganizer -import com.android.wm.shell.TestShellExecutor import com.android.wm.shell.bubbles.Bubble import com.android.wm.shell.bubbles.BubbleController import com.android.wm.shell.bubbles.BubbleData @@ -58,6 +57,7 @@ import com.android.wm.shell.common.DisplayInsetsController import com.android.wm.shell.common.FloatingContentCoordinator import com.android.wm.shell.common.SyncTransactionQueue import com.android.wm.shell.common.TaskStackListenerImpl +import com.android.wm.shell.common.TestShellExecutor import com.android.wm.shell.shared.TransactionPool import com.android.wm.shell.shared.animation.PhysicsAnimatorTestUtils import com.android.wm.shell.shared.bubbles.BubbleBarLocation diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/TestShellExecutor.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/common/TestShellExecutor.kt index ef8e71c2590b..6b549b42cdcd 100644 --- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/TestShellExecutor.kt +++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/common/TestShellExecutor.kt @@ -14,9 +14,7 @@ * limitations under the License. */ -package com.android.wm.shell - -import com.android.wm.shell.common.ShellExecutor +package com.android.wm.shell.common /** * Simple implementation of [ShellExecutor] that collects all runnables and executes them diff --git a/libs/WindowManager/Shell/res/drawable/decor_desktop_mode_maximize_button_dark.xml b/libs/WindowManager/Shell/res/drawable/decor_desktop_mode_maximize_button_dark.xml index e5fe1b5431eb..83a3959ee2f9 100644 --- a/libs/WindowManager/Shell/res/drawable/decor_desktop_mode_maximize_button_dark.xml +++ b/libs/WindowManager/Shell/res/drawable/decor_desktop_mode_maximize_button_dark.xml @@ -22,5 +22,6 @@ android:viewportWidth="960"> <path android:fillColor="@android:color/black" - android:pathData="M160,800Q127,800 103.5,776.5Q80,753 80,720L80,240Q80,207 103.5,183.5Q127,160 160,160L800,160Q833,160 856.5,183.5Q880,207 880,240L880,720Q880,753 856.5,776.5Q833,800 800,800L160,800ZM160,720L800,720Q800,720 800,720Q800,720 800,720L800,320L160,320L160,720Q160,720 160,720Q160,720 160,720Z"/> + android:pathData="M 244.79 796.408 C 222.79 796.408 203.79 788.74 187.79 773.408 C 172.457 757.408 164.79 738.408 164.79 716.408 L 164.79 236.408 C 164.79 214.408 172.457 195.741 187.79 180.408 C 203.79 164.408 222.79 156.408 244.79 156.408 L 724.79 156.408 C 746.79 156.408 765.458 164.408 780.79 180.408 C 796.79 195.741 804.79 214.408 804.79 236.408 L 804.79 716.408 C 804.79 738.408 796.79 757.408 780.79 773.408 C 765.458 788.74 746.79 796.408 724.79 796.408 Z M 244.79 716.408 L 724.79 716.408 L 724.79 236.408 L 244.79 236.408 Z M 244.79 236.408 L 244.79 716.408 Z" + /> </vector>
\ No newline at end of file diff --git a/libs/WindowManager/Shell/shared/Android.bp b/libs/WindowManager/Shell/shared/Android.bp index c3ee0f76f550..d7669ed5cf34 100644 --- a/libs/WindowManager/Shell/shared/Android.bp +++ b/libs/WindowManager/Shell/shared/Android.bp @@ -56,6 +56,7 @@ android_library { static_libs: [ "androidx.core_core-animation", "androidx.dynamicanimation_dynamicanimation", + "com_android_wm_shell_flags_lib", "jsr330", ], kotlincflags: ["-Xjvm-default=all"], diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java index e033f673d07d..840de2c86f92 100644 --- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java @@ -40,7 +40,6 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SuppressLint; import android.app.ActivityManager; -import android.app.TaskInfo; import android.app.WindowConfiguration; import android.graphics.Rect; import android.util.ArrayMap; @@ -57,6 +56,9 @@ public class TransitionUtil { /** Flag applied to a transition change to identify it as a divider bar for animation. */ public static final int FLAG_IS_DIVIDER_BAR = FLAG_FIRST_CUSTOM; + /** Flag applied to a transition change to identify it as a desktop wallpaper activity. */ + public static final int FLAG_IS_DESKTOP_WALLPAPER_ACTIVITY = FLAG_FIRST_CUSTOM << 1; + /** @return true if the transition was triggered by opening something vs closing something */ public static boolean isOpeningType(@WindowManager.TransitionType int type) { return type == TRANSIT_OPEN diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubbleAnythingFlagHelper.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubbleAnythingFlagHelper.java new file mode 100644 index 000000000000..e1f1d0c32eb0 --- /dev/null +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubbleAnythingFlagHelper.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.shared.bubbles; + +import com.android.wm.shell.Flags; + +/** + * Bubble anything has some dependent flags, this class simplifies the checks. + * (TODO: b/389737359 - remove this when the feature is launched). + */ +public class BubbleAnythingFlagHelper { + + private BubbleAnythingFlagHelper() {} + + /** Whether creating any bubble or the overall bubble anything feature is enabled. */ + public static boolean enableCreateAnyBubble() { + return enableBubbleAnything() || Flags.enableCreateAnyBubble(); + } + + /** + * Whether creating any bubble and transforming to fullscreen, or the overall bubble anything + * feature is enabled. + */ + public static boolean enableBubbleToFullscreen() { + return enableBubbleAnything() + || (Flags.enableBubbleToFullscreen() + && Flags.enableCreateAnyBubble()); + } + + /** Whether the overall bubble anything feature is enabled. */ + public static boolean enableBubbleAnything() { + return Flags.enableBubbleAnything(); + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/ComponentUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/ComponentUtils.kt index 67592e60e954..0d0bc9be72b3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/ComponentUtils.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/ComponentUtils.kt @@ -16,6 +16,7 @@ package com.android.wm.shell.common import android.app.PendingIntent +import android.app.TaskInfo import android.content.ComponentName import android.content.Intent import com.android.wm.shell.ShellTaskOrganizer @@ -34,7 +35,11 @@ object ComponentUtils { /** Retrieves the package name from a [taskId]. */ @JvmStatic fun getPackageName(taskId: Int, taskOrganizer: ShellTaskOrganizer): String? { - val taskInfo = taskOrganizer.getRunningTaskInfo(taskId) - return getPackageName(taskInfo?.baseIntent) + val taskInfo = taskOrganizer.getRunningTaskInfo(taskId) ?: return null + return getPackageName(taskInfo) } + + /** Retrieves the package name from a [TaskInfo]. */ + @JvmStatic + fun getPackageName(taskInfo: TaskInfo): String? = getPackageName(taskInfo.baseIntent) } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/MultiDisplayDragMoveBoundsCalculator.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/MultiDisplayDragMoveBoundsCalculator.kt new file mode 100644 index 000000000000..a13ad20f8c05 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/MultiDisplayDragMoveBoundsCalculator.kt @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.common + +import android.graphics.PointF +import android.graphics.Rect +import android.graphics.RectF + +/** + * Utility class for calculating bounds during multi-display drag operations. + * + * This class provides helper functions to perform bounds calculation during window drag. + */ +object MultiDisplayDragMoveBoundsCalculator { + /** + * Calculates the global DP bounds of a window being dragged across displays. + * + * @param startDisplayLayout The DisplayLayout object of the display where the drag started. + * @param repositionStartPoint The starting position of the drag (in pixels), relative to the + * display where the drag started. + * @param boundsAtDragStart The initial bounds of the window (in pixels), relative to the + * display where the drag started. + * @param currentDisplayLayout The DisplayLayout object of the display where the pointer is + * currently located. + * @param x The current x-coordinate of the drag pointer (in pixels). + * @param y The current y-coordinate of the drag pointer (in pixels). + * @return A RectF object representing the calculated global DP bounds of the window. + */ + fun calculateGlobalDpBoundsForDrag( + startDisplayLayout: DisplayLayout, + repositionStartPoint: PointF, + boundsAtDragStart: Rect, + currentDisplayLayout: DisplayLayout, + x: Float, + y: Float, + ): RectF { + // Convert all pixel values to DP. + val startCursorDp = + startDisplayLayout.localPxToGlobalDp(repositionStartPoint.x, repositionStartPoint.y) + val currentCursorDp = currentDisplayLayout.localPxToGlobalDp(x, y) + val startLeftTopDp = + startDisplayLayout.localPxToGlobalDp(boundsAtDragStart.left, boundsAtDragStart.top) + val widthDp = startDisplayLayout.pxToDp(boundsAtDragStart.width()) + val heightDp = startDisplayLayout.pxToDp(boundsAtDragStart.height()) + + // Calculate DP bounds based on pointer movement delta. + val currentLeftDp = startLeftTopDp.x + (currentCursorDp.x - startCursorDp.x) + val currentTopDp = startLeftTopDp.y + (currentCursorDp.y - startCursorDp.y) + val currentRightDp = currentLeftDp + widthDp + val currentBottomDp = currentTopDp + heightDp + + return RectF(currentLeftDp, currentTopDp, currentRightDp, currentBottomDp) + } + + /** + * Converts global DP bounds to local pixel bounds for a specific display. + * + * @param rectDp The global DP bounds to convert. + * @param displayLayout The DisplayLayout representing the display to convert the bounds to. + * @return A Rect object representing the local pixel bounds on the specified display. + */ + fun convertGlobalDpToLocalPxForRect(rectDp: RectF, displayLayout: DisplayLayout): Rect { + val leftTopPxDisplay = displayLayout.globalDpToLocalPx(rectDp.left, rectDp.top) + val rightBottomPxDisplay = displayLayout.globalDpToLocalPx(rectDp.right, rectDp.bottom) + return Rect( + leftTopPxDisplay.x.toInt(), + leftTopPxDisplay.y.toInt(), + rightBottomPxDisplay.x.toInt(), + rightBottomPxDisplay.y.toInt(), + ) + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt index f38957e48dbf..91e06300f1b0 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt @@ -35,6 +35,7 @@ import android.graphics.PointF import android.graphics.Rect import android.graphics.Region import android.os.Binder +import android.os.Bundle import android.os.Handler import android.os.IBinder import android.os.SystemProperties @@ -884,6 +885,36 @@ class DesktopTasksController( } /** + * Start an intent through a launch transition for starting tasks whose transition does not get + * handled by [handleRequest] + */ + fun startLaunchIntentTransition(intent: Intent, options: Bundle, displayId: Int) { + val wct = WindowContainerTransaction() + val displayLayout = displayController.getDisplayLayout(displayId) ?: return + val bounds = calculateDefaultDesktopTaskBounds(displayLayout) + if (DesktopModeFlags.ENABLE_CASCADING_WINDOWS.isTrue) { + cascadeWindow(bounds, displayLayout, displayId) + } + val pendingIntent = + PendingIntent.getActivity( + context, + /* requestCode= */ 0, + intent, + PendingIntent.FLAG_IMMUTABLE, + ) + val ops = + ActivityOptions.fromBundle(options).apply { + launchWindowingMode = WINDOWING_MODE_FREEFORM + pendingIntentBackgroundActivityStartMode = + ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS + launchBounds = bounds + } + + wct.sendPendingIntent(pendingIntent, intent, ops.toBundle()) + startLaunchTransition(TRANSIT_OPEN, wct, launchingTaskId = null) + } + + /** * Move [task] to display with [displayId]. * * No-op if task is already on that display per [RunningTaskInfo.displayId]. @@ -2425,6 +2456,25 @@ class DesktopTasksController( // Update task bounds so that the task position will match the position of its leash val wct = WindowContainerTransaction() wct.setBounds(taskInfo.token, destinationBounds) + + // TODO: b/362720497 - reparent to a specific desk within the target display. + // Reparent task if it has been moved to a new display. + if (Flags.enableConnectedDisplaysWindowDrag()) { + val newDisplayId = motionEvent.getDisplayId() + if (newDisplayId != taskInfo.getDisplayId()) { + val displayAreaInfo = + rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(newDisplayId) + if (displayAreaInfo == null) { + logW( + "Task reparent cannot find DisplayAreaInfo for displayId=%d", + newDisplayId, + ) + } else { + wct.reparent(taskInfo.token, displayAreaInfo.token, /* onTop= */ true) + } + } + } + transitions.startTransition(TRANSIT_CHANGE, wct, null) releaseVisualIndicator() @@ -2885,6 +2935,12 @@ class DesktopTasksController( c.moveToNextDisplay(taskId) } } + + override fun startLaunchIntentTransition(intent: Intent, options: Bundle, displayId: Int) { + executeRemoteCallWithTaskPermission(controller, "startLaunchIntentTransition") { c -> + c.startLaunchIntentTransition(intent, options, displayId) + } + } } private fun logV(msg: String, vararg arguments: Any?) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl index fa383cb55118..54f031293486 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl @@ -17,6 +17,8 @@ package com.android.wm.shell.desktopmode; import android.app.ActivityManager.RunningTaskInfo; +import android.content.Intent; +import android.os.Bundle; import android.window.RemoteTransition; import com.android.wm.shell.desktopmode.IDesktopTaskListener; import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource; @@ -61,4 +63,7 @@ interface IDesktopMode { /** Move a task with given `taskId` to external display */ void moveToExternalDisplay(int taskId); + + /** Start a transition when launching an intent in desktop mode */ + void startLaunchIntentTransition(in Intent intent, in Bundle options, in int displayId); }
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java index 975b65023f20..0182588398a4 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java @@ -54,7 +54,6 @@ import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import com.android.internal.protolog.ProtoLog; -import com.android.launcher3.Flags; import com.android.wm.shell.common.ExternalInterfaceBinder; import com.android.wm.shell.common.RemoteCallable; import com.android.wm.shell.common.ShellExecutor; @@ -553,9 +552,7 @@ public class RecentTasksController implements TaskStackListenerCallback, groupedTasks.add(GroupedTaskInfo.forSplitTasks(taskInfo, pairedTaskInfo, mTaskSplitBoundsMap.get(pairedTaskId))); } else { - if ( - Flags.enableUseTopVisibleActivityForExcludeFromRecentTask() - && isWallpaperTask(taskInfo)) { + if (isWallpaperTask(taskInfo)) { // Don't add the wallpaper task as an entry in grouped tasks continue; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java index d5929f010e02..abfb41bb513a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java @@ -39,6 +39,7 @@ import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPI import static com.android.systemui.shared.Flags.returnAnimationFrameworkLongLived; import static com.android.window.flags.Flags.ensureWallpaperInTransitions; +import static com.android.wm.shell.shared.TransitionUtil.FLAG_IS_DESKTOP_WALLPAPER_ACTIVITY; import static com.android.wm.shell.shared.TransitionUtil.isClosingType; import static com.android.wm.shell.shared.TransitionUtil.isOpeningType; @@ -84,6 +85,7 @@ import com.android.wm.shell.common.ExternalInterfaceBinder; import com.android.wm.shell.common.RemoteCallable; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes; +import com.android.wm.shell.desktopmode.DesktopWallpaperActivity; import com.android.wm.shell.keyguard.KeyguardTransitionHandler; import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.shared.FocusTransitionListener; @@ -795,6 +797,14 @@ public class Transitions implements RemoteCallable<Transitions>, mReadyDuringSync.remove(active); } + // If any of the changes are on DesktopWallpaperActivity, add the flag to the change. + for (TransitionInfo.Change change : info.getChanges()) { + if (change.getTaskInfo() != null + && DesktopWallpaperActivity.isWallpaperTask(change.getTaskInfo())) { + change.setFlags(FLAG_IS_DESKTOP_WALLPAPER_ACTIVITY); + } + } + final Track track = getOrCreateTrack(info.getTrack()); track.mReadyTransitions.add(active); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java index 055bc8f5f092..d6d393f2500c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java @@ -99,6 +99,7 @@ import com.android.wm.shell.RootTaskDisplayAreaOrganizer; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.apptoweb.AppToWebGenericLinksParser; import com.android.wm.shell.apptoweb.AssistContentRequester; +import com.android.wm.shell.common.ComponentUtils; import com.android.wm.shell.common.DisplayChangeController; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayInsetsController; @@ -1926,14 +1927,21 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, // instances, then refer to the list's size and reuse the list for Manage Windows menu. final IActivityTaskManager activityTaskManager = ActivityTaskManager.getService(); try { + // TODO(b/389184897): Move the following into a helper method of + // RecentsTasksController, similar to #findTaskInBackground. + final String packageName = ComponentUtils.getPackageName(info); return activityTaskManager.getRecentTasks(Integer.MAX_VALUE, ActivityManager.RECENT_WITH_EXCLUDED, info.userId).getList().stream().filter( - recentTaskInfo -> (recentTaskInfo.taskId != info.taskId - && recentTaskInfo.baseActivity != null - && recentTaskInfo.baseActivity.getPackageName() - .equals(info.baseActivity.getPackageName()) - ) + recentTaskInfo -> { + if (recentTaskInfo.taskId == info.taskId) { + return false; + } + final String recentTaskPackageName = + ComponentUtils.getPackageName(recentTaskInfo); + return packageName != null + && packageName.equals(recentTaskPackageName); + } ).toList().size(); } catch (RemoteException e) { throw new RuntimeException(e); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositioner.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositioner.kt index 8dc921c986ce..07496eb0e526 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositioner.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositioner.kt @@ -31,6 +31,7 @@ import com.android.internal.jank.Cuj import com.android.internal.jank.InteractionJankMonitor import com.android.wm.shell.ShellTaskOrganizer import com.android.wm.shell.common.DisplayController +import com.android.wm.shell.common.MultiDisplayDragMoveBoundsCalculator import com.android.wm.shell.shared.annotations.ShellMainThread import com.android.wm.shell.transition.Transitions import java.util.concurrent.TimeUnit @@ -69,6 +70,7 @@ class MultiDisplayVeiledResizeTaskPositioner( @DragPositioningCallback.CtrlType private var ctrlType = 0 private var isResizingOrAnimatingResize = false @Surface.Rotation private var rotation = 0 + private var startDisplayId = 0 constructor( taskOrganizer: ShellTaskOrganizer, @@ -95,6 +97,7 @@ class MultiDisplayVeiledResizeTaskPositioner( override fun onDragPositioningStart(ctrlType: Int, displayId: Int, x: Float, y: Float): Rect { this.ctrlType = ctrlType + startDisplayId = displayId taskBoundsAtDragStart.set( desktopWindowDecoration.mTaskInfo.configuration.windowConfiguration.bounds ) @@ -160,16 +163,47 @@ class MultiDisplayVeiledResizeTaskPositioner( interactionJankMonitor.begin( createLongTimeoutJankConfigBuilder(Cuj.CUJ_DESKTOP_MODE_DRAG_WINDOW) ) + val t = transactionSupplier.get() - DragPositioningCallbackUtility.setPositionOnDrag( - desktopWindowDecoration, - repositionTaskBounds, - taskBoundsAtDragStart, - repositionStartPoint, - t, - x, - y, - ) + val startDisplayLayout = displayController.getDisplayLayout(startDisplayId) + val currentDisplayLayout = displayController.getDisplayLayout(displayId) + + if (startDisplayLayout == null || currentDisplayLayout == null) { + // Fall back to single-display drag behavior if any display layout is unavailable. + DragPositioningCallbackUtility.setPositionOnDrag( + desktopWindowDecoration, + repositionTaskBounds, + taskBoundsAtDragStart, + repositionStartPoint, + t, + x, + y, + ) + } else { + val boundsDp = + MultiDisplayDragMoveBoundsCalculator.calculateGlobalDpBoundsForDrag( + startDisplayLayout, + repositionStartPoint, + taskBoundsAtDragStart, + currentDisplayLayout, + x, + y, + ) + repositionTaskBounds.set( + MultiDisplayDragMoveBoundsCalculator.convertGlobalDpToLocalPxForRect( + boundsDp, + startDisplayLayout, + ) + ) + + // TODO(b/383069173): Render drag indicator(s) + + t.setPosition( + desktopWindowDecoration.mTaskSurface, + repositionTaskBounds.left.toFloat(), + repositionTaskBounds.top.toFloat(), + ) + } t.setFrameTimeline(Choreographer.getInstance().vsyncId) t.apply() } @@ -200,13 +234,38 @@ class MultiDisplayVeiledResizeTaskPositioner( } interactionJankMonitor.end(Cuj.CUJ_DESKTOP_MODE_RESIZE_WINDOW) } else { - DragPositioningCallbackUtility.updateTaskBounds( - repositionTaskBounds, - taskBoundsAtDragStart, - repositionStartPoint, - x, - y, - ) + val startDisplayLayout = displayController.getDisplayLayout(startDisplayId) + val currentDisplayLayout = displayController.getDisplayLayout(displayId) + + if (startDisplayLayout == null || currentDisplayLayout == null) { + // Fall back to single-display drag behavior if any display layout is unavailable. + DragPositioningCallbackUtility.updateTaskBounds( + repositionTaskBounds, + taskBoundsAtDragStart, + repositionStartPoint, + x, + y, + ) + } else { + val boundsDp = + MultiDisplayDragMoveBoundsCalculator.calculateGlobalDpBoundsForDrag( + startDisplayLayout, + repositionStartPoint, + taskBoundsAtDragStart, + currentDisplayLayout, + x, + y, + ) + repositionTaskBounds.set( + MultiDisplayDragMoveBoundsCalculator.convertGlobalDpToLocalPxForRect( + boundsDp, + currentDisplayLayout, + ) + ) + + // TODO(b/383069173): Clear drag indicator(s) + } + interactionJankMonitor.end(Cuj.CUJ_DESKTOP_MODE_DRAG_WINDOW) } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/MultiDisplayDragMoveBoundsCalculatorTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/MultiDisplayDragMoveBoundsCalculatorTest.kt new file mode 100644 index 000000000000..bd924c2b47c1 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/MultiDisplayDragMoveBoundsCalculatorTest.kt @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.common + +import android.content.res.Configuration +import android.graphics.PointF +import android.graphics.Rect +import android.graphics.RectF +import android.testing.TestableResources +import com.android.wm.shell.ShellTestCase +import org.junit.Assert.assertEquals +import org.junit.Before +import org.junit.Test + +/** + * Tests for [MultiDisplayDragMoveBoundsCalculator]. + * + * Build/Install/Run: atest WMShellUnitTests:MultiDisplayDragMoveBoundsCalculatorTest + */ +class MultiDisplayDragMoveBoundsCalculatorTest : ShellTestCase() { + private lateinit var resources: TestableResources + + @Before + fun setUp() { + resources = mContext.getOrCreateTestableResources() + val configuration = Configuration() + configuration.uiMode = 0 + resources.overrideConfiguration(configuration) + } + + @Test + fun testCalculateGlobalDpBoundsForDrag() { + val repositionStartPoint = PointF(20f, 40f) + val boundsAtDragStart = Rect(10, 20, 110, 120) + val x = 300f + val y = 400f + val displayLayout0 = + MultiDisplayTestUtil.createSpyDisplayLayout( + MultiDisplayTestUtil.DISPLAY_GLOBAL_BOUNDS_0, + MultiDisplayTestUtil.DISPLAY_DPI_0, + resources.resources, + ) + val displayLayout1 = + MultiDisplayTestUtil.createSpyDisplayLayout( + MultiDisplayTestUtil.DISPLAY_GLOBAL_BOUNDS_1, + MultiDisplayTestUtil.DISPLAY_DPI_1, + resources.resources, + ) + + val actualBoundsDp = + MultiDisplayDragMoveBoundsCalculator.calculateGlobalDpBoundsForDrag( + displayLayout0, + repositionStartPoint, + boundsAtDragStart, + displayLayout1, + x, + y, + ) + + val expectedBoundsDp = RectF(240f, -820f, 340f, -720f) + assertEquals(expectedBoundsDp, actualBoundsDp) + } + + @Test + fun testConvertGlobalDpToLocalPxForRect() { + val displayLayout = + MultiDisplayTestUtil.createSpyDisplayLayout( + MultiDisplayTestUtil.DISPLAY_GLOBAL_BOUNDS_1, + MultiDisplayTestUtil.DISPLAY_DPI_1, + resources.resources, + ) + val rectDp = RectF(150f, -350f, 300f, -250f) + + val actualBoundsPx = + MultiDisplayDragMoveBoundsCalculator.convertGlobalDpToLocalPxForRect( + rectDp, + displayLayout, + ) + + val expectedBoundsPx = Rect(100, 1300, 400, 1500) + assertEquals(expectedBoundsPx, actualBoundsPx) + } +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/MultiDisplayTestUtil.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/MultiDisplayTestUtil.kt new file mode 100644 index 000000000000..c8bebf11a82c --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/MultiDisplayTestUtil.kt @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.common + +import android.content.res.Resources +import android.graphics.RectF +import android.util.DisplayMetrics +import android.view.DisplayInfo +import org.mockito.Mockito.spy + +/** Utility class for tests of [DesktopModeWindowDecorViewModel] */ +object MultiDisplayTestUtil { + // We have two displays, display#1 is placed on middle top of display#0: + // +---+ + // | 1 | + // +-+---+-+ + // | 0 | + // +-------+ + val DISPLAY_GLOBAL_BOUNDS_0 = RectF(0f, 0f, 1200f, 800f) + val DISPLAY_GLOBAL_BOUNDS_1 = RectF(100f, -1000f, 1100f, 0f) + val DISPLAY_DPI_0 = DisplayMetrics.DENSITY_DEFAULT + val DISPLAY_DPI_1 = DisplayMetrics.DENSITY_DEFAULT * 2 + + fun createSpyDisplayLayout(globalBounds: RectF, dpi: Int, resources: Resources): DisplayLayout { + val displayInfo = DisplayInfo() + displayInfo.logicalDensityDpi = dpi + val displayLayout = spy(DisplayLayout(displayInfo, resources, true, true)) + displayLayout.setGlobalBoundsDp(globalBounds) + return displayLayout + } +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt index 2b986d184c20..d13ff79b9518 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt @@ -257,6 +257,7 @@ class DesktopTasksControllerTest : ShellTestCase() { // Mock running tasks are registered here so we can get the list from mock shell task organizer private val runningTasks = mutableListOf<RunningTaskInfo>() + private val SECONDARY_DISPLAY_ID = 1 private val DISPLAY_DIMENSION_SHORT = 1600 private val DISPLAY_DIMENSION_LONG = 2560 private val DEFAULT_LANDSCAPE_BOUNDS = Rect(320, 75, 2240, 1275) @@ -316,6 +317,8 @@ class DesktopTasksControllerTest : ShellTestCase() { val tda = DisplayAreaInfo(MockToken().token(), DEFAULT_DISPLAY, 0) tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)).thenReturn(tda) + whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(SECONDARY_DISPLAY_ID)) + .thenReturn(tda) whenever( mMockDesktopImmersiveController.exitImmersiveIfApplicable( any(), @@ -1143,6 +1146,21 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test + fun launchIntent_taskInDesktopMode_transitionStarted() { + setUpLandscapeDisplay() + val freeformTask = setUpFreeformTask() + + controller.startLaunchIntentTransition( + freeformTask.baseIntent, + Bundle.EMPTY, + DEFAULT_DISPLAY, + ) + + val wct = getLatestDesktopMixedTaskWct(type = TRANSIT_OPEN) + assertThat(wct.hierarchyOps).hasSize(1) + } + + @Test @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) fun addMoveToDesktopChanges_landscapeDevice_userFullscreenOverride_defaultPortraitBounds() { setUpLandscapeDisplay() @@ -3588,6 +3606,45 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test + @EnableFlags(Flags.FLAG_ENABLE_CONNECTED_DISPLAYS_WINDOW_DRAG) + fun onDesktopDragEnd_noIndicatorAndMoveToNewDisplay_reparent() { + val task = setUpFreeformTask() + val spyController = spy(controller) + val mockSurface = mock(SurfaceControl::class.java) + val mockDisplayLayout = mock(DisplayLayout::class.java) + whenever(displayController.getDisplayLayout(task.displayId)).thenReturn(mockDisplayLayout) + whenever(mockDisplayLayout.stableInsets()).thenReturn(Rect(0, 100, 2000, 2000)) + spyController.onDragPositioningMove(task, mockSurface, 200f, Rect(100, 200, 500, 1000)) + + val currentDragBounds = Rect(100, 200, 500, 1000) + whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator) + whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull())) + .thenReturn(DesktopModeVisualIndicator.IndicatorType.NO_INDICATOR) + whenever(motionEvent.displayId).thenReturn(SECONDARY_DISPLAY_ID) + + spyController.onDragPositioningEnd( + task, + mockSurface, + position = Point(100, 200), + inputCoordinate = PointF(200f, 300f), + currentDragBounds, + validDragArea = Rect(0, 50, 2000, 2000), + dragStartBounds = Rect(), + motionEvent, + desktopWindowDecoration, + ) + + verify(transitions) + .startTransition( + eq(TRANSIT_CHANGE), + Mockito.argThat { wct -> + return@argThat wct.hierarchyOps[0].isReparent + }, + eq(null), + ) + } + + @Test fun onDesktopDragEnd_fullscreenIndicator_dragToExitDesktop() { val task = setUpFreeformTask(bounds = Rect(0, 0, 100, 100)) val spyController = spy(controller) @@ -5162,7 +5219,7 @@ class DesktopTasksControllerTest : ShellTestCase() { val arg: ArgumentCaptor<WindowContainerTransaction> = ArgumentCaptor.forClass(WindowContainerTransaction::class.java) verify(desktopMixedTransitionHandler) - .startLaunchTransition(eq(type), capture(arg), anyInt(), anyOrNull(), anyOrNull()) + .startLaunchTransition(eq(type), capture(arg), anyOrNull(), anyOrNull(), anyOrNull()) return arg.value } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java index 7e5d6ce38c5a..28f4ea0c7ada 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java @@ -22,13 +22,13 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; -import static com.android.launcher3.Flags.FLAG_ENABLE_USE_TOP_VISIBLE_ACTIVITY_FOR_EXCLUDE_FROM_RECENT_TASK; import static com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE; import static com.android.wm.shell.shared.GroupedTaskInfo.TYPE_FREEFORM; import static com.android.wm.shell.shared.GroupedTaskInfo.TYPE_FULLSCREEN; import static com.android.wm.shell.shared.GroupedTaskInfo.TYPE_SPLIT; import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50; +import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; @@ -65,8 +65,8 @@ import android.platform.test.annotations.EnableFlags; import android.platform.test.flag.junit.SetFlagsRule; import android.view.SurfaceControl; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; import com.android.dx.mockito.inline.extended.ExtendedMockito; import com.android.dx.mockito.inline.extended.StaticMockitoSession; @@ -253,7 +253,6 @@ public class RecentTasksControllerTest extends ShellTestCase { t3.taskId, -1); } - @EnableFlags(FLAG_ENABLE_USE_TOP_VISIBLE_ACTIVITY_FOR_EXCLUDE_FROM_RECENT_TASK) @Test public void testGetRecentTasks_removesDesktopWallpaperActivity() { RecentTaskInfo t1 = makeTaskInfo(1); @@ -753,15 +752,9 @@ public class RecentTasksControllerTest extends ShellTestCase { /** * Helper to set the raw task list on the controller. */ - private ArrayList<RecentTaskInfo> setRawList( - RecentTaskInfo... tasks) { - ArrayList<RecentTaskInfo> rawList = new ArrayList<>(); - for (RecentTaskInfo task : tasks) { - rawList.add(task); - } - doReturn(rawList).when(mActivityTaskManager).getRecentTasks(anyInt(), anyInt(), + private void setRawList(RecentTaskInfo... tasks) { + doReturn(Arrays.asList(tasks)).when(mActivityTaskManager).getRecentTasks(anyInt(), anyInt(), anyInt()); - return rawList; } /** @@ -794,8 +787,9 @@ public class RecentTasksControllerTest extends ShellTestCase { assertNull(pair.getSplitBounds()); } } - assertTrue("Expected: " + Arrays.toString(expectedTaskIds) + assertArrayEquals("Expected: " + Arrays.toString(expectedTaskIds) + " Received: " + Arrays.toString(flattenedTaskIds), - Arrays.equals(flattenedTaskIds, expectedTaskIds)); + flattenedTaskIds, + expectedTaskIds); } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java index dd645fd968e4..0a19be4eb959 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java @@ -1781,6 +1781,7 @@ public class ShellTransitionTests extends ShellTestCase { taskInfo.configuration.windowConfiguration.setWindowingMode(windowingMode); taskInfo.configuration.windowConfiguration.setActivityType(activityType); taskInfo.token = mock(WindowContainerToken.class); + taskInfo.baseIntent = mock(Intent.class); return taskInfo; } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositionerTest.kt index f179cac32244..2207c705d7dc 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositionerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositionerTest.kt @@ -17,14 +17,14 @@ package com.android.wm.shell.windowdecor import android.app.ActivityManager import android.app.WindowConfiguration -import android.content.Context -import android.content.res.Resources +import android.content.res.Configuration import android.graphics.Point import android.graphics.Rect import android.os.Handler import android.os.IBinder import android.os.Looper import android.testing.AndroidTestingRunner +import android.testing.TestableResources import android.view.Display import android.view.Surface.ROTATION_0 import android.view.Surface.ROTATION_270 @@ -41,6 +41,7 @@ import com.android.wm.shell.ShellTaskOrganizer import com.android.wm.shell.ShellTestCase import com.android.wm.shell.common.DisplayController import com.android.wm.shell.common.DisplayLayout +import com.android.wm.shell.common.MultiDisplayTestUtil import com.android.wm.shell.transition.Transitions import com.android.wm.shell.transition.Transitions.TransitionFinishCallback import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_BOTTOM @@ -55,6 +56,7 @@ import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.Mockito.any import org.mockito.Mockito.argThat +import org.mockito.Mockito.doAnswer import org.mockito.Mockito.eq import org.mockito.Mockito.mock import org.mockito.Mockito.never @@ -82,7 +84,6 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() { @Mock private lateinit var taskBinder: IBinder @Mock private lateinit var mockDisplayController: DisplayController - @Mock private lateinit var mockDisplayLayout: DisplayLayout @Mock private lateinit var mockDisplay: Display @Mock private lateinit var mockTransactionFactory: Supplier<SurfaceControl.Transaction> @Mock private lateinit var mockTransaction: SurfaceControl.Transaction @@ -90,9 +91,11 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() { @Mock private lateinit var mockTransitionInfo: TransitionInfo @Mock private lateinit var mockFinishCallback: TransitionFinishCallback @Mock private lateinit var mockTransitions: Transitions - @Mock private lateinit var mockContext: Context - @Mock private lateinit var mockResources: Resources @Mock private lateinit var mockInteractionJankMonitor: InteractionJankMonitor + private lateinit var resources: TestableResources + private lateinit var spyDisplayLayout0: DisplayLayout + private lateinit var spyDisplayLayout1: DisplayLayout + private val mainHandler = Handler(Looper.getMainLooper()) private lateinit var taskPositioner: MultiDisplayVeiledResizeTaskPositioner @@ -101,24 +104,45 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() { fun setUp() { MockitoAnnotations.initMocks(this) - mockDesktopWindowDecoration.mDisplay = mockDisplay - mockDesktopWindowDecoration.mDecorWindowContext = mockContext - whenever(mockContext.getResources()).thenReturn(mockResources) whenever(taskToken.asBinder()).thenReturn(taskBinder) - whenever(mockDisplayController.getDisplayLayout(DISPLAY_ID)).thenReturn(mockDisplayLayout) - whenever(mockDisplayLayout.densityDpi()).thenReturn(DENSITY_DPI) - whenever(mockDisplayLayout.getStableBounds(any())).thenAnswer { i -> - if ( - mockDesktopWindowDecoration.mTaskInfo.configuration.windowConfiguration - .displayRotation == ROTATION_90 || + mockDesktopWindowDecoration.mDisplay = mockDisplay + mockDesktopWindowDecoration.mDecorWindowContext = mContext + resources = mContext.orCreateTestableResources + val resourceConfiguration = Configuration() + resourceConfiguration.uiMode = 0 + resources.overrideConfiguration(resourceConfiguration) + spyDisplayLayout0 = + MultiDisplayTestUtil.createSpyDisplayLayout( + MultiDisplayTestUtil.DISPLAY_GLOBAL_BOUNDS_0, + MultiDisplayTestUtil.DISPLAY_DPI_0, + resources.resources, + ) + spyDisplayLayout1 = + MultiDisplayTestUtil.createSpyDisplayLayout( + MultiDisplayTestUtil.DISPLAY_GLOBAL_BOUNDS_1, + MultiDisplayTestUtil.DISPLAY_DPI_1, + resources.resources, + ) + whenever(mockDisplayController.getDisplayLayout(DISPLAY_ID_0)).thenReturn(spyDisplayLayout0) + whenever(mockDisplayController.getDisplayLayout(DISPLAY_ID_1)).thenReturn(spyDisplayLayout1) + whenever(spyDisplayLayout0.densityDpi()).thenReturn(DENSITY_DPI) + whenever(spyDisplayLayout1.densityDpi()).thenReturn(DENSITY_DPI) + doAnswer { i -> + val rect = i.getArgument<Rect>(0) + if ( mockDesktopWindowDecoration.mTaskInfo.configuration.windowConfiguration - .displayRotation == ROTATION_270 - ) { - (i.arguments.first() as Rect).set(STABLE_BOUNDS_LANDSCAPE) - } else { - (i.arguments.first() as Rect).set(STABLE_BOUNDS_PORTRAIT) + .displayRotation == ROTATION_90 || + mockDesktopWindowDecoration.mTaskInfo.configuration.windowConfiguration + .displayRotation == ROTATION_270 + ) { + rect.set(STABLE_BOUNDS_LANDSCAPE) + } else { + rect.set(STABLE_BOUNDS_PORTRAIT) + } + null } - } + .`when`(spyDisplayLayout0) + .getStableBounds(any()) `when`(mockTransactionFactory.get()).thenReturn(mockTransaction) mockDesktopWindowDecoration.mTaskInfo = ActivityManager.RunningTaskInfo().apply { @@ -127,14 +151,14 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() { minWidth = MIN_WIDTH minHeight = MIN_HEIGHT defaultMinSize = DEFAULT_MIN - displayId = DISPLAY_ID + displayId = DISPLAY_ID_0 configuration.windowConfiguration.setBounds(STARTING_BOUNDS) configuration.windowConfiguration.displayRotation = ROTATION_90 isResizeable = true } `when`(mockDesktopWindowDecoration.calculateValidDragArea()).thenReturn(VALID_DRAG_AREA) mockDesktopWindowDecoration.mDisplay = mockDisplay - whenever(mockDisplay.displayId).thenAnswer { DISPLAY_ID } + whenever(mockDisplay.displayId).thenAnswer { DISPLAY_ID_0 } taskPositioner = MultiDisplayVeiledResizeTaskPositioner( @@ -153,14 +177,14 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() { fun testDragResize_noMove_doesNotShowResizeVeil() = runOnUiThread { taskPositioner.onDragPositioningStart( CTRL_TYPE_TOP or CTRL_TYPE_RIGHT, - DISPLAY_ID, + DISPLAY_ID_0, STARTING_BOUNDS.left.toFloat(), STARTING_BOUNDS.top.toFloat(), ) verify(mockDesktopWindowDecoration, never()).showResizeVeil(STARTING_BOUNDS) taskPositioner.onDragPositioningEnd( - DISPLAY_ID, + DISPLAY_ID_0, STARTING_BOUNDS.left.toFloat(), STARTING_BOUNDS.top.toFloat(), ) @@ -185,13 +209,13 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() { fun testDragResize_movesTask_doesNotShowResizeVeil() = runOnUiThread { taskPositioner.onDragPositioningStart( CTRL_TYPE_UNDEFINED, - DISPLAY_ID, + DISPLAY_ID_0, STARTING_BOUNDS.left.toFloat(), STARTING_BOUNDS.top.toFloat(), ) taskPositioner.onDragPositioningMove( - DISPLAY_ID, + DISPLAY_ID_0, STARTING_BOUNDS.left.toFloat() + 60, STARTING_BOUNDS.top.toFloat() + 100, ) @@ -205,7 +229,7 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() { val endBounds = taskPositioner.onDragPositioningEnd( - DISPLAY_ID, + DISPLAY_ID_0, STARTING_BOUNDS.left.toFloat() + 70, STARTING_BOUNDS.top.toFloat() + 20, ) @@ -221,16 +245,39 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() { } @Test + fun testDragResize_movesTaskToNewDisplay() = runOnUiThread { + taskPositioner.onDragPositioningStart( + CTRL_TYPE_UNDEFINED, + DISPLAY_ID_0, + STARTING_BOUNDS.left.toFloat(), + STARTING_BOUNDS.top.toFloat(), + ) + + taskPositioner.onDragPositioningMove(DISPLAY_ID_1, 200f, 1900f) + + val rectAfterMove = Rect(200, -50, 300, 50) + verify(mockTransaction) + .setPosition(any(), eq(rectAfterMove.left.toFloat()), eq(rectAfterMove.top.toFloat())) + + val endBounds = taskPositioner.onDragPositioningEnd(DISPLAY_ID_1, 300f, 450f) + val rectAfterEnd = Rect(300, 450, 500, 650) + + verify(mockDesktopWindowDecoration, never()).showResizeVeil(any()) + verify(mockDesktopWindowDecoration, never()).hideResizeVeil() + Assert.assertEquals(rectAfterEnd, endBounds) + } + + @Test fun testDragResize_resize_boundsUpdateOnEnd() = runOnUiThread { taskPositioner.onDragPositioningStart( CTRL_TYPE_RIGHT or CTRL_TYPE_TOP, - DISPLAY_ID, + DISPLAY_ID_0, STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.top.toFloat(), ) taskPositioner.onDragPositioningMove( - DISPLAY_ID, + DISPLAY_ID_0, STARTING_BOUNDS.right.toFloat() + 10, STARTING_BOUNDS.top.toFloat() + 10, ) @@ -252,7 +299,7 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() { ) taskPositioner.onDragPositioningEnd( - DISPLAY_ID, + DISPLAY_ID_0, STARTING_BOUNDS.right.toFloat() + 20, STARTING_BOUNDS.top.toFloat() + 20, ) @@ -278,20 +325,20 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() { @Test fun testDragResize_noEffectiveMove_skipsTransactionOnEnd() = runOnUiThread { taskPositioner.onDragPositioningStart( - DISPLAY_ID, + DISPLAY_ID_0, CTRL_TYPE_TOP or CTRL_TYPE_RIGHT, STARTING_BOUNDS.left.toFloat(), STARTING_BOUNDS.top.toFloat(), ) taskPositioner.onDragPositioningMove( - DISPLAY_ID, + DISPLAY_ID_0, STARTING_BOUNDS.left.toFloat(), STARTING_BOUNDS.top.toFloat(), ) taskPositioner.onDragPositioningEnd( - DISPLAY_ID, + DISPLAY_ID_0, STARTING_BOUNDS.left.toFloat() + 10, STARTING_BOUNDS.top.toFloat() + 10, ) @@ -326,16 +373,16 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() { fun testDragResize_drag_setBoundsNotRunIfDragEndsInDisallowedEndArea() = runOnUiThread { taskPositioner.onDragPositioningStart( CTRL_TYPE_UNDEFINED, // drag - DISPLAY_ID, + DISPLAY_ID_0, STARTING_BOUNDS.left.toFloat(), STARTING_BOUNDS.top.toFloat(), ) val newX = STARTING_BOUNDS.left.toFloat() + 5 val newY = DISALLOWED_AREA_FOR_END_BOUNDS_HEIGHT.toFloat() - 1 - taskPositioner.onDragPositioningMove(DISPLAY_ID, newX, newY) + taskPositioner.onDragPositioningMove(DISPLAY_ID_0, newX, newY) - taskPositioner.onDragPositioningEnd(DISPLAY_ID, newX, newY) + taskPositioner.onDragPositioningEnd(DISPLAY_ID_0, newX, newY) verify(mockShellTaskOrganizer, never()) .applyTransaction( @@ -354,7 +401,7 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() { mockDesktopWindowDecoration.mHasGlobalFocus = false taskPositioner.onDragPositioningStart( CTRL_TYPE_RIGHT, // Resize right - DISPLAY_ID, + DISPLAY_ID_0, STARTING_BOUNDS.left.toFloat(), STARTING_BOUNDS.top.toFloat(), ) @@ -375,7 +422,7 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() { mockDesktopWindowDecoration.mHasGlobalFocus = true taskPositioner.onDragPositioningStart( CTRL_TYPE_RIGHT, // Resize right - DISPLAY_ID, + DISPLAY_ID_0, STARTING_BOUNDS.left.toFloat(), STARTING_BOUNDS.top.toFloat(), ) @@ -396,7 +443,7 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() { mockDesktopWindowDecoration.mHasGlobalFocus = false taskPositioner.onDragPositioningStart( CTRL_TYPE_UNDEFINED, // drag - DISPLAY_ID, + DISPLAY_ID_0, STARTING_BOUNDS.left.toFloat(), STARTING_BOUNDS.top.toFloat(), ) @@ -427,7 +474,7 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() { rectAfterDrag.right += 2000 rectAfterDrag.bottom = STABLE_BOUNDS_LANDSCAPE.bottom // First drag; we should fetch stable bounds. - verify(mockDisplayLayout, times(1)).getStableBounds(any()) + verify(spyDisplayLayout0, times(1)).getStableBounds(any()) verify(mockTransitions) .startTransition( eq(TRANSIT_CHANGE), @@ -451,7 +498,7 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() { ) // Display did not rotate; we should use previous stable bounds - verify(mockDisplayLayout, times(1)).getStableBounds(any()) + verify(spyDisplayLayout0, times(1)).getStableBounds(any()) // Rotate the screen to portrait mockDesktopWindowDecoration.mTaskInfo.apply { @@ -482,7 +529,7 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() { eq(taskPositioner), ) // Display has rotated; we expect a new stable bounds. - verify(mockDisplayLayout, times(2)).getStableBounds(any()) + verify(spyDisplayLayout0, times(2)).getStableBounds(any()) } @Test @@ -491,13 +538,13 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() { taskPositioner.onDragPositioningStart( CTRL_TYPE_TOP or CTRL_TYPE_RIGHT, - DISPLAY_ID, + DISPLAY_ID_0, STARTING_BOUNDS.left.toFloat(), STARTING_BOUNDS.top.toFloat(), ) taskPositioner.onDragPositioningMove( - DISPLAY_ID, + DISPLAY_ID_0, STARTING_BOUNDS.left.toFloat() - 20, STARTING_BOUNDS.top.toFloat() - 20, ) @@ -507,7 +554,7 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() { verify(mockDragEventListener, times(1)).onDragMove(eq(TASK_ID)) taskPositioner.onDragPositioningEnd( - DISPLAY_ID, + DISPLAY_ID_0, STARTING_BOUNDS.left.toFloat(), STARTING_BOUNDS.top.toFloat(), ) @@ -568,10 +615,10 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() { } private fun performDrag(startX: Float, startY: Float, endX: Float, endY: Float, ctrlType: Int) { - taskPositioner.onDragPositioningStart(ctrlType, DISPLAY_ID, startX, startY) - taskPositioner.onDragPositioningMove(DISPLAY_ID, endX, endY) + taskPositioner.onDragPositioningStart(ctrlType, DISPLAY_ID_0, startX, startY) + taskPositioner.onDragPositioningMove(DISPLAY_ID_0, endX, endY) - taskPositioner.onDragPositioningEnd(DISPLAY_ID, endX, endY) + taskPositioner.onDragPositioningEnd(DISPLAY_ID_0, endX, endY) } companion object { @@ -580,7 +627,8 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() { private const val MIN_HEIGHT = 10 private const val DENSITY_DPI = 20 private const val DEFAULT_MIN = 40 - private const val DISPLAY_ID = 1 + private const val DISPLAY_ID_0 = 0 + private const val DISPLAY_ID_1 = 1 private const val NAVBAR_HEIGHT = 50 private const val CAPTION_HEIGHT = 50 private const val DISALLOWED_AREA_FOR_END_BOUNDS_HEIGHT = 10 diff --git a/libs/androidfw/ApkParsing.cpp b/libs/androidfw/ApkParsing.cpp index 7eedfdb5c921..b80c8755f23e 100644 --- a/libs/androidfw/ApkParsing.cpp +++ b/libs/androidfw/ApkParsing.cpp @@ -33,7 +33,7 @@ const size_t LIB_SUFFIX_LEN = LIB_SUFFIX.size(); static const std::array<std::string_view, 2> abis = {"arm64-v8a", "x86_64"}; namespace android::util { -const char* ValidLibraryPathLastSlash(const char* fileName, bool suppress64Bit, bool debuggable) { +const char* ValidLibraryPathLastSlash(const char* fileName, bool suppress64Bit) { // Make sure the filename is at least to the minimum library name size. const size_t fileNameLen = strlen(fileName); static const size_t minLength = APK_LIB_LEN + 2 + LIB_PREFIX_LEN + 1 + LIB_SUFFIX_LEN; @@ -66,14 +66,6 @@ const char* ValidLibraryPathLastSlash(const char* fileName, bool suppress64Bit, return nullptr; } - if (!debuggable) { - // Make sure the filename starts with lib and ends with ".so". - if (strncmp(fileName + fileNameLen - LIB_SUFFIX_LEN, LIB_SUFFIX.data(), LIB_SUFFIX_LEN) != 0 - || strncmp(lastSlash, LIB_PREFIX.data(), LIB_PREFIX_LEN) != 0) { - return nullptr; - } - } - // Don't include 64 bit versions if they are suppressed if (suppress64Bit && std::find(abis.begin(), abis.end(), std::string_view( fileName + APK_LIB_LEN, lastSlash - fileName - APK_LIB_LEN)) != abis.end()) { diff --git a/libs/androidfw/include/androidfw/ApkParsing.h b/libs/androidfw/include/androidfw/ApkParsing.h index 194eaae8e12a..b288e152f383 100644 --- a/libs/androidfw/include/androidfw/ApkParsing.h +++ b/libs/androidfw/include/androidfw/ApkParsing.h @@ -24,7 +24,7 @@ extern const size_t APK_LIB_LEN; namespace android::util { // Checks if filename is a valid library path and returns a pointer to the last slash in the path // if it is, nullptr otherwise -const char* ValidLibraryPathLastSlash(const char* filename, bool suppress64Bit, bool debuggable); +const char* ValidLibraryPathLastSlash(const char* filename, bool suppress64Bit); // Equivalent to android.os.FileUtils.isFilenameSafe bool isFilenameSafe(const char* filename); diff --git a/libs/androidfw/tests/ApkParsing_test.cpp b/libs/androidfw/tests/ApkParsing_test.cpp index ac1dc9b88463..f1f9d7166914 100644 --- a/libs/androidfw/tests/ApkParsing_test.cpp +++ b/libs/androidfw/tests/ApkParsing_test.cpp @@ -27,57 +27,45 @@ using ::testing::NotNull; namespace android { TEST(ApkParsingTest, ValidArm64Path) { const char* path = "lib/arm64-v8a/library.so"; - auto lastSlash = util::ValidLibraryPathLastSlash(path, false, false); + auto lastSlash = util::ValidLibraryPathLastSlash(path, false); ASSERT_THAT(lastSlash, NotNull()); ASSERT_THAT(lastSlash, Eq(path + 13)); } TEST(ApkParsingTest, ValidArm64PathButSuppressed) { const char* path = "lib/arm64-v8a/library.so"; - auto lastSlash = util::ValidLibraryPathLastSlash(path, true, false); + auto lastSlash = util::ValidLibraryPathLastSlash(path, true); ASSERT_THAT(lastSlash, IsNull()); } TEST(ApkParsingTest, ValidArm32Path) { const char* path = "lib/armeabi-v7a/library.so"; - auto lastSlash = util::ValidLibraryPathLastSlash(path, false, false); + auto lastSlash = util::ValidLibraryPathLastSlash(path, false); ASSERT_THAT(lastSlash, NotNull()); ASSERT_THAT(lastSlash, Eq(path + 15)); } -TEST(ApkParsingTest, InvalidMustStartWithLib) { - const char* path = "lib/arm64-v8a/random.so"; - auto lastSlash = util::ValidLibraryPathLastSlash(path, false, false); - ASSERT_THAT(lastSlash, IsNull()); -} - -TEST(ApkParsingTest, InvalidMustEndInSo) { - const char* path = "lib/arm64-v8a/library.txt"; - auto lastSlash = util::ValidLibraryPathLastSlash(path, false, false); - ASSERT_THAT(lastSlash, IsNull()); -} - TEST(ApkParsingTest, InvalidCharacter) { const char* path = "lib/arm64-v8a/lib#.so"; - auto lastSlash = util::ValidLibraryPathLastSlash(path, false, false); + auto lastSlash = util::ValidLibraryPathLastSlash(path, false); ASSERT_THAT(lastSlash, IsNull()); } TEST(ApkParsingTest, InvalidSubdirectories) { const char* path = "lib/arm64-v8a/anything/library.so"; - auto lastSlash = util::ValidLibraryPathLastSlash(path, false, false); + auto lastSlash = util::ValidLibraryPathLastSlash(path, false); ASSERT_THAT(lastSlash, IsNull()); } TEST(ApkParsingTest, InvalidFileAtRoot) { const char* path = "lib/library.so"; - auto lastSlash = util::ValidLibraryPathLastSlash(path, false, false); + auto lastSlash = util::ValidLibraryPathLastSlash(path, false); ASSERT_THAT(lastSlash, IsNull()); } TEST(ApkParsingTest, InvalidPrefix) { const char* path = "assets/libhello.so"; - auto lastSlash = util::ValidLibraryPathLastSlash(path, false, false); + auto lastSlash = util::ValidLibraryPathLastSlash(path, false); ASSERT_THAT(lastSlash, IsNull()); } }
\ No newline at end of file diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp index 69fe40c755ea..6ab8e4e0e2ab 100644 --- a/libs/hwui/renderthread/RenderThread.cpp +++ b/libs/hwui/renderthread/RenderThread.cpp @@ -323,6 +323,7 @@ void RenderThread::initGrContextOptions(GrContextOptions& options) { } void RenderThread::destroyRenderingContext() { + ATRACE_CALL(); mFunctorManager.onContextDestroyed(); if (Properties::getRenderPipelineType() == RenderPipelineType::SkiaGL) { if (mEglManager->hasEglContext()) { @@ -520,7 +521,10 @@ void RenderThread::preload() { // EGL driver is always preloaded only if HWUI renders with GL. if (Properties::getRenderPipelineType() == RenderPipelineType::SkiaGL) { if (Properties::earlyPreloadGlContext()) { - queue().post([this]() { requireGlContext(); }); + queue().post([this]() { + ATRACE_NAME("earlyPreloadGlContext"); + requireGlContext(); + }); } else { std::thread eglInitThread([]() { eglGetDisplay(EGL_DEFAULT_DISPLAY); }); eglInitThread.detach(); @@ -528,9 +532,6 @@ void RenderThread::preload() { } else { requireVkContext(); } - if (Properties::earlyPreloadGlContext()) { - queue().post([]() { GraphicBufferAllocator::getInstance(); }); - } HardwareBitmapUploader::initialize(); } diff --git a/media/java/android/media/quality/MediaQualityManager.java b/media/java/android/media/quality/MediaQualityManager.java index aeb028ccd0a6..b7269256a449 100644 --- a/media/java/android/media/quality/MediaQualityManager.java +++ b/media/java/android/media/quality/MediaQualityManager.java @@ -18,6 +18,7 @@ package android.media.quality; import android.annotation.CallbackExecutor; import android.annotation.FlaggedApi; +import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SuppressLint; @@ -35,6 +36,8 @@ import androidx.annotation.RequiresPermission; import com.android.internal.util.Preconditions; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Iterator; import java.util.List; @@ -74,6 +77,39 @@ public final class MediaQualityManager { */ public static final String OPTION_INCLUDE_PARAMETERS = "include_parameters"; + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({AMBIENT_BACKLIGHT_EVENT_ENABLED, AMBIENT_BACKLIGHT_EVENT_DISABLED, + AMBIENT_BACKLIGHT_EVENT_METADATA, + AMBIENT_BACKLIGHT_EVENT_INTERRUPTED}) + public @interface AmbientBacklightEventTypes {} + + /** + * Event type for ambient backlight events. The ambient backlight is enabled. + * @hide + */ + public static final int AMBIENT_BACKLIGHT_EVENT_ENABLED = 1; + + /** + * Event type for ambient backlight events. The ambient backlight is disabled. + * @hide + */ + public static final int AMBIENT_BACKLIGHT_EVENT_DISABLED = 2; + + /** + * Event type for ambient backlight events. The ambient backlight metadata is + * available. + * @hide + */ + public static final int AMBIENT_BACKLIGHT_EVENT_METADATA = 3; + + /** + * Event type for ambient backlight events. The ambient backlight event is + * preempted by another application. + * @hide + */ + public static final int AMBIENT_BACKLIGHT_EVENT_INTERRUPTED = 4; + /** * @hide diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp index 933c512313ce..e5b58370e6dd 100644 --- a/packages/SettingsLib/Android.bp +++ b/packages/SettingsLib/Android.bp @@ -46,6 +46,8 @@ android_library { "SettingsLibIntroPreference", "SettingsLibLayoutPreference", "SettingsLibMainSwitchPreference", + "SettingsLibMetadata", + "SettingsLibPreference", "SettingsLibProfileSelector", "SettingsLibProgressBar", "SettingsLibRestrictedLockUtils", @@ -77,6 +79,7 @@ android_library { "src/**/*.kt", "src/**/I*.aidl", ], + kotlincflags: ["-Xjvm-default=all"], } // defaults for lint option diff --git a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt index 8b29b0044eea..ce66a360a99f 100644 --- a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt +++ b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt @@ -40,7 +40,6 @@ import com.android.settingslib.graph.proto.PreferenceProto import com.android.settingslib.graph.proto.PreferenceProto.ActionTarget import com.android.settingslib.graph.proto.PreferenceScreenProto import com.android.settingslib.graph.proto.TextProto -import com.android.settingslib.metadata.FloatPersistentPreference import com.android.settingslib.metadata.PersistentPreference import com.android.settingslib.metadata.PreferenceAvailabilityProvider import com.android.settingslib.metadata.PreferenceHierarchy @@ -410,13 +409,13 @@ fun PreferenceMetadata.toProto( value = preferenceValueProto { when (metadata) { is RangeValue -> storage.getInt(metadata.key)?.let { intValue = it } - is FloatPersistentPreference -> - storage.getFloat(metadata.key)?.let { floatValue = it } else -> {} } when (metadata.valueType) { Boolean::class.javaObjectType -> storage.getBoolean(metadata.key)?.let { booleanValue = it } + Float::class.javaObjectType -> + storage.getFloat(metadata.key)?.let { floatValue = it } } } } @@ -428,12 +427,12 @@ fun PreferenceMetadata.toProto( max = metadata.getMaxValue(context) step = metadata.getIncrementStep(context) } - is FloatPersistentPreference -> floatType = true else -> {} } if (metadata is PersistentPreference<*>) { when (metadata.valueType) { Boolean::class.javaObjectType -> booleanType = true + Float::class.javaObjectType -> floatType = true } } } diff --git a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/Metrics.kt b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/Metrics.kt new file mode 100644 index 000000000000..7323488c5299 --- /dev/null +++ b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/Metrics.kt @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.metadata + +/** Metrics logger for preference actions triggered by user interaction. */ +interface PreferenceUiActionMetricsLogger { + + /** + * Logs preference value change due to user interaction. + * + * Note: Preference value changed by external Set is excluded. + */ + fun logPreferenceValueChange( + screen: PreferenceScreenMetadata, + preference: PreferenceMetadata, + value: Any?, + ) {} +} + +/** Metrics logger for preference remote operations (e.g. external get/set). */ +interface PreferenceRemoteOpMetricsLogger diff --git a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PersistentPreference.kt b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PersistentPreference.kt index 4cc65815a78a..8e850465ef7d 100644 --- a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PersistentPreference.kt +++ b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PersistentPreference.kt @@ -219,12 +219,3 @@ interface RangeValue : ValueDescriptor { override fun isValidValue(context: Context, index: Int) = index in getMinValue(context)..getMaxValue(context) } - -/** A persistent preference that has a boolean value. */ -interface BooleanPreference : PersistentPreference<Boolean> { - override val valueType: Class<Boolean> - get() = Boolean::class.javaObjectType -} - -/** A persistent preference that has a float value. */ -interface FloatPersistentPreference : PersistentPreference<Float> diff --git a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceScreenRegistry.kt b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceScreenRegistry.kt index 9fc21343b6a0..c74b3151abb2 100644 --- a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceScreenRegistry.kt +++ b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceScreenRegistry.kt @@ -32,6 +32,9 @@ object PreferenceScreenRegistry : ReadWritePermitProvider { */ var preferenceScreenMetadataFactories = FixedArrayMap<String, PreferenceScreenMetadataFactory>() + /** Metrics logger for preference actions triggered by user interaction. */ + var preferenceUiActionMetricsLogger: PreferenceUiActionMetricsLogger? = null + private var readWritePermitProvider: ReadWritePermitProvider = object : ReadWritePermitProvider {} diff --git a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceTypes.kt b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceTypes.kt index b79a0c4f6381..623ea22410e5 100644 --- a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceTypes.kt +++ b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceTypes.kt @@ -18,8 +18,20 @@ package com.android.settingslib.metadata import androidx.annotation.StringRes +/** A persistent preference that has a boolean value. */ +interface BooleanValuePreference : PersistentPreference<Boolean> { + override val valueType: Class<Boolean> + get() = Boolean::class.javaObjectType +} + +/** A persistent preference that has a float value. */ +interface FloatValuePreference : PersistentPreference<Float> { + override val valueType: Class<Float> + get() = Float::class.javaObjectType +} + /** Common base class for preferences that have two selectable states and save a boolean value. */ -interface TwoStatePreference : PreferenceMetadata, BooleanPreference +interface TwoStatePreference : PreferenceMetadata, BooleanValuePreference /** A preference that provides a two-state toggleable option. */ open class SwitchPreference diff --git a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBinding.kt b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBinding.kt index 6b7be91c1903..c61c6a5c75fa 100644 --- a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBinding.kt +++ b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBinding.kt @@ -17,6 +17,7 @@ package com.android.settingslib.preference import android.content.Context +import androidx.annotation.CallSuper import androidx.preference.DialogPreference import androidx.preference.ListPreference import androidx.preference.Preference @@ -59,6 +60,7 @@ interface PreferenceBinding { * @param preference preference widget created by [createWidget] * @param metadata metadata to apply */ + @CallSuper fun bind(preference: Preference, metadata: PreferenceMetadata) { metadata.apply { preference.key = key diff --git a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindings.kt b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindings.kt index bd5d17cb2468..b82c554ea26a 100644 --- a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindings.kt +++ b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindings.kt @@ -45,7 +45,7 @@ interface PreferenceScreenBinding : PreferenceBinding { context.getString(screenTitle) } else { screenMetadata.getScreenTitle(context) - ?: (this as? PreferenceTitleProvider)?.getTitle(context) + ?: (screenMetadata as? PreferenceTitleProvider)?.getTitle(context) } } } diff --git a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceDataStoreDelegate.kt b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceDataStoreDelegate.kt new file mode 100644 index 000000000000..482eaf9146e5 --- /dev/null +++ b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceDataStoreDelegate.kt @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.preference + +import androidx.preference.PreferenceDataStore + +/** [PreferenceDataStore] delegate. */ +open class PreferenceDataStoreDelegate(internal val delegate: PreferenceDataStore) : + PreferenceDataStore() { + + override fun getBoolean(key: String, defValue: Boolean): Boolean = + delegate.getBoolean(key, defValue) + + override fun getFloat(key: String, defValue: Float): Float = delegate.getFloat(key, defValue) + + override fun getInt(key: String, defValue: Int): Int = delegate.getInt(key, defValue) + + override fun getLong(key: String, defValue: Long): Long = delegate.getLong(key, defValue) + + override fun getString(key: String, defValue: String?): String? = + delegate.getString(key, defValue) + + override fun getStringSet(key: String, defValues: Set<String>?): Set<String>? = + delegate.getStringSet(key, defValues) + + override fun putBoolean(key: String, value: Boolean) = delegate.putBoolean(key, value) + + override fun putFloat(key: String, value: Float) = delegate.putFloat(key, value) + + override fun putInt(key: String, value: Int) = delegate.putInt(key, value) + + override fun putLong(key: String, value: Long) = delegate.putLong(key, value) + + override fun putString(key: String, value: String?) = delegate.putString(key, value) + + override fun putStringSet(key: String, values: Set<String>?) = + delegate.putStringSet(key, values) +} diff --git a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceFragment.kt b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceFragment.kt index e237a6a4cf14..ffe181d0c350 100644 --- a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceFragment.kt +++ b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceFragment.kt @@ -21,6 +21,7 @@ import android.content.Intent import android.os.Bundle import android.util.Log import androidx.annotation.XmlRes +import androidx.lifecycle.Lifecycle import androidx.preference.PreferenceScreen import com.android.settingslib.metadata.EXTRA_BINDING_SCREEN_KEY import com.android.settingslib.metadata.PreferenceScreenBindingKeyProvider @@ -38,6 +39,11 @@ open class PreferenceFragment : preferenceScreen = createPreferenceScreen() } + override fun setPreferenceScreen(preferenceScreen: PreferenceScreen?) { + super.setPreferenceScreen(preferenceScreen) + updateActivityTitle() + } + fun createPreferenceScreen(): PreferenceScreen? = createPreferenceScreen(PreferenceScreenFactory(this)) @@ -102,9 +108,19 @@ open class PreferenceFragment : override fun onResume() { super.onResume() + // Even when activity has several fragments with preference screen, this will keep activity + // title in sync when fragment manager pops back stack. + updateActivityTitle() preferenceScreenBindingHelper?.onResume() } + internal fun updateActivityTitle() { + if (!lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) return + val activity = activity ?: return + val title = preferenceScreen?.title ?: return + if (activity.title != title) activity.title = title + } + override fun onPause() { preferenceScreenBindingHelper?.onPause() super.onPause() diff --git a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceHierarchyInflater.kt b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceHierarchyInflater.kt index 657f69a738f2..f3f854c0dc47 100644 --- a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceHierarchyInflater.kt +++ b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceHierarchyInflater.kt @@ -18,15 +18,30 @@ package com.android.settingslib.preference import androidx.preference.PreferenceDataStore import androidx.preference.PreferenceGroup +import androidx.preference.PreferenceScreen import com.android.settingslib.datastore.KeyValueStore import com.android.settingslib.metadata.PersistentPreference import com.android.settingslib.metadata.PreferenceHierarchy +import com.android.settingslib.metadata.PreferenceScreenMetadata /** Inflates [PreferenceHierarchy] into given [PreferenceGroup] recursively. */ -fun PreferenceGroup.inflatePreferenceHierarchy( +fun PreferenceScreen.inflatePreferenceHierarchy( preferenceBindingFactory: PreferenceBindingFactory, hierarchy: PreferenceHierarchy, - storages: MutableMap<KeyValueStore, PreferenceDataStore> = mutableMapOf(), +) = + inflatePreferenceHierarchy( + hierarchy.metadata as PreferenceScreenMetadata, + preferenceBindingFactory, + hierarchy, + mutableMapOf(), + ) + +/** Inflates [PreferenceHierarchy] into given [PreferenceGroup] recursively. */ +private fun PreferenceGroup.inflatePreferenceHierarchy( + preferenceScreenMetadata: PreferenceScreenMetadata, + preferenceBindingFactory: PreferenceBindingFactory, + hierarchy: PreferenceHierarchy, + storages: MutableMap<KeyValueStore, PreferenceDataStore>, ) { preferenceBindingFactory.bind(this, hierarchy) hierarchy.forEach { @@ -38,11 +53,18 @@ fun PreferenceGroup.inflatePreferenceHierarchy( val preferenceGroup = widget as PreferenceGroup // MUST add preference before binding, otherwise exception is raised when add child addPreference(preferenceGroup) - preferenceGroup.inflatePreferenceHierarchy(preferenceBindingFactory, it) + preferenceGroup.inflatePreferenceHierarchy( + preferenceScreenMetadata, + preferenceBindingFactory, + it, + storages, + ) } else { (metadata as? PersistentPreference<*>)?.storage(context)?.let { storage -> widget.preferenceDataStore = - storages.getOrPut(storage) { PreferenceDataStoreAdapter(storage) } + storages.getOrPut(storage) { + storage.toPreferenceDataStore(preferenceScreenMetadata, metadata) + } } preferenceBindingFactory.bind(widget, it, preferenceBinding) // MUST add preference after binding for persistent preference to get initial value diff --git a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceScreenBindingHelper.kt b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceScreenBindingHelper.kt index 8358ab921fb6..4a6a589cd3c9 100644 --- a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceScreenBindingHelper.kt +++ b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceScreenBindingHelper.kt @@ -38,6 +38,7 @@ import com.android.settingslib.metadata.PreferenceHierarchyNode import com.android.settingslib.metadata.PreferenceLifecycleContext import com.android.settingslib.metadata.PreferenceLifecycleProvider import com.android.settingslib.metadata.PreferenceMetadata +import com.android.settingslib.metadata.PreferenceScreenMetadata import com.android.settingslib.metadata.PreferenceScreenRegistry import com.google.common.collect.ImmutableMap import com.google.common.collect.ImmutableMultimap @@ -51,7 +52,7 @@ import com.google.common.collect.ImmutableMultimap */ class PreferenceScreenBindingHelper( context: Context, - fragment: PreferenceFragment, + private val fragment: PreferenceFragment, private val preferenceBindingFactory: PreferenceBindingFactory, private val preferenceScreen: PreferenceScreen, private val preferenceHierarchy: PreferenceHierarchy, @@ -70,9 +71,7 @@ class PreferenceScreenBindingHelper( override fun <T : Any> requirePreference(key: String) = findPreference<T>(key)!! override fun getKeyValueStore(key: String) = - (findPreference<Preference>(key)?.preferenceDataStore - as? PreferenceDataStoreAdapter) - ?.keyValueStore + findPreference<Preference>(key)?.preferenceDataStore?.findKeyValueStore() override fun notifyPreferenceChange(key: String) = notifyChange(key, PreferenceChangeReason.STATE) @@ -137,22 +136,29 @@ class PreferenceScreenBindingHelper( addObserver(preferenceObserver, mainExecutor) preferenceScreen.forEachRecursively { - val preferenceDataStore = it.preferenceDataStore - if (preferenceDataStore is PreferenceDataStoreAdapter) { + it.preferenceDataStore?.findKeyValueStore()?.let { keyValueStore -> val key = it.key - val keyValueStore = preferenceDataStore.keyValueStore storages[key] = keyValueStore keyValueStore.addObserver(key, storageObserver, mainExecutor) } } } + private fun PreferenceDataStore.findKeyValueStore(): KeyValueStore? = + when (this) { + is PreferenceDataStoreAdapter -> keyValueStore + is PreferenceDataStoreDelegate -> delegate.findKeyValueStore() + else -> null + } + private fun onPreferenceChange(key: String?, reason: Int) { if (key == null) return // bind preference to update UI preferenceScreen.findPreference<Preference>(key)?.let { - preferences[key]?.let { node -> preferenceBindingFactory.bind(it, node) } + val node = preferences[key] ?: return@let + preferenceBindingFactory.bind(it, node) + if (it == preferenceScreen) fragment.updateActivityTitle() } // check reason to avoid potential infinite loop @@ -239,17 +245,17 @@ class PreferenceScreenBindingHelper( preferenceBindingFactory: PreferenceBindingFactory, preferenceHierarchy: PreferenceHierarchy, ) { + val preferenceScreenMetadata = preferenceHierarchy.metadata as PreferenceScreenMetadata val preferences = mutableMapOf<String, PreferenceHierarchyNode>() - preferenceHierarchy.forEachRecursively { - val metadata = it.metadata - preferences[metadata.key] = it - } + preferenceHierarchy.forEachRecursively { preferences[it.metadata.key] = it } val storages = mutableMapOf<KeyValueStore, PreferenceDataStore>() fun Preference.setPreferenceDataStore(metadata: PreferenceMetadata) { (metadata as? PersistentPreference<*>)?.storage(context)?.let { storage -> preferenceDataStore = - storages.getOrPut(storage) { PreferenceDataStoreAdapter(storage) } + storages.getOrPut(storage) { + storage.toPreferenceDataStore(preferenceScreenMetadata, metadata) + } } } diff --git a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/Utils.kt b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/Utils.kt index 2e7221bd4d7f..f5ab4b2e38d8 100644 --- a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/Utils.kt +++ b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/Utils.kt @@ -17,7 +17,12 @@ package com.android.settingslib.preference import androidx.preference.Preference +import androidx.preference.PreferenceDataStore import androidx.preference.PreferenceGroup +import com.android.settingslib.datastore.KeyValueStore +import com.android.settingslib.metadata.PreferenceMetadata +import com.android.settingslib.metadata.PreferenceScreenMetadata +import com.android.settingslib.metadata.PreferenceScreenRegistry /** Traversals preference hierarchy recursively and applies an action. */ fun PreferenceGroup.forEachRecursively(action: (Preference) -> Unit) { @@ -31,3 +36,51 @@ fun PreferenceGroup.forEachRecursively(action: (Preference) -> Unit) { } } } + +/** + * Converts [KeyValueStore] to [PreferenceDataStore]. + * + * [PreferenceScreenRegistry.preferenceUiActionMetricsLogger] is wrapped on top of + * [PreferenceDataStoreDelegate] to log metrics. + * + * Note: Only user interaction changes are logged. + */ +fun KeyValueStore.toPreferenceDataStore( + screen: PreferenceScreenMetadata, + preference: PreferenceMetadata, +): PreferenceDataStore { + val preferenceDataStore: PreferenceDataStore = PreferenceDataStoreAdapter(this) + val metricsLogger = + PreferenceScreenRegistry.preferenceUiActionMetricsLogger ?: return preferenceDataStore + return object : PreferenceDataStoreDelegate(preferenceDataStore) { + override fun putBoolean(key: String, value: Boolean) { + super.putBoolean(key, value) + metricsLogger.logPreferenceValueChange(screen, preference, value) + } + + override fun putFloat(key: String, value: Float) { + super.putFloat(key, value) + metricsLogger.logPreferenceValueChange(screen, preference, value) + } + + override fun putInt(key: String, value: Int) { + super.putInt(key, value) + metricsLogger.logPreferenceValueChange(screen, preference, value) + } + + override fun putLong(key: String, value: Long) { + super.putLong(key, value) + metricsLogger.logPreferenceValueChange(screen, preference, value) + } + + override fun putString(key: String, value: String?) { + super.putString(key, value) + metricsLogger.logPreferenceValueChange(screen, preference, value) + } + + override fun putStringSet(key: String, values: Set<String>?) { + super.putStringSet(key, values) + metricsLogger.logPreferenceValueChange(screen, preference, values) + } + } +} diff --git a/packages/SettingsLib/Preference/testutils/Android.bp b/packages/SettingsLib/Preference/testutils/Android.bp index 5287d6ce93f1..68b724280ac1 100644 --- a/packages/SettingsLib/Preference/testutils/Android.bp +++ b/packages/SettingsLib/Preference/testutils/Android.bp @@ -27,6 +27,7 @@ android_library { "androidx.test.core", "androidx.test.ext.junit", "flag-junit", + "mockito-kotlin2", "truth", ], } diff --git a/packages/SettingsLib/Preference/testutils/com/android/settingslib/preference/PreferenceBindingTestUtils.kt b/packages/SettingsLib/Preference/testutils/com/android/settingslib/preference/PreferenceBindingTestUtils.kt index 220614bf064f..172c68a4bd37 100644 --- a/packages/SettingsLib/Preference/testutils/com/android/settingslib/preference/PreferenceBindingTestUtils.kt +++ b/packages/SettingsLib/Preference/testutils/com/android/settingslib/preference/PreferenceBindingTestUtils.kt @@ -22,6 +22,8 @@ import androidx.preference.Preference import androidx.preference.PreferenceScreen import com.android.settingslib.metadata.PersistentPreference import com.android.settingslib.metadata.PreferenceMetadata +import com.android.settingslib.metadata.PreferenceScreenMetadata +import org.mockito.kotlin.mock /** Creates [Preference] widget and binds with metadata. */ @Suppress("UNCHECKED_CAST") @@ -29,13 +31,13 @@ import com.android.settingslib.metadata.PreferenceMetadata fun <P : Preference> PreferenceMetadata.createAndBindWidget( context: Context, preferenceScreen: PreferenceScreen? = null, + preferenceScreenMetadata: PreferenceScreenMetadata = mock(), ): P { val binding = PreferenceBindingFactory.defaultFactory.getPreferenceBinding(this)!! return (binding.createWidget(context) as P).also { if (this is PersistentPreference<*>) { - storage(context).let { keyValueStore -> - it.preferenceDataStore = PreferenceDataStoreAdapter(keyValueStore) - } + it.preferenceDataStore = + storage(context).toPreferenceDataStore(preferenceScreenMetadata, this) // Attach preference to preference screen, otherwise `Preference.performClick` does not // interact with underlying datastore (preferenceScreen ?: PreferenceScreenFactory(context).getOrCreatePreferenceScreen()) diff --git a/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_collapsable_textview.xml b/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_collapsable_textview.xml index 2776544e2948..7d7bec14ed78 100644 --- a/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_collapsable_textview.xml +++ b/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_collapsable_textview.xml @@ -48,6 +48,7 @@ android:layout_height="wrap_content" app:layout_constraintTop_toBottomOf="@android:id/title" app:layout_constraintStart_toStartOf="parent" + android:paddingTop="@dimen/settingslib_expressive_space_extrasmall6" android:textAlignment="viewStart" android:clickable="true" android:visibility="gone" diff --git a/packages/SettingsLib/ZeroStatePreference/Android.bp b/packages/SettingsLib/ZeroStatePreference/Android.bp index 4fc00bdbfee0..0949e2c75c55 100644 --- a/packages/SettingsLib/ZeroStatePreference/Android.bp +++ b/packages/SettingsLib/ZeroStatePreference/Android.bp @@ -29,5 +29,6 @@ android_library { min_sdk_version: "28", apex_available: [ "//apex_available:platform", + "com.android.healthfitness", ], } diff --git a/packages/SettingsLib/ZeroStatePreference/res/layout/settingslib_expressive_preference_zerostate.xml b/packages/SettingsLib/ZeroStatePreference/res/layout/settingslib_expressive_preference_zerostate.xml index c0b195cc1f74..ae3f1dde8a3a 100644 --- a/packages/SettingsLib/ZeroStatePreference/res/layout/settingslib_expressive_preference_zerostate.xml +++ b/packages/SettingsLib/ZeroStatePreference/res/layout/settingslib_expressive_preference_zerostate.xml @@ -17,7 +17,7 @@ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" - android:layout_height="match_parent" + android:layout_height="wrap_content" android:layout_gravity="center" android:gravity="center" android:orientation="vertical"> diff --git a/packages/SettingsLib/src/com/android/settingslib/PreferenceBindings.kt b/packages/SettingsLib/src/com/android/settingslib/PreferenceBindings.kt new file mode 100644 index 000000000000..a64e8cc07b15 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/PreferenceBindings.kt @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +@file:Suppress("ktlint:standard:filename") // remove once we have more bindings + +package com.android.settingslib + +import android.content.Context +import androidx.preference.Preference +import com.android.settingslib.metadata.PreferenceMetadata +import com.android.settingslib.preference.PreferenceBinding + +/** Preference binding for [PrimarySwitchPreference]. */ +interface PrimarySwitchPreferenceBinding : PreferenceBinding { + + override fun createWidget(context: Context): Preference = PrimarySwitchPreference(context) + + override fun bind(preference: Preference, metadata: PreferenceMetadata) { + super.bind(preference, metadata) + (preference as PrimarySwitchPreference).apply { + isChecked = preferenceDataStore!!.getBoolean(key, false) + isSwitchEnabled = isEnabled + } + } +} diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java index 5bcdcc09206b..d9e79fa7c9dc 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java @@ -52,6 +52,14 @@ import java.util.concurrent.Executors; * BluetoothLeBroadcastAssistant.Callback} to get the result callback. */ public class LocalBluetoothLeBroadcastAssistant implements LocalBluetoothProfile { + /** A derived source state based on {@link BluetoothLeBroadcastReceiveState}. */ + public enum LocalBluetoothLeBroadcastSourceState { + UNKNOWN, + STREAMING, + DECRYPTION_FAILED, + PAUSED, + } + private static final String TAG = "LocalBluetoothLeBroadcastAssistant"; private static final int UNKNOWN_VALUE_PLACEHOLDER = -1; private static final boolean DEBUG = BluetoothUtils.D; @@ -59,6 +67,13 @@ public class LocalBluetoothLeBroadcastAssistant implements LocalBluetoothProfile static final String NAME = "LE_AUDIO_BROADCAST_ASSISTANT"; // Order of this profile in device profiles list private static final int ORDINAL = 1; + // Referring to Broadcast Audio Scan Service 1.0 + // Table 3.9: Broadcast Receive State characteristic format + // 0x00000000: 0b0 = Not synchronized to BIS_index[x] + // 0xFFFFFFFF: Failed to sync to BIG + private static final long BIS_SYNC_NOT_SYNC_TO_BIS = 0x00000000L; + private static final long BIS_SYNC_FAILED_SYNC_TO_BIG = 0xFFFFFFFFL; + private static final String EMPTY_DEVICE_ADDRESS = "00:00:00:00:00:00"; private LocalBluetoothProfileManager mProfileManager; private BluetoothLeBroadcastAssistant mService; @@ -558,4 +573,30 @@ public class LocalBluetoothLeBroadcastAssistant implements LocalBluetoothProfile } } } + + /** Checks the source connection status based on the provided broadcast receive state. */ + public static LocalBluetoothLeBroadcastSourceState getLocalSourceState( + BluetoothLeBroadcastReceiveState state) { + // Source is actively streaming + if (state.getBisSyncState().stream() + .anyMatch( + bitmap -> + (bitmap != BIS_SYNC_NOT_SYNC_TO_BIS + && bitmap != BIS_SYNC_FAILED_SYNC_TO_BIG))) { + return LocalBluetoothLeBroadcastSourceState.STREAMING; + } + // Wrong password is used for the source + if (state.getPaSyncState() == BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_SYNCHRONIZED + && state.getBigEncryptionState() + == BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_BAD_CODE) { + return LocalBluetoothLeBroadcastSourceState.DECRYPTION_FAILED; + } + // Source in hysteresis mode + if (!state.getSourceDevice().getAddress().equals(EMPTY_DEVICE_ADDRESS)) { + // Referring to Broadcast Audio Scan Service 1.0 + // All zero address means no source on the sink device + return LocalBluetoothLeBroadcastSourceState.PAUSED; + } + return LocalBluetoothLeBroadcastSourceState.UNKNOWN; + } } diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java index b0309a8fa5a5..6681c014f2e0 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java @@ -454,5 +454,6 @@ public class SecureSettingsValidators { VALIDATORS.put(Secure.MANDATORY_BIOMETRICS_REQUIREMENTS_SATISFIED, new InclusiveIntegerRangeValidator(0, 1)); VALIDATORS.put(Secure.ADVANCED_PROTECTION_MODE, BOOLEAN_VALIDATOR); + VALIDATORS.put(Secure.DISABLE_ADAPTIVE_AUTH_LIMIT_LOCK, BOOLEAN_VALIDATOR); } } diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java index cbdb36fff98c..9aad5d5f8367 100644 --- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java +++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java @@ -689,6 +689,7 @@ public class SettingsBackupTest { Settings.Secure.DEFAULT_DEVICE_INPUT_METHOD, Settings.Secure.DEVICE_PAIRED, Settings.Secure.DIALER_DEFAULT_APPLICATION, + Settings.Secure.DISABLE_ADAPTIVE_AUTH_LIMIT_LOCK, Settings.Secure.DISABLED_PRINT_SERVICES, Settings.Secure.DISABLE_SECURE_WINDOWS, Settings.Secure.DISABLED_SYSTEM_INPUT_METHODS, diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index 2d68ab8ff451..9531bc38e797 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -1930,3 +1930,13 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + name: "magnetic_notification_horizontal_swipe" + namespace: "systemui" + description: "Add support for magnetic behavior on horizontal notification swipes." + bug: "390179908" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteAnimationRunnerCompat.java b/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteAnimationRunnerCompat.java index 8a57e8cbbb20..f36f0306d82b 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteAnimationRunnerCompat.java +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteAnimationRunnerCompat.java @@ -27,6 +27,7 @@ import static android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_EXIT_TRAN import static android.window.TransitionInfo.FLAG_IS_WALLPAPER; import static com.android.internal.util.Preconditions.checkArgument; +import static com.android.wm.shell.shared.TransitionUtil.FLAG_IS_DESKTOP_WALLPAPER_ACTIVITY; import static com.android.wm.shell.shared.TransitionUtil.isClosingMode; import static com.android.wm.shell.shared.TransitionUtil.isClosingType; import static com.android.wm.shell.shared.TransitionUtil.isOpeningMode; @@ -270,7 +271,8 @@ public abstract class RemoteAnimationRunnerCompat extends IRemoteAnimationRunner // skip changes that we didn't wrap if (!leashMap.containsKey(change.getLeash())) continue; // Only make the update if we are closing Desktop tasks. - if (change.getTaskInfo() != null && change.getTaskInfo().isFreeform() + if (change.getTaskInfo() != null && (change.getTaskInfo().isFreeform() + || change.hasFlags(FLAG_IS_DESKTOP_WALLPAPER_ACTIVITY)) && isClosingMode(change.getMode())) { startTransaction.setAlpha(leashMap.get(launcherChange.getLeash()), 0f); return; diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt index 439968590dad..d43b596b32f1 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt @@ -87,10 +87,10 @@ import androidx.compose.ui.unit.times import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.compose.PlatformButton import com.android.compose.animation.Easings +import com.android.compose.animation.scene.ContentScope import com.android.compose.animation.scene.ElementKey import com.android.compose.animation.scene.MutableSceneTransitionLayoutState import com.android.compose.animation.scene.SceneKey -import com.android.compose.animation.scene.SceneScope import com.android.compose.animation.scene.SceneTransitionLayout import com.android.compose.animation.scene.transitions import com.android.compose.windowsizeclass.LocalWindowSizeClass @@ -515,7 +515,7 @@ private fun FoldAware( } @Composable -private fun SceneScope.FoldableScene( +private fun ContentScope.FoldableScene( aboveFold: @Composable BoxScope.() -> Unit, belowFold: @Composable BoxScope.() -> Unit, isSplit: Boolean, diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt index 55b42931b1fa..fad8ae7e3ba2 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt @@ -24,8 +24,8 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.ui.Modifier +import com.android.compose.animation.scene.ContentScope import com.android.compose.animation.scene.ElementKey -import com.android.compose.animation.scene.SceneScope import com.android.compose.animation.scene.UserAction import com.android.compose.animation.scene.UserActionResult import com.android.systemui.bouncer.ui.BouncerDialogFactory @@ -73,7 +73,7 @@ constructor( } @Composable - override fun SceneScope.Content(modifier: Modifier) = + override fun ContentScope.Content(modifier: Modifier) = BouncerScene( viewModel = rememberViewModel("BouncerScene") { contentViewModelFactory.create() }, dialogFactory = dialogFactory, @@ -82,7 +82,7 @@ constructor( } @Composable -private fun SceneScope.BouncerScene( +private fun ContentScope.BouncerScene( viewModel: BouncerSceneContentViewModel, dialogFactory: BouncerDialogFactory, modifier: Modifier = Modifier, @@ -96,8 +96,8 @@ private fun SceneScope.BouncerScene( drawRect(color = backgroundColor) } - // Separate the bouncer content into a reusable composable that doesn't have any SceneScope - // dependencies + // Separate the bouncer content into a reusable composable that doesn't have any + // ContentScope dependencies BouncerContent( viewModel, dialogFactory, diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt index a2a91fcd5d52..9b5ff7f9e7e8 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt @@ -33,13 +33,13 @@ import androidx.compose.ui.semantics.semantics import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.compose.animation.scene.ContentKey +import com.android.compose.animation.scene.ContentScope import com.android.compose.animation.scene.Edge import com.android.compose.animation.scene.ElementKey import com.android.compose.animation.scene.ElementMatcher import com.android.compose.animation.scene.LowestZIndexContentPicker import com.android.compose.animation.scene.MutableSceneTransitionLayoutState import com.android.compose.animation.scene.SceneKey -import com.android.compose.animation.scene.SceneScope import com.android.compose.animation.scene.SceneTransitionLayout import com.android.compose.animation.scene.Swipe import com.android.compose.animation.scene.observableTransitionState @@ -229,7 +229,7 @@ fun CommunalContainer( /** Scene containing the glanceable hub UI. */ @Composable -fun SceneScope.CommunalScene( +fun ContentScope.CommunalScene( backgroundType: CommunalBackgroundType, colors: CommunalColors, content: CommunalContent, diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt index 0a0003ee9a8a..fea34921b853 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt @@ -31,7 +31,7 @@ import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.IntRect import androidx.compose.ui.unit.dp import androidx.compose.ui.zIndex -import com.android.compose.animation.scene.SceneScope +import com.android.compose.animation.scene.ContentScope import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor import com.android.systemui.communal.smartspace.SmartspaceInteractionHandler import com.android.systemui.communal.ui.compose.section.AmbientStatusBarSection @@ -65,7 +65,7 @@ constructor( ) { @Composable - fun SceneScope.Content(modifier: Modifier = Modifier) { + fun ContentScope.Content(modifier: Modifier = Modifier) { CommunalTouchableSurface(viewModel = viewModel, modifier = modifier) { Layout( modifier = Modifier.fillMaxSize(), @@ -81,7 +81,7 @@ constructor( dialogFactory = dialogFactory, widgetSection = widgetSection, modifier = Modifier.element(Communal.Elements.Grid), - sceneScope = this@Content, + contentScope = this@Content, ) } if (communalSettingsInteractor.isV2FlagEnabled()) { @@ -193,6 +193,7 @@ constructor( companion object { private val screensaverButtonSize: Dp = 64.dp private val screensaverButtonPadding: Dp = 24.dp + // TODO(b/382739998): Remove these hardcoded values once lock icon size and bottom area // position are sorted. private val lockIconSize: Dp = 54.dp diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt index 068df8e31ed0..70a74f064563 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt @@ -171,7 +171,7 @@ import androidx.compose.ui.zIndex import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.window.layout.WindowMetricsCalculator import com.android.compose.animation.Easings.Emphasized -import com.android.compose.animation.scene.SceneScope +import com.android.compose.animation.scene.ContentScope import com.android.compose.modifiers.thenIf import com.android.compose.ui.graphics.painter.rememberDrawablePainter import com.android.internal.R.dimen.system_app_widget_background_radius @@ -217,7 +217,7 @@ fun CommunalHub( widgetConfigurator: WidgetConfigurator? = null, onOpenWidgetPicker: (() -> Unit)? = null, onEditDone: (() -> Unit)? = null, - sceneScope: SceneScope? = null, + contentScope: ContentScope? = null, ) { val communalContent by viewModel.communalContent.collectAsStateWithLifecycle(initialValue = emptyList()) @@ -437,7 +437,7 @@ fun CommunalHub( widgetConfigurator = widgetConfigurator, interactionHandler = interactionHandler, widgetSection = widgetSection, - sceneScope = sceneScope, + contentScope = contentScope, ) } } @@ -827,7 +827,7 @@ private fun BoxScope.CommunalHubLazyGrid( widgetConfigurator: WidgetConfigurator?, interactionHandler: RemoteViews.InteractionHandler?, widgetSection: CommunalAppWidgetSection, - sceneScope: SceneScope?, + contentScope: ContentScope?, ) { var gridModifier = Modifier.align(Alignment.TopStart).onGloballyPositioned { setGridCoordinates(it) } @@ -1009,7 +1009,7 @@ private fun BoxScope.CommunalHubLazyGrid( interactionHandler = interactionHandler, widgetSection = widgetSection, resizeableItemFrameViewModel = resizeableItemFrameViewModel, - sceneScope = sceneScope, + contentScope = contentScope, ) } } @@ -1261,7 +1261,7 @@ private fun CommunalContent( interactionHandler: RemoteViews.InteractionHandler?, widgetSection: CommunalAppWidgetSection, resizeableItemFrameViewModel: ResizeableItemFrameViewModel, - sceneScope: SceneScope? = null, + contentScope: ContentScope? = null, ) { when (model) { is CommunalContentModel.WidgetContent.Widget -> @@ -1285,7 +1285,7 @@ private fun CommunalContent( is CommunalContentModel.CtaTileInViewMode -> CtaTileInViewModeContent(viewModel, modifier) is CommunalContentModel.Smartspace -> SmartspaceContent(interactionHandler, model, modifier) is CommunalContentModel.Tutorial -> TutorialContent(modifier) - is CommunalContentModel.Umo -> Umo(viewModel, sceneScope, modifier) + is CommunalContentModel.Umo -> Umo(viewModel, contentScope, modifier) is CommunalContentModel.Spacer -> Box(Modifier.fillMaxSize()) } } @@ -1451,7 +1451,6 @@ private fun WidgetContent( } else { Modifier } - Box( modifier = modifier @@ -1539,7 +1538,10 @@ private fun WidgetContent( with(widgetSection) { Widget( isFocusable = isFocusable, - openWidgetEditor = { viewModel.onOpenWidgetEditor() }, + openWidgetEditor = { + viewModel.setSelectedKey(model.key) + viewModel.onOpenWidgetEditor() + }, model = model, size = size, modifier = Modifier.fillMaxSize().allowGestures(allowed = !viewModel.isEditMode), @@ -1701,11 +1703,11 @@ private fun TutorialContent(modifier: Modifier = Modifier) { @Composable private fun Umo( viewModel: BaseCommunalViewModel, - sceneScope: SceneScope?, + contentScope: ContentScope?, modifier: Modifier = Modifier, ) { - if (SceneContainerFlag.isEnabled && sceneScope != null) { - sceneScope.MediaCarousel( + if (SceneContainerFlag.isEnabled && contentScope != null) { + contentScope.MediaCarousel( modifier = modifier.fillMaxSize(), isVisible = true, mediaHost = viewModel.mediaHost, @@ -1788,6 +1790,7 @@ fun AccessibilityContainer(viewModel: BaseCommunalViewModel, content: @Composabl CustomAccessibilityAction( context.getString(R.string.accessibility_action_label_edit_widgets) ) { + viewModel.setSelectedKey(null) viewModel.onOpenWidgetEditor() true }, diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt index 88b651019c4a..143fbe4de550 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt @@ -20,7 +20,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.android.compose.animation.scene.SceneScope +import com.android.compose.animation.scene.ContentScope import com.android.compose.animation.scene.UserAction import com.android.compose.animation.scene.UserActionResult import com.android.systemui.communal.shared.model.CommunalBackgroundType @@ -55,7 +55,7 @@ constructor( } @Composable - override fun SceneScope.Content(modifier: Modifier) { + override fun ContentScope.Content(modifier: Modifier) { val backgroundType by contentViewModel.communalBackground.collectAsStateWithLifecycle( initialValue = CommunalBackgroundType.ANIMATED diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/section/AmbientStatusBarSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/section/AmbientStatusBarSection.kt index 3b335fa3141e..1b0ddcb13ee2 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/section/AmbientStatusBarSection.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/section/AmbientStatusBarSection.kt @@ -22,7 +22,7 @@ import android.widget.FrameLayout import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.viewinterop.AndroidView -import com.android.compose.animation.scene.SceneScope +import com.android.compose.animation.scene.ContentScope import com.android.systemui.ambient.statusbar.dagger.AmbientStatusBarComponent import com.android.systemui.ambient.statusbar.ui.AmbientStatusBarView import com.android.systemui.communal.ui.compose.Communal @@ -31,11 +31,9 @@ import javax.inject.Inject class AmbientStatusBarSection @Inject -constructor( - private val factory: AmbientStatusBarComponent.Factory, -) { +constructor(private val factory: AmbientStatusBarComponent.Factory) { @Composable - fun SceneScope.AmbientStatusBar(modifier: Modifier = Modifier) { + fun ContentScope.AmbientStatusBar(modifier: Modifier = Modifier) { AndroidView( factory = { context -> (LayoutInflater.from(context) @@ -49,7 +47,7 @@ constructor( factory.create(this).getController().apply { init() } } }, - modifier = modifier.element(Communal.Elements.StatusBar) + modifier = modifier.element(Communal.Elements.StatusBar), ) } } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/dream/ui/composable/DreamScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/dream/ui/composable/DreamScene.kt index f4374c6c9487..6cd0c5dfd15c 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/dream/ui/composable/DreamScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/dream/ui/composable/DreamScene.kt @@ -24,7 +24,7 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp -import com.android.compose.animation.scene.SceneScope +import com.android.compose.animation.scene.ContentScope import com.android.compose.animation.scene.UserAction import com.android.compose.animation.scene.UserActionResult import com.android.systemui.dagger.SysUISingleton @@ -54,7 +54,7 @@ constructor(private val actionsViewModelFactory: DreamUserActionsViewModel.Facto } @Composable - override fun SceneScope.Content(modifier: Modifier) { + override fun ContentScope.Content(modifier: Modifier) { Box(modifier = modifier.fillMaxSize()) { // Render a sleep emoji to make the scene appear visible. Text( diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt index 5c5514aec03e..7b2f9dc76158 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt @@ -24,7 +24,7 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalView import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.android.compose.animation.scene.SceneScope +import com.android.compose.animation.scene.ContentScope import com.android.systemui.compose.modifiers.sysuiResTag import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor import com.android.systemui.keyguard.ui.composable.blueprint.ComposableLockscreenSceneBlueprint @@ -50,7 +50,7 @@ class LockscreenContent( } @Composable - fun SceneScope.Content(modifier: Modifier = Modifier) { + fun ContentScope.Content(modifier: Modifier = Modifier) { val viewModel = rememberViewModel("LockscreenContent-viewModel") { viewModelFactory.create() } val notificationLockscreenScrimViewModel = diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt index c7c29f9fdb7c..5e61af634bbc 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt @@ -19,7 +19,7 @@ package com.android.systemui.keyguard.ui.composable import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import com.android.compose.animation.scene.SceneScope +import com.android.compose.animation.scene.ContentScope import com.android.compose.animation.scene.UserAction import com.android.compose.animation.scene.UserActionResult import com.android.compose.animation.scene.animateContentFloatAsState @@ -54,18 +54,13 @@ constructor( } @Composable - override fun SceneScope.Content( - modifier: Modifier, - ) { - LockscreenScene( - lockscreenContent = lockscreenContent, - modifier = modifier, - ) + override fun ContentScope.Content(modifier: Modifier) { + LockscreenScene(lockscreenContent = lockscreenContent, modifier = modifier) } } @Composable -private fun SceneScope.LockscreenScene( +private fun ContentScope.LockscreenScene( lockscreenContent: Lazy<LockscreenContent>, modifier: Modifier = Modifier, ) { diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/CommunalBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/CommunalBlueprint.kt index adad4468b751..c365ec590a5f 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/CommunalBlueprint.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/CommunalBlueprint.kt @@ -23,7 +23,7 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -import com.android.compose.animation.scene.SceneScope +import com.android.compose.animation.scene.ContentScope import com.android.systemui.keyguard.ui.composable.LockscreenLongPress import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel import dagger.Binds @@ -37,14 +37,8 @@ class CommunalBlueprint @Inject constructor() : ComposableLockscreenSceneBluepri override val id: String = "communal" @Composable - override fun SceneScope.Content( - viewModel: LockscreenContentViewModel, - modifier: Modifier, - ) { - LockscreenLongPress( - viewModel = viewModel.touchHandling, - modifier = modifier, - ) { _ -> + override fun ContentScope.Content(viewModel: LockscreenContentViewModel, modifier: Modifier) { + LockscreenLongPress(viewModel = viewModel.touchHandling, modifier = modifier) { _ -> Box(modifier.background(Color.Black)) { Text( text = "TODO(b/316211368): communal blueprint", diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ComposableLockscreenSceneBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ComposableLockscreenSceneBlueprint.kt index df36d0774f11..cfafb6214c45 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ComposableLockscreenSceneBlueprint.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ComposableLockscreenSceneBlueprint.kt @@ -18,16 +18,12 @@ package com.android.systemui.keyguard.ui.composable.blueprint import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import com.android.compose.animation.scene.SceneScope +import com.android.compose.animation.scene.ContentScope import com.android.systemui.keyguard.shared.model.LockscreenSceneBlueprint import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel /** Defines interface for classes that can render the content for a specific blueprint/layout. */ interface ComposableLockscreenSceneBlueprint : LockscreenSceneBlueprint { /** Renders the content of this blueprint. */ - @Composable - fun SceneScope.Content( - viewModel: LockscreenContentViewModel, - modifier: Modifier, - ) + @Composable fun ContentScope.Content(viewModel: LockscreenContentViewModel, modifier: Modifier) } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt index 9643f192e066..c55a3fdfc6c0 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt @@ -32,7 +32,7 @@ import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.IntRect import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.android.compose.animation.scene.SceneScope +import com.android.compose.animation.scene.ContentScope import com.android.compose.modifiers.padding import com.android.systemui.compose.modifiers.sysuiResTag import com.android.systemui.keyguard.ui.composable.LockscreenLongPress @@ -68,7 +68,7 @@ constructor( override val id: String = "default" @Composable - override fun SceneScope.Content(viewModel: LockscreenContentViewModel, modifier: Modifier) { + override fun ContentScope.Content(viewModel: LockscreenContentViewModel, modifier: Modifier) { val isUdfpsVisible = viewModel.isUdfpsVisible val isShadeLayoutWide by viewModel.isShadeLayoutWide.collectAsStateWithLifecycle() val unfoldTranslations by viewModel.unfoldTranslations.collectAsStateWithLifecycle() diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/AmbientIndicationSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/AmbientIndicationSection.kt index af9a195c4575..99a7633e3cd3 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/AmbientIndicationSection.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/AmbientIndicationSection.kt @@ -18,9 +18,9 @@ package com.android.systemui.keyguard.ui.composable.section import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import com.android.compose.animation.scene.SceneScope +import com.android.compose.animation.scene.ContentScope /** Defines interface for classes that can render the ambient indication area. */ interface AmbientIndicationSection { - @Composable fun SceneScope.AmbientIndication(modifier: Modifier) + @Composable fun ContentScope.AmbientIndication(modifier: Modifier) } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/BottomAreaSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/BottomAreaSection.kt index 5e9ade163ac2..52ccab3b4d1e 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/BottomAreaSection.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/BottomAreaSection.kt @@ -30,8 +30,8 @@ import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.unit.DpSize import androidx.compose.ui.viewinterop.AndroidView import androidx.core.content.res.ResourcesCompat +import com.android.compose.animation.scene.ContentScope import com.android.compose.animation.scene.ElementKey -import com.android.compose.animation.scene.SceneScope import com.android.systemui.animation.view.LaunchableImageView import com.android.systemui.keyguard.ui.binder.KeyguardIndicationAreaBinder import com.android.systemui.keyguard.ui.binder.KeyguardQuickAffordanceViewBinder @@ -61,7 +61,7 @@ constructor( * shortcut is placed along the edges of the display. */ @Composable - fun SceneScope.Shortcut( + fun ContentScope.Shortcut( isStart: Boolean, applyPadding: Boolean, modifier: Modifier = Modifier, @@ -89,7 +89,7 @@ constructor( } @Composable - fun SceneScope.IndicationArea(modifier: Modifier = Modifier) { + fun ContentScope.IndicationArea(modifier: Modifier = Modifier) { Element(key = IndicationAreaElementKey, modifier = modifier.indicationAreaPadding()) { content { IndicationArea( diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/DefaultClockSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/DefaultClockSection.kt index fb01e7039edd..34c0bcaca997 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/DefaultClockSection.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/DefaultClockSection.kt @@ -32,7 +32,7 @@ import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.viewinterop.AndroidView import androidx.core.view.contains import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.android.compose.animation.scene.SceneScope +import com.android.compose.animation.scene.ContentScope import com.android.compose.modifiers.padding import com.android.systemui.customization.R import com.android.systemui.keyguard.ui.composable.blueprint.ClockElementKeys.largeClockElementKey @@ -54,7 +54,7 @@ constructor( private val aodBurnInViewModel: AodBurnInViewModel, ) { @Composable - fun SceneScope.SmallClock( + fun ContentScope.SmallClock( burnInParams: BurnInParameters, onTopChanged: (top: Float?) -> Unit, modifier: Modifier = Modifier, @@ -87,7 +87,7 @@ constructor( } @Composable - fun SceneScope.LargeClock(burnInParams: BurnInParameters, modifier: Modifier = Modifier) { + fun ContentScope.LargeClock(burnInParams: BurnInParameters, modifier: Modifier = Modifier) { val currentClock by viewModel.currentClock.collectAsStateWithLifecycle() if (currentClock?.largeClock?.view == null) { return diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt index 597cbf24729b..4795e7cef87c 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt @@ -28,8 +28,8 @@ import androidx.compose.ui.unit.Constraints import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.IntRect import androidx.compose.ui.viewinterop.AndroidView +import com.android.compose.animation.scene.ContentScope import com.android.compose.animation.scene.ElementKey -import com.android.compose.animation.scene.SceneScope import com.android.systemui.biometrics.AuthController import com.android.systemui.customization.R as customR import com.android.systemui.dagger.qualifiers.Application @@ -66,7 +66,7 @@ constructor( @LongPressTouchLog private val logBuffer: LogBuffer, ) { @Composable - fun SceneScope.LockIcon(overrideColor: Color? = null, modifier: Modifier = Modifier) { + fun ContentScope.LockIcon(overrideColor: Color? = null, modifier: Modifier = Modifier) { val context = LocalContext.current AndroidView( diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/MediaCarouselSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/MediaCarouselSection.kt index 4a9f44b74099..0ff567bf90ad 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/MediaCarouselSection.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/MediaCarouselSection.kt @@ -23,7 +23,7 @@ import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.dimensionResource import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.android.compose.animation.scene.SceneScope +import com.android.compose.animation.scene.ContentScope import com.android.systemui.keyguard.ui.viewmodel.KeyguardMediaViewModel import com.android.systemui.media.controls.ui.composable.MediaCarousel import com.android.systemui.media.controls.ui.controller.MediaCarouselController @@ -42,7 +42,7 @@ constructor( ) { @Composable - fun SceneScope.KeyguardMediaCarousel( + fun ContentScope.KeyguardMediaCarousel( isShadeLayoutWide: Boolean, modifier: Modifier = Modifier, ) { diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt index 0344ab8e0196..2bc392d386bf 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt @@ -34,7 +34,7 @@ import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.unit.dp import androidx.compose.ui.viewinterop.AndroidView import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.android.compose.animation.scene.SceneScope +import com.android.compose.animation.scene.ContentScope import com.android.compose.modifiers.thenIf import com.android.systemui.common.ui.ConfigurationState import com.android.systemui.dagger.SysUISingleton @@ -148,7 +148,7 @@ constructor( } @Composable - fun SceneScope.HeadsUpNotifications() { + fun ContentScope.HeadsUpNotifications() { SnoozeableHeadsUpNotificationSpace( stackScrollView = stackScrollView.get(), viewModel = rememberViewModel("HeadsUpNotifications") { viewModelFactory.create() }, @@ -160,7 +160,7 @@ constructor( * adjustment */ @Composable - fun SceneScope.Notifications( + fun ContentScope.Notifications( areNotificationsVisible: Boolean, isShadeLayoutWide: Boolean, burnInParams: BurnInParameters?, diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SmartSpaceSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SmartSpaceSection.kt index 1cee4d67df3b..c3ba7ab2fd19 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SmartSpaceSection.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SmartSpaceSection.kt @@ -35,7 +35,7 @@ import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.unit.dp import androidx.compose.ui.viewinterop.AndroidView import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.android.compose.animation.scene.SceneScope +import com.android.compose.animation.scene.ContentScope import com.android.compose.modifiers.padding import com.android.systemui.keyguard.KeyguardUnlockAnimationController import com.android.systemui.keyguard.ui.composable.blueprint.ClockElementKeys @@ -57,7 +57,7 @@ constructor( private val aodBurnInViewModel: AodBurnInViewModel, ) { @Composable - fun SceneScope.SmartSpace( + fun ContentScope.SmartSpace( burnInParams: BurnInParameters, onTopChanged: (top: Float?) -> Unit, smartSpacePaddingTop: (Resources) -> Int, diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/StatusBarSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/StatusBarSection.kt index 0d8a47019a08..172c3f5a1135 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/StatusBarSection.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/StatusBarSection.kt @@ -26,7 +26,7 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.viewinterop.AndroidView -import com.android.compose.animation.scene.SceneScope +import com.android.compose.animation.scene.ContentScope import com.android.compose.modifiers.height import com.android.keyguard.dagger.KeyguardStatusBarViewComponent import com.android.systemui.common.ui.compose.windowinsets.LocalDisplayCutout @@ -45,9 +45,10 @@ constructor( private val notificationPanelView: Lazy<NotificationPanelView>, ) { @Composable - fun SceneScope.StatusBar(modifier: Modifier = Modifier) { + fun ContentScope.StatusBar(modifier: Modifier = Modifier) { val context = LocalContext.current val viewDisplayCutout = LocalDisplayCutout.current.viewDisplayCutoutKeyguardStatusBarView + @SuppressLint("InflateParams") val view = remember(context) { diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/WeatherClockSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/WeatherClockSection.kt index 73c4fab7b646..6250da379402 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/WeatherClockSection.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/WeatherClockSection.kt @@ -30,9 +30,8 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.viewinterop.AndroidView +import com.android.compose.animation.scene.ContentScope import com.android.compose.animation.scene.ElementKey -import com.android.compose.animation.scene.SceneScope -import com.android.compose.modifiers.padding import com.android.systemui.customization.R as customR import com.android.systemui.keyguard.ui.composable.blueprint.WeatherClockElementKeys import com.android.systemui.keyguard.ui.composable.modifier.burnInAware @@ -50,15 +49,10 @@ constructor( private val aodBurnInViewModel: AodBurnInViewModel, ) { @Composable - fun SceneScope.Time( - clock: ClockController, - burnInParams: BurnInParameters, - ) { + fun ContentScope.Time(clock: ClockController, burnInParams: BurnInParameters) { Row( modifier = - Modifier.padding( - horizontal = dimensionResource(customR.dimen.clock_padding_start) - ) + Modifier.padding(horizontal = dimensionResource(customR.dimen.clock_padding_start)) .burnInAware(aodBurnInViewModel, burnInParams, isClock = true) ) { WeatherElement( @@ -70,10 +64,7 @@ constructor( } @Composable - private fun SceneScope.Date( - clock: ClockController, - modifier: Modifier = Modifier, - ) { + private fun ContentScope.Date(clock: ClockController, modifier: Modifier = Modifier) { WeatherElement( weatherClockElementViewId = customR.id.weather_clock_date, clock = clock, @@ -83,10 +74,7 @@ constructor( } @Composable - private fun SceneScope.Weather( - clock: ClockController, - modifier: Modifier = Modifier, - ) { + private fun ContentScope.Weather(clock: ClockController, modifier: Modifier = Modifier) { WeatherElement( weatherClockElementViewId = customR.id.weather_clock_weather_icon, clock = clock, @@ -96,10 +84,7 @@ constructor( } @Composable - private fun SceneScope.DndAlarmStatus( - clock: ClockController, - modifier: Modifier = Modifier, - ) { + private fun ContentScope.DndAlarmStatus(clock: ClockController, modifier: Modifier = Modifier) { WeatherElement( weatherClockElementViewId = customR.id.weather_clock_alarm_dnd, clock = clock, @@ -109,10 +94,7 @@ constructor( } @Composable - private fun SceneScope.Temperature( - clock: ClockController, - modifier: Modifier = Modifier, - ) { + private fun ContentScope.Temperature(clock: ClockController, modifier: Modifier = Modifier) { WeatherElement( weatherClockElementViewId = customR.id.weather_clock_temperature, clock = clock, @@ -122,7 +104,7 @@ constructor( } @Composable - private fun SceneScope.WeatherElement( + private fun ContentScope.WeatherElement( weatherClockElementViewId: Int, clock: ClockController, elementKey: ElementKey, @@ -144,32 +126,28 @@ constructor( } }, update = {}, - modifier = modifier + modifier = modifier, ) } } } @Composable - fun SceneScope.LargeClockSectionBelowSmartspace( + fun ContentScope.LargeClockSectionBelowSmartspace( burnInParams: BurnInParameters, clock: ClockController, ) { Row( modifier = Modifier.height(IntrinsicSize.Max) - .padding( - horizontal = dimensionResource(customR.dimen.clock_padding_start) - ) + .padding(horizontal = dimensionResource(customR.dimen.clock_padding_start)) .burnInAware(aodBurnInViewModel, burnInParams, isClock = true) ) { Date(clock = clock, modifier = Modifier.wrapContentSize()) Box( modifier = Modifier.fillMaxSize() - .padding( - start = dimensionResource(customR.dimen.clock_padding_start) - ) + .padding(start = dimensionResource(customR.dimen.clock_padding_start)) ) { Weather(clock = clock, modifier = Modifier.align(Alignment.TopStart)) Temperature(clock = clock, modifier = Modifier.align(Alignment.BottomEnd)) diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarousel.kt b/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarousel.kt index b5d78398028d..f5de7dca6d9d 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarousel.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarousel.kt @@ -31,8 +31,8 @@ import androidx.compose.ui.layout.layout import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.viewinterop.AndroidView +import com.android.compose.animation.scene.ContentScope import com.android.compose.animation.scene.MovableElementKey -import com.android.compose.animation.scene.SceneScope import com.android.compose.windowsizeclass.LocalWindowSizeClass import com.android.systemui.media.controls.ui.composable.MediaCarouselStateLoader.stateForMediaCarouselContent import com.android.systemui.media.controls.ui.controller.MediaCarouselController @@ -52,7 +52,7 @@ object MediaCarousel { } @Composable -fun SceneScope.MediaCarousel( +fun ContentScope.MediaCarousel( isVisible: Boolean, mediaHost: MediaHost, modifier: Modifier = Modifier, @@ -136,6 +136,6 @@ private fun ViewGroup.setView(view: View) { } @Composable -fun SceneScope.isLandscape(): Boolean { +fun ContentScope.isLandscape(): Boolean { return LocalWindowSizeClass.current.heightSizeClass == WindowHeightSizeClass.Compact } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarouselStateLoader.kt b/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarouselStateLoader.kt index bad74052b669..525284207744 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarouselStateLoader.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarouselStateLoader.kt @@ -17,8 +17,8 @@ package com.android.systemui.media.controls.ui.composable import com.android.compose.animation.scene.ContentKey +import com.android.compose.animation.scene.ContentScope import com.android.compose.animation.scene.SceneKey -import com.android.compose.animation.scene.SceneScope import com.android.compose.animation.scene.content.state.TransitionState import com.android.systemui.media.controls.ui.controller.MediaCarouselController import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager @@ -50,6 +50,7 @@ object MediaCarouselStateLoader { if (isSplitShade) MediaHierarchyManager.LOCATION_QS else MediaHierarchyManager.LOCATION_QQS } + Scenes.Lockscreen -> MediaHierarchyManager.LOCATION_LOCKSCREEN Scenes.Communal -> MediaHierarchyManager.LOCATION_COMMUNAL_HUB else -> MediaHierarchyManager.LOCATION_UNKNOWN @@ -69,6 +70,7 @@ object MediaCarouselStateLoader { /** State for media carousel. */ sealed interface State { val transitionProgress: Float + // TODO b/368368388: implement media squishiness val squishFraction: () -> Float @MediaLocation val startLocation: Int @@ -100,7 +102,7 @@ object MediaCarouselStateLoader { } /** Returns the state of media carousel */ - fun SceneScope.stateForMediaCarouselContent(isInSplitShade: Boolean): State { + fun ContentScope.stateForMediaCarouselContent(isInSplitShade: Boolean): State { return when (val transitionState = layoutState.transitionState) { is TransitionState.Idle -> { if (MediaContentPicker.contents.contains(transitionState.currentScene)) { @@ -109,6 +111,7 @@ object MediaCarouselStateLoader { State.Gone } } + is TransitionState.Transition.ChangeScene -> with(transitionState) { if ( @@ -130,6 +133,7 @@ object MediaCarouselStateLoader { State.Gone } } + is TransitionState.Transition.OverlayTransition -> with(transitionState) { if ( diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt index 7f7273d710a1..25b673bee1cd 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt @@ -72,7 +72,7 @@ import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.repeatOnLifecycle import com.android.compose.animation.Expandable -import com.android.compose.animation.scene.SceneScope +import com.android.compose.animation.scene.ContentScope import com.android.compose.modifiers.fadingBackground import com.android.compose.theme.colorAttr import com.android.systemui.Flags.notificationShadeBlur @@ -91,7 +91,7 @@ import com.android.systemui.res.R import kotlinx.coroutines.launch @Composable -fun SceneScope.FooterActionsWithAnimatedVisibility( +fun ContentScope.FooterActionsWithAnimatedVisibility( viewModel: FooterActionsViewModel, isCustomizing: Boolean, customizingAnimationDuration: Int, @@ -256,6 +256,7 @@ private fun RowScope.ForegroundServicesButton( } else { NumberButton( model.foregroundServicesCount, + contentDescription = model.text, showNewDot = model.hasNewChanges, onClick = model.onClick, ) @@ -284,6 +285,7 @@ fun IconButton(model: FooterActionsButtonViewModel, modifier: Modifier = Modifie @Composable private fun NumberButton( number: Int, + contentDescription: String, showNewDot: Boolean, onClick: (Expandable) -> Unit, modifier: Modifier = Modifier, @@ -308,14 +310,16 @@ private fun NumberButton( ) { Box(Modifier.size(40.dp)) { Box( - Modifier - .fillMaxSize() + Modifier.fillMaxSize() .clip(CircleShape) .indication(interactionSource, LocalIndication.current) ) { Text( number.toString(), - modifier = Modifier.align(Alignment.Center), + modifier = + Modifier.align(Alignment.Center).semantics { + this.contentDescription = contentDescription + }, style = MaterialTheme.typography.bodyLarge, color = colorAttr(R.attr.onShadeInactiveVariant), // TODO(b/242040009): This should only use a standard text style instead and @@ -337,9 +341,7 @@ private fun NewChangesDot(modifier: Modifier = Modifier) { val contentDescription = stringResource(R.string.fgs_dot_content_description) val color = MaterialTheme.colorScheme.tertiary - Canvas(modifier - .size(12.dp) - .semantics { this.contentDescription = contentDescription }) { + Canvas(modifier.size(12.dp).semantics { this.contentDescription = contentDescription }) { drawCircle(color) } } @@ -368,9 +370,7 @@ private fun TextButton( Modifier.padding(horizontal = dimensionResource(R.dimen.qs_footer_padding)), verticalAlignment = Alignment.CenterVertically, ) { - Icon(icon, Modifier - .padding(end = 12.dp) - .size(20.dp)) + Icon(icon, Modifier.padding(end = 12.dp).size(20.dp)) Text( text, @@ -391,9 +391,7 @@ private fun TextButton( Icon( painterResource(com.android.internal.R.drawable.ic_chevron_end), contentDescription = null, - Modifier - .padding(start = 8.dp) - .size(20.dp), + Modifier.padding(start = 8.dp).size(20.dp), ) } } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt index 58336c2e9d41..b826187578e0 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt @@ -36,10 +36,10 @@ import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.viewinterop.AndroidView import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.android.compose.animation.scene.ContentScope import com.android.compose.animation.scene.ElementKey import com.android.compose.animation.scene.MovableElementContentPicker import com.android.compose.animation.scene.MovableElementKey -import com.android.compose.animation.scene.SceneScope import com.android.compose.animation.scene.SceneTransitionLayoutState import com.android.compose.animation.scene.ValueKey import com.android.compose.animation.scene.content.state.TransitionState @@ -98,7 +98,7 @@ object QuickSettings { } } -private fun SceneScope.stateForQuickSettingsContent( +private fun ContentScope.stateForQuickSettingsContent( isSplitShade: Boolean, squishiness: () -> Float = { QuickSettings.SharedValues.SquishinessValues.Default }, ): QSSceneAdapter.State { @@ -141,7 +141,7 @@ private fun SceneScope.stateForQuickSettingsContent( /** * This composable will show QuickSettingsContent in the correct state (as determined by its - * [SceneScope]). + * [ContentScope]). * * If adding to scenes not in: * * QuickSettingsScene @@ -153,7 +153,7 @@ private fun SceneScope.stateForQuickSettingsContent( * * this doc. */ @Composable -fun SceneScope.QuickSettings( +fun ContentScope.QuickSettings( qsSceneAdapter: QSSceneAdapter, heightProvider: () -> Int, isSplitShade: Boolean, diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt index 26cf7066124a..4bfbb3a908fa 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt @@ -68,7 +68,7 @@ import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.android.compose.animation.scene.SceneScope +import com.android.compose.animation.scene.ContentScope import com.android.compose.animation.scene.UserAction import com.android.compose.animation.scene.UserActionResult import com.android.compose.animation.scene.animateSceneDpAsState @@ -144,7 +144,7 @@ constructor( } @Composable - override fun SceneScope.Content(modifier: Modifier) { + override fun ContentScope.Content(modifier: Modifier) { QuickSettingsScene( notificationStackScrollView = notificationStackScrollView.get(), viewModelFactory = contentViewModelFactory, @@ -164,7 +164,7 @@ constructor( } @Composable -private fun SceneScope.QuickSettingsScene( +private fun ContentScope.QuickSettingsScene( notificationStackScrollView: NotificationScrollView, viewModelFactory: QuickSettingsSceneContentViewModel.Factory, notificationsPlaceholderViewModel: NotificationsPlaceholderViewModel, diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt index 9ee25c3404ec..2175a59e8e4d 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt @@ -25,7 +25,7 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalDensity -import com.android.compose.animation.scene.SceneScope +import com.android.compose.animation.scene.ContentScope import com.android.compose.animation.scene.UserAction import com.android.compose.animation.scene.UserActionResult import com.android.compose.animation.scene.animateContentDpAsState @@ -71,7 +71,7 @@ constructor( } @Composable - override fun SceneScope.Content(modifier: Modifier) { + override fun ContentScope.Content(modifier: Modifier) { val isIdleAndNotOccluded by remember { derivedStateOf { diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/Scene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/Scene.kt index 8d8ab8ee7949..6c80c69b7a34 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/Scene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/Scene.kt @@ -18,8 +18,8 @@ package com.android.systemui.scene.ui.composable import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import com.android.compose.animation.scene.ContentScope import com.android.compose.animation.scene.SceneKey -import com.android.compose.animation.scene.SceneScope import com.android.systemui.lifecycle.Activatable /** @@ -35,5 +35,5 @@ interface Scene : Activatable, ActionableContent { /** Uniquely-identifying key for this scene. The key must be unique within its container. */ val key: SceneKey - @Composable fun SceneScope.Content(modifier: Modifier) + @Composable fun ContentScope.Content(modifier: Modifier) } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt index bfcde7dab6d2..3131b539c6af 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt @@ -58,9 +58,9 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.max import androidx.compose.ui.viewinterop.AndroidView import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.android.compose.animation.scene.ContentScope import com.android.compose.animation.scene.ElementKey import com.android.compose.animation.scene.LowestZIndexContentPicker -import com.android.compose.animation.scene.SceneScope import com.android.compose.animation.scene.ValueKey import com.android.compose.animation.scene.animateElementFloatAsState import com.android.compose.animation.scene.content.state.TransitionState @@ -122,7 +122,7 @@ object ShadeHeader { } @Composable -fun SceneScope.CollapsedShadeHeader( +fun ContentScope.CollapsedShadeHeader( viewModelFactory: ShadeHeaderViewModel.Factory, createTintedIconManager: (ViewGroup, StatusBarLocation) -> TintedIconManager, createBatteryMeterViewController: (ViewGroup, StatusBarLocation) -> BatteryMeterViewController, @@ -264,7 +264,7 @@ fun SceneScope.CollapsedShadeHeader( } @Composable -fun SceneScope.ExpandedShadeHeader( +fun ContentScope.ExpandedShadeHeader( viewModelFactory: ShadeHeaderViewModel.Factory, createTintedIconManager: (ViewGroup, StatusBarLocation) -> TintedIconManager, createBatteryMeterViewController: (ViewGroup, StatusBarLocation) -> BatteryMeterViewController, @@ -339,7 +339,7 @@ fun SceneScope.ExpandedShadeHeader( } @Composable -private fun SceneScope.Clock(scale: Float, viewModel: ShadeHeaderViewModel, modifier: Modifier) { +private fun ContentScope.Clock(scale: Float, viewModel: ShadeHeaderViewModel, modifier: Modifier) { val layoutDirection = LocalLayoutDirection.current Element(key = ShadeHeader.Elements.Clock, modifier = modifier) { @@ -446,7 +446,7 @@ private fun ShadeCarrierGroup(viewModel: ShadeHeaderViewModel, modifier: Modifie } @Composable -private fun SceneScope.StatusIcons( +private fun ContentScope.StatusIcons( viewModel: ShadeHeaderViewModel, createTintedIconManager: (ViewGroup, StatusBarLocation) -> TintedIconManager, statusBarIconController: StatusBarIconController, @@ -548,7 +548,10 @@ private fun SystemIconContainer( } @Composable -private fun SceneScope.PrivacyChip(viewModel: ShadeHeaderViewModel, modifier: Modifier = Modifier) { +private fun ContentScope.PrivacyChip( + viewModel: ShadeHeaderViewModel, + modifier: Modifier = Modifier, +) { val privacyList by viewModel.privacyItems.collectAsStateWithLifecycle() AndroidView( diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt index 0d3bab24f68f..f829a0d6facf 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt @@ -62,9 +62,9 @@ import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.unit.dp import androidx.compose.ui.zIndex import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.android.compose.animation.scene.ContentScope import com.android.compose.animation.scene.ElementKey import com.android.compose.animation.scene.LowestZIndexContentPicker -import com.android.compose.animation.scene.SceneScope import com.android.compose.animation.scene.UserAction import com.android.compose.animation.scene.UserActionResult import com.android.compose.animation.scene.animateContentDpAsState @@ -160,7 +160,7 @@ constructor( override val userActions: Flow<Map<UserAction, UserActionResult>> = actionsViewModel.actions @Composable - override fun SceneScope.Content(modifier: Modifier) = + override fun ContentScope.Content(modifier: Modifier) = ShadeScene( notificationStackScrollView.get(), viewModel = @@ -193,7 +193,7 @@ constructor( } @Composable -private fun SceneScope.ShadeScene( +private fun ContentScope.ShadeScene( notificationStackScrollView: NotificationScrollView, viewModel: ShadeSceneContentViewModel, notificationsPlaceholderViewModel: NotificationsPlaceholderViewModel, @@ -242,7 +242,7 @@ private fun SceneScope.ShadeScene( } @Composable -private fun SceneScope.SingleShade( +private fun ContentScope.SingleShade( notificationStackScrollView: NotificationScrollView, viewModel: ShadeSceneContentViewModel, notificationsPlaceholderViewModel: NotificationsPlaceholderViewModel, @@ -410,7 +410,7 @@ private fun SceneScope.SingleShade( } @Composable -private fun SceneScope.SplitShade( +private fun ContentScope.SplitShade( notificationStackScrollView: NotificationScrollView, viewModel: ShadeSceneContentViewModel, notificationsPlaceholderViewModel: NotificationsPlaceholderViewModel, @@ -632,7 +632,7 @@ private fun SceneScope.SplitShade( } @Composable -private fun SceneScope.ShadeMediaCarousel( +private fun ContentScope.ShadeMediaCarousel( isVisible: Boolean, isInRow: Boolean, mediaHost: MediaHost, diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt index 25892c5a75cc..d9e8f02f005b 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt @@ -19,7 +19,6 @@ package com.android.systemui.volume.panel.component.volume.ui.composable import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.EnterTransition import androidx.compose.animation.ExitTransition -import androidx.compose.animation.ExperimentalAnimationApi import androidx.compose.animation.core.AnimationSpec import androidx.compose.animation.core.animateDpAsState import androidx.compose.animation.core.tween @@ -70,8 +69,6 @@ private const val SHRINK_FRACTION = 0.55f private const val SCALE_FRACTION = 0.9f private const val EXPAND_BUTTON_SCALE = 0.8f -/** Volume sliders laid out in a collapsable column */ -@OptIn(ExperimentalAnimationApi::class) @Composable fun ColumnVolumeSliders( viewModels: List<SliderViewModel>, @@ -144,8 +141,7 @@ fun ColumnVolumeSliders( VolumeSlider( modifier = - Modifier.padding(top = 16.dp) - .fillMaxWidth() + Modifier.fillMaxWidth() .animateEnterExit( enter = enterTransition( @@ -157,7 +153,10 @@ fun ColumnVolumeSliders( index = index, totalCount = viewModels.size, ), - ), + ) + .thenIf(!Flags.volumeRedesign()) { + Modifier.padding(top = 16.dp) + }, state = sliderState, onValueChange = { newValue: Float -> sliderViewModel.onValueChanged(sliderState, newValue) diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt index 5f991fbb50df..bdd0da9ce4a4 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt @@ -17,10 +17,12 @@ package com.android.systemui.volume.panel.component.volume.ui.composable import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.animateContentSize import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.tween import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut +import androidx.compose.foundation.basicMarquee import androidx.compose.foundation.clickable import androidx.compose.foundation.gestures.Orientation import androidx.compose.foundation.interaction.MutableInteractionSource @@ -33,6 +35,7 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.material3.Icon as MaterialIcon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Slider import androidx.compose.material3.Text @@ -47,6 +50,7 @@ import androidx.compose.runtime.setValue import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource import androidx.compose.ui.semantics.CustomAccessibilityAction import androidx.compose.ui.semantics.ProgressBarRangeInfo import androidx.compose.ui.semantics.clearAndSetSemantics @@ -67,6 +71,7 @@ import com.android.systemui.haptics.slider.SeekableSliderTrackerConfig import com.android.systemui.haptics.slider.SliderHapticFeedbackConfig import com.android.systemui.haptics.slider.compose.ui.SliderHapticsViewModel import com.android.systemui.lifecycle.rememberViewModel +import com.android.systemui.res.R import com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel.SliderState import kotlin.math.round import kotlinx.coroutines.flow.distinctUntilChanged @@ -98,7 +103,7 @@ fun VolumeSlider( } val value by valueState(state) - Column(modifier) { + Column(modifier = modifier.animateContentSize(), verticalArrangement = Arrangement.Top) { Row( horizontalArrangement = Arrangement.spacedBy(12.dp), modifier = Modifier.fillMaxWidth().height(40.dp), @@ -127,7 +132,7 @@ fun VolumeSlider( enabled = state.isEnabled, modifier = Modifier.height(40.dp) - .padding(vertical = 8.dp) + .padding(top = 4.dp, bottom = 12.dp) .sysuiResTag(state.label) .clearAndSetSemantics { if (state.isEnabled) { @@ -168,6 +173,28 @@ fun VolumeSlider( } }, ) + state.disabledMessage?.let { disabledMessage -> + AnimatedVisibility(visible = !state.isEnabled) { + Row( + modifier = Modifier.padding(bottom = 12.dp), + horizontalArrangement = Arrangement.spacedBy(8.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + MaterialIcon( + painter = painterResource(R.drawable.ic_error_outline), + contentDescription = null, + tint = MaterialTheme.colorScheme.onSurfaceVariant, + modifier = Modifier.size(16.dp), + ) + Text( + text = disabledMessage, + color = MaterialTheme.colorScheme.onSurfaceVariant, + style = MaterialTheme.typography.labelSmall, + modifier = Modifier.basicMarquee(), + ) + } + } + } } } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VerticalVolumePanelContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VerticalVolumePanelContent.kt index 6349c1406a12..bc3013239289 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VerticalVolumePanelContent.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VerticalVolumePanelContent.kt @@ -37,18 +37,17 @@ fun VolumePanelComposeScope.VerticalVolumePanelContent( layout: ComponentsLayout, modifier: Modifier = Modifier, ) { - Column( - modifier = modifier.verticalScroll(rememberScrollState()), - verticalArrangement = Arrangement.spacedBy(20.dp), - ) { + Column(modifier = modifier, verticalArrangement = Arrangement.spacedBy(20.dp)) { for (component in layout.headerComponents) { AnimatedVisibility(component.isVisible) { with(component.component as ComposeVolumePanelUiComponent) { Content(Modifier) } } } - for (component in layout.contentComponents) { - AnimatedVisibility(component.isVisible) { - with(component.component as ComposeVolumePanelUiComponent) { Content(Modifier) } + Column(Modifier.verticalScroll(rememberScrollState())) { + for (component in layout.contentComponents) { + AnimatedVisibility(component.isVisible) { + with(component.component as ComposeVolumePanelUiComponent) { Content(Modifier) } + } } } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt index ef11932867a0..a1117e1bc1db 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt @@ -264,9 +264,6 @@ interface BaseContentScope : ElementStateScope { ) } -@Deprecated("Use ContentScope instead", ReplaceWith("ContentScope")) -typealias SceneScope = ContentScope - @Stable @ElementDsl interface ContentScope : BaseContentScope { diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt index 005146997813..b405fbe89302 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt @@ -2095,7 +2095,7 @@ class ElementTest { val foo = ElementKey("Foo", placeAllCopies = true) @Composable - fun SceneScope.Foo(size: Dp, modifier: Modifier = Modifier) { + fun ContentScope.Foo(size: Dp, modifier: Modifier = Modifier) { Box(modifier.element(foo).size(size)) } @@ -2159,7 +2159,7 @@ class ElementTest { // Foo is a simple element that does not move or resize during the transition. @Composable - fun SceneScope.Foo(modifier: Modifier = Modifier) { + fun ContentScope.Foo(modifier: Modifier = Modifier) { Box( modifier .element(TestElements.Foo) @@ -2211,7 +2211,7 @@ class ElementTest { @Ignore("b/363964445") fun interruption_considerPreviousUniqueState() { @Composable - fun SceneScope.Foo(modifier: Modifier = Modifier) { + fun ContentScope.Foo(modifier: Modifier = Modifier) { Box(modifier.element(TestElements.Foo).size(50.dp)) } diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt index b4c8ad7c3327..9e1bae577ed2 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt @@ -373,7 +373,7 @@ class MovableElementTest { val fooParentInOverlayTag = "fooParentTagInOverlay" @Composable - fun SceneScope.Foo(modifier: Modifier = Modifier) { + fun ContentScope.Foo(modifier: Modifier = Modifier) { // Foo wraps its content, so there is no way for STL to know its size in advance. MovableElement(foo, modifier) { content { Box(Modifier.size(fooSize)) } } } diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/OverlayTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/OverlayTest.kt index bad4c6298e6b..93fa51654ca1 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/OverlayTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/OverlayTest.kt @@ -547,7 +547,7 @@ class OverlayTest { val sharedIntValueByContent = mutableMapOf<ContentKey, Int>() @Composable - fun SceneScope.animateContentInt(targetValue: Int) { + fun ContentScope.animateContentInt(targetValue: Int) { val animatedValue = animateContentIntAsState(targetValue, sharedIntKey) LaunchedEffect(animatedValue) { try { diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/composable/BouncerPredictiveBackTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/composable/BouncerPredictiveBackTest.kt index a65415509d38..a6ed37ead7ae 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/composable/BouncerPredictiveBackTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/composable/BouncerPredictiveBackTest.kt @@ -37,10 +37,10 @@ import androidx.compose.ui.semantics.SemanticsNode import androidx.compose.ui.test.junit4.AndroidComposeTestRule import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.LargeTest +import com.android.compose.animation.scene.ContentScope import com.android.compose.animation.scene.ObservableTransitionState import com.android.compose.animation.scene.Scale import com.android.compose.animation.scene.SceneKey -import com.android.compose.animation.scene.SceneScope import com.android.compose.animation.scene.UserAction import com.android.compose.animation.scene.UserActionResult import com.android.compose.animation.scene.isElement @@ -270,7 +270,7 @@ class BouncerPredictiveBackTest : SysuiTestCase() { override val userActions: Flow<Map<UserAction, UserActionResult>> = flowOf() @Composable - override fun SceneScope.Content(modifier: Modifier) { + override fun ContentScope.Content(modifier: Modifier) { Box(modifier = modifier, contentAlignment = Alignment.Center) { Text(text = "Fake Lockscreen") } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt index 47cba0723804..030233625027 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt @@ -367,8 +367,6 @@ class DeviceUnlockedInteractorTest : SysuiTestCase() { DeviceEntryRestrictionReason.DeviceNotUnlockedSinceReboot, LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST to DeviceEntryRestrictionReason.AdaptiveAuthRequest, - LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT to - DeviceEntryRestrictionReason.BouncerLockedOut, LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT to DeviceEntryRestrictionReason.SecurityTimeout, LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to @@ -403,8 +401,6 @@ class DeviceUnlockedInteractorTest : SysuiTestCase() { DeviceEntryRestrictionReason.DeviceNotUnlockedSinceReboot, LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST to DeviceEntryRestrictionReason.AdaptiveAuthRequest, - LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT to - DeviceEntryRestrictionReason.BouncerLockedOut, LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT to DeviceEntryRestrictionReason.SecurityTimeout, LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to @@ -440,8 +436,6 @@ class DeviceUnlockedInteractorTest : SysuiTestCase() { DeviceEntryRestrictionReason.DeviceNotUnlockedSinceReboot, LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST to DeviceEntryRestrictionReason.AdaptiveAuthRequest, - LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT to - DeviceEntryRestrictionReason.BouncerLockedOut, LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT to DeviceEntryRestrictionReason.SecurityTimeout, LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt index 605a5d261424..bafabe07d370 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt @@ -22,6 +22,7 @@ import android.platform.test.annotations.RequiresFlagsEnabled import android.platform.test.flag.junit.CheckFlagsRule import android.platform.test.flag.junit.DeviceFlagsValueProvider import android.view.IRemoteAnimationFinishedCallback +import android.view.RemoteAnimationTarget import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase @@ -39,11 +40,11 @@ import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.eq import org.mockito.Mock import org.mockito.Mockito.anyInt -import org.mockito.Mockito.mock import org.mockito.Mockito.verify import org.mockito.Mockito.verifyNoMoreInteractions import org.mockito.MockitoAnnotations import org.mockito.kotlin.any +import org.mockito.kotlin.doAnswer import org.mockito.kotlin.mock import org.mockito.kotlin.whenever @@ -230,12 +231,15 @@ class WindowManagerLockscreenVisibilityManagerTest : SysuiTestCase() { @Test fun remoteAnimationInstantlyFinished_ifDismissTransitionNotStarted() { val mockedCallback = mock<IRemoteAnimationFinishedCallback>() - whenever(keyguardDismissTransitionInteractor.startDismissKeyguardTransition(any())) - .thenReturn(false) + + // Call the onAlreadyGone callback immediately. + doAnswer { invocation -> (invocation.getArgument(1) as (() -> Unit)).invoke() } + .whenever(keyguardDismissTransitionInteractor) + .startDismissKeyguardTransition(any(), any()) underTest.onKeyguardGoingAwayRemoteAnimationStart( transit = 0, - apps = emptyArray(), + apps = arrayOf(mock<RemoteAnimationTarget>()), wallpapers = emptyArray(), nonApps = emptyArray(), finishedCallback = mockedCallback, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/QSColumnsRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/QSColumnsRepositoryTest.kt index ec0773f79328..5a350435002f 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/QSColumnsRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/QSColumnsRepositoryTest.kt @@ -24,7 +24,6 @@ import com.android.systemui.coroutines.collectLastValue import com.android.systemui.kosmos.testCase import com.android.systemui.kosmos.testScope import com.android.systemui.res.R -import com.android.systemui.shade.data.repository.fakeShadeRepository import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.test.runTest @@ -35,7 +34,17 @@ import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) class QSColumnsRepositoryTest : SysuiTestCase() { - private val kosmos = testKosmos() + private val kosmos = + testKosmos().apply { + testCase.context.orCreateTestableResources.addOverride( + R.integer.quick_settings_dual_shade_num_columns, + 2, + ) + testCase.context.orCreateTestableResources.addOverride( + R.integer.quick_settings_split_shade_num_columns, + 3, + ) + } private lateinit var underTest: QSColumnsRepository @Before @@ -63,7 +72,7 @@ class QSColumnsRepositoryTest : SysuiTestCase() { testScope.runTest { val latest by collectLastValue(underTest.dualShadeColumns) - assertThat(latest).isEqualTo(4) + assertThat(latest).isEqualTo(2) } } @@ -72,9 +81,8 @@ class QSColumnsRepositoryTest : SysuiTestCase() { with(kosmos) { testScope.runTest { val latest by collectLastValue(underTest.splitShadeColumns) - fakeShadeRepository.setShadeLayoutWide(true) - assertThat(latest).isEqualTo(4) + assertThat(latest).isEqualTo(3) } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ModesTileTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ModesTileTest.kt index 9173ac969324..f005375a2ef9 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ModesTileTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ModesTileTest.kt @@ -48,6 +48,7 @@ import com.android.systemui.res.R import com.android.systemui.statusbar.policy.data.repository.zenModeRepository import com.android.systemui.statusbar.policy.domain.interactor.zenModeInteractor import com.android.systemui.statusbar.policy.ui.dialog.ModesDialogDelegate +import com.android.systemui.statusbar.policy.ui.dialog.modesDialogEventLogger import com.android.systemui.testKosmos import com.android.systemui.util.mockito.any import com.android.systemui.util.settings.FakeSettings @@ -123,7 +124,12 @@ class ModesTileTest : SysuiTestCase() { ) userActionInteractor = - ModesTileUserActionInteractor(inputHandler, dialogDelegate, kosmos.zenModeInteractor) + ModesTileUserActionInteractor( + inputHandler, + dialogDelegate, + kosmos.zenModeInteractor, + kosmos.modesDialogEventLogger, + ) underTest = ModesTile( diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorTest.kt index c8b3aba9b846..89b8e9171076 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorTest.kt @@ -38,6 +38,7 @@ import com.android.systemui.qs.tiles.impl.modes.domain.model.ModesTileModel import com.android.systemui.statusbar.policy.data.repository.zenModeRepository import com.android.systemui.statusbar.policy.domain.interactor.zenModeInteractor import com.android.systemui.statusbar.policy.ui.dialog.mockModesDialogDelegate +import com.android.systemui.statusbar.policy.ui.dialog.modesDialogEventLogger import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -60,7 +61,12 @@ class ModesTileUserActionInteractorTest : SysuiTestCase() { private val zenModeInteractor = kosmos.zenModeInteractor private val underTest = - ModesTileUserActionInteractor(inputHandler, mockDialogDelegate, zenModeInteractor) + ModesTileUserActionInteractor( + inputHandler, + mockDialogDelegate, + zenModeInteractor, + kosmos.modesDialogEventLogger, + ) @Test fun handleClick_active_showsDialog() = runTest { diff --git a/packages/SystemUI/multivalentTests/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 ac3089d9286b..b0af8b180cce 100644 --- a/packages/SystemUI/multivalentTests/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 @@ -30,10 +30,12 @@ import com.android.systemui.kosmos.testScope import com.android.systemui.plugins.activityStarter import com.android.systemui.res.R import com.android.systemui.statusbar.StatusBarIconView +import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips import com.android.systemui.statusbar.chips.ui.model.ColorsModel import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer import com.android.systemui.statusbar.core.StatusBarConnectedDisplays +import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel import com.android.systemui.statusbar.phone.ongoingcall.data.repository.ongoingCallRepository import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel import com.android.systemui.statusbar.phone.ongoingcall.shared.model.inCallModel @@ -64,7 +66,7 @@ class CallChipViewModelTest : SysuiTestCase() { .thenReturn(chipBackgroundView) } - private val underTest = kosmos.callChipViewModel + private val underTest by lazy { kosmos.callChipViewModel } @Test fun chip_noCall_isHidden() = @@ -219,28 +221,94 @@ class CallChipViewModelTest : SysuiTestCase() { } @Test - fun chip_positiveStartTime_colorsAreThemed() = + fun chip_positiveStartTime_notPromoted_colorsAreThemed() = testScope.runTest { val latest by collectLastValue(underTest.chip) - repo.setOngoingCallState(inCallModel(startTimeMs = 1000)) + repo.setOngoingCallState(inCallModel(startTimeMs = 1000, promotedContent = null)) assertThat((latest as OngoingActivityChipModel.Shown).colors) .isEqualTo(ColorsModel.Themed) } @Test - fun chip_zeroStartTime_colorsAreThemed() = + fun chip_zeroStartTime_notPromoted_colorsAreThemed() = testScope.runTest { val latest by collectLastValue(underTest.chip) - repo.setOngoingCallState(inCallModel(startTimeMs = 0)) + repo.setOngoingCallState(inCallModel(startTimeMs = 0, promotedContent = null)) + + assertThat((latest as OngoingActivityChipModel.Shown).colors) + .isEqualTo(ColorsModel.Themed) + } + + @Test + @DisableFlags(StatusBarNotifChips.FLAG_NAME) + fun chip_positiveStartTime_promoted_notifChipsFlagOff_colorsAreThemed() = + testScope.runTest { + val latest by collectLastValue(underTest.chip) + + repo.setOngoingCallState( + inCallModel(startTimeMs = 1000, promotedContent = PROMOTED_CONTENT_WITH_COLOR) + ) assertThat((latest as OngoingActivityChipModel.Shown).colors) .isEqualTo(ColorsModel.Themed) } @Test + @DisableFlags(StatusBarNotifChips.FLAG_NAME) + fun chip_zeroStartTime_promoted_notifChipsFlagOff_colorsAreThemed() = + testScope.runTest { + val latest by collectLastValue(underTest.chip) + + repo.setOngoingCallState( + inCallModel(startTimeMs = 0, promotedContent = PROMOTED_CONTENT_WITH_COLOR) + ) + + assertThat((latest as OngoingActivityChipModel.Shown).colors) + .isEqualTo(ColorsModel.Themed) + } + + @Test + @EnableFlags(StatusBarNotifChips.FLAG_NAME) + fun chip_positiveStartTime_promoted_notifChipsFlagOn_colorsAreCustom() = + testScope.runTest { + val latest by collectLastValue(underTest.chip) + + repo.setOngoingCallState( + inCallModel(startTimeMs = 1000, promotedContent = PROMOTED_CONTENT_WITH_COLOR) + ) + + assertThat((latest as OngoingActivityChipModel.Shown).colors) + .isEqualTo( + ColorsModel.Custom( + backgroundColorInt = PROMOTED_BACKGROUND_COLOR, + primaryTextColorInt = PROMOTED_PRIMARY_TEXT_COLOR, + ) + ) + } + + @Test + @EnableFlags(StatusBarNotifChips.FLAG_NAME) + fun chip_zeroStartTime_promoted_notifChipsFlagOff_colorsAreCustom() = + testScope.runTest { + val latest by collectLastValue(underTest.chip) + + repo.setOngoingCallState( + inCallModel(startTimeMs = 0, promotedContent = PROMOTED_CONTENT_WITH_COLOR) + ) + + assertThat((latest as OngoingActivityChipModel.Shown).colors) + .isEqualTo( + ColorsModel.Custom( + backgroundColorInt = PROMOTED_BACKGROUND_COLOR, + primaryTextColorInt = PROMOTED_PRIMARY_TEXT_COLOR, + ) + ) + } + + @Test fun chip_resetsCorrectly() = testScope.runTest { val latest by collectLastValue(underTest.chip) @@ -275,7 +343,7 @@ class CallChipViewModelTest : SysuiTestCase() { repo.setOngoingCallState(inCallModel(startTimeMs = 1000, intent = null)) - assertThat((latest as OngoingActivityChipModel.Shown).onClickListener).isNull() + assertThat((latest as OngoingActivityChipModel.Shown).onClickListenerLegacy).isNull() } @Test @@ -285,7 +353,7 @@ class CallChipViewModelTest : SysuiTestCase() { val pendingIntent = mock<PendingIntent>() repo.setOngoingCallState(inCallModel(startTimeMs = 1000, intent = pendingIntent)) - val clickListener = (latest as OngoingActivityChipModel.Shown).onClickListener + val clickListener = (latest as OngoingActivityChipModel.Shown).onClickListenerLegacy assertThat(clickListener).isNotNull() clickListener!!.onClick(chipView) @@ -302,7 +370,8 @@ class CallChipViewModelTest : SysuiTestCase() { val pendingIntent = mock<PendingIntent>() repo.setOngoingCallState(inCallModel(startTimeMs = 0, intent = pendingIntent)) - val clickListener = (latest as OngoingActivityChipModel.Shown).onClickListener + val clickListener = (latest as OngoingActivityChipModel.Shown).onClickListenerLegacy + assertThat(clickListener).isNotNull() clickListener!!.onClick(chipView) @@ -319,5 +388,19 @@ class CallChipViewModelTest : SysuiTestCase() { } else { mock<StatusBarIconView>() } + + private val PROMOTED_CONTENT_WITH_COLOR = + PromotedNotificationContentModel.Builder("notif") + .apply { + this.colors = + PromotedNotificationContentModel.Colors( + backgroundColor = PROMOTED_BACKGROUND_COLOR, + primaryTextColor = PROMOTED_PRIMARY_TEXT_COLOR, + ) + } + .build() + + private const val PROMOTED_BACKGROUND_COLOR = 65 + private const val PROMOTED_PRIMARY_TEXT_COLOR = 98 } } diff --git a/packages/SystemUI/multivalentTests/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 c511c433d92d..fcf8c834dc12 100644 --- a/packages/SystemUI/multivalentTests/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 @@ -17,6 +17,7 @@ package com.android.systemui.statusbar.chips.casttootherdevice.ui.viewmodel import android.content.DialogInterface +import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags import android.view.View import androidx.test.ext.junit.runners.AndroidJUnit4 @@ -25,6 +26,7 @@ import com.android.internal.jank.Cuj import com.android.systemui.Flags.FLAG_STATUS_BAR_SHOW_AUDIO_ONLY_PROJECTION_CHIP import com.android.systemui.SysuiTestCase import com.android.systemui.animation.DialogCuj +import com.android.systemui.animation.Expandable import com.android.systemui.animation.mockDialogTransitionAnimator import com.android.systemui.common.shared.model.ContentDescription import com.android.systemui.common.shared.model.Icon @@ -46,8 +48,10 @@ import com.android.systemui.statusbar.chips.ui.model.ColorsModel import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipsViewModelTest.Companion.getStopActionFromDialog +import com.android.systemui.statusbar.core.StatusBarRootModernization import com.android.systemui.statusbar.phone.SystemUIDialog import com.android.systemui.statusbar.phone.mockSystemUIDialogFactory +import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization import com.android.systemui.statusbar.policy.CastDevice import com.android.systemui.util.time.fakeSystemClock import com.google.common.truth.Truth.assertThat @@ -84,6 +88,8 @@ class CastToOtherDeviceChipViewModelTest : SysuiTestCase() { ) .thenReturn(chipBackgroundView) } + private val mockExpandable: Expandable = + mock<Expandable>().apply { whenever(dialogTransitionController(any())).thenReturn(mock()) } private val underTest = kosmos.castToOtherDeviceChipViewModel @@ -263,7 +269,13 @@ class CastToOtherDeviceChipViewModelTest : SysuiTestCase() { // WHEN the stop action on the dialog is clicked val dialogStopAction = - getStopActionFromDialog(latest, chipView, mockScreenCastDialog, kosmos) + getStopActionFromDialog( + latest, + chipView, + mockExpandable, + mockScreenCastDialog, + kosmos, + ) dialogStopAction.onClick(mock<DialogInterface>(), 0) // THEN the chip is immediately hidden... @@ -296,7 +308,13 @@ class CastToOtherDeviceChipViewModelTest : SysuiTestCase() { // WHEN the stop action on the dialog is clicked val dialogStopAction = - getStopActionFromDialog(latest, chipView, mockGenericCastDialog, kosmos) + getStopActionFromDialog( + latest, + chipView, + mockExpandable, + mockGenericCastDialog, + kosmos, + ) dialogStopAction.onClick(mock<DialogInterface>(), 0) // THEN the chip is immediately hidden... @@ -416,13 +434,14 @@ class CastToOtherDeviceChipViewModelTest : SysuiTestCase() { } @Test + @DisableFlags(StatusBarChipsModernization.FLAG_NAME) fun chip_projectionStateEntireScreen_clickListenerShowsScreenCastDialog() = testScope.runTest { val latest by collectLastValue(underTest.chip) mediaProjectionRepo.mediaProjectionState.value = MediaProjectionState.Projecting.EntireScreen(CAST_TO_OTHER_DEVICES_PACKAGE) - val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener) + val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListenerLegacy) assertThat(clickListener).isNotNull() clickListener!!.onClick(chipView) @@ -431,6 +450,7 @@ class CastToOtherDeviceChipViewModelTest : SysuiTestCase() { } @Test + @DisableFlags(StatusBarChipsModernization.FLAG_NAME) fun chip_projectionStateSingleTask_clickListenerShowsScreenCastDialog() = testScope.runTest { val latest by collectLastValue(underTest.chip) @@ -442,7 +462,7 @@ class CastToOtherDeviceChipViewModelTest : SysuiTestCase() { createTask(taskId = 1), ) - val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener) + val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListenerLegacy) assertThat(clickListener).isNotNull() clickListener!!.onClick(chipView) @@ -451,6 +471,7 @@ class CastToOtherDeviceChipViewModelTest : SysuiTestCase() { } @Test + @DisableFlags(StatusBarChipsModernization.FLAG_NAME) fun chip_routerStateCasting_clickListenerShowsGenericCastDialog() = testScope.runTest { val latest by collectLastValue(underTest.chip) @@ -466,7 +487,7 @@ class CastToOtherDeviceChipViewModelTest : SysuiTestCase() { ) ) - val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener) + val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListenerLegacy) assertThat(clickListener).isNotNull() clickListener!!.onClick(chipView) @@ -480,13 +501,14 @@ class CastToOtherDeviceChipViewModelTest : SysuiTestCase() { } @Test + @DisableFlags(StatusBarChipsModernization.FLAG_NAME) fun chip_projectionStateCasting_clickListenerHasCuj() = testScope.runTest { val latest by collectLastValue(underTest.chip) mediaProjectionRepo.mediaProjectionState.value = MediaProjectionState.Projecting.EntireScreen(CAST_TO_OTHER_DEVICES_PACKAGE) - val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener) + val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListenerLegacy) clickListener!!.onClick(chipView) val cujCaptor = argumentCaptor<DialogCuj>() @@ -499,6 +521,7 @@ class CastToOtherDeviceChipViewModelTest : SysuiTestCase() { } @Test + @DisableFlags(StatusBarChipsModernization.FLAG_NAME) fun chip_routerStateCasting_clickListenerHasCuj() = testScope.runTest { val latest by collectLastValue(underTest.chip) @@ -514,7 +537,7 @@ class CastToOtherDeviceChipViewModelTest : SysuiTestCase() { ) ) - val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener) + val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListenerLegacy) clickListener!!.onClick(chipView) val cujCaptor = argumentCaptor<DialogCuj>() @@ -525,4 +548,103 @@ class CastToOtherDeviceChipViewModelTest : SysuiTestCase() { .isEqualTo(Cuj.CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP) assertThat(cujCaptor.firstValue.tag).contains("Cast") } + + @Test + @EnableFlags(StatusBarChipsModernization.FLAG_NAME) + fun chip_routerStateCasting_hasClickBehavior() = + testScope.runTest { + val latest by collectLastValue(underTest.chip) + + mediaRouterRepo.castDevices.value = + listOf( + CastDevice( + state = CastDevice.CastState.Connected, + id = "id", + name = "name", + description = "desc", + origin = CastDevice.CastOrigin.MediaRouter, + ) + ) + + assertThat((latest as OngoingActivityChipModel.Shown).clickBehavior) + .isInstanceOf(OngoingActivityChipModel.ClickBehavior.ExpandAction::class.java) + } + + @Test + @EnableFlags(StatusBarChipsModernization.FLAG_NAME) + fun chip_projectionStateCasting_hasClickBehavior() = + testScope.runTest { + val latest by collectLastValue(underTest.chip) + + mediaProjectionRepo.mediaProjectionState.value = + MediaProjectionState.Projecting.EntireScreen(CAST_TO_OTHER_DEVICES_PACKAGE) + + assertThat((latest as OngoingActivityChipModel.Shown).clickBehavior) + .isInstanceOf(OngoingActivityChipModel.ClickBehavior.ExpandAction::class.java) + } + + @Test + @EnableFlags(StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME) + fun chip_projectionStateEntireScreen_clickBehaviorShowsScreenCastDialog() = + testScope.runTest { + val latest by collectLastValue(underTest.chip) + mediaProjectionRepo.mediaProjectionState.value = + MediaProjectionState.Projecting.EntireScreen(CAST_TO_OTHER_DEVICES_PACKAGE) + + val expandAction = + ((latest as OngoingActivityChipModel.Shown).clickBehavior + as OngoingActivityChipModel.ClickBehavior.ExpandAction) + expandAction.onClick(mockExpandable) + + verify(kosmos.mockDialogTransitionAnimator) + .show(eq(mockScreenCastDialog), any(), anyBoolean()) + } + + @Test + @EnableFlags(StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME) + fun chip_projectionStateSingleTask_clickBehaviorShowsScreenCastDialog() = + testScope.runTest { + val latest by collectLastValue(underTest.chip) + + mediaProjectionRepo.mediaProjectionState.value = + MediaProjectionState.Projecting.SingleTask( + CAST_TO_OTHER_DEVICES_PACKAGE, + hostDeviceName = null, + createTask(taskId = 1), + ) + + val expandAction = + ((latest as OngoingActivityChipModel.Shown).clickBehavior + as OngoingActivityChipModel.ClickBehavior.ExpandAction) + expandAction.onClick(mockExpandable) + + verify(kosmos.mockDialogTransitionAnimator) + .show(eq(mockScreenCastDialog), any(), anyBoolean()) + } + + @Test + @EnableFlags(StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME) + fun chip_routerStateCasting_clickBehaviorShowsGenericCastDialog() = + testScope.runTest { + val latest by collectLastValue(underTest.chip) + + mediaRouterRepo.castDevices.value = + listOf( + CastDevice( + state = CastDevice.CastState.Connected, + id = "id", + name = "name", + description = "desc", + origin = CastDevice.CastOrigin.MediaRouter, + ) + ) + + val expandAction = + ((latest as OngoingActivityChipModel.Shown).clickBehavior + as OngoingActivityChipModel.ClickBehavior.ExpandAction) + expandAction.onClick(mockExpandable) + + verify(kosmos.mockDialogTransitionAnimator) + .show(eq(mockGenericCastDialog), any(), anyBoolean()) + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractorTest.kt index ee4a52d35d68..e89c929a5827 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractorTest.kt @@ -36,6 +36,7 @@ import com.android.systemui.statusbar.notification.data.repository.ActiveNotific import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel +import com.android.systemui.statusbar.notification.shared.CallType import com.android.systemui.testKosmos import com.android.systemui.util.time.fakeSystemClock import com.google.common.truth.Truth.assertThat @@ -178,6 +179,37 @@ class StatusBarNotificationChipsInteractorTest : SysuiTestCase() { assertThat(latest!![1].statusBarChipIconView).isEqualTo(secondIcon) } + /** Regression test for b/388521980. */ + @Test + @EnableFlags(StatusBarNotifChips.FLAG_NAME) + fun notificationChips_callNotifIsAlsoPromoted_callNotifExcluded() = + kosmos.runTest { + val latest by collectLastValue(underTest.notificationChips) + + setNotifs( + listOf( + activeNotificationModel( + key = "promotedNormal", + statusBarChipIcon = mock(), + promotedContent = + PromotedNotificationContentModel.Builder("promotedNormal").build(), + callType = CallType.None, + ), + activeNotificationModel( + key = "promotedCall", + statusBarChipIcon = mock(), + promotedContent = + PromotedNotificationContentModel.Builder("promotedCall").build(), + callType = CallType.Ongoing, + ), + ) + ) + + // Verify the promoted call notification is not included + assertThat(latest).hasSize(1) + assertThat(latest!![0].key).isEqualTo("promotedNormal") + } + @Test @EnableFlags(StatusBarNotifChips.FLAG_NAME) fun notificationChips_notifUpdatesGoThrough() = diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt index 902db5e10589..eec23d3ffb1a 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt @@ -650,7 +650,7 @@ class NotifChipsViewModelTest : SysuiTestCase() { ) val chip = latest!![0] - chip.onClickListener!!.onClick(mock<View>()) + chip.onClickListenerLegacy!!.onClick(mock<View>()) assertThat(latestChipTap).isEqualTo("clickTest") } diff --git a/packages/SystemUI/multivalentTests/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 48d8add6b33a..1f82dcd9c308 100644 --- a/packages/SystemUI/multivalentTests/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 @@ -17,12 +17,15 @@ package com.android.systemui.statusbar.chips.screenrecord.ui.viewmodel import android.content.DialogInterface +import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags import android.view.View import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.jank.Cuj import com.android.systemui.SysuiTestCase import com.android.systemui.animation.DialogCuj +import com.android.systemui.animation.Expandable import com.android.systemui.animation.mockDialogTransitionAnimator import com.android.systemui.common.shared.model.Icon import com.android.systemui.coroutines.collectLastValue @@ -41,8 +44,10 @@ import com.android.systemui.statusbar.chips.ui.model.ColorsModel import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipsViewModelTest.Companion.getStopActionFromDialog +import com.android.systemui.statusbar.core.StatusBarRootModernization import com.android.systemui.statusbar.phone.SystemUIDialog import com.android.systemui.statusbar.phone.mockSystemUIDialogFactory +import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization import com.android.systemui.testKosmos import com.android.systemui.util.time.fakeSystemClock import com.google.common.truth.Truth.assertThat @@ -77,6 +82,8 @@ class ScreenRecordChipViewModelTest : SysuiTestCase() { ) .thenReturn(chipBackgroundView) } + private val mockExpandable: Expandable = + mock<Expandable>().apply { whenever(dialogTransitionController(any())).thenReturn(mock()) } private val underTest = kosmos.screenRecordChipViewModel @@ -106,7 +113,7 @@ class ScreenRecordChipViewModelTest : SysuiTestCase() { assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown.Countdown::class.java) assertThat((latest as OngoingActivityChipModel.Shown).icon).isNull() - assertThat((latest as OngoingActivityChipModel.Shown).onClickListener).isNull() + assertThat((latest as OngoingActivityChipModel.Shown).onClickListenerLegacy).isNull() } // The millis we typically get from [ScreenRecordRepository] are around 2995, 1995, and 995. @@ -177,7 +184,13 @@ class ScreenRecordChipViewModelTest : SysuiTestCase() { // WHEN the stop action on the dialog is clicked val dialogStopAction = - getStopActionFromDialog(latest, chipView, mockSystemUIDialog, kosmos) + getStopActionFromDialog( + latest, + chipView, + mockExpandable, + mockSystemUIDialog, + kosmos, + ) dialogStopAction.onClick(mock<DialogInterface>(), 0) // THEN both the screen record chip and the share-to-app chip are immediately hidden... @@ -263,13 +276,14 @@ class ScreenRecordChipViewModelTest : SysuiTestCase() { } @Test + @DisableFlags(StatusBarChipsModernization.FLAG_NAME) fun chip_notProjecting_clickListenerShowsDialog() = testScope.runTest { val latest by collectLastValue(underTest.chip) screenRecordRepo.screenRecordState.value = ScreenRecordModel.Recording mediaProjectionRepo.mediaProjectionState.value = MediaProjectionState.NotProjecting - val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener) + val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListenerLegacy) assertThat(clickListener).isNotNull() clickListener!!.onClick(chipView) @@ -279,6 +293,7 @@ class ScreenRecordChipViewModelTest : SysuiTestCase() { } @Test + @DisableFlags(StatusBarChipsModernization.FLAG_NAME) fun chip_projectingEntireScreen_clickListenerShowsDialog() = testScope.runTest { val latest by collectLastValue(underTest.chip) @@ -286,7 +301,7 @@ class ScreenRecordChipViewModelTest : SysuiTestCase() { mediaProjectionRepo.mediaProjectionState.value = MediaProjectionState.Projecting.EntireScreen("host.package") - val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener) + val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListenerLegacy) assertThat(clickListener).isNotNull() clickListener!!.onClick(chipView) @@ -296,6 +311,7 @@ class ScreenRecordChipViewModelTest : SysuiTestCase() { } @Test + @DisableFlags(StatusBarChipsModernization.FLAG_NAME) fun chip_projectingSingleTask_clickListenerShowsDialog() = testScope.runTest { val latest by collectLastValue(underTest.chip) @@ -307,7 +323,7 @@ class ScreenRecordChipViewModelTest : SysuiTestCase() { FakeActivityTaskManager.createTask(taskId = 1), ) - val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener) + val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListenerLegacy) assertThat(clickListener).isNotNull() clickListener!!.onClick(chipView) @@ -317,22 +333,85 @@ class ScreenRecordChipViewModelTest : SysuiTestCase() { } @Test - fun chip_clickListenerHasCuj() = + @DisableFlags(StatusBarChipsModernization.FLAG_NAME) + fun chip_clickListenerHasCujLegacy() = testScope.runTest { val latest by collectLastValue(underTest.chip) screenRecordRepo.screenRecordState.value = ScreenRecordModel.Recording mediaProjectionRepo.mediaProjectionState.value = MediaProjectionState.Projecting.EntireScreen("host.package") - val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener) + val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListenerLegacy) clickListener!!.onClick(chipView) val cujCaptor = argumentCaptor<DialogCuj>() verify(kosmos.mockDialogTransitionAnimator) .showFromView(any(), any(), cujCaptor.capture(), anyBoolean()) - assertThat(cujCaptor.firstValue.cujType) .isEqualTo(Cuj.CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP) assertThat(cujCaptor.firstValue.tag).contains("Screen record") } + + @Test + @EnableFlags(StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME) + fun chip_recordingState_hasClickBehavior() = + testScope.runTest { + val latest by collectLastValue(underTest.chip) + screenRecordRepo.screenRecordState.value = ScreenRecordModel.Recording + assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown.Timer::class.java) + assertThat((latest as OngoingActivityChipModel.Shown).clickBehavior) + .isInstanceOf(OngoingActivityChipModel.ClickBehavior.ExpandAction::class.java) + } + + @Test + @EnableFlags(StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME) + fun chip_notProjecting_expandActionBehaviorShowsDialog() = + testScope.runTest { + val latest by collectLastValue(underTest.chip) + screenRecordRepo.screenRecordState.value = ScreenRecordModel.Recording + mediaProjectionRepo.mediaProjectionState.value = MediaProjectionState.NotProjecting + + val expandAction = + ((latest as OngoingActivityChipModel.Shown).clickBehavior + as OngoingActivityChipModel.ClickBehavior.ExpandAction) + + expandAction.onClick(mockExpandable) + verify(kosmos.mockDialogTransitionAnimator).show(any(), any(), anyBoolean()) + } + + @Test + @EnableFlags(StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME) + fun chip_projectingEntireScreen_expandActionBehaviorShowsDialog() = + testScope.runTest { + val latest by collectLastValue(underTest.chip) + screenRecordRepo.screenRecordState.value = ScreenRecordModel.Recording + + val expandAction = + ((latest as OngoingActivityChipModel.Shown).clickBehavior + as OngoingActivityChipModel.ClickBehavior.ExpandAction) + + expandAction.onClick(mockExpandable) + verify(kosmos.mockDialogTransitionAnimator).show(any(), any(), anyBoolean()) + } + + @Test + @EnableFlags(StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME) + fun chip_projectingSingleTask_expandActionBehaviorShowsDialog() = + testScope.runTest { + val latest by collectLastValue(underTest.chip) + screenRecordRepo.screenRecordState.value = ScreenRecordModel.Recording + mediaProjectionRepo.mediaProjectionState.value = + MediaProjectionState.Projecting.SingleTask( + "host.package", + hostDeviceName = null, + FakeActivityTaskManager.createTask(taskId = 1), + ) + + val expandAction = + ((latest as OngoingActivityChipModel.Shown).clickBehavior + as OngoingActivityChipModel.ClickBehavior.ExpandAction) + + expandAction.onClick(mockExpandable) + verify(kosmos.mockDialogTransitionAnimator).show(any(), any(), anyBoolean()) + } } diff --git a/packages/SystemUI/multivalentTests/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 b3dec2eaa1c6..36fc5aa16407 100644 --- a/packages/SystemUI/multivalentTests/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 @@ -17,6 +17,7 @@ package com.android.systemui.statusbar.chips.sharetoapp.ui.viewmodel import android.content.DialogInterface +import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags import android.view.View import androidx.test.ext.junit.runners.AndroidJUnit4 @@ -25,6 +26,7 @@ import com.android.internal.jank.Cuj import com.android.systemui.Flags.FLAG_STATUS_BAR_SHOW_AUDIO_ONLY_PROJECTION_CHIP import com.android.systemui.SysuiTestCase import com.android.systemui.animation.DialogCuj +import com.android.systemui.animation.Expandable import com.android.systemui.animation.mockDialogTransitionAnimator import com.android.systemui.common.shared.model.ContentDescription import com.android.systemui.common.shared.model.Icon @@ -45,8 +47,10 @@ import com.android.systemui.statusbar.chips.ui.model.ColorsModel import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipsViewModelTest.Companion.getStopActionFromDialog +import com.android.systemui.statusbar.core.StatusBarRootModernization import com.android.systemui.statusbar.phone.SystemUIDialog import com.android.systemui.statusbar.phone.mockSystemUIDialogFactory +import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization import com.android.systemui.util.time.fakeSystemClock import com.google.common.truth.Truth.assertThat import kotlin.test.Test @@ -81,6 +85,8 @@ class ShareToAppChipViewModelTest : SysuiTestCase() { ) .thenReturn(chipBackgroundView) } + private val mockExpandable: Expandable = + mock<Expandable>().apply { whenever(dialogTransitionController(any())).thenReturn(mock()) } private val underTest = kosmos.shareToAppChipViewModel @@ -215,7 +221,13 @@ class ShareToAppChipViewModelTest : SysuiTestCase() { // WHEN the stop action on the dialog is clicked val dialogStopAction = - getStopActionFromDialog(latest, chipView, mockScreenShareDialog, kosmos) + getStopActionFromDialog( + latest, + chipView, + mockExpandable, + mockScreenShareDialog, + kosmos, + ) dialogStopAction.onClick(mock<DialogInterface>(), 0) // THEN the chip is immediately hidden... @@ -268,13 +280,14 @@ class ShareToAppChipViewModelTest : SysuiTestCase() { @Test @EnableFlags(FLAG_STATUS_BAR_SHOW_AUDIO_ONLY_PROJECTION_CHIP) + @DisableFlags(StatusBarChipsModernization.FLAG_NAME) fun chip_noScreen_clickListenerShowsGenericShareDialog() = testScope.runTest { val latest by collectLastValue(underTest.chip) mediaProjectionRepo.mediaProjectionState.value = MediaProjectionState.Projecting.NoScreen(NORMAL_PACKAGE) - val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener) + val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListenerLegacy) assertThat(clickListener).isNotNull() clickListener!!.onClick(chipView) @@ -288,13 +301,14 @@ class ShareToAppChipViewModelTest : SysuiTestCase() { } @Test + @DisableFlags(StatusBarChipsModernization.FLAG_NAME) fun chip_entireScreen_clickListenerShowsScreenShareDialog() = testScope.runTest { val latest by collectLastValue(underTest.chip) mediaProjectionRepo.mediaProjectionState.value = MediaProjectionState.Projecting.EntireScreen(NORMAL_PACKAGE) - val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener) + val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListenerLegacy) assertThat(clickListener).isNotNull() clickListener!!.onClick(chipView) @@ -308,6 +322,7 @@ class ShareToAppChipViewModelTest : SysuiTestCase() { } @Test + @DisableFlags(StatusBarChipsModernization.FLAG_NAME) fun chip_singleTask_clickListenerShowsScreenShareDialog() = testScope.runTest { val latest by collectLastValue(underTest.chip) @@ -318,7 +333,7 @@ class ShareToAppChipViewModelTest : SysuiTestCase() { createTask(taskId = 1), ) - val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener) + val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListenerLegacy) assertThat(clickListener).isNotNull() clickListener!!.onClick(chipView) @@ -332,6 +347,7 @@ class ShareToAppChipViewModelTest : SysuiTestCase() { } @Test + @DisableFlags(StatusBarChipsModernization.FLAG_NAME) fun chip_clickListenerHasCuj() = testScope.runTest { val latest by collectLastValue(underTest.chip) @@ -342,7 +358,7 @@ class ShareToAppChipViewModelTest : SysuiTestCase() { createTask(taskId = 1), ) - val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener) + val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListenerLegacy) clickListener!!.onClick(chipView) val cujCaptor = argumentCaptor<DialogCuj>() @@ -353,4 +369,101 @@ class ShareToAppChipViewModelTest : SysuiTestCase() { .isEqualTo(Cuj.CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP) assertThat(cujCaptor.firstValue.tag).contains("Share") } + + @Test + @EnableFlags(StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME) + fun chip_noScreen_hasClickBehavior() = + testScope.runTest { + val latest by collectLastValue(underTest.chip) + mediaProjectionRepo.mediaProjectionState.value = + MediaProjectionState.Projecting.NoScreen(NORMAL_PACKAGE) + + assertThat((latest as OngoingActivityChipModel.Shown).clickBehavior) + .isInstanceOf(OngoingActivityChipModel.ClickBehavior.ExpandAction::class.java) + } + + @Test + @EnableFlags(StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME) + fun chip_entireScreen_hasClickBehavior() = + testScope.runTest { + val latest by collectLastValue(underTest.chip) + mediaProjectionRepo.mediaProjectionState.value = + MediaProjectionState.Projecting.EntireScreen(NORMAL_PACKAGE) + + assertThat((latest as OngoingActivityChipModel.Shown).clickBehavior) + .isInstanceOf(OngoingActivityChipModel.ClickBehavior.ExpandAction::class.java) + } + + @Test + @EnableFlags(StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME) + fun chip_singleTask_hasClickBehavior() = + testScope.runTest { + val latest by collectLastValue(underTest.chip) + mediaProjectionRepo.mediaProjectionState.value = + MediaProjectionState.Projecting.SingleTask( + NORMAL_PACKAGE, + hostDeviceName = null, + createTask(taskId = 1), + ) + + assertThat((latest as OngoingActivityChipModel.Shown).clickBehavior) + .isInstanceOf(OngoingActivityChipModel.ClickBehavior.ExpandAction::class.java) + } + + @Test + @EnableFlags( + FLAG_STATUS_BAR_SHOW_AUDIO_ONLY_PROJECTION_CHIP, + StatusBarRootModernization.FLAG_NAME, + StatusBarChipsModernization.FLAG_NAME, + ) + fun chip_noScreen_clickBehaviorShowsGenericShareDialog() = + testScope.runTest { + val latest by collectLastValue(underTest.chip) + mediaProjectionRepo.mediaProjectionState.value = + MediaProjectionState.Projecting.NoScreen(NORMAL_PACKAGE) + + val expandAction = + ((latest as OngoingActivityChipModel.Shown).clickBehavior + as OngoingActivityChipModel.ClickBehavior.ExpandAction) + expandAction.onClick(mockExpandable) + verify(kosmos.mockDialogTransitionAnimator) + .show(eq(mockGenericShareDialog), any(), any()) + } + + @Test + @EnableFlags(StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME) + fun chip_entireScreen_clickBehaviorShowsScreenShareDialog() = + testScope.runTest { + val latest by collectLastValue(underTest.chip) + mediaProjectionRepo.mediaProjectionState.value = + MediaProjectionState.Projecting.EntireScreen(NORMAL_PACKAGE) + + val expandAction = + ((latest as OngoingActivityChipModel.Shown).clickBehavior + as OngoingActivityChipModel.ClickBehavior.ExpandAction) + expandAction.onClick(mockExpandable) + verify(kosmos.mockDialogTransitionAnimator) + .show(eq(mockScreenShareDialog), any(), any()) + } + + @Test + @EnableFlags(StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME) + fun chip_singleTask_clickBehaviorShowsScreenShareDialog() = + testScope.runTest { + val latest by collectLastValue(underTest.chip) + mediaProjectionRepo.mediaProjectionState.value = + MediaProjectionState.Projecting.SingleTask( + NORMAL_PACKAGE, + hostDeviceName = null, + createTask(taskId = 1), + ) + + val expandAction = + ((latest as OngoingActivityChipModel.Shown).clickBehavior + as OngoingActivityChipModel.ClickBehavior.ExpandAction) + expandAction.onClick(mockExpandable) + + verify(kosmos.mockDialogTransitionAnimator) + .show(eq(mockScreenShareDialog), any(), any()) + } } diff --git a/packages/SystemUI/multivalentTests/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 8d4c68de8c79..d099e70c9bc5 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/ChipTransitionHelperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/ChipTransitionHelperTest.kt @@ -57,7 +57,8 @@ class ChipTransitionHelperTest : SysuiTestCase() { icon = createIcon(R.drawable.ic_cake), colors = ColorsModel.Themed, startTimeMs = 100L, - onClickListener = null, + onClickListenerLegacy = null, + clickBehavior = OngoingActivityChipModel.ClickBehavior.None, ) inputChipFlow.value = newChip @@ -68,7 +69,8 @@ class ChipTransitionHelperTest : SysuiTestCase() { OngoingActivityChipModel.Shown.IconOnly( icon = createIcon(R.drawable.ic_hotspot), colors = ColorsModel.Themed, - onClickListener = null, + onClickListenerLegacy = null, + clickBehavior = OngoingActivityChipModel.ClickBehavior.None, ) inputChipFlow.value = newerChip @@ -89,7 +91,8 @@ class ChipTransitionHelperTest : SysuiTestCase() { icon = createIcon(R.drawable.ic_cake), colors = ColorsModel.Themed, startTimeMs = 100L, - onClickListener = null, + onClickListenerLegacy = null, + clickBehavior = OngoingActivityChipModel.ClickBehavior.None, ) inputChipFlow.value = shownChip @@ -129,7 +132,8 @@ class ChipTransitionHelperTest : SysuiTestCase() { icon = createIcon(R.drawable.ic_cake), colors = ColorsModel.Themed, startTimeMs = 100L, - onClickListener = null, + onClickListenerLegacy = null, + clickBehavior = OngoingActivityChipModel.ClickBehavior.None, ) inputChipFlow.value = shownChip diff --git a/packages/SystemUI/multivalentTests/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 e3510f5ce280..fc3af11c30b3 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModelTest.kt @@ -16,6 +16,8 @@ package com.android.systemui.statusbar.chips.ui.viewmodel +import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags import android.view.View import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest @@ -23,14 +25,19 @@ import com.android.internal.jank.Cuj import com.android.systemui.SysuiTestCase import com.android.systemui.animation.DialogCuj import com.android.systemui.animation.DialogTransitionAnimator +import com.android.systemui.animation.Expandable import com.android.systemui.log.logcatLogBuffer import com.android.systemui.res.R import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer +import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel.Companion.createDialogLaunchOnClickCallback import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel.Companion.createDialogLaunchOnClickListener +import com.android.systemui.statusbar.core.StatusBarRootModernization import com.android.systemui.statusbar.phone.SystemUIDialog +import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization import kotlin.test.Test import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.anyBoolean +import org.mockito.kotlin.any import org.mockito.kotlin.eq import org.mockito.kotlin.mock import org.mockito.kotlin.verify @@ -53,8 +60,11 @@ class OngoingActivityChipViewModelTest : SysuiTestCase() { ) .thenReturn(chipBackgroundView) } + private val mockExpandable: Expandable = + mock<Expandable>().apply { whenever(dialogTransitionController(any())).thenReturn(mock()) } @Test + @DisableFlags(StatusBarChipsModernization.FLAG_NAME) fun createDialogLaunchOnClickListener_showsDialogOnClick() { val cuj = DialogCuj(Cuj.CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP, tag = "Test") val clickListener = @@ -68,11 +78,23 @@ class OngoingActivityChipViewModelTest : SysuiTestCase() { clickListener.onClick(chipView) verify(dialogTransitionAnimator) - .showFromView( - eq(mockSystemUIDialog), - eq(chipBackgroundView), - eq(cuj), - anyBoolean(), + .showFromView(eq(mockSystemUIDialog), eq(chipBackgroundView), eq(cuj), anyBoolean()) + } + + @Test + @EnableFlags(StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME) + fun createDialogLaunchOnClickCallback_showsDialogOnClick() { + val cuj = DialogCuj(Cuj.CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP, tag = "Test") + val clickCallback = + createDialogLaunchOnClickCallback( + dialogDelegate, + dialogTransitionAnimator, + cuj, + logcatLogBuffer("OngoingActivityChipViewModelTest"), + "tag", ) + + clickCallback.invoke(mockExpandable) + verify(dialogTransitionAnimator).show(eq(mockSystemUIDialog), any(), anyBoolean()) } } diff --git a/packages/SystemUI/multivalentTests/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 42358cce59a2..a4b6a841d61b 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt @@ -26,6 +26,7 @@ import android.view.View import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.animation.Expandable import com.android.systemui.common.shared.model.Icon import com.android.systemui.coroutines.collectLastValue import com.android.systemui.kosmos.Kosmos @@ -44,6 +45,7 @@ import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer import com.android.systemui.statusbar.core.StatusBarConnectedDisplays import com.android.systemui.statusbar.phone.SystemUIDialog import com.android.systemui.statusbar.phone.mockSystemUIDialogFactory +import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization import com.android.systemui.statusbar.phone.ongoingcall.data.repository.ongoingCallRepository import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel import com.android.systemui.statusbar.phone.ongoingcall.shared.model.inCallModel @@ -91,6 +93,8 @@ class OngoingActivityChipsViewModelTest : SysuiTestCase() { ) .thenReturn(chipBackgroundView) } + private val mockExpandable: Expandable = + mock<Expandable>().apply { whenever(dialogTransitionController(any())).thenReturn(mock()) } private val underTest = kosmos.ongoingActivityChipsViewModel @@ -294,7 +298,13 @@ class OngoingActivityChipsViewModelTest : SysuiTestCase() { // WHEN screen record gets stopped via dialog val dialogStopAction = - getStopActionFromDialog(latest, chipView, mockSystemUIDialog, kosmos) + getStopActionFromDialog( + latest, + chipView, + mockExpandable, + mockSystemUIDialog, + kosmos, + ) dialogStopAction.onClick(mock<DialogInterface>(), 0) // THEN the chip is immediately hidden with no animation @@ -315,7 +325,13 @@ class OngoingActivityChipsViewModelTest : SysuiTestCase() { // WHEN media projection gets stopped via dialog val dialogStopAction = - getStopActionFromDialog(latest, chipView, mockSystemUIDialog, kosmos) + getStopActionFromDialog( + latest, + chipView, + mockExpandable, + mockSystemUIDialog, + kosmos, + ) dialogStopAction.onClick(mock<DialogInterface>(), 0) // THEN the chip is immediately hidden with no animation @@ -330,6 +346,7 @@ class OngoingActivityChipsViewModelTest : SysuiTestCase() { fun getStopActionFromDialog( latest: OngoingActivityChipModel?, chipView: View, + expandable: Expandable, dialog: SystemUIDialog, kosmos: Kosmos, ): DialogInterface.OnClickListener { @@ -349,9 +366,17 @@ class OngoingActivityChipsViewModelTest : SysuiTestCase() { .create(any<SystemUIDialog.Delegate>()) whenever(kosmos.packageManager.getApplicationInfo(eq(NORMAL_PACKAGE), any<Int>())) .thenThrow(PackageManager.NameNotFoundException()) - // Click the chip so that we open the dialog and we fill in [dialogStopAction] - val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener) - clickListener!!.onClick(chipView) + + if (StatusBarChipsModernization.isEnabled) { + val clickBehavior = (latest as OngoingActivityChipModel.Shown).clickBehavior + (clickBehavior as OngoingActivityChipModel.ClickBehavior.ExpandAction).onClick( + expandable + ) + } else { + val clickListener = + ((latest as OngoingActivityChipModel.Shown).onClickListenerLegacy) + clickListener!!.onClick(chipView) + } return dialogStopAction } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt index 0f42f29e76ee..28f360108e50 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt @@ -25,6 +25,7 @@ import android.view.View import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.animation.Expandable import com.android.systemui.coroutines.collectLastValue import com.android.systemui.kosmos.testScope import com.android.systemui.kosmos.useUnconfinedTestDispatcher @@ -104,6 +105,8 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() { ) .thenReturn(chipBackgroundView) } + private val mockExpandable: Expandable = + mock<Expandable>().apply { whenever(dialogTransitionController(any())).thenReturn(mock()) } private val underTest by lazy { kosmos.ongoingActivityChipsViewModel } @@ -679,7 +682,13 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() { // WHEN screen record gets stopped via dialog val dialogStopAction = - getStopActionFromDialog(latest, chipView, mockSystemUIDialog, kosmos) + getStopActionFromDialog( + latest, + chipView, + mockExpandable, + mockSystemUIDialog, + kosmos, + ) dialogStopAction.onClick(mock<DialogInterface>(), 0) // THEN the chip is immediately hidden with no animation @@ -700,7 +709,13 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() { // WHEN media projection gets stopped via dialog val dialogStopAction = - getStopActionFromDialog(latest, chipView, mockSystemUIDialog, kosmos) + getStopActionFromDialog( + latest, + chipView, + mockExpandable, + mockSystemUIDialog, + kosmos, + ) dialogStopAction.onClick(mock<DialogInterface>(), 0) // THEN the chip is immediately hidden with no animation diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImplTest.kt index 739a9c956178..9dfc922eb7d0 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImplTest.kt @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.notification.headsup import android.app.Notification +import android.app.Notification.FLAG_PROMOTED_ONGOING import android.app.PendingIntent import android.app.Person import android.os.Handler @@ -677,10 +678,12 @@ class HeadsUpManagerImplTest(flags: FlagsParameterization) : SysuiTestCase() { } @Test - fun testIsSticky_rowPinnedAndExpanded_true() { - val notifEntry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext) - val row = testHelper.createRow() - row.setPinnedStatus(PinnedStatus.PinnedBySystem) + @DisableFlags(StatusBarNotifChips.FLAG_NAME) + fun testIsSticky_promotedAndExpanded_notifChipsFlagOff_true() { + val notif = Notification.Builder(mContext, "").setSmallIcon(R.drawable.ic_person).build() + notif.flags = FLAG_PROMOTED_ONGOING + val notifEntry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, notif) + val row = testHelper.createRow().apply { setPinnedStatus(PinnedStatus.PinnedBySystem) } notifEntry.row = row underTest.showNotification(notifEntry) @@ -692,6 +695,23 @@ class HeadsUpManagerImplTest(flags: FlagsParameterization) : SysuiTestCase() { } @Test + @EnableFlags(StatusBarNotifChips.FLAG_NAME) + fun testIsSticky_promotedAndExpanded_notifChipsFlagOn_false() { + val notif = Notification.Builder(mContext, "").setSmallIcon(R.drawable.ic_person).build() + notif.flags = FLAG_PROMOTED_ONGOING + val notifEntry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, notif) + val row = testHelper.createRow().apply { setPinnedStatus(PinnedStatus.PinnedBySystem) } + notifEntry.row = row + + underTest.showNotification(notifEntry) + + val headsUpEntry = underTest.getHeadsUpEntry(notifEntry.key) + headsUpEntry!!.setExpanded(true) + + assertThat(underTest.isSticky(notifEntry.key)).isFalse() + } + + @Test fun testIsSticky_remoteInputActive_true() { val notifEntry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/SystemUIDialogTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/SystemUIDialogTest.java index 650fa7ce46de..58856d970711 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/SystemUIDialogTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/SystemUIDialogTest.java @@ -16,6 +16,8 @@ package com.android.systemui.statusbar.phone; +import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DIALOG_SHOWING; + import static com.google.common.truth.Truth.assertThat; import static junit.framework.Assert.assertFalse; @@ -44,7 +46,9 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.animation.DialogTransitionAnimator; import com.android.systemui.animation.back.BackAnimationSpec; import com.android.systemui.broadcast.BroadcastDispatcher; +import com.android.systemui.kosmos.KosmosJavaAdapter; import com.android.systemui.model.SysUiState; +import com.android.systemui.settings.FakeDisplayTracker; import org.junit.Before; import org.junit.Rule; @@ -68,6 +72,7 @@ public class SystemUIDialogTest extends SysuiTestCase { private BroadcastDispatcher mBroadcastDispatcher; @Mock private SystemUIDialog.Delegate mDelegate; + private SysUiState mSysUiState; // TODO(b/292141694): build out Ravenwood support for DeviceFlagsValueProvider // Ravenwood already has solid support for SetFlagsRule, but CheckFlagsRule will be added soon @@ -78,7 +83,9 @@ public class SystemUIDialogTest extends SysuiTestCase { @Before public void setup() { MockitoAnnotations.initMocks(this); - + KosmosJavaAdapter kosmos = new KosmosJavaAdapter(this); + FakeDisplayTracker displayTracker = new FakeDisplayTracker(mContext); + mSysUiState = new SysUiState(displayTracker, kosmos.getSceneContainerPlugin()); mDependency.injectTestDependency(BroadcastDispatcher.class, mBroadcastDispatcher); when(mDelegate.getBackAnimationSpec(ArgumentMatchers.any())) .thenReturn(mock(BackAnimationSpec.class)); @@ -173,6 +180,30 @@ public class SystemUIDialogTest extends SysuiTestCase { assertThat(calledStop.get()).isTrue(); } + /** Regression test for b/386871258 */ + @Test + public void sysuiStateUpdated() { + SystemUIDialog dialog1 = + createDialogWithDelegate(mContext, mDelegate, /* shouldAcsDismissDialog */ true); + SystemUIDialog dialog2 = + createDialogWithDelegate(mContext, mDelegate, /* shouldAcsDismissDialog */ true); + + dialog1.show(); + assertThat((mSysUiState.getFlags() & SYSUI_STATE_DIALOG_SHOWING) != 0).isTrue(); + + dialog2.show(); + assertThat((mSysUiState.getFlags() & SYSUI_STATE_DIALOG_SHOWING) != 0).isTrue(); + + dialog2.dismiss(); + // explicitly call onWindowFocusChanged to simulate dialog 1 regaining focus + dialog1.onWindowFocusChanged(/* hasFocus= */ true); + assertThat((mSysUiState.getFlags() & SYSUI_STATE_DIALOG_SHOWING) != 0).isTrue(); + + dialog1.dismiss(); + assertThat((mSysUiState.getFlags() & SYSUI_STATE_DIALOG_SHOWING) != 0).isFalse(); + } + + @Test public void delegateIsCalled_inCorrectOrder() { Configuration configuration = new Configuration(); @@ -198,7 +229,7 @@ public class SystemUIDialogTest extends SysuiTestCase { SystemUIDialog.Factory factory = new SystemUIDialog.Factory( getContext(), Dependency.get(SystemUIDialogManager.class), - Dependency.get(SysUiState.class), + mSysUiState, Dependency.get(BroadcastDispatcher.class), Dependency.get(DialogTransitionAnimator.class) ); diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt index 6da06a36f63d..02135f6a7836 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt @@ -41,6 +41,7 @@ import com.android.systemui.statusbar.notification.data.model.activeNotification import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor +import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel import com.android.systemui.statusbar.notification.shared.CallType import com.android.systemui.statusbar.phone.ongoingcall.data.repository.ongoingCallRepository @@ -169,6 +170,27 @@ class OngoingCallControllerTest : SysuiTestCase() { } @Test + fun interactorHasOngoingCallNotif_repoHasPromotedContent() = + testScope.runTest { + val promotedContent = PromotedNotificationContentModel.Builder("ongoingNotif").build() + setNotifOnRepo( + activeNotificationModel( + key = "ongoingNotif", + callType = CallType.Ongoing, + uid = CALL_UID, + statusBarChipIcon = mock(), + whenTime = 567, + promotedContent = promotedContent, + ) + ) + + val repoState = ongoingCallRepository.ongoingCallState.value + assertThat(repoState).isInstanceOf(OngoingCallModel.InCall::class.java) + assertThat((repoState as OngoingCallModel.InCall).promotedContent) + .isEqualTo(promotedContent) + } + + @Test fun notifRepoHasOngoingCallNotif_isOngoingCallNotif_windowControllerUpdated() { setCallNotifOnRepo() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractorTest.kt index 8fb95e843ec1..14263c4b1b9b 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractorTest.kt @@ -35,6 +35,7 @@ import com.android.systemui.statusbar.gesture.swipeStatusBarAwayGestureHandler import com.android.systemui.statusbar.notification.data.model.activeNotificationModel import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository +import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel import com.android.systemui.statusbar.notification.shared.CallType import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel import com.android.systemui.statusbar.window.fakeStatusBarWindowControllerStore @@ -69,33 +70,37 @@ class OngoingCallInteractorTest : SysuiTestCase() { } @Test - fun ongoingCallNotification_setsNotificationIconAndIntent() = + fun ongoingCallNotification_setsAllFields() = kosmos.runTest { val latest by collectLastValue(underTest.ongoingCallState) // Set up notification with icon view and intent val testIconView: StatusBarIconView = mock() val testIntent: PendingIntent = mock() + val testPromotedContent = + PromotedNotificationContentModel.Builder("promotedCall").build() repository.activeNotifications.value = ActiveNotificationsStore.Builder() .apply { addIndividualNotif( activeNotificationModel( - key = "notif1", + key = "promotedCall", whenTime = 1000L, callType = CallType.Ongoing, statusBarChipIcon = testIconView, contentIntent = testIntent, + promotedContent = testPromotedContent, ) ) } .build() - // Verify model is InCall and has the correct icon and intent. + // Verify model is InCall and has the correct icon, intent, and promoted content. assertThat(latest).isInstanceOf(OngoingCallModel.InCall::class.java) val model = latest as OngoingCallModel.InCall assertThat(model.notificationIconView).isSameInstanceAs(testIconView) assertThat(model.intent).isSameInstanceAs(testIntent) + assertThat(model.promotedContent).isSameInstanceAs(testPromotedContent) } @Test diff --git a/packages/SystemUI/multivalentTests/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 b2378d2c3aae..2d6315014164 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateTest.kt @@ -66,13 +66,14 @@ class ModesDialogDelegateTest : SysuiTestCase() { whenever( mockDialogTransitionAnimator.createActivityTransitionController( any<SystemUIDialog>(), - eq(null) + eq(null), ) ) .thenReturn(mockAnimationController) underTest = ModesDialogDelegate( + context, kosmos.systemUIDialogFactory, mockDialogTransitionAnimator, activityStarter, diff --git a/packages/SystemUI/proguard_common.flags b/packages/SystemUI/proguard_common.flags index 02b2bcf8e40d..e152c98a93e5 100644 --- a/packages/SystemUI/proguard_common.flags +++ b/packages/SystemUI/proguard_common.flags @@ -58,3 +58,8 @@ static *** v(...); } -maximumremovedandroidloglevel 2 + +#Keep the R +-keepclassmembers class com.android.systemui.customization.R$* { + public static <fields>; +} diff --git a/packages/SystemUI/res/drawable/ic_media_connecting_button_container.xml b/packages/SystemUI/res/drawable/ic_media_connecting_button_container.xml new file mode 100644 index 000000000000..32aacf6522e7 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_media_connecting_button_container.xml @@ -0,0 +1,45 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2025 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. + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="88dp" + android:height="56dp" + android:viewportHeight="56" + android:viewportWidth="88"> + <group android:name="_R_G"> + <group + android:name="_R_G_L_1_G" + android:scaleX="1.05905" + android:scaleY="1.0972" + android:translateX="43.528999999999996" + android:translateY="27.898" /> + <group + android:name="_R_G_L_0_G" + android:pivotX="0.493" + android:pivotY="0.124" + android:scaleX="1.05905" + android:scaleY="1.0972" + android:translateX="43.528999999999996" + android:translateY="27.898"> + <path + android:name="_R_G_L_0_G_D_0_P_0" + android:fillAlpha="1" + android:fillColor="#3d90ff" + android:fillType="nonZero" + android:pathData=" M34.49 -5.75 C34.49,-5.75 34.49,6 34.49,6 C34.49,14.84 27.32,22 18.49,22 C18.49,22 -17.5,22 -17.5,22 C-26.34,22 -33.5,14.84 -33.5,6 C-33.5,6 -33.5,-5.75 -33.5,-5.75 C-33.5,-14.59 -26.34,-21.75 -17.5,-21.75 C-17.5,-21.75 18.49,-21.75 18.49,-21.75 C27.32,-21.75 34.49,-14.59 34.49,-5.75c " /> + </group> + </group> +</vector>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/ic_media_connecting_status_container.xml b/packages/SystemUI/res/drawable/ic_media_connecting_status_container.xml deleted file mode 100644 index f8c0fa04cd39..000000000000 --- a/packages/SystemUI/res/drawable/ic_media_connecting_status_container.xml +++ /dev/null @@ -1,199 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?><!-- - ~ Copyright (C) 2025 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. - --> -<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:aapt="http://schemas.android.com/aapt"> - <target android:name="_R_G_L_1_G"> - <aapt:attr name="android:animation"> - <set android:ordering="together"> - <objectAnimator - android:duration="83" - android:propertyName="scaleX" - android:startOffset="1000" - android:valueFrom="0.45561" - android:valueTo="0.69699" - android:valueType="floatType"> - <aapt:attr name="android:interpolator"> - <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.8,0.15 1.0,1.0" /> - </aapt:attr> - </objectAnimator> - <objectAnimator - android:duration="83" - android:propertyName="scaleY" - android:startOffset="1000" - android:valueFrom="0.6288400000000001" - android:valueTo="0.81618" - android:valueType="floatType"> - <aapt:attr name="android:interpolator"> - <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.8,0.15 1.0,1.0" /> - </aapt:attr> - </objectAnimator> - <objectAnimator - android:duration="417" - android:propertyName="scaleX" - android:startOffset="1083" - android:valueFrom="0.69699" - android:valueTo="1.05905" - android:valueType="floatType"> - <aapt:attr name="android:interpolator"> - <pathInterpolator android:pathData="M 0.0,0.0 c0.05,0.7 0.1,1 1.0,1.0" /> - </aapt:attr> - </objectAnimator> - <objectAnimator - android:duration="417" - android:propertyName="scaleY" - android:startOffset="1083" - android:valueFrom="0.81618" - android:valueTo="1.0972" - android:valueType="floatType"> - <aapt:attr name="android:interpolator"> - <pathInterpolator android:pathData="M 0.0,0.0 c0.05,0.7 0.1,1 1.0,1.0" /> - </aapt:attr> - </objectAnimator> - </set> - </aapt:attr> - </target> - <target android:name="_R_G_L_0_G"> - <aapt:attr name="android:animation"> - <set android:ordering="together"> - <objectAnimator - android:duration="500" - android:propertyName="rotation" - android:startOffset="0" - android:valueFrom="90" - android:valueTo="135" - android:valueType="floatType"> - <aapt:attr name="android:interpolator"> - <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" /> - </aapt:attr> - </objectAnimator> - <objectAnimator - android:duration="500" - android:propertyName="rotation" - android:startOffset="500" - android:valueFrom="135" - android:valueTo="180" - android:valueType="floatType"> - <aapt:attr name="android:interpolator"> - <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" /> - </aapt:attr> - </objectAnimator> - </set> - </aapt:attr> - </target> - <target android:name="_R_G_L_0_G"> - <aapt:attr name="android:animation"> - <set android:ordering="together"> - <objectAnimator - android:duration="83" - android:propertyName="scaleX" - android:startOffset="1000" - android:valueFrom="0.0434" - android:valueTo="0.05063" - android:valueType="floatType"> - <aapt:attr name="android:interpolator"> - <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.8,0.15 1.0,1.0" /> - </aapt:attr> - </objectAnimator> - <objectAnimator - android:duration="83" - android:propertyName="scaleY" - android:startOffset="1000" - android:valueFrom="0.0434" - android:valueTo="0.042350000000000006" - android:valueType="floatType"> - <aapt:attr name="android:interpolator"> - <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.8,0.15 1.0,1.0" /> - </aapt:attr> - </objectAnimator> - <objectAnimator - android:duration="417" - android:propertyName="scaleX" - android:startOffset="1083" - android:valueFrom="0.05063" - android:valueTo="0.06146" - android:valueType="floatType"> - <aapt:attr name="android:interpolator"> - <pathInterpolator android:pathData="M 0.0,0.0 c0.05,0.7 0.1,1 1.0,1.0" /> - </aapt:attr> - </objectAnimator> - <objectAnimator - android:duration="417" - android:propertyName="scaleY" - android:startOffset="1083" - android:valueFrom="0.042350000000000006" - android:valueTo="0.040780000000000004" - android:valueType="floatType"> - <aapt:attr name="android:interpolator"> - <pathInterpolator android:pathData="M 0.0,0.0 c0.05,0.7 0.1,1 1.0,1.0" /> - </aapt:attr> - </objectAnimator> - </set> - </aapt:attr> - </target> - <target android:name="time_group"> - <aapt:attr name="android:animation"> - <set android:ordering="together"> - <objectAnimator - android:duration="1017" - android:propertyName="translateX" - android:startOffset="0" - android:valueFrom="0" - android:valueTo="1" - android:valueType="floatType" /> - </set> - </aapt:attr> - </target> - <aapt:attr name="android:drawable"> - <vector - android:width="88dp" - android:height="56dp" - android:viewportHeight="56" - android:viewportWidth="88"> - <group android:name="_R_G"> - <group - android:name="_R_G_L_1_G" - android:pivotX="0.493" - android:pivotY="0.124" - android:scaleX="1.05905" - android:scaleY="1.0972" - android:translateX="43.528999999999996" - android:translateY="27.898"> - <path - android:name="_R_G_L_1_G_D_0_P_0" - android:fillAlpha="1" - android:fillColor="#3d90ff" - android:fillType="nonZero" - android:pathData=" M34.47 0.63 C34.47,0.63 34.42,0.64 34.42,0.64 C33.93,12.88 24.69,21.84 13.06,21.97 C13.06,21.97 -12.54,21.97 -12.54,21.97 C-23.11,21.84 -33.38,13.11 -33.52,-0.27 C-33.52,-0.27 -33.52,-0.05 -33.52,-0.05 C-33.5,-13.21 -21.73,-21.76 -12.9,-21.76 C-12.9,-21.76 14.59,-21.76 14.59,-21.76 C24.81,-21.88 34.49,-10.58 34.47,0.63c " /> - </group> - <group - android:name="_R_G_L_0_G" - android:rotation="0" - android:scaleX="0.06146" - android:scaleY="0.040780000000000004" - android:translateX="44" - android:translateY="28"> - <path - android:name="_R_G_L_0_G_D_0_P_0" - android:fillAlpha="1" - android:fillColor="#3d90ff" - android:fillType="nonZero" - android:pathData=" M-0.65 -437.37 C-0.65,-437.37 8.33,-437.66 8.33,-437.66 C8.33,-437.66 17.31,-437.95 17.31,-437.95 C17.31,-437.95 26.25,-438.78 26.25,-438.78 C26.25,-438.78 35.16,-439.95 35.16,-439.95 C35.16,-439.95 44.07,-441.11 44.07,-441.11 C44.07,-441.11 52.85,-443 52.85,-443 C52.85,-443 61.6,-445.03 61.6,-445.03 C61.6,-445.03 70.35,-447.09 70.35,-447.09 C70.35,-447.09 78.91,-449.83 78.91,-449.83 C78.91,-449.83 87.43,-452.67 87.43,-452.67 C87.43,-452.67 95.79,-455.97 95.79,-455.97 C95.79,-455.97 104.11,-459.35 104.11,-459.35 C104.11,-459.35 112.36,-462.93 112.36,-462.93 C112.36,-462.93 120.6,-466.51 120.6,-466.51 C120.6,-466.51 128.84,-470.09 128.84,-470.09 C128.84,-470.09 137.09,-473.67 137.09,-473.67 C137.09,-473.67 145.49,-476.84 145.49,-476.84 C145.49,-476.84 153.9,-480.01 153.9,-480.01 C153.9,-480.01 162.31,-483.18 162.31,-483.18 C162.31,-483.18 170.98,-485.54 170.98,-485.54 C170.98,-485.54 179.66,-487.85 179.66,-487.85 C179.66,-487.85 188.35,-490.15 188.35,-490.15 C188.35,-490.15 197.22,-491.58 197.22,-491.58 C197.22,-491.58 206.09,-493.01 206.09,-493.01 C206.09,-493.01 214.98,-494.28 214.98,-494.28 C214.98,-494.28 223.95,-494.81 223.95,-494.81 C223.95,-494.81 232.93,-495.33 232.93,-495.33 C232.93,-495.33 241.9,-495.5 241.9,-495.5 C241.9,-495.5 250.88,-495.13 250.88,-495.13 C250.88,-495.13 259.86,-494.75 259.86,-494.75 C259.86,-494.75 268.78,-493.78 268.78,-493.78 C268.78,-493.78 277.68,-492.52 277.68,-492.52 C277.68,-492.52 286.57,-491.26 286.57,-491.26 C286.57,-491.26 295.31,-489.16 295.31,-489.16 C295.31,-489.16 304.04,-487.04 304.04,-487.04 C304.04,-487.04 312.7,-484.65 312.7,-484.65 C312.7,-484.65 321.19,-481.72 321.19,-481.72 C321.19,-481.72 329.68,-478.78 329.68,-478.78 C329.68,-478.78 337.96,-475.31 337.96,-475.31 C337.96,-475.31 346.14,-471.59 346.14,-471.59 C346.14,-471.59 354.3,-467.82 354.3,-467.82 C354.3,-467.82 362.11,-463.38 362.11,-463.38 C362.11,-463.38 369.92,-458.93 369.92,-458.93 C369.92,-458.93 377.53,-454.17 377.53,-454.17 C377.53,-454.17 384.91,-449.04 384.91,-449.04 C384.91,-449.04 392.29,-443.91 392.29,-443.91 C392.29,-443.91 399.26,-438.24 399.26,-438.24 C399.26,-438.24 406.15,-432.48 406.15,-432.48 C406.15,-432.48 412.92,-426.57 412.92,-426.57 C412.92,-426.57 419.27,-420.22 419.27,-420.22 C419.27,-420.22 425.62,-413.87 425.62,-413.87 C425.62,-413.87 431.61,-407.18 431.61,-407.18 C431.61,-407.18 437.38,-400.29 437.38,-400.29 C437.38,-400.29 443.14,-393.39 443.14,-393.39 C443.14,-393.39 448.27,-386.01 448.27,-386.01 C448.27,-386.01 453.4,-378.64 453.4,-378.64 C453.4,-378.64 458.26,-371.09 458.26,-371.09 C458.26,-371.09 462.71,-363.28 462.71,-363.28 C462.71,-363.28 467.16,-355.47 467.16,-355.47 C467.16,-355.47 471.03,-347.37 471.03,-347.37 C471.03,-347.37 474.75,-339.19 474.75,-339.19 C474.75,-339.19 478.34,-330.95 478.34,-330.95 C478.34,-330.95 481.28,-322.46 481.28,-322.46 C481.28,-322.46 484.21,-313.97 484.21,-313.97 C484.21,-313.97 486.72,-305.35 486.72,-305.35 C486.72,-305.35 488.84,-296.62 488.84,-296.62 C488.84,-296.62 490.96,-287.88 490.96,-287.88 C490.96,-287.88 492.33,-279.01 492.33,-279.01 C492.33,-279.01 493.59,-270.11 493.59,-270.11 C493.59,-270.11 494.69,-261.2 494.69,-261.2 C494.69,-261.2 495.07,-252.22 495.07,-252.22 C495.07,-252.22 495.44,-243.24 495.44,-243.24 C495.44,-243.24 495.41,-234.27 495.41,-234.27 C495.41,-234.27 494.88,-225.29 494.88,-225.29 C494.88,-225.29 494.35,-216.32 494.35,-216.32 C494.35,-216.32 493.22,-207.42 493.22,-207.42 C493.22,-207.42 491.79,-198.55 491.79,-198.55 C491.79,-198.55 490.36,-189.68 490.36,-189.68 C490.36,-189.68 488.19,-180.96 488.19,-180.96 C488.19,-180.96 485.88,-172.28 485.88,-172.28 C485.88,-172.28 483.56,-163.6 483.56,-163.6 C483.56,-163.6 480.48,-155.16 480.48,-155.16 C480.48,-155.16 477.31,-146.75 477.31,-146.75 C477.31,-146.75 474.14,-138.34 474.14,-138.34 C474.14,-138.34 470.62,-130.07 470.62,-130.07 C470.62,-130.07 467.04,-121.83 467.04,-121.83 C467.04,-121.83 463.46,-113.59 463.46,-113.59 C463.46,-113.59 459.88,-105.35 459.88,-105.35 C459.88,-105.35 456.54,-97.01 456.54,-97.01 C456.54,-97.01 453.37,-88.6 453.37,-88.6 C453.37,-88.6 450.21,-80.19 450.21,-80.19 C450.21,-80.19 447.68,-71.57 447.68,-71.57 C447.68,-71.57 445.36,-62.89 445.36,-62.89 C445.36,-62.89 443.04,-54.21 443.04,-54.21 C443.04,-54.21 441.54,-45.35 441.54,-45.35 C441.54,-45.35 440.09,-36.48 440.09,-36.48 C440.09,-36.48 438.78,-27.6 438.78,-27.6 C438.78,-27.6 438.19,-18.63 438.19,-18.63 C438.19,-18.63 437.61,-9.66 437.61,-9.66 C437.61,-9.66 437.36,-0.69 437.36,-0.69 C437.36,-0.69 437.65,8.29 437.65,8.29 C437.65,8.29 437.95,17.27 437.95,17.27 C437.95,17.27 438.77,26.21 438.77,26.21 C438.77,26.21 439.94,35.12 439.94,35.12 C439.94,35.12 441.11,44.03 441.11,44.03 C441.11,44.03 442.99,52.81 442.99,52.81 C442.99,52.81 445.02,61.57 445.02,61.57 C445.02,61.57 447.07,70.31 447.07,70.31 C447.07,70.31 449.82,78.87 449.82,78.87 C449.82,78.87 452.65,87.4 452.65,87.4 C452.65,87.4 455.96,95.75 455.96,95.75 C455.96,95.75 459.33,104.08 459.33,104.08 C459.33,104.08 462.91,112.32 462.91,112.32 C462.91,112.32 466.49,120.57 466.49,120.57 C466.49,120.57 470.07,128.81 470.07,128.81 C470.07,128.81 473.65,137.05 473.65,137.05 C473.65,137.05 476.82,145.46 476.82,145.46 C476.82,145.46 479.99,153.87 479.99,153.87 C479.99,153.87 483.17,162.28 483.17,162.28 C483.17,162.28 485.52,170.94 485.52,170.94 C485.52,170.94 487.84,179.63 487.84,179.63 C487.84,179.63 490.14,188.31 490.14,188.31 C490.14,188.31 491.57,197.18 491.57,197.18 C491.57,197.18 493,206.06 493,206.06 C493,206.06 494.27,214.95 494.27,214.95 C494.27,214.95 494.8,223.92 494.8,223.92 C494.8,223.92 495.33,232.89 495.33,232.89 C495.33,232.89 495.5,241.86 495.5,241.86 C495.5,241.86 495.12,250.84 495.12,250.84 C495.12,250.84 494.75,259.82 494.75,259.82 C494.75,259.82 493.78,268.74 493.78,268.74 C493.78,268.74 492.52,277.64 492.52,277.64 C492.52,277.64 491.27,286.54 491.27,286.54 C491.27,286.54 489.16,295.27 489.16,295.27 C489.16,295.27 487.05,304.01 487.05,304.01 C487.05,304.01 484.66,312.66 484.66,312.66 C484.66,312.66 481.73,321.16 481.73,321.16 C481.73,321.16 478.79,329.65 478.79,329.65 C478.79,329.65 475.32,337.93 475.32,337.93 C475.32,337.93 471.6,346.11 471.6,346.11 C471.6,346.11 467.84,354.27 467.84,354.27 C467.84,354.27 463.39,362.08 463.39,362.08 C463.39,362.08 458.94,369.89 458.94,369.89 C458.94,369.89 454.19,377.5 454.19,377.5 C454.19,377.5 449.06,384.88 449.06,384.88 C449.06,384.88 443.93,392.26 443.93,392.26 C443.93,392.26 438.26,399.23 438.26,399.23 C438.26,399.23 432.5,406.12 432.5,406.12 C432.5,406.12 426.6,412.89 426.6,412.89 C426.6,412.89 420.24,419.24 420.24,419.24 C420.24,419.24 413.89,425.6 413.89,425.6 C413.89,425.6 407.2,431.59 407.2,431.59 C407.2,431.59 400.31,437.36 400.31,437.36 C400.31,437.36 393.42,443.12 393.42,443.12 C393.42,443.12 386.04,448.25 386.04,448.25 C386.04,448.25 378.66,453.38 378.66,453.38 C378.66,453.38 371.11,458.24 371.11,458.24 C371.11,458.24 363.31,462.69 363.31,462.69 C363.31,462.69 355.5,467.14 355.5,467.14 C355.5,467.14 347.4,471.02 347.4,471.02 C347.4,471.02 339.22,474.73 339.22,474.73 C339.22,474.73 330.99,478.33 330.99,478.33 C330.99,478.33 322.49,481.27 322.49,481.27 C322.49,481.27 314,484.2 314,484.2 C314,484.2 305.38,486.71 305.38,486.71 C305.38,486.71 296.65,488.83 296.65,488.83 C296.65,488.83 287.91,490.95 287.91,490.95 C287.91,490.95 279.04,492.33 279.04,492.33 C279.04,492.33 270.14,493.59 270.14,493.59 C270.14,493.59 261.23,494.69 261.23,494.69 C261.23,494.69 252.25,495.07 252.25,495.07 C252.25,495.07 243.28,495.44 243.28,495.44 C243.28,495.44 234.3,495.41 234.3,495.41 C234.3,495.41 225.33,494.88 225.33,494.88 C225.33,494.88 216.36,494.35 216.36,494.35 C216.36,494.35 207.45,493.23 207.45,493.23 C207.45,493.23 198.58,491.8 198.58,491.8 C198.58,491.8 189.71,490.37 189.71,490.37 C189.71,490.37 180.99,488.21 180.99,488.21 C180.99,488.21 172.31,485.89 172.31,485.89 C172.31,485.89 163.63,483.57 163.63,483.57 C163.63,483.57 155.19,480.5 155.19,480.5 C155.19,480.5 146.78,477.32 146.78,477.32 C146.78,477.32 138.37,474.15 138.37,474.15 C138.37,474.15 130.11,470.63 130.11,470.63 C130.11,470.63 121.86,467.06 121.86,467.06 C121.86,467.06 113.62,463.48 113.62,463.48 C113.62,463.48 105.38,459.9 105.38,459.9 C105.38,459.9 97.04,456.56 97.04,456.56 C97.04,456.56 88.63,453.39 88.63,453.39 C88.63,453.39 80.22,450.22 80.22,450.22 C80.22,450.22 71.6,447.7 71.6,447.7 C71.6,447.7 62.92,445.37 62.92,445.37 C62.92,445.37 54.24,443.05 54.24,443.05 C54.24,443.05 45.38,441.55 45.38,441.55 C45.38,441.55 36.52,440.1 36.52,440.1 C36.52,440.1 27.63,438.78 27.63,438.78 C27.63,438.78 18.66,438.2 18.66,438.2 C18.66,438.2 9.7,437.61 9.7,437.61 C9.7,437.61 0.72,437.36 0.72,437.36 C0.72,437.36 -8.26,437.65 -8.26,437.65 C-8.26,437.65 -17.24,437.95 -17.24,437.95 C-17.24,437.95 -26.18,438.77 -26.18,438.77 C-26.18,438.77 -35.09,439.94 -35.09,439.94 C-35.09,439.94 -44,441.1 -44,441.1 C-44,441.1 -52.78,442.98 -52.78,442.98 C-52.78,442.98 -61.53,445.02 -61.53,445.02 C-61.53,445.02 -70.28,447.07 -70.28,447.07 C-70.28,447.07 -78.84,449.81 -78.84,449.81 C-78.84,449.81 -87.37,452.64 -87.37,452.64 C-87.37,452.64 -95.72,455.95 -95.72,455.95 C-95.72,455.95 -104.05,459.32 -104.05,459.32 C-104.05,459.32 -112.29,462.9 -112.29,462.9 C-112.29,462.9 -120.53,466.48 -120.53,466.48 C-120.53,466.48 -128.78,470.06 -128.78,470.06 C-128.78,470.06 -137.02,473.63 -137.02,473.63 C-137.02,473.63 -145.43,476.81 -145.43,476.81 C-145.43,476.81 -153.84,479.98 -153.84,479.98 C-153.84,479.98 -162.24,483.15 -162.24,483.15 C-162.24,483.15 -170.91,485.52 -170.91,485.52 C-170.91,485.52 -179.59,487.83 -179.59,487.83 C-179.59,487.83 -188.28,490.13 -188.28,490.13 C-188.28,490.13 -197.15,491.56 -197.15,491.56 C-197.15,491.56 -206.02,492.99 -206.02,492.99 C-206.02,492.99 -214.91,494.27 -214.91,494.27 C-214.91,494.27 -223.88,494.8 -223.88,494.8 C-223.88,494.8 -232.85,495.33 -232.85,495.33 C-232.85,495.33 -241.83,495.5 -241.83,495.5 C-241.83,495.5 -250.81,495.13 -250.81,495.13 C-250.81,495.13 -259.79,494.75 -259.79,494.75 C-259.79,494.75 -268.71,493.79 -268.71,493.79 C-268.71,493.79 -277.61,492.53 -277.61,492.53 C-277.61,492.53 -286.51,491.27 -286.51,491.27 C-286.51,491.27 -295.24,489.17 -295.24,489.17 C-295.24,489.17 -303.98,487.06 -303.98,487.06 C-303.98,487.06 -312.63,484.67 -312.63,484.67 C-312.63,484.67 -321.12,481.74 -321.12,481.74 C-321.12,481.74 -329.62,478.8 -329.62,478.8 C-329.62,478.8 -337.9,475.33 -337.9,475.33 C-337.9,475.33 -346.08,471.62 -346.08,471.62 C-346.08,471.62 -354.24,467.85 -354.24,467.85 C-354.24,467.85 -362.05,463.41 -362.05,463.41 C-362.05,463.41 -369.86,458.96 -369.86,458.96 C-369.86,458.96 -377.47,454.21 -377.47,454.21 C-377.47,454.21 -384.85,449.08 -384.85,449.08 C-384.85,449.08 -392.23,443.95 -392.23,443.95 C-392.23,443.95 -399.2,438.29 -399.2,438.29 C-399.2,438.29 -406.09,432.52 -406.09,432.52 C-406.09,432.52 -412.86,426.62 -412.86,426.62 C-412.86,426.62 -419.22,420.27 -419.22,420.27 C-419.22,420.27 -425.57,413.91 -425.57,413.91 C-425.57,413.91 -431.57,407.23 -431.57,407.23 C-431.57,407.23 -437.33,400.34 -437.33,400.34 C-437.33,400.34 -443.1,393.44 -443.1,393.44 C-443.1,393.44 -448.23,386.07 -448.23,386.07 C-448.23,386.07 -453.36,378.69 -453.36,378.69 C-453.36,378.69 -458.23,371.15 -458.23,371.15 C-458.23,371.15 -462.67,363.33 -462.67,363.33 C-462.67,363.33 -467.12,355.53 -467.12,355.53 C-467.12,355.53 -471,347.43 -471,347.43 C-471,347.43 -474.72,339.25 -474.72,339.25 C-474.72,339.25 -478.32,331.02 -478.32,331.02 C-478.32,331.02 -481.25,322.52 -481.25,322.52 C-481.25,322.52 -484.19,314.03 -484.19,314.03 C-484.19,314.03 -486.71,305.42 -486.71,305.42 C-486.71,305.42 -488.82,296.68 -488.82,296.68 C-488.82,296.68 -490.94,287.95 -490.94,287.95 C-490.94,287.95 -492.32,279.07 -492.32,279.07 C-492.32,279.07 -493.58,270.18 -493.58,270.18 C-493.58,270.18 -494.69,261.27 -494.69,261.27 C-494.69,261.27 -495.07,252.29 -495.07,252.29 C-495.07,252.29 -495.44,243.31 -495.44,243.31 C-495.44,243.31 -495.42,234.33 -495.42,234.33 C-495.42,234.33 -494.89,225.36 -494.89,225.36 C-494.89,225.36 -494.36,216.39 -494.36,216.39 C-494.36,216.39 -493.23,207.49 -493.23,207.49 C-493.23,207.49 -491.8,198.61 -491.8,198.61 C-491.8,198.61 -490.37,189.74 -490.37,189.74 C-490.37,189.74 -488.22,181.02 -488.22,181.02 C-488.22,181.02 -485.9,172.34 -485.9,172.34 C-485.9,172.34 -483.58,163.66 -483.58,163.66 C-483.58,163.66 -480.51,155.22 -480.51,155.22 C-480.51,155.22 -477.34,146.81 -477.34,146.81 C-477.34,146.81 -474.17,138.41 -474.17,138.41 C-474.17,138.41 -470.65,130.14 -470.65,130.14 C-470.65,130.14 -467.07,121.9 -467.07,121.9 C-467.07,121.9 -463.49,113.65 -463.49,113.65 C-463.49,113.65 -459.91,105.41 -459.91,105.41 C-459.91,105.41 -456.57,97.07 -456.57,97.07 C-456.57,97.07 -453.4,88.66 -453.4,88.66 C-453.4,88.66 -450.23,80.25 -450.23,80.25 C-450.23,80.25 -447.7,71.64 -447.7,71.64 C-447.7,71.64 -445.38,62.96 -445.38,62.96 C-445.38,62.96 -443.06,54.28 -443.06,54.28 C-443.06,54.28 -441.56,45.42 -441.56,45.42 C-441.56,45.42 -440.1,36.55 -440.1,36.55 C-440.1,36.55 -438.78,27.67 -438.78,27.67 C-438.78,27.67 -438.2,18.7 -438.2,18.7 C-438.2,18.7 -437.62,9.73 -437.62,9.73 C-437.62,9.73 -437.36,0.76 -437.36,0.76 C-437.36,0.76 -437.66,-8.22 -437.66,-8.22 C-437.66,-8.22 -437.95,-17.2 -437.95,-17.2 C-437.95,-17.2 -438.77,-26.14 -438.77,-26.14 C-438.77,-26.14 -439.93,-35.05 -439.93,-35.05 C-439.93,-35.05 -441.1,-43.96 -441.1,-43.96 C-441.1,-43.96 -442.98,-52.75 -442.98,-52.75 C-442.98,-52.75 -445.01,-61.5 -445.01,-61.5 C-445.01,-61.5 -447.06,-70.25 -447.06,-70.25 C-447.06,-70.25 -449.8,-78.81 -449.8,-78.81 C-449.8,-78.81 -452.63,-87.33 -452.63,-87.33 C-452.63,-87.33 -455.94,-95.69 -455.94,-95.69 C-455.94,-95.69 -459.31,-104.02 -459.31,-104.02 C-459.31,-104.02 -462.89,-112.26 -462.89,-112.26 C-462.89,-112.26 -466.47,-120.5 -466.47,-120.5 C-466.47,-120.5 -470.05,-128.74 -470.05,-128.74 C-470.05,-128.74 -473.68,-137.12 -473.68,-137.12 C-473.68,-137.12 -476.85,-145.53 -476.85,-145.53 C-476.85,-145.53 -480.03,-153.94 -480.03,-153.94 C-480.03,-153.94 -483.2,-162.34 -483.2,-162.34 C-483.2,-162.34 -485.55,-171.02 -485.55,-171.02 C-485.55,-171.02 -487.86,-179.7 -487.86,-179.7 C-487.86,-179.7 -490.15,-188.39 -490.15,-188.39 C-490.15,-188.39 -491.58,-197.26 -491.58,-197.26 C-491.58,-197.26 -493.01,-206.13 -493.01,-206.13 C-493.01,-206.13 -494.28,-215.02 -494.28,-215.02 C-494.28,-215.02 -494.81,-223.99 -494.81,-223.99 C-494.81,-223.99 -495.33,-232.96 -495.33,-232.96 C-495.33,-232.96 -495.5,-241.94 -495.5,-241.94 C-495.5,-241.94 -495.12,-250.92 -495.12,-250.92 C-495.12,-250.92 -494.75,-259.9 -494.75,-259.9 C-494.75,-259.9 -493.78,-268.82 -493.78,-268.82 C-493.78,-268.82 -492.52,-277.72 -492.52,-277.72 C-492.52,-277.72 -491.26,-286.61 -491.26,-286.61 C-491.26,-286.61 -489.15,-295.35 -489.15,-295.35 C-489.15,-295.35 -487.03,-304.08 -487.03,-304.08 C-487.03,-304.08 -484.64,-312.73 -484.64,-312.73 C-484.64,-312.73 -481.7,-321.23 -481.7,-321.23 C-481.7,-321.23 -478.77,-329.72 -478.77,-329.72 C-478.77,-329.72 -475.29,-338 -475.29,-338 C-475.29,-338 -471.57,-346.18 -471.57,-346.18 C-471.57,-346.18 -467.8,-354.33 -467.8,-354.33 C-467.8,-354.33 -463.36,-362.14 -463.36,-362.14 C-463.36,-362.14 -458.91,-369.95 -458.91,-369.95 C-458.91,-369.95 -454.15,-377.56 -454.15,-377.56 C-454.15,-377.56 -449.02,-384.94 -449.02,-384.94 C-449.02,-384.94 -443.88,-392.32 -443.88,-392.32 C-443.88,-392.32 -438.22,-399.28 -438.22,-399.28 C-438.22,-399.28 -432.45,-406.18 -432.45,-406.18 C-432.45,-406.18 -426.55,-412.94 -426.55,-412.94 C-426.55,-412.94 -420.19,-419.3 -420.19,-419.3 C-420.19,-419.3 -413.84,-425.65 -413.84,-425.65 C-413.84,-425.65 -407.15,-431.64 -407.15,-431.64 C-407.15,-431.64 -400.26,-437.41 -400.26,-437.41 C-400.26,-437.41 -393.36,-443.16 -393.36,-443.16 C-393.36,-443.16 -385.98,-448.29 -385.98,-448.29 C-385.98,-448.29 -378.6,-453.43 -378.6,-453.43 C-378.6,-453.43 -371.05,-458.28 -371.05,-458.28 C-371.05,-458.28 -363.24,-462.73 -363.24,-462.73 C-363.24,-462.73 -355.43,-467.18 -355.43,-467.18 C-355.43,-467.18 -347.33,-471.05 -347.33,-471.05 C-347.33,-471.05 -339.15,-474.76 -339.15,-474.76 C-339.15,-474.76 -330.92,-478.35 -330.92,-478.35 C-330.92,-478.35 -322.42,-481.29 -322.42,-481.29 C-322.42,-481.29 -313.93,-484.23 -313.93,-484.23 C-313.93,-484.23 -305.31,-486.73 -305.31,-486.73 C-305.31,-486.73 -296.58,-488.85 -296.58,-488.85 C-296.58,-488.85 -287.85,-490.97 -287.85,-490.97 C-287.85,-490.97 -278.97,-492.34 -278.97,-492.34 C-278.97,-492.34 -270.07,-493.6 -270.07,-493.6 C-270.07,-493.6 -261.16,-494.7 -261.16,-494.7 C-261.16,-494.7 -252.18,-495.07 -252.18,-495.07 C-252.18,-495.07 -243.2,-495.44 -243.2,-495.44 C-243.2,-495.44 -234.23,-495.41 -234.23,-495.41 C-234.23,-495.41 -225.26,-494.88 -225.26,-494.88 C-225.26,-494.88 -216.29,-494.35 -216.29,-494.35 C-216.29,-494.35 -207.38,-493.22 -207.38,-493.22 C-207.38,-493.22 -198.51,-491.79 -198.51,-491.79 C-198.51,-491.79 -189.64,-490.36 -189.64,-490.36 C-189.64,-490.36 -180.92,-488.19 -180.92,-488.19 C-180.92,-488.19 -172.24,-485.87 -172.24,-485.87 C-172.24,-485.87 -163.56,-483.56 -163.56,-483.56 C-163.56,-483.56 -155.12,-480.47 -155.12,-480.47 C-155.12,-480.47 -146.72,-477.3 -146.72,-477.3 C-146.72,-477.3 -138.31,-474.13 -138.31,-474.13 C-138.31,-474.13 -130.04,-470.61 -130.04,-470.61 C-130.04,-470.61 -121.8,-467.03 -121.8,-467.03 C-121.8,-467.03 -113.55,-463.45 -113.55,-463.45 C-113.55,-463.45 -105.31,-459.87 -105.31,-459.87 C-105.31,-459.87 -96.97,-456.53 -96.97,-456.53 C-96.97,-456.53 -88.56,-453.37 -88.56,-453.37 C-88.56,-453.37 -80.15,-450.2 -80.15,-450.2 C-80.15,-450.2 -71.53,-447.68 -71.53,-447.68 C-71.53,-447.68 -62.85,-445.36 -62.85,-445.36 C-62.85,-445.36 -54.17,-443.04 -54.17,-443.04 C-54.17,-443.04 -45.31,-441.54 -45.31,-441.54 C-45.31,-441.54 -36.44,-440.09 -36.44,-440.09 C-36.44,-440.09 -27.56,-438.78 -27.56,-438.78 C-27.56,-438.78 -18.59,-438.19 -18.59,-438.19 C-18.59,-438.19 -9.62,-437.61 -9.62,-437.61 C-9.62,-437.61 -0.65,-437.37 -0.65,-437.37c " /> - </group> - </group> - <group android:name="time_group" /> - </vector> - </aapt:attr> -</animated-vector>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/touchpad_tutorial_apps_icon.xml b/packages/SystemUI/res/drawable/touchpad_tutorial_apps_icon.xml new file mode 100644 index 000000000000..5f9d4212e440 --- /dev/null +++ b/packages/SystemUI/res/drawable/touchpad_tutorial_apps_icon.xml @@ -0,0 +1,25 @@ +<!-- + ~ Copyright (C) 2025 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. + --> + +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:height="24dp" + android:width="24dp" + android:autoMirrored="true" + android:viewportHeight="960" + android:viewportWidth="960"> + <path android:fillColor="@android:color/white" + android:pathData="M240,800q-33,0 -56.5,-23.5T160,720q0,-33 23.5,-56.5T240,640q33,0 56.5,23.5T320,720q0,33 -23.5,56.5T240,800ZM480,800q-33,0 -56.5,-23.5T400,720q0,-33 23.5,-56.5T480,640q33,0 56.5,23.5T560,720q0,33 -23.5,56.5T480,800ZM720,800q-33,0 -56.5,-23.5T640,720q0,-33 23.5,-56.5T720,640q33,0 56.5,23.5T800,720q0,33 -23.5,56.5T720,800ZM240,560q-33,0 -56.5,-23.5T160,480q0,-33 23.5,-56.5T240,400q33,0 56.5,23.5T320,480q0,33 -23.5,56.5T240,560ZM480,560q-33,0 -56.5,-23.5T400,480q0,-33 23.5,-56.5T480,400q33,0 56.5,23.5T560,480q0,33 -23.5,56.5T480,560ZM720,560q-33,0 -56.5,-23.5T640,480q0,-33 23.5,-56.5T720,400q33,0 56.5,23.5T800,480q0,33 -23.5,56.5T720,560ZM240,320q-33,0 -56.5,-23.5T160,240q0,-33 23.5,-56.5T240,160q33,0 56.5,23.5T320,240q0,33 -23.5,56.5T240,320ZM480,320q-33,0 -56.5,-23.5T400,240q0,-33 23.5,-56.5T480,160q33,0 56.5,23.5T560,240q0,33 -23.5,56.5T480,320ZM720,320q-33,0 -56.5,-23.5T640,240q0,-33 23.5,-56.5T720,160q33,0 56.5,23.5T800,240q0,33 -23.5,56.5T720,320Z"/> +</vector> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index cf975d43293d..f9904e336f24 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -1122,6 +1122,7 @@ <dimen name="smart_reply_button_corner_radius">8dp</dimen> <dimen name="smart_action_button_icon_size">18dp</dimen> <dimen name="smart_action_button_icon_padding">8dp</dimen> + <dimen name="smart_action_button_outline_stroke_width">2dp</dimen> <!-- A reasonable upper bound for the height of the smart reply button. The measuring code needs to start with a guess for the maximum size. Currently two-line smart reply buttons diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index f2c648cb3ab0..414d3f1d17d5 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -3954,6 +3954,8 @@ <string name="touchpad_tutorial_home_gesture_button">Go home</string> <!-- Label for button opening tutorial for "view recent apps" gesture on touchpad [CHAR LIMIT=NONE] --> <string name="touchpad_tutorial_recent_apps_gesture_button">View recent apps</string> + <!-- Label for button opening tutorial for "switch apps" gesture on touchpad [CHAR LIMIT=NONE] --> + <string name="touchpad_tutorial_switch_apps_gesture_button">Switch apps</string> <!-- Label for button finishing touchpad tutorial [CHAR LIMIT=NONE] --> <string name="touchpad_tutorial_done_button">Done</string> <!-- Screen title after gesture was not done correctly [CHAR LIMIT=NONE] --> @@ -3991,6 +3993,17 @@ <string name="touchpad_recent_apps_gesture_success_body">You completed the view recent apps gesture.</string> <!-- Text shown to the user after recent gesture was not done correctly [CHAR LIMIT=NONE] --> <string name="touchpad_recent_gesture_error_body">To view recent apps, swipe up and hold using three fingers on your touchpad</string> + <!-- SWITCH APPS GESTURE --> + <!-- Touchpad switch apps gesture action name in tutorial [CHAR LIMIT=NONE] --> + <string name="touchpad_switch_apps_gesture_action_title">Switch apps</string> + <!-- Touchpad switch apps gesture guidance in gestures tutorial [CHAR LIMIT=NONE] --> + <string name="touchpad_switch_apps_gesture_guidance">Swipe left or right using four fingers on your touchpad</string> + <!-- Screen title after switch apps gesture was done successfully [CHAR LIMIT=NONE] --> + <string name="touchpad_switch_apps_gesture_success_title">Great job!</string> + <!-- Text shown to the user after they complete switch apps gesture tutorial [CHAR LIMIT=NONE] --> + <string name="touchpad_switch_apps_gesture_success_body">You completed the switch apps gesture.</string> + <!-- Text shown to the user after switch gesture was not done correctly [CHAR LIMIT=NONE] --> + <string name="touchpad_switch_gesture_error_body">Swipe left or right using four fingers on your touchpad to switch apps</string> <!-- KEYBOARD TUTORIAL--> <!-- Action key tutorial title [CHAR LIMIT=NONE] --> diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl index 83ca496dbef2..2b71c87bfa27 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl @@ -19,10 +19,11 @@ package com.android.systemui.shared.recents; import android.graphics.Rect; import android.graphics.Region; import android.os.Bundle; +import android.os.IRemoteCallback; import android.view.MotionEvent; import com.android.systemui.shared.recents.ISystemUiProxy; -// Next ID: 34 +// Next ID: 36 oneway interface IOverviewProxy { void onActiveNavBarRegionChanges(in Region activeRegion) = 11; @@ -137,4 +138,10 @@ oneway interface IOverviewProxy { * Sent when {@link TaskbarDelegate#appTransitionPending} is called. */ void appTransitionPending(boolean pending) = 34; + + /** + * Sent right after OverviewProxy calls unbindService() on the TouchInteractionService. + * TouchInteractionService is expected to send the reply once it has finished cleaning up. + */ + void onUnbind(IRemoteCallback reply) = 35; } diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/FullscreenMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/FullscreenMagnificationController.java index caf043a1b1be..b2f3df60c82b 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/FullscreenMagnificationController.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/FullscreenMagnificationController.java @@ -200,7 +200,15 @@ public class FullscreenMagnificationController implements ComponentCallbacks { valueAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(@NonNull Animator animation) { - mHandler.post(() -> setState(ENABLED)); + // This could be called when the animation ends or is canceled. Therefore, we need + // to check the state of fullscreen magnification for the following actions. We only + // update the state to ENABLED when the previous state is ENABLING which implies + // fullscreen magnification is experiencing an ongoing create border process. + mHandler.post(() -> { + if (getState() == ENABLING) { + setState(ENABLED); + } + }); }}); return valueAnimator; } @@ -221,7 +229,14 @@ public class FullscreenMagnificationController implements ComponentCallbacks { valueAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(@NonNull Animator animation) { - mHandler.post(() -> cleanUpBorder()); + // This could be called when the animation ends or is canceled. Therefore, we need + // to check the state of fullscreen magnification for the following actions. Border + // cleanup should only happens after a removal process. + mHandler.post(() -> { + if (getState() == DISABLING) { + cleanUpBorder(); + } + }); }}); return valueAnimator; } @@ -250,6 +265,8 @@ public class FullscreenMagnificationController implements ComponentCallbacks { // If there is an ongoing disable process or it is already disabled, return return; } + // The state should be updated as early as possible so others could check + // the ongoing process. setState(DISABLING); mShowHideBorderAnimator = createHideTargetAnimator(mFullscreenBorder); mShowHideBorderAnimator.start(); @@ -297,10 +314,13 @@ public class FullscreenMagnificationController implements ComponentCallbacks { // If there is an ongoing enable process or it is already enabled, return return; } + // The state should be updated as early as possible so others could check + // the ongoing process. + setState(ENABLING); + if (mShowHideBorderAnimator != null) { mShowHideBorderAnimator.cancel(); } - setState(ENABLING); onConfigurationChanged(mContext.getResources().getConfiguration()); mContext.registerComponentCallbacks(this); diff --git a/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt index 6635d8b06a5d..a061d38d0c18 100644 --- a/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt @@ -215,7 +215,8 @@ constructor( override val isAutoConfirmFeatureEnabled: StateFlow<Boolean> = refreshingFlow( - initialValue = false, + initialValue = + lockPatternUtils.isAutoPinConfirmEnabled(userRepository.getSelectedUserInfo().id), getFreshValue = lockPatternUtils::isAutoPinConfirmEnabled, ) diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/composable/BouncerContainer.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/composable/BouncerContainer.kt index c05dcd5cea83..c59c6816a350 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/composable/BouncerContainer.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/composable/BouncerContainer.kt @@ -41,13 +41,12 @@ fun BouncerContainer( Box { Canvas(Modifier.fillMaxSize()) { drawRect(color = backgroundColor) } - // Separate the bouncer content into a reusable composable that - // doesn't have any SceneScope - // dependencies + // Separate the bouncer content into a reusable composable that doesn't have any + // ContentScope dependencies BouncerContent( bouncerViewModel, dialogFactory, - Modifier.sysuiResTag(Bouncer.TestTags.Root).fillMaxSize() + Modifier.sysuiResTag(Bouncer.TestTags.Root).fillMaxSize(), ) } } diff --git a/packages/SystemUI/src/com/android/systemui/communal/DevicePosturingCommandListener.kt b/packages/SystemUI/src/com/android/systemui/communal/DevicePosturingCommandListener.kt new file mode 100644 index 000000000000..c7b7050340a0 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/communal/DevicePosturingCommandListener.kt @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.communal + +import android.annotation.SuppressLint +import android.app.DreamManager +import com.android.systemui.CoreStartable +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.statusbar.commandline.Command +import com.android.systemui.statusbar.commandline.CommandRegistry +import java.io.PrintWriter +import javax.inject.Inject + +@SysUISingleton +class DevicePosturingCommandListener +@Inject +constructor(private val commandRegistry: CommandRegistry, private val dreamManager: DreamManager) : + CoreStartable { + private val command = DevicePosturingCommand() + + override fun start() { + commandRegistry.registerCommand(COMMAND_ROOT) { command } + } + + internal inner class DevicePosturingCommand : Command { + @SuppressLint("MissingPermission") + override fun execute(pw: PrintWriter, args: List<String>) { + val arg = args.getOrNull(0) + if (arg == null || arg.lowercase() == "help") { + help(pw) + return + } + + when (arg.lowercase()) { + "true" -> dreamManager.setDevicePostured(true) + "false" -> dreamManager.setDevicePostured(false) + else -> { + pw.println("Invalid argument!") + help(pw) + } + } + } + + override fun help(pw: PrintWriter) { + pw.println("Usage: $ adb shell cmd statusbar device-postured <true|false>") + } + } + + private companion object { + const val COMMAND_ROOT = "device-postured" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalStartableModule.kt b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalStartableModule.kt index 2d19b026489c..e3443227685f 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalStartableModule.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalStartableModule.kt @@ -22,6 +22,7 @@ import com.android.systemui.communal.CommunalDreamStartable import com.android.systemui.communal.CommunalMetricsStartable import com.android.systemui.communal.CommunalOngoingContentStartable import com.android.systemui.communal.CommunalSceneStartable +import com.android.systemui.communal.DevicePosturingCommandListener import com.android.systemui.communal.log.CommunalLoggerStartable import com.android.systemui.communal.widgets.CommunalAppWidgetHostStartable import com.android.systemui.dagger.qualifiers.PerUser @@ -67,4 +68,9 @@ interface CommunalStartableModule { @IntoMap @ClassKey(CommunalMetricsStartable::class) fun bindCommunalMetricsStartable(impl: CommunalMetricsStartable): CoreStartable + + @Binds + @IntoMap + @ClassKey(DevicePosturingCommandListener::class) + fun bindDevicePosturingCommandListener(impl: DevicePosturingCommandListener): CoreStartable } diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt index 7d684cab39f7..5e3b2ae0b59f 100644 --- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt @@ -132,8 +132,6 @@ constructor( DeviceEntryRestrictionReason.UnattendedUpdate authFlags.isPrimaryAuthRequiredAfterTimeout -> DeviceEntryRestrictionReason.SecurityTimeout - authFlags.isPrimaryAuthRequiredAfterLockout -> - DeviceEntryRestrictionReason.BouncerLockedOut isFingerprintLockedOut -> DeviceEntryRestrictionReason.StrongBiometricsLockedOut isFaceLockedOut && faceAuthInteractor.isFaceAuthStrong() -> @@ -376,8 +374,7 @@ constructor( private val interactor: DeviceUnlockedInteractor, ) : CoreStartable { override fun start() { - if (!SceneContainerFlag.isEnabled) - return + if (!SceneContainerFlag.isEnabled) return applicationScope.launch { interactor.activate() } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/OWNERS b/packages/SystemUI/src/com/android/systemui/keyguard/OWNERS index 208a17c0a220..ebe603b7428c 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/OWNERS +++ b/packages/SystemUI/src/com/android/systemui/keyguard/OWNERS @@ -2,6 +2,8 @@ set noparent # Bug component: 78010 +include /services/core/java/com/android/server/biometrics/OWNERS + amiko@google.com beverlyt@google.com bhinegardner@google.com diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt index a74384f61469..58692746d1e0 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt @@ -17,6 +17,7 @@ package com.android.systemui.keyguard import android.app.IActivityTaskManager +import android.os.RemoteException import android.util.Log import android.view.IRemoteAnimationFinishedCallback import android.view.RemoteAnimationTarget @@ -174,25 +175,24 @@ constructor( if (!isKeyguardGoingAway) { // Since WM triggered this, we're likely not transitioning to GONE yet. See if we can // start that transition. - val startedDismiss = - keyguardDismissTransitionInteractor.startDismissKeyguardTransition( - reason = "Going away remote animation started" - ) - - if (!startedDismiss) { - // If the transition wasn't started, we're already GONE. This can happen with timing - // issues, where the remote animation took a long time to start, and something else - // caused us to unlock in the meantime. Since we're already GONE, simply end the - // remote animatiom immediately. - Log.d( - TAG, - "onKeyguardGoingAwayRemoteAnimationStart: " + - "Dismiss transition was not started; we're already GONE. " + - "Ending remote animation.", - ) - finishedCallback.onAnimationFinished() - return - } + keyguardDismissTransitionInteractor.startDismissKeyguardTransition( + reason = "Going away remote animation started", + onAlreadyGone = { + // Called if we're already GONE by the time the dismiss transition would have + // started. This can happen due to timing issues, where the remote animation + // took a long time to start, and something else caused us to unlock in the + // meantime. Since we're already GONE, simply end the remote animation + // immediately. + Log.d( + TAG, + "onKeyguardGoingAwayRemoteAnimationStart: " + + "Dismiss transition was not started; we're already GONE. " + + "Ending remote animation.", + ) + finishedCallback.onAnimationFinished() + isKeyguardGoingAway = false + }, + ) isKeyguardGoingAway = true } @@ -266,7 +266,11 @@ constructor( if (enableNewKeyguardShellTransitions) { startKeyguardTransition(lockscreenShowing, aodVisible) } else { - activityTaskManagerService.setLockScreenShown(lockscreenShowing, aodVisible) + try { + activityTaskManagerService.setLockScreenShown(lockscreenShowing, aodVisible) + } catch (e: RemoteException) { + Log.e(TAG, "Remote exception", e) + } } this.isLockscreenShowing = lockscreenShowing this.isAodVisible = aodVisible diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/PrimaryBouncerTransitionModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/PrimaryBouncerTransitionModule.kt index cc070b66917b..d3e2560d6a21 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/PrimaryBouncerTransitionModule.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/PrimaryBouncerTransitionModule.kt @@ -17,6 +17,7 @@ package com.android.systemui.keyguard.dagger import android.content.res.Resources +import com.android.systemui.Flags import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.keyguard.ui.transitions.BlurConfig @@ -34,7 +35,6 @@ import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransition import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToLockscreenTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToOccludedTransitionViewModel import com.android.systemui.res.R -import com.android.systemui.window.flag.WindowBlurFlag import dagger.Binds import dagger.Module import dagger.Provides @@ -56,7 +56,7 @@ interface PrimaryBouncerTransitionModule { fun provideBlurConfig(@Main resources: Resources): BlurConfig { val minBlurRadius = resources.getDimensionPixelSize(R.dimen.min_window_blur_radius) val maxBlurRadius = - if (WindowBlurFlag.isEnabled) { + if (Flags.notificationShadeBlur() || Flags.bouncerUiRevamp()) { resources.getDimensionPixelSize(R.dimen.max_shade_window_blur_radius) } else { resources.getDimensionPixelSize(R.dimen.max_window_blur_radius) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissTransitionInteractor.kt index 089e5dc42df3..c0a486c005ab 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissTransitionInteractor.kt @@ -16,23 +16,31 @@ package com.android.systemui.keyguard.domain.interactor +import android.animation.ValueAnimator import android.util.Log import com.android.systemui.Flags.transitionRaceCondition import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER import com.android.systemui.keyguard.shared.model.KeyguardState.AOD import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN +import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER +import com.android.systemui.keyguard.shared.model.TransitionInfo +import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled import com.android.systemui.scene.shared.flag.SceneContainerFlag import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch @SysUISingleton class KeyguardDismissTransitionInteractor @Inject constructor( + @Background private val scope: CoroutineScope, private val repository: KeyguardTransitionRepository, private val fromLockscreenTransitionInteractor: FromLockscreenTransitionInteractor, private val fromPrimaryBouncerTransitionInteractor: FromPrimaryBouncerTransitionInteractor, @@ -43,45 +51,63 @@ constructor( ) { /** - * Called to start a transition that will ultimately dismiss the keyguard from the current - * state. + * Launches a coroutine to start a transition that will ultimately dismiss the keyguard from the + * current state. * * This is called exclusively by sources that can authoritatively say we should be unlocked, * including KeyguardSecurityContainerController and WindowManager. * - * Returns [false] if the transition was not started, because we're already GONE or we don't - * know how to dismiss keyguard from the current state. + * This is one of the few transitions that is started outside of the From*TransitionInteractor + * classes. This is because this is an external call that must be respected, so it doesn't + * matter what state we're in/coming from - we must transition from that state to GONE. + * + * Invokes [onAlreadyGone] if the transition was not started because we're already GONE by the + * time the coroutine runs. */ - fun startDismissKeyguardTransition(reason: String = ""): Boolean { - if (SceneContainerFlag.isEnabled) return false + @JvmOverloads + fun startDismissKeyguardTransition(reason: String = "", onAlreadyGone: (() -> Unit)? = null) { + if (SceneContainerFlag.isEnabled) return Log.d(TAG, "#startDismissKeyguardTransition(reason=$reason)") - val startedState = - if (transitionRaceCondition()) { - repository.currentTransitionInfo.to + + scope.launch { + val startedState = + if (transitionRaceCondition()) { + repository.currentTransitionInfo.to + } else { + repository.currentTransitionInfoInternal.value.to + } + + val animator: ValueAnimator? = + when (startedState) { + LOCKSCREEN -> fromLockscreenTransitionInteractor + PRIMARY_BOUNCER -> fromPrimaryBouncerTransitionInteractor + ALTERNATE_BOUNCER -> fromAlternateBouncerTransitionInteractor + AOD -> fromAodTransitionInteractor + DOZING -> fromDozingTransitionInteractor + OCCLUDED -> fromOccludedTransitionInteractor + else -> null + }?.getDefaultAnimatorForTransitionsToState(KeyguardState.GONE) + + if (startedState != KeyguardState.GONE && animator != null) { + repository.startTransition( + TransitionInfo( + "KeyguardDismissTransitionInteractor" + + if (reason.isNotBlank()) "($reason)" else "", + startedState, + KeyguardState.GONE, + animator, + TransitionModeOnCanceled.LAST_VALUE, + ) + ) } else { - repository.currentTransitionInfoInternal.value.to - } - when (startedState) { - LOCKSCREEN -> fromLockscreenTransitionInteractor.dismissKeyguard() - PRIMARY_BOUNCER -> fromPrimaryBouncerTransitionInteractor.dismissPrimaryBouncer() - ALTERNATE_BOUNCER -> fromAlternateBouncerTransitionInteractor.dismissAlternateBouncer() - AOD -> fromAodTransitionInteractor.dismissAod() - DOZING -> fromDozingTransitionInteractor.dismissFromDozing() - KeyguardState.OCCLUDED -> fromOccludedTransitionInteractor.dismissFromOccluded() - KeyguardState.GONE -> { Log.i( TAG, - "Already transitioning to GONE; ignoring startDismissKeyguardTransition.", + "Can't transition to GONE from $startedState; " + + "ignoring startDismissKeyguardTransition.", ) - return false - } - else -> { - Log.e(TAG, "We don't know how to dismiss keyguard from state $startedState.") - return false + onAlreadyGone?.invoke() } } - - return true } companion object { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt index 213083db71c9..9c886b228ca9 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt @@ -31,6 +31,7 @@ import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.res.R import com.android.systemui.shared.R as sharedR import kotlinx.coroutines.DisposableHandle +import kotlinx.coroutines.flow.combine object KeyguardSmartspaceViewBinder { @JvmStatic @@ -43,21 +44,25 @@ object KeyguardSmartspaceViewBinder { return keyguardRootView.repeatWhenAttached { repeatOnLifecycle(Lifecycle.State.CREATED) { launch("$TAG#clockViewModel.hasCustomWeatherDataDisplay") { - clockViewModel.hasCustomWeatherDataDisplay.collect { hasCustomWeatherDataDisplay - -> - updateDateWeatherToBurnInLayer( - keyguardRootView, - clockViewModel, - smartspaceViewModel, + combine( + smartspaceViewModel.isWeatherVisible, + clockViewModel.hasCustomWeatherDataDisplay, + ::Pair, ) - blueprintInteractor.refreshBlueprint( - Config( - Type.SmartspaceVisibility, - checkPriority = false, - terminatePrevious = false, + .collect { + updateDateWeatherToBurnInLayer( + keyguardRootView, + clockViewModel, + smartspaceViewModel, ) - ) - } + blueprintInteractor.refreshBlueprint( + Config( + Type.SmartspaceVisibility, + checkPriority = false, + terminatePrevious = false, + ) + ) + } } launch("$TAG#smartspaceViewModel.bcSmartspaceVisibility") { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt index cd038d799f42..9319bc890c6c 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt @@ -23,6 +23,8 @@ import android.view.ViewTreeObserver.OnGlobalLayoutListener import androidx.constraintlayout.widget.Barrier import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintSet +import androidx.constraintlayout.widget.ConstraintSet.GONE +import androidx.constraintlayout.widget.ConstraintSet.VISIBLE import com.android.systemui.customization.R as customR import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.KeyguardUnlockAnimationController @@ -195,24 +197,13 @@ constructor( smartspaceController.requestSmartspaceUpdate() constraintSet.apply { - val weatherVisibility = - when (keyguardSmartspaceViewModel.isWeatherVisible.value) { - true -> ConstraintSet.VISIBLE - false -> ConstraintSet.GONE - } - setVisibility(sharedR.id.weather_smartspace_view, weatherVisibility) - setAlpha( - sharedR.id.weather_smartspace_view, - if (weatherVisibility == View.VISIBLE) 1f else 0f, - ) - val dateVisibility = - if (keyguardClockViewModel.hasCustomWeatherDataDisplay.value) ConstraintSet.GONE - else ConstraintSet.VISIBLE - setVisibility(sharedR.id.date_smartspace_view, dateVisibility) - setAlpha( - sharedR.id.date_smartspace_view, - if (dateVisibility == ConstraintSet.VISIBLE) 1f else 0f, - ) + val showWeather = keyguardSmartspaceViewModel.isWeatherVisible.value + setVisibility(sharedR.id.weather_smartspace_view, if (showWeather) VISIBLE else GONE) + setAlpha(sharedR.id.weather_smartspace_view, if (showWeather) 1f else 0f) + + val showDateView = !keyguardClockViewModel.hasCustomWeatherDataDisplay.value + setVisibility(sharedR.id.date_smartspace_view, if (showDateView) VISIBLE else GONE) + setAlpha(sharedR.id.date_smartspace_view, if (showDateView) 1f else 0f) } } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModel.kt index 92bb5e6029cb..733d7d71061e 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModel.kt @@ -30,7 +30,6 @@ import com.android.systemui.keyguard.ui.transitions.PrimaryBouncerTransition import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.scene.ui.composable.transitions.TO_BOUNCER_FADE_FRACTION -import com.android.systemui.window.flag.WindowBlurFlag import javax.inject.Inject import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow @@ -73,7 +72,7 @@ constructor( onStep = alphaForAnimationStep, ) - val lockscreenAlpha: Flow<Float> = if (WindowBlurFlag.isEnabled) alphaFlow else emptyFlow() + val lockscreenAlpha: Flow<Float> = if (Flags.bouncerUiRevamp()) alphaFlow else emptyFlow() val notificationAlpha: Flow<Float> = if (Flags.bouncerUiRevamp()) { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModel.kt index 5ee80a7b7442..f8425c16c341 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModel.kt @@ -77,7 +77,7 @@ constructor( isWeatherVisible( clockIncludesCustomWeatherDisplay = keyguardClockViewModel.hasCustomWeatherDataDisplay.value, - isWeatherEnabled = smartspaceInteractor.isWeatherEnabled.value, + isWeatherEnabled = isWeatherEnabled.value, ), ) diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/Media3ActionFactory.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/Media3ActionFactory.kt index a6b9442b1270..71c8d1f5b4c7 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/Media3ActionFactory.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/Media3ActionFactory.kt @@ -130,7 +130,7 @@ constructor( null, // no action to perform when clicked context.getString(R.string.controls_media_button_connecting), if (Flags.mediaControlsUiUpdate()) { - context.getDrawable(R.drawable.ic_media_connecting_status_container) + context.getDrawable(R.drawable.ic_media_connecting_button_container) } else { context.getDrawable(R.drawable.ic_media_connecting_container) }, diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaActions.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaActions.kt index 9bf556cf07c2..5fef81f4596a 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaActions.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaActions.kt @@ -71,7 +71,7 @@ fun createActionsFromState( null, // no action to perform when clicked context.getString(R.string.controls_media_button_connecting), if (Flags.mediaControlsUiUpdate()) { - context.getDrawable(R.drawable.ic_media_connecting_status_container) + context.getDrawable(R.drawable.ic_media_connecting_button_container) } else { context.getDrawable(R.drawable.ic_media_connecting_container) }, 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 65fba28c5465..b53685ee3cd4 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt @@ -88,11 +88,11 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle import com.android.compose.animation.scene.ContentKey +import com.android.compose.animation.scene.ContentScope import com.android.compose.animation.scene.ElementKey import com.android.compose.animation.scene.ElementMatcher import com.android.compose.animation.scene.MutableSceneTransitionLayoutState import com.android.compose.animation.scene.SceneKey -import com.android.compose.animation.scene.SceneScope import com.android.compose.animation.scene.SceneTransitionLayout import com.android.compose.animation.scene.content.state.TransitionState import com.android.compose.animation.scene.transitions @@ -580,7 +580,7 @@ constructor( } @Composable - private fun SceneScope.QuickQuickSettingsElement() { + private fun ContentScope.QuickQuickSettingsElement() { val qqsPadding = viewModel.qqsHeaderHeight val bottomPadding = viewModel.qqsBottomPadding DisposableEffect(Unit) { @@ -664,7 +664,7 @@ constructor( } @Composable - private fun SceneScope.QuickSettingsElement() { + private fun ContentScope.QuickSettingsElement() { val qqsPadding = viewModel.qqsHeaderHeight val qsExtraPadding = dimensionResource(R.dimen.qs_panel_padding_top) Column( diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/GridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/GridLayout.kt index a22eb3a8d517..185ea93387a3 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/GridLayout.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/GridLayout.kt @@ -18,7 +18,7 @@ package com.android.systemui.qs.panels.ui.compose import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import com.android.compose.animation.scene.SceneScope +import com.android.compose.animation.scene.ContentScope import com.android.systemui.qs.panels.shared.model.SizedTile import com.android.systemui.qs.panels.shared.model.TileRow import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel @@ -27,7 +27,7 @@ import com.android.systemui.qs.pipeline.shared.TileSpec /** A layout of tiles, indicating how they should be composed when showing in QS or in edit mode. */ interface GridLayout { - @Composable fun SceneScope.TileGrid(tiles: List<TileViewModel>, modifier: Modifier) + @Composable fun ContentScope.TileGrid(tiles: List<TileViewModel>, modifier: Modifier) @Composable fun EditTileGrid( diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt index 39408d3dee72..c72381f45239 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt @@ -40,7 +40,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.input.pointer.pointerInteropFilter import androidx.compose.ui.unit.dp -import com.android.compose.animation.scene.SceneScope +import com.android.compose.animation.scene.ContentScope import com.android.compose.modifiers.padding import com.android.systemui.compose.modifiers.sysuiResTag import com.android.systemui.development.ui.compose.BuildNumber @@ -63,7 +63,7 @@ constructor( @PaginatedBaseLayoutType private val delegateGridLayout: PaginatableGridLayout, ) : GridLayout by delegateGridLayout { @Composable - override fun SceneScope.TileGrid(tiles: List<TileViewModel>, modifier: Modifier) { + override fun ContentScope.TileGrid(tiles: List<TileViewModel>, modifier: Modifier) { val viewModel = rememberViewModel(traceName = "PaginatedGridLayout-TileGrid") { viewModelFactory.create() diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt index 8fda23d54625..5cb30b999e13 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt @@ -27,7 +27,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.util.fastMap import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.android.compose.animation.scene.SceneScope +import com.android.compose.animation.scene.ContentScope import com.android.systemui.compose.modifiers.sysuiResTag import com.android.systemui.grid.ui.compose.VerticalSpannedGrid import com.android.systemui.qs.composefragment.ui.GridAnchor @@ -38,7 +38,7 @@ import com.android.systemui.qs.shared.ui.ElementKeys.toElementKey import com.android.systemui.res.R @Composable -fun SceneScope.QuickQuickSettings( +fun ContentScope.QuickQuickSettings( viewModel: QuickQuickSettingsViewModel, modifier: Modifier = Modifier, ) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/TileGrid.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/TileGrid.kt index 6c1906bb906f..bcc44d397eb2 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/TileGrid.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/TileGrid.kt @@ -20,11 +20,11 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.android.compose.animation.scene.SceneScope +import com.android.compose.animation.scene.ContentScope import com.android.systemui.qs.panels.ui.viewmodel.TileGridViewModel @Composable -fun SceneScope.TileGrid(viewModel: TileGridViewModel, modifier: Modifier = Modifier) { +fun ContentScope.TileGrid(viewModel: TileGridViewModel, modifier: Modifier = Modifier) { val gridLayout by viewModel.gridLayout.collectAsStateWithLifecycle() val tiles by viewModel.tileViewModels.collectAsStateWithLifecycle(emptyList()) diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt index 66961b6efe46..4432d336237f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt @@ -26,7 +26,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.util.fastMap import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.android.compose.animation.scene.SceneScope +import com.android.compose.animation.scene.ContentScope import com.android.systemui.dagger.SysUISingleton import com.android.systemui.grid.ui.compose.VerticalSpannedGrid import com.android.systemui.haptics.msdl.qs.TileHapticsViewModelFactoryProvider @@ -58,7 +58,7 @@ constructor( ) : PaginatableGridLayout { @Composable - override fun SceneScope.TileGrid(tiles: List<TileViewModel>, modifier: Modifier) { + override fun ContentScope.TileGrid(tiles: List<TileViewModel>, modifier: Modifier) { DisposableEffect(tiles) { val token = Any() tiles.forEach { it.startListening(token) } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java index e93cec875429..42b35c736d42 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java @@ -191,8 +191,9 @@ public class ScreenRecordTile extends QSTileImpl<QSTile.BooleanState> @Override public boolean getDetailsViewModel(Consumer<TileDetailsViewModel> callback) { - handleClick(() -> - callback.accept(new ScreenRecordDetailsViewModel()) + handleClick(() -> executeWhenUnlockedKeyguard( + () -> callback.accept(new ScreenRecordDetailsViewModel(mController, + this::onStartRecordingClicked))) ); return true; } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDetailsContentController.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDetailsContentController.java index 340cb68a83a4..6b5a22a4fc09 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDetailsContentController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDetailsContentController.java @@ -1506,15 +1506,17 @@ public class InternetDetailsContentController implements AccessPointController.A Intent getConfiguratorQrCodeGeneratorIntentOrNull(WifiEntry wifiEntry) { if (!mFeatureFlags.isEnabled(Flags.SHARE_WIFI_QS_BUTTON) || wifiEntry == null - || mWifiManager == null || !wifiEntry.canShare() - || wifiEntry.getWifiConfiguration() == null) { + || mWifiManager == null || !wifiEntry.canShare()) { + return null; + } + var wifiConfiguration = wifiEntry.getWifiConfiguration(); + if (wifiConfiguration == null) { return null; } Intent intent = new Intent(); intent.setAction(WifiDppIntentHelper.ACTION_CONFIGURATOR_AUTH_QR_CODE_GENERATOR); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); - WifiDppIntentHelper.setConfiguratorIntentExtra(intent, mWifiManager, - wifiEntry.getWifiConfiguration()); + WifiDppIntentHelper.setConfiguratorIntentExtra(intent, mWifiManager, wifiConfiguration); return intent; } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/ScreenRecordDetailsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/ScreenRecordDetailsViewModel.kt index 42cb1248ccff..54e4a521c239 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/ScreenRecordDetailsViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/ScreenRecordDetailsViewModel.kt @@ -17,27 +17,45 @@ package com.android.systemui.qs.tiles.dialog import android.view.LayoutInflater +import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.heightIn import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.dp import androidx.compose.ui.viewinterop.AndroidView import com.android.systemui.plugins.qs.TileDetailsViewModel import com.android.systemui.res.R +import com.android.systemui.screenrecord.RecordingController +import com.android.systemui.screenrecord.ScreenRecordPermissionViewBinder /** The view model used for the screen record details view in the Quick Settings */ -class ScreenRecordDetailsViewModel() : TileDetailsViewModel() { +class ScreenRecordDetailsViewModel( + private val recordingController: RecordingController, + private val onStartRecordingClicked: Runnable, +) : TileDetailsViewModel() { + + private var viewBinder: ScreenRecordPermissionViewBinder = + recordingController.createScreenRecordPermissionViewBinder(onStartRecordingClicked) + @Composable override fun GetContentView() { // TODO(b/378514312): Finish implementing this function. + + if (recordingController.isScreenCaptureDisabled) { + // TODO(b/388345506): Show disabled page here. + return + } + AndroidView( - modifier = Modifier.fillMaxWidth().heightIn(max = VIEW_MAX_HEIGHT), + modifier = Modifier.fillMaxWidth().fillMaxHeight(), factory = { context -> // Inflate with the existing dialog xml layout - LayoutInflater.from(context).inflate(R.layout.screen_share_dialog, null) + val view = LayoutInflater.from(context).inflate(R.layout.screen_share_dialog, null) + viewBinder.bind(view) + + view + // TODO(b/378514473): Revamp the details view according to the spec. }, + onRelease = { viewBinder.unbind() }, ) } @@ -54,8 +72,4 @@ class ScreenRecordDetailsViewModel() : TileDetailsViewModel() { // No sub-title in this tile. return "" } - - companion object { - private val VIEW_MAX_HEIGHT: Dp = 320.dp - } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractor.kt index 594394f68d48..5ce7f0d039c8 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractor.kt @@ -29,6 +29,7 @@ import com.android.systemui.qs.tiles.impl.modes.domain.model.ModesTileModel import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction import com.android.systemui.statusbar.policy.domain.interactor.ZenModeInteractor import com.android.systemui.statusbar.policy.ui.dialog.ModesDialogDelegate +import com.android.systemui.statusbar.policy.ui.dialog.ModesDialogEventLogger import javax.inject.Inject @SysUISingleton @@ -39,6 +40,7 @@ constructor( // TODO(b/353896370): The domain layer should not have to depend on the UI layer. private val dialogDelegate: ModesDialogDelegate, private val zenModeInteractor: ZenModeInteractor, + private val dialogEventLogger: ModesDialogEventLogger, ) : QSTileUserActionInteractor<ModesTileModel> { val longClickIntent = Intent(Settings.ACTION_ZEN_MODE_SETTINGS) @@ -78,7 +80,16 @@ constructor( Log.wtf(TAG, "Triggered DND but it's null!?") return } - zenModeInteractor.activateMode(dnd) + + if (zenModeInteractor.shouldAskForZenDuration(dnd)) { + dialogEventLogger.logOpenDurationDialog(dnd) + // NOTE: The dialog handles turning on the mode itself. + val dialog = dialogDelegate.makeDndDurationDialog() + dialog.show() + } else { + dialogEventLogger.logModeOn(dnd) + zenModeInteractor.activateMode(dnd) + } } else { zenModeInteractor.deactivateAllModes() } diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java index e3cf41191384..adf9eb44e162 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java +++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java @@ -58,6 +58,7 @@ import android.os.Binder; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; +import android.os.IRemoteCallback; import android.os.Looper; import android.os.PatternMatcher; import android.os.Process; @@ -146,7 +147,6 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis public static final String TAG_OPS = "OverviewProxyService"; private static final long BACKOFF_MILLIS = 1000; private static final long DEFERRED_CALLBACK_MILLIS = 5000; - // Max backoff caps at 5 mins private static final long MAX_BACKOFF_MILLIS = 10 * 60 * 1000; @@ -183,6 +183,10 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis private int mConnectionBackoffAttempts; private boolean mBound; private boolean mIsEnabled; + // This is set to false when the overview service is requested to be bound until it is notified + // that the previous service has been cleaned up in IOverviewProxy#onUnbind(). It is also set to + // true after a 1000ms timeout by mDeferredBindAfterTimedOutCleanup. + private boolean mIsPrevServiceCleanedUp = true; private boolean mIsSystemOrVisibleBgUser; private int mCurrentBoundedUserId = -1; @@ -489,6 +493,12 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis retryConnectionWithBackoff(); }; + private final Runnable mDeferredBindAfterTimedOutCleanup = () -> { + Log.w(TAG_OPS, "Timed out waiting for previous service to clean up, binding to new one"); + mIsPrevServiceCleanedUp = true; + maybeBindService(); + }; + private final BroadcastReceiver mUserEventReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { @@ -859,6 +869,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis mShadeViewControllerLazy.get().cancelInputFocusTransfer(); }); } + mIsPrevServiceCleanedUp = true; startConnectionToCurrentUser(); } @@ -889,6 +900,19 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis } mHandler.removeCallbacks(mConnectionRunnable); + maybeBindService(); + } + + private void maybeBindService() { + if (!mIsPrevServiceCleanedUp) { + Log.w(TAG_OPS, "Skipping connection to TouchInteractionService until previous" + + " instance is cleaned up."); + if (!mHandler.hasCallbacks(mDeferredConnectionCallback)) { + mHandler.postDelayed(mDeferredBindAfterTimedOutCleanup, BACKOFF_MILLIS); + } + return; + } + // Avoid creating TouchInteractionService because the System user in HSUM mode does not // interact with UI elements UserHandle currentUser = UserHandle.of(mUserTracker.getUserId()); @@ -907,6 +931,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis Log.e(TAG_OPS, "Unable to bind because of security error", e); } if (mBound) { + mIsPrevServiceCleanedUp = false; // Ensure that connection has been established even if it thinks it is bound mHandler.postDelayed(mDeferredConnectionCallback, DEFERRED_CALLBACK_MILLIS); } else { @@ -960,6 +985,24 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis // Always unbind the service (ie. if called through onNullBinding or onBindingDied) mContext.unbindService(mOverviewServiceConnection); mBound = false; + if (mOverviewProxy != null) { + try { + mOverviewProxy.onUnbind(new IRemoteCallback.Stub() { + @Override + public void sendResult(Bundle data) throws RemoteException { + // Received Launcher reply, try to bind anew. + mIsPrevServiceCleanedUp = true; + if (mHandler.hasCallbacks(mDeferredBindAfterTimedOutCleanup)) { + mHandler.removeCallbacks(mDeferredBindAfterTimedOutCleanup); + maybeBindService(); + } + } + }); + } catch (RemoteException e) { + Log.w(TAG_OPS, "disconnectFromLauncherService failed to notify Launcher"); + mIsPrevServiceCleanedUp = true; + } + } } if (mOverviewProxy != null) { @@ -1189,6 +1232,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis pw.print(" mInputFocusTransferStartMillis="); pw.println(mInputFocusTransferStartMillis); pw.print(" mActiveNavBarRegion="); pw.println(mActiveNavBarRegion); pw.print(" mNavBarMode="); pw.println(mNavBarMode); + pw.print(" mIsPrevServiceCleanedUp="); pw.println(mIsPrevServiceCleanedUp); mSysUiState.dump(pw, args); } diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java index 9ee99e45ceeb..140fbf340f8b 100644 --- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java +++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java @@ -70,6 +70,8 @@ public class RecordingController private final ScreenCaptureDisabledDialogDelegate mScreenCaptureDisabledDialogDelegate; private final ScreenRecordPermissionDialogDelegate.Factory mScreenRecordPermissionDialogDelegateFactory; + private final ScreenRecordPermissionViewBinder.Factory + mScreenRecordPermissionViewBinderFactory; protected static final String INTENT_UPDATE_STATE = "com.android.systemui.screenrecord.UPDATE_STATE"; @@ -118,7 +120,8 @@ public class RecordingController MediaProjectionMetricsLogger mediaProjectionMetricsLogger, ScreenCaptureDisabledDialogDelegate screenCaptureDisabledDialogDelegate, ScreenRecordPermissionDialogDelegate.Factory - screenRecordPermissionDialogDelegateFactory) { + screenRecordPermissionDialogDelegateFactory, + ScreenRecordPermissionViewBinder.Factory screenRecordPermissionViewBinderFactory) { mMainExecutor = mainExecutor; mDevicePolicyResolver = devicePolicyResolver; mBroadcastDispatcher = broadcastDispatcher; @@ -127,6 +130,7 @@ public class RecordingController mMediaProjectionMetricsLogger = mediaProjectionMetricsLogger; mScreenCaptureDisabledDialogDelegate = screenCaptureDisabledDialogDelegate; mScreenRecordPermissionDialogDelegateFactory = screenRecordPermissionDialogDelegateFactory; + mScreenRecordPermissionViewBinderFactory = screenRecordPermissionViewBinderFactory; BroadcastOptions options = BroadcastOptions.makeBasic(); options.setInteractive(true); @@ -151,8 +155,7 @@ public class RecordingController * If screen capturing is currently not allowed it will return a dialog * that warns users about it. */ public Dialog createScreenRecordDialog(@Nullable Runnable onStartRecordingClicked) { - if (mDevicePolicyResolver.get() - .isScreenCaptureCompletelyDisabled(getHostUserHandle())) { + if (isScreenCaptureDisabled()) { return mScreenCaptureDisabledDialogDelegate.createSysUIDialog(); } @@ -165,6 +168,27 @@ public class RecordingController } /** + * Create a view binder that controls the logic of views inside the screen record permission + * view. + * @param onStartRecordingClicked the callback that is run when the start button is clicked. + */ + public ScreenRecordPermissionViewBinder createScreenRecordPermissionViewBinder( + @Nullable Runnable onStartRecordingClicked + ) { + return mScreenRecordPermissionViewBinderFactory + .create(getHostUserHandle(), getHostUid(), this, + onStartRecordingClicked); + } + + /** + * Check if screen capture is currently disabled for this device and user. + */ + public boolean isScreenCaptureDisabled() { + return mDevicePolicyResolver.get() + .isScreenCaptureCompletelyDisabled(getHostUserHandle()); + } + + /** * Start counting down in preparation to start a recording * @param ms Total time in ms to wait before starting * @param interval Time in ms per countdown step diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionViewBinder.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionViewBinder.kt index 9fcb3dfc0ad3..23df1c5441bf 100644 --- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionViewBinder.kt @@ -49,6 +49,9 @@ import com.android.systemui.mediaprojection.permission.ScreenShareOption import com.android.systemui.plugins.ActivityStarter import com.android.systemui.res.R import com.android.systemui.settings.UserContextProvider +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject class ScreenRecordPermissionViewBinder( private val hostUserHandle: UserHandle, @@ -68,6 +71,38 @@ class ScreenRecordPermissionViewBinder( mediaProjectionMetricsLogger, defaultSelectedMode, ) { + @AssistedInject + constructor( + @Assisted hostUserHandle: UserHandle, + @Assisted hostUid: Int, + mediaProjectionMetricsLogger: MediaProjectionMetricsLogger, + displayManager: DisplayManager, + @Assisted controller: RecordingController, + activityStarter: ActivityStarter, + userContextProvider: UserContextProvider, + @Assisted onStartRecordingClicked: Runnable?, + ) : this( + hostUserHandle, + hostUid, + mediaProjectionMetricsLogger, + defaultSelectedMode = SINGLE_APP, + displayManager, + controller, + activityStarter, + userContextProvider, + onStartRecordingClicked, + ) + + @AssistedFactory + interface Factory { + fun create( + hostUserHandle: UserHandle, + hostUid: Int, + recordingController: RecordingController, + onStartRecordingClicked: Runnable?, + ): ScreenRecordPermissionViewBinder + } + private lateinit var tapsSwitch: Switch private lateinit var audioSwitch: Switch private lateinit var tapsView: View diff --git a/packages/SystemUI/src/com/android/systemui/scrim/ScrimDrawable.java b/packages/SystemUI/src/com/android/systemui/scrim/ScrimDrawable.java index 0650f8606ba9..9a1ffcbab8d1 100644 --- a/packages/SystemUI/src/com/android/systemui/scrim/ScrimDrawable.java +++ b/packages/SystemUI/src/com/android/systemui/scrim/ScrimDrawable.java @@ -35,7 +35,6 @@ import android.view.animation.DecelerateInterpolator; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.graphics.ColorUtils; import com.android.systemui.statusbar.notification.stack.StackStateAnimator; -import com.android.systemui.window.flag.WindowBlurFlag; /** * Drawable used on SysUI scrims. @@ -214,10 +213,6 @@ public class ScrimDrawable extends Drawable { public void draw(@NonNull Canvas canvas) { mPaint.setColor(mMainColor); mPaint.setAlpha(mAlpha); - if (WindowBlurFlag.isEnabled()) { - // TODO (b/381263600), wire this at ScrimController, move it to PrimaryBouncerTransition - mPaint.setAlpha((int) (0.5f * mAlpha)); - } if (mConcaveInfo != null) { drawConcave(canvas); } else if (mCornerRadiusEnabled && mCornerRadius > 0) { diff --git a/packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java b/packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java index 03a8d17847f9..49f3cfc4ceaf 100644 --- a/packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java +++ b/packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java @@ -39,10 +39,8 @@ import androidx.core.graphics.ColorUtils; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.colorextraction.ColorExtractor; -import com.android.systemui.res.R; import com.android.systemui.shade.TouchLogger; import com.android.systemui.util.LargeScreenUtils; -import com.android.systemui.window.flag.WindowBlurFlag; import java.util.concurrent.Executor; @@ -252,13 +250,6 @@ public class ScrimView extends View { if (mBlendWithMainColor) { mainTinted = ColorUtils.blendARGB(mColors.getMainColor(), mTintColor, tintAmount); } - if (WindowBlurFlag.isEnabled()) { - int layerAbove = ColorUtils.setAlphaComponent( - getResources().getColor(R.color.shade_panel, null), - (int) (0.4f * 255)); - int layerBelow = ColorUtils.setAlphaComponent(Color.WHITE, (int) (0.1f * 255)); - mainTinted = ColorUtils.compositeColors(layerAbove, layerBelow); - } drawable.setColor(mainTinted, animated); } else { boolean hasAlpha = Color.alpha(mTintColor) != 0; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt index 38f7c39203f0..ca2fbdd1cdd9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt @@ -34,6 +34,7 @@ import androidx.dynamicanimation.animation.SpringForce import com.android.app.animation.Interpolators import com.android.app.tracing.coroutines.TrackTracer import com.android.systemui.Dumpable +import com.android.systemui.Flags import com.android.systemui.Flags.spatialModelAppPushback import com.android.systemui.animation.ShadeInterpolation import com.android.systemui.dagger.SysUISingleton @@ -52,8 +53,8 @@ import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.statusbar.policy.SplitShadeStateController import com.android.systemui.util.WallpaperController import com.android.systemui.window.domain.interactor.WindowRootViewBlurInteractor -import com.android.systemui.window.flag.WindowBlurFlag import com.android.wm.shell.appzoomout.AppZoomOut + import java.io.PrintWriter import java.util.Optional import javax.inject.Inject @@ -230,7 +231,7 @@ constructor( val zoomOut = blurRadiusToZoomOut(blurRadius = shadeRadius) // Make blur be 0 if it is necessary to stop blur effect. if (scrimsVisible) { - if (!WindowBlurFlag.isEnabled) { + if (!Flags.notificationShadeBlur()) { blur = 0 } } @@ -258,7 +259,9 @@ constructor( } private val shouldBlurBeOpaque: Boolean - get() = if (WindowBlurFlag.isEnabled) false else scrimsVisible && !blursDisabledForAppLaunch + get() = + if (Flags.notificationShadeBlur()) false + else scrimsVisible && !blursDisabledForAppLaunch /** Callback that updates the window blur value and is called only once per frame. */ @VisibleForTesting @@ -388,7 +391,7 @@ constructor( } private fun initBlurListeners() { - if (!WindowBlurFlag.isEnabled) return + if (!Flags.bouncerUiRevamp()) return applicationScope.launch { Log.d(TAG, "Starting coroutines for window root view blur") @@ -523,7 +526,7 @@ constructor( private fun scheduleUpdate() { val (blur, zoomOutFromShadeRadius) = computeBlurAndZoomOut() zoomOutCalculatedFromShadeRadius = zoomOutFromShadeRadius - if (WindowBlurFlag.isEnabled) { + if (Flags.bouncerUiRevamp()) { updateScheduled = windowRootViewBlurInteractor.requestBlurForShade(blur, shouldBlurBeOpaque) return diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/OWNERS b/packages/SystemUI/src/com/android/systemui/statusbar/OWNERS index 72b03bfa20c3..b2764e1a2302 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/OWNERS +++ b/packages/SystemUI/src/com/android/systemui/statusbar/OWNERS @@ -16,6 +16,12 @@ per-file *Keyguard* = set noparent per-file *Keyguard* = file:../keyguard/OWNERS # Not setting noparent here, since *Notification* also matches some status bar notification chips files (statusbar/chips/notification) which should be owned by the status bar team. per-file *Notification* = file:notification/OWNERS +# Files that control blur effects on shade +per-file *NotificationShadeDepth* = set noparent +per-file *NotificationShadeDepth* = shanh@google.com, rahulbanerjee@google.com +per-file *NotificationShadeDepth* = file:../keyguard/OWNERS +per-file *Blur* = set noparent +per-file *Blur* = shanh@google.com, rahulbanerjee@google.com # Not setting noparent here, since *Mode* matches many other classes (e.g., *ViewModel*) per-file *Mode* = file:notification/OWNERS per-file *RemoteInput* = set noparent diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt index 86954d569199..108d737e7658 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt @@ -30,7 +30,9 @@ import com.android.systemui.res.R import com.android.systemui.statusbar.chips.StatusBarChipLogTags.pad import com.android.systemui.statusbar.chips.StatusBarChipsLog import com.android.systemui.statusbar.chips.call.domain.interactor.CallChipInteractor +import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips import com.android.systemui.statusbar.chips.ui.model.ColorsModel +import com.android.systemui.statusbar.chips.ui.model.ColorsModel.Companion.toCustomColorsModel import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel @@ -76,14 +78,24 @@ constructor( OngoingActivityChipModel.ChipIcon.SingleColorIcon(phoneIcon) } + val colors = + if (StatusBarNotifChips.isEnabled && state.promotedContent != null) { + state.promotedContent.toCustomColorsModel() + } else { + ColorsModel.Themed + } + // This block mimics OngoingCallController#updateChip. if (state.startTimeMs <= 0L) { // If the start time is invalid, don't show a timer and show just an // icon. See b/192379214. OngoingActivityChipModel.Shown.IconOnly( icon = icon, - colors = ColorsModel.Themed, - getOnClickListener(state), + colors = colors, + onClickListenerLegacy = getOnClickListener(state), + // TODO(b/372657935): Add click support for the call chip when + // StatusBarChipModernization is enabled. + clickBehavior = OngoingActivityChipModel.ClickBehavior.None, ) } else { val startTimeInElapsedRealtime = @@ -91,9 +103,12 @@ constructor( systemClock.elapsedRealtime() OngoingActivityChipModel.Shown.Timer( icon = icon, - colors = ColorsModel.Themed, + colors = colors, startTimeMs = startTimeInElapsedRealtime, - getOnClickListener(state), + onClickListenerLegacy = getOnClickListener(state), + // TODO(b/372657935): Add click support for the call chip when + // StatusBarChipModernization is enabled. + clickBehavior = OngoingActivityChipModel.ClickBehavior.None, ) } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModel.kt index 3422337523f9..baa8eec5f767 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModel.kt @@ -41,6 +41,7 @@ import com.android.systemui.statusbar.chips.ui.model.ColorsModel import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel import com.android.systemui.statusbar.chips.ui.viewmodel.ChipTransitionHelper import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel +import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel.Companion.createDialogLaunchOnClickCallback import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel.Companion.createDialogLaunchOnClickListener import com.android.systemui.util.time.SystemClock import javax.inject.Inject @@ -204,13 +205,25 @@ constructor( colors = ColorsModel.Red, // TODO(b/332662551): Maybe use a MediaProjection API to fetch this time. startTimeMs = systemClock.elapsedRealtime(), - createDialogLaunchOnClickListener( - createCastScreenToOtherDeviceDialogDelegate(state), - dialogTransitionAnimator, - DialogCuj(Cuj.CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP, tag = "Cast to other device"), - logger, - TAG, - ), + onClickListenerLegacy = + createDialogLaunchOnClickListener( + createCastScreenToOtherDeviceDialogDelegate(state), + dialogTransitionAnimator, + DIALOG_CUJ, + logger, + TAG, + ), + clickBehavior = + OngoingActivityChipModel.ClickBehavior.ExpandAction( + onClick = + createDialogLaunchOnClickCallback( + createCastScreenToOtherDeviceDialogDelegate(state), + dialogTransitionAnimator, + DIALOG_CUJ, + logger, + TAG, + ) + ), ) } @@ -225,16 +238,24 @@ constructor( ) ), colors = ColorsModel.Red, - createDialogLaunchOnClickListener( - createGenericCastToOtherDeviceDialogDelegate(deviceName), - dialogTransitionAnimator, - DialogCuj( - Cuj.CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP, - tag = "Cast to other device audio only", + onClickListenerLegacy = + createDialogLaunchOnClickListener( + createGenericCastToOtherDeviceDialogDelegate(deviceName), + dialogTransitionAnimator, + DIALOG_CUJ_AUDIO_ONLY, + logger, + TAG, + ), + clickBehavior = + OngoingActivityChipModel.ClickBehavior.ExpandAction( + createDialogLaunchOnClickCallback( + createGenericCastToOtherDeviceDialogDelegate(deviceName), + dialogTransitionAnimator, + DIALOG_CUJ_AUDIO_ONLY, + logger, + TAG, + ) ), - logger, - TAG, - ), ) } @@ -256,6 +277,13 @@ constructor( companion object { @DrawableRes val CAST_TO_OTHER_DEVICE_ICON = R.drawable.ic_cast_connected + private val DIALOG_CUJ = + DialogCuj(Cuj.CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP, tag = "Cast to other device") + private val DIALOG_CUJ_AUDIO_ONLY = + DialogCuj( + Cuj.CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP, + tag = "Cast to other device audio only", + ) private val TAG = "CastToOtherVM".pad() } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractor.kt index 2121f94caced..4fad01d9f448 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractor.kt @@ -28,6 +28,7 @@ import com.android.systemui.statusbar.chips.StatusBarChipsLog import com.android.systemui.statusbar.chips.notification.domain.model.NotificationChipModel import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor +import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor.Companion.isOngoingCallNotification import com.android.systemui.util.kotlin.pairwise import com.android.systemui.util.time.SystemClock import javax.inject.Inject @@ -41,6 +42,7 @@ import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.map /** An interactor for the notification chips shown in the status bar. */ @SysUISingleton @@ -88,45 +90,56 @@ constructor( private val promotedNotificationInteractors = MutableStateFlow<List<SingleNotificationChipInteractor>>(emptyList()) + /** + * The notifications that are promoted and ongoing. + * + * Explicitly does *not* include any ongoing call notifications, even if the call notifications + * meet the promotion criteria. Those call notifications will be handled by + * [com.android.systemui.statusbar.chips.call.domain.CallChipInteractor] instead. See + * b/388521980. + */ + private val promotedOngoingNotifications = + activeNotificationsInteractor.promotedOngoingNotifications.map { notifs -> + notifs.filterNot { it.isOngoingCallNotification() } + } + override fun start() { if (!StatusBarNotifChips.isEnabled) { return } backgroundScope.launch("StatusBarNotificationChipsInteractor") { - activeNotificationsInteractor.promotedOngoingNotifications - .pairwise(initialValue = emptyList()) - .collect { (oldNotifs, currentNotifs) -> - val removedNotifKeys = - oldNotifs.map { it.key }.minus(currentNotifs.map { it.key }.toSet()) - removedNotifKeys.forEach { removedNotifKey -> - val wasRemoved = promotedNotificationInteractorMap.remove(removedNotifKey) - if (wasRemoved == null) { - logger.w({ - "Attempted to remove $str1 from interactor map but it wasn't present" - }) { - str1 = removedNotifKey - } + promotedOngoingNotifications.pairwise(initialValue = emptyList()).collect { + (oldNotifs, currentNotifs) -> + val removedNotifKeys = + oldNotifs.map { it.key }.minus(currentNotifs.map { it.key }.toSet()) + removedNotifKeys.forEach { removedNotifKey -> + val wasRemoved = promotedNotificationInteractorMap.remove(removedNotifKey) + if (wasRemoved == null) { + logger.w({ + "Attempted to remove $str1 from interactor map but it wasn't present" + }) { + str1 = removedNotifKey } } + } - currentNotifs.forEach { notif -> - val interactor = - promotedNotificationInteractorMap.computeIfAbsent(notif.key) { - singleNotificationChipInteractorFactory.create( - notif, - creationTime = systemClock.currentTimeMillis(), - ) - } - interactor.setNotification(notif) - } - logger.d({ "Interactors: $str1" }) { - str1 = - promotedNotificationInteractorMap.keys.joinToString(separator = " /// ") - } - promotedNotificationInteractors.value = - promotedNotificationInteractorMap.values.toList() + currentNotifs.forEach { notif -> + val interactor = + promotedNotificationInteractorMap.computeIfAbsent(notif.key) { + singleNotificationChipInteractorFactory.create( + notif, + creationTime = systemClock.currentTimeMillis(), + ) + } + interactor.setNotification(notif) } + logger.d({ "Interactors: $str1" }) { + str1 = promotedNotificationInteractorMap.keys.joinToString(separator = " /// ") + } + promotedNotificationInteractors.value = + promotedNotificationInteractorMap.values.toList() + } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt index ec3a5b271e35..b7cad625b7b8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt @@ -23,7 +23,7 @@ import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.statusbar.chips.notification.domain.interactor.StatusBarNotificationChipsInteractor import com.android.systemui.statusbar.chips.notification.domain.model.NotificationChipModel import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips -import com.android.systemui.statusbar.chips.ui.model.ColorsModel +import com.android.systemui.statusbar.chips.ui.model.ColorsModel.Companion.toCustomColorsModel import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel import com.android.systemui.statusbar.core.StatusBarConnectedDisplays import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationInteractor @@ -72,11 +72,7 @@ constructor( StatusBarConnectedDisplays.assertInNewMode() OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon(this.key) } - val colors = - ColorsModel.Custom( - backgroundColorInt = this.promotedContent.colors.backgroundColor, - primaryTextColorInt = this.promotedContent.colors.primaryTextColor, - ) + val colors = this.promotedContent.toCustomColorsModel() val onClickListener = View.OnClickListener { // The notification pipeline needs everything to run on the main thread, so keep @@ -87,6 +83,7 @@ constructor( ) } } + val clickBehavior = OngoingActivityChipModel.ClickBehavior.None val isShowingHeadsUpFromChipTap = headsUpState is TopPinnedState.Pinned && @@ -95,7 +92,12 @@ constructor( if (isShowingHeadsUpFromChipTap) { // If the user tapped this chip to show the HUN, we want to just show the icon because // the HUN will show the rest of the information. - return OngoingActivityChipModel.Shown.IconOnly(icon, colors, onClickListener) + return OngoingActivityChipModel.Shown.IconOnly( + icon, + colors, + onClickListener, + clickBehavior, + ) } if (this.promotedContent.shortCriticalText != null) { @@ -104,6 +106,7 @@ constructor( colors, this.promotedContent.shortCriticalText, onClickListener, + clickBehavior, ) } @@ -115,11 +118,21 @@ constructor( // notification will likely just be set to the current time, which would cause the chip // to always show "now". We don't want early testers to get that experience since it's // not what will happen at launch, so just don't show any time. - return OngoingActivityChipModel.Shown.IconOnly(icon, colors, onClickListener) + return OngoingActivityChipModel.Shown.IconOnly( + icon, + colors, + onClickListener, + clickBehavior, + ) } if (this.promotedContent.time == null) { - return OngoingActivityChipModel.Shown.IconOnly(icon, colors, onClickListener) + return OngoingActivityChipModel.Shown.IconOnly( + icon, + colors, + onClickListener, + clickBehavior, + ) } when (this.promotedContent.time.mode) { PromotedNotificationContentModel.When.Mode.BasicTime -> { @@ -128,6 +141,7 @@ constructor( colors, time = this.promotedContent.time.time, onClickListener, + clickBehavior, ) } PromotedNotificationContentModel.When.Mode.CountUp -> { @@ -136,6 +150,7 @@ constructor( colors, startTimeMs = this.promotedContent.time.time, onClickListener, + clickBehavior, ) } PromotedNotificationContentModel.When.Mode.CountDown -> { @@ -145,6 +160,7 @@ constructor( colors, startTimeMs = this.promotedContent.time.time, onClickListener, + clickBehavior, ) } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModel.kt index 0065593c7b73..7f2327a742e2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModel.kt @@ -41,6 +41,7 @@ import com.android.systemui.statusbar.chips.ui.model.ColorsModel import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel import com.android.systemui.statusbar.chips.ui.viewmodel.ChipTransitionHelper import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel +import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel.Companion.createDialogLaunchOnClickCallback import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel.Companion.createDialogLaunchOnClickListener import com.android.systemui.util.kotlin.pairwise import com.android.systemui.util.time.SystemClock @@ -91,16 +92,24 @@ constructor( ), colors = ColorsModel.Red, startTimeMs = systemClock.elapsedRealtime(), - createDialogLaunchOnClickListener( - createDelegate(state.recordedTask), - dialogTransitionAnimator, - DialogCuj( - Cuj.CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP, - tag = "Screen record", + onClickListenerLegacy = + createDialogLaunchOnClickListener( + createDelegate(state.recordedTask), + dialogTransitionAnimator, + DIALOG_CUJ, + logger, + TAG, + ), + clickBehavior = + OngoingActivityChipModel.ClickBehavior.ExpandAction( + createDialogLaunchOnClickCallback( + dialogDelegate = createDelegate(state.recordedTask), + dialogTransitionAnimator = dialogTransitionAnimator, + DIALOG_CUJ, + logger, + TAG, + ) ), - logger, - TAG, - ), ) } } @@ -154,6 +163,8 @@ constructor( companion object { @DrawableRes val ICON = R.drawable.ic_screenrecord + private val DIALOG_CUJ = + DialogCuj(Cuj.CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP, tag = "Screen record") private val TAG = "ScreenRecordVM".pad() } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModel.kt index 2af86a51cf70..6654d4a8f104 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModel.kt @@ -39,6 +39,7 @@ import com.android.systemui.statusbar.chips.ui.model.ColorsModel import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel import com.android.systemui.statusbar.chips.ui.viewmodel.ChipTransitionHelper import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel +import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel.Companion.createDialogLaunchOnClickCallback import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel.Companion.createDialogLaunchOnClickListener import com.android.systemui.util.time.SystemClock import javax.inject.Inject @@ -128,13 +129,25 @@ constructor( colors = ColorsModel.Red, // TODO(b/332662551): Maybe use a MediaProjection API to fetch this time. startTimeMs = systemClock.elapsedRealtime(), - createDialogLaunchOnClickListener( - createShareScreenToAppDialogDelegate(state), - dialogTransitionAnimator, - DialogCuj(Cuj.CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP, tag = "Share to app"), - logger, - TAG, - ), + onClickListenerLegacy = + createDialogLaunchOnClickListener( + createShareScreenToAppDialogDelegate(state), + dialogTransitionAnimator, + DIALOG_CUJ, + logger, + TAG, + ), + clickBehavior = + OngoingActivityChipModel.ClickBehavior.ExpandAction( + onClick = + createDialogLaunchOnClickCallback( + createShareScreenToAppDialogDelegate(state), + dialogTransitionAnimator, + DIALOG_CUJ, + logger, + TAG, + ) + ), ) } @@ -150,16 +163,24 @@ constructor( ) ), colors = ColorsModel.Red, - createDialogLaunchOnClickListener( - createGenericShareToAppDialogDelegate(), - dialogTransitionAnimator, - DialogCuj( - Cuj.CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP, - tag = "Share to app audio only", + onClickListenerLegacy = + createDialogLaunchOnClickListener( + createGenericShareToAppDialogDelegate(), + dialogTransitionAnimator, + DIALOG_CUJ_AUDIO_ONLY, + logger, + TAG, + ), + clickBehavior = + OngoingActivityChipModel.ClickBehavior.ExpandAction( + createDialogLaunchOnClickCallback( + createGenericShareToAppDialogDelegate(), + dialogTransitionAnimator, + DIALOG_CUJ_AUDIO_ONLY, + logger, + TAG, + ) ), - logger, - TAG, - ), ) } @@ -180,6 +201,10 @@ constructor( companion object { @DrawableRes val SHARE_TO_APP_ICON = R.drawable.ic_present_to_all + private val DIALOG_CUJ = + DialogCuj(Cuj.CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP, tag = "Share to app") + private val DIALOG_CUJ_AUDIO_ONLY = + DialogCuj(Cuj.CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP, tag = "Share to app audio only") private val TAG = "ShareToAppVM".pad() } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt index b0fa9d842480..d46638fac46c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt @@ -56,7 +56,8 @@ object OngoingActivityChipBinder { // Data setChipIcon(chipModel, chipBackgroundView, chipDefaultIconView, iconViewStore) setChipMainContent(chipModel, chipTextView, chipTimeView, chipShortTimeDeltaView) - viewBinding.rootView.setOnClickListener(chipModel.onClickListener) + + viewBinding.rootView.setOnClickListener(chipModel.onClickListenerLegacy) updateChipPadding( chipModel, chipBackgroundView, @@ -424,7 +425,7 @@ object OngoingActivityChipBinder { // Clickable chips need to be a minimum size for accessibility purposes, but let // non-clickable chips be smaller. val minimumWidth = - if (chipModel.onClickListener != null) { + if (chipModel.onClickListenerLegacy != null) { chipBackgroundView.context.resources.getDimensionPixelSize( R.dimen.min_clickable_item_size ) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChip.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChip.kt index 1be5842bceeb..6ce3228531d2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChip.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChip.kt @@ -35,10 +35,13 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Shape import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.unit.dp import androidx.compose.ui.viewinterop.AndroidView +import com.android.compose.animation.Expandable +import com.android.systemui.animation.Expandable import com.android.systemui.common.ui.compose.Icon import com.android.systemui.res.R import com.android.systemui.statusbar.chips.ui.compose.modifiers.neverDecreaseWidth @@ -47,23 +50,42 @@ import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel @Composable fun OngoingActivityChip(model: OngoingActivityChipModel.Shown, modifier: Modifier = Modifier) { + when (val clickBehavior = model.clickBehavior) { + is OngoingActivityChipModel.ClickBehavior.ExpandAction -> { + // Wrap the chip in an Expandable so we can animate the expand transition. + ExpandableChip( + color = { Color.Transparent }, + shape = + RoundedCornerShape( + dimensionResource(id = R.dimen.ongoing_activity_chip_corner_radius) + ), + modifier = modifier, + ) { expandable -> + ChipBody(model, onClick = { clickBehavior.onClick(expandable) }) + } + } + + is OngoingActivityChipModel.ClickBehavior.None -> { + ChipBody(model, modifier = modifier) + } + } +} + +@Composable +private fun ChipBody( + model: OngoingActivityChipModel.Shown, + modifier: Modifier = Modifier, + onClick: () -> Unit = {}, +) { val context = LocalContext.current - val isClickable = model.onClickListener != null + val isClickable = onClick != {} val hasEmbeddedIcon = model.icon is OngoingActivityChipModel.ChipIcon.StatusBarView // Use a Box with `fillMaxHeight` to create a larger click surface for the chip. The visible // height of the chip is determined by the height of the background of the Row below. Box( contentAlignment = Alignment.Center, - modifier = - modifier - .fillMaxHeight() - .clickable( - enabled = isClickable, - onClick = { - // TODO(b/372657935): Implement click actions. - }, - ), + modifier = modifier.fillMaxHeight().clickable(enabled = isClickable, onClick = onClick), ) { Row( horizontalArrangement = Arrangement.Center, @@ -206,3 +228,13 @@ private fun ChipContent(viewModel: OngoingActivityChipModel.Shown, modifier: Mod } } } + +@Composable +private fun ExpandableChip( + color: () -> Color, + shape: Shape, + modifier: Modifier = Modifier, + content: @Composable (Expandable) -> Unit, +) { + Expandable(color = color(), shape = shape, modifier = modifier.clip(shape)) { content(it) } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/ColorsModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/ColorsModel.kt index cac25d04f1a5..25f90f9a0065 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/ColorsModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/ColorsModel.kt @@ -21,6 +21,7 @@ import android.content.res.ColorStateList import androidx.annotation.ColorInt import com.android.settingslib.Utils import com.android.systemui.res.R +import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel /** Model representing how the chip in the status bar should be colored. */ sealed interface ColorsModel { @@ -55,4 +56,14 @@ sealed interface ColorsModel { override fun text(context: Context) = context.getColor(android.R.color.white) } + + companion object { + /** Converts the promoted notification colors to a [Custom] colors model. */ + fun PromotedNotificationContentModel.toCustomColorsModel(): Custom { + return Custom( + backgroundColorInt = this.colors.backgroundColor, + primaryTextColorInt = this.colors.primaryTextColor, + ) + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt index 956d99e46766..68c8f8cb4254 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt @@ -17,6 +17,7 @@ package com.android.systemui.statusbar.chips.ui.model import android.view.View +import com.android.systemui.animation.Expandable import com.android.systemui.common.shared.model.Icon import com.android.systemui.statusbar.StatusBarIconView import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips @@ -46,17 +47,20 @@ sealed class OngoingActivityChipModel { open val colors: ColorsModel, /** * Listener method to invoke when this chip is clicked. If null, the chip won't be - * clickable. + * clickable. Will be deprecated after [StatusBarChipsModernization] is enabled. */ - open val onClickListener: View.OnClickListener?, + open val onClickListenerLegacy: View.OnClickListener?, + /** Data class that determines how clicks on the chip should be handled. */ + open val clickBehavior: ClickBehavior, ) : OngoingActivityChipModel() { /** This chip shows only an icon and nothing else. */ data class IconOnly( override val icon: ChipIcon, override val colors: ColorsModel, - override val onClickListener: View.OnClickListener?, - ) : Shown(icon, colors, onClickListener) { + override val onClickListenerLegacy: View.OnClickListener?, + override val clickBehavior: ClickBehavior, + ) : Shown(icon, colors, onClickListenerLegacy, clickBehavior) { override val logName = "Shown.Icon" } @@ -74,8 +78,9 @@ sealed class OngoingActivityChipModel { * [android.widget.Chronometer.setBase]. */ val startTimeMs: Long, - override val onClickListener: View.OnClickListener?, - ) : Shown(icon, colors, onClickListener) { + override val onClickListenerLegacy: View.OnClickListener?, + override val clickBehavior: ClickBehavior, + ) : Shown(icon, colors, onClickListenerLegacy, clickBehavior) { override val logName = "Shown.Timer" } @@ -88,8 +93,9 @@ sealed class OngoingActivityChipModel { override val colors: ColorsModel, /** The time of the event that this chip represents. */ val time: Long, - override val onClickListener: View.OnClickListener?, - ) : Shown(icon, colors, onClickListener) { + override val onClickListenerLegacy: View.OnClickListener?, + override val clickBehavior: ClickBehavior, + ) : Shown(icon, colors, onClickListenerLegacy, clickBehavior) { init { StatusBarNotifChips.assertInNewMode() } @@ -105,7 +111,13 @@ sealed class OngoingActivityChipModel { override val colors: ColorsModel, /** The number of seconds until an event is started. */ val secondsUntilStarted: Long, - ) : Shown(icon = null, colors, onClickListener = null) { + ) : + Shown( + icon = null, + colors, + onClickListenerLegacy = null, + clickBehavior = ClickBehavior.None, + ) { override val logName = "Shown.Countdown" } @@ -115,8 +127,9 @@ sealed class OngoingActivityChipModel { override val colors: ColorsModel, // TODO(b/361346412): Enforce a max length requirement? val text: String, - override val onClickListener: View.OnClickListener? = null, - ) : Shown(icon, colors, onClickListener) { + override val onClickListenerLegacy: View.OnClickListener? = null, + override val clickBehavior: ClickBehavior, + ) : Shown(icon, colors, onClickListenerLegacy, clickBehavior) { override val logName = "Shown.Text" } } @@ -149,4 +162,13 @@ sealed class OngoingActivityChipModel { */ data class SingleColorIcon(val impl: Icon) : ChipIcon } + + /** Defines the behavior of the chip when it is clicked. */ + sealed interface ClickBehavior { + /** No specific click behavior. */ + data object None : ClickBehavior + + /** The chip expands into a dialog or activity on click. */ + data class ExpandAction(val onClick: (Expandable) -> Unit) : ClickBehavior + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModel.kt index 2fc366b7f078..a978c04d2a2e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModel.kt @@ -19,6 +19,7 @@ package com.android.systemui.statusbar.chips.ui.viewmodel import android.view.View import com.android.systemui.animation.DialogCuj import com.android.systemui.animation.DialogTransitionAnimator +import com.android.systemui.animation.Expandable import com.android.systemui.log.LogBuffer import com.android.systemui.log.core.LogLevel import com.android.systemui.res.R @@ -26,6 +27,7 @@ import com.android.systemui.statusbar.chips.StatusBarChipsLog import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer import com.android.systemui.statusbar.phone.SystemUIDialog +import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization import kotlinx.coroutines.flow.StateFlow /** @@ -46,6 +48,7 @@ interface OngoingActivityChipViewModel { tag: String, ): View.OnClickListener { return View.OnClickListener { view -> + StatusBarChipsModernization.assertInLegacyMode() logger.log(tag, LogLevel.INFO, {}, { "Chip clicked" }) val dialog = dialogDelegate.createDialog() val launchableView = @@ -55,5 +58,28 @@ interface OngoingActivityChipViewModel { dialogTransitionAnimator.showFromView(dialog, launchableView, cuj) } } + + /** + * Creates a chip click callback with an [Expandable] parameter that launches a dialog + * created by [dialogDelegate]. + */ + fun createDialogLaunchOnClickCallback( + dialogDelegate: SystemUIDialog.Delegate, + dialogTransitionAnimator: DialogTransitionAnimator, + cuj: DialogCuj, + @StatusBarChipsLog logger: LogBuffer, + tag: String, + ): (Expandable) -> Unit { + return { expandable -> + StatusBarChipsModernization.assertInNewMode() + logger.log(tag, LogLevel.INFO, {}, { "Chip clicked" }) + val dialog = dialogDelegate.createDialog() + + val controller = expandable.dialogTransitionController(cuj) + if (controller != null) { + dialogTransitionAnimator.show(dialog, controller) + } + } + } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java index c38b84b710bc..417e57d2205f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java @@ -34,6 +34,8 @@ import static com.android.systemui.statusbar.notification.stack.NotificationPrio import static java.util.Objects.requireNonNull; +import android.annotation.FlaggedApi; +import android.app.Flags; import android.app.Notification; import android.app.Notification.MessagingStyle.Message; import android.app.NotificationChannel; @@ -1091,6 +1093,14 @@ public final class NotificationEntry extends ListEntry { } /** + * Returns whether the NotificationEntry is promoted ongoing. + */ + @FlaggedApi(Flags.FLAG_API_RICH_ONGOING) + public boolean isPromotedOngoing() { + return PromotedNotificationContentModel.isPromotedForStatusBarChip(mSbn.getNotification()); + } + + /** * Sets the content needed to render this notification as a promoted notification on various * surfaces (like status bar chips and AOD). */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt index 0c040c855368..502fb51ba5c8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt @@ -76,11 +76,14 @@ constructor( val allNotificationsCountValue: Int get() = repository.activeNotifications.value.individuals.size - /** The notifications that are promoted and ongoing. Sorted by priority order. */ + /** + * The notifications that are promoted and ongoing. + * + * This *may* include ongoing call notifications if the call notification also meets promotion + * criteria. + */ val promotedOngoingNotifications: Flow<List<ActiveNotificationModel>> = if (StatusBarNotifChips.isEnabled) { - // TODO(b/364653005): [ongoingCallNotification] should be incorporated into this flow - // instead of being separate. topLevelRepresentativeNotifications .map { notifs -> notifs.filter { it.promotedContent != null } } .distinctUntilChanged() @@ -98,10 +101,10 @@ constructor( val ongoingCallNotification: Flow<ActiveNotificationModel?> = allRepresentativeNotifications .map { notifMap -> - // Once a call has started, its `whenTime` should stay the same, so we can use it as - // a stable sort value. notifMap.values - .filter { it.callType == CallType.Ongoing } + .filter { it.isOngoingCallNotification() } + // Once a call has started, its `whenTime` should stay the same, so we can use + // it as a stable sort value. .minByOrNull { it.whenTime } } .distinctUntilChanged() @@ -153,4 +156,8 @@ constructor( fun setNotifStats(notifStats: NotifStats) { repository.notifStats.value = notifStats } + + companion object { + fun ActiveNotificationModel.isOngoingCallNotification() = this.callType == CallType.Ongoing + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java index c88dd7af6b24..d401283aa84e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java @@ -154,10 +154,12 @@ public class FooterView extends StackScrollerDecorView { DumpUtilsKt.withIncreasedIndent(pw, () -> { // TODO: b/375010573 - update dumps for redesign pw.println("visibility: " + DumpUtilsKt.visibilityString(getVisibility())); - pw.println("manageButton visibility: " - + DumpUtilsKt.visibilityString(mClearAllButton.getVisibility())); - pw.println("dismissButton visibility: " - + DumpUtilsKt.visibilityString(mClearAllButton.getVisibility())); + if (mManageOrHistoryButton != null) + pw.println("mManageOrHistoryButton visibility: " + + DumpUtilsKt.visibilityString(mManageOrHistoryButton.getVisibility())); + if (mClearAllButton != null) + pw.println("mClearAllButton visibility: " + + DumpUtilsKt.visibilityString(mClearAllButton.getVisibility())); }); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImpl.java index d02e17cab534..0171fb72e158 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImpl.java @@ -1425,7 +1425,12 @@ public class HeadsUpManagerImpl } } - return (mEntry.isRowPinned() && mExpanded) + // Promoted notifications are always shown as expanded, and we don't want them to ever + // be sticky. + boolean isStickyDueToExpansion = + mEntry.isRowPinned() && mExpanded && !mEntry.isPromotedOngoing(); + + return isStickyDueToExpansion || mRemoteInputActive || hasFullScreenIntent(mEntry); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/shared/model/PromotedNotificationContentModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/shared/model/PromotedNotificationContentModel.kt index 258d80c60cd7..a175f90c384a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/shared/model/PromotedNotificationContentModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/shared/model/PromotedNotificationContentModel.kt @@ -147,6 +147,7 @@ data class PromotedNotificationContentModel( * Returns true if the given notification should be considered promoted when deciding * whether or not to show the status bar chip UI. */ + @JvmStatic fun isPromotedForStatusBarChip(notification: Notification): Boolean { // Notification.isPromotedOngoing checks the ui_rich_ongoing flag, but we want the // status bar chip to be ready before all the features behind the ui_rich_ongoing flag diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/MagicActionBackgroundDrawable.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/MagicActionBackgroundDrawable.kt index e27ff7d6746b..793b3b8b1e42 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/MagicActionBackgroundDrawable.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/MagicActionBackgroundDrawable.kt @@ -17,39 +17,210 @@ package com.android.systemui.statusbar.notification.row import android.content.Context +import android.graphics.BlendMode import android.graphics.Canvas +import android.graphics.Color import android.graphics.ColorFilter +import android.graphics.LinearGradient import android.graphics.Paint +import android.graphics.Path import android.graphics.PixelFormat +import android.graphics.Rect +import android.graphics.RuntimeShader +import android.graphics.Shader import android.graphics.drawable.Drawable +import android.widget.FrameLayout +import androidx.compose.foundation.layout.size +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import androidx.compose.ui.viewinterop.AndroidView +import com.android.internal.graphics.ColorUtils +import com.android.systemui.res.R +import com.android.systemui.surfaceeffects.shaderutil.ShaderUtilLibrary +import kotlin.math.max +import kotlin.math.roundToInt /** - * A background style for smarter-smart-actions. - * - * TODO(b/383567383) implement final UX + * A background style for smarter-smart-actions. The style is composed by a simplex3d noise, + * overlaid with sparkles. */ -class MagicActionBackgroundDrawable(context: Context) : Drawable() { +class MagicActionBackgroundDrawable( + context: Context, + primaryContainer: Int? = null, + private val seed: Float = 0f, +) : Drawable() { + + private val pixelDensity = context.resources.displayMetrics.density + private val cornerRadius = + context.resources.getDimensionPixelSize(R.dimen.smart_reply_button_corner_radius).toFloat() + private val outlineStrokeWidth = + context.resources + .getDimensionPixelSize(R.dimen.smart_action_button_outline_stroke_width) + .toFloat() + private val buttonShape = Path() + private val paddingVertical = + context.resources + .getDimensionPixelSize(R.dimen.smart_reply_button_padding_vertical) + .toFloat() + + /** The color of the button background. */ + private val mainColor = + primaryContainer + ?: context.getColor(com.android.internal.R.color.materialColorPrimaryContainer) - private var _alpha: Int = 255 - private var _colorFilter: ColorFilter? = null - private val paint = - Paint().apply { - color = context.getColor(com.android.internal.R.color.materialColorPrimaryContainer) + /** Slightly dimmed down version of [mainColor] used on the simplex noise. */ + private val dimColor: Int + get() { + val labColor = arrayOf(0.0, 0.0, 0.0).toDoubleArray() + ColorUtils.colorToLAB(mainColor, labColor) + val camColor = ColorUtils.colorToCAM(mainColor) + return ColorUtils.CAMToColor( + camColor.hue, + camColor.chroma, + max(0f, (labColor[0] - 20).toFloat()), + ) } + private val bgShader = MagicActionBackgroundShader() + private val bgPaint = Paint() + private val outlinePaint = Paint() + + init { + bgShader.setColorUniform("in_color", mainColor) + bgShader.setColorUniform("in_dimColor", dimColor) + bgPaint.shader = bgShader + outlinePaint.style = Paint.Style.STROKE + // Stroke is doubled in width and then clipped, to avoid anti-aliasing artifacts at the edge + // of the rectangle. + outlinePaint.strokeWidth = outlineStrokeWidth * 2 + outlinePaint.blendMode = BlendMode.SCREEN + outlinePaint.alpha = (255 * 0.32f).roundToInt() + } + override fun draw(canvas: Canvas) { - canvas.drawRect(bounds, paint) + // We clip instead of drawing 2 rounded rects, otherwise there will be artifacts where + // around the button background and the outline. + canvas.clipPath(buttonShape) + + canvas.drawRect(bounds, bgPaint) + canvas.drawRoundRect( + bounds.left.toFloat(), + bounds.top + paddingVertical, + bounds.right.toFloat(), + bounds.bottom - paddingVertical, + cornerRadius, + cornerRadius, + outlinePaint, + ) + } + + override fun onBoundsChange(bounds: Rect) { + super.onBoundsChange(bounds) + + val width = bounds.width().toFloat() + val height = bounds.height() - paddingVertical * 2 + if (width == 0f || height == 0f) return + + bgShader.setFloatUniform("in_gridNum", NOISE_SIZE) + bgShader.setFloatUniform("in_spkarkleAlpha", SPARKLE_ALPHA) + bgShader.setFloatUniform("in_noiseMove", 0f, 0f, 0f) + bgShader.setFloatUniform("in_size", width, height) + bgShader.setFloatUniform("in_aspectRatio", width / height) + bgShader.setFloatUniform("in_time", seed) + bgShader.setFloatUniform("in_pixelDensity", pixelDensity) + + buttonShape.reset() + buttonShape.addRoundRect( + bounds.left.toFloat(), + bounds.top + paddingVertical, + bounds.right.toFloat(), + bounds.bottom - paddingVertical, + cornerRadius, + cornerRadius, + Path.Direction.CW, + ) + + val outlineGradient = + LinearGradient( + bounds.left.toFloat(), + 0f, + bounds.right.toFloat(), + 0f, + mainColor, + ColorUtils.setAlphaComponent(mainColor, 0), + Shader.TileMode.CLAMP, + ) + outlinePaint.shader = outlineGradient } override fun setAlpha(alpha: Int) { - _alpha = alpha + bgPaint.alpha = alpha invalidateSelf() } override fun setColorFilter(colorFilter: ColorFilter?) { - _colorFilter = colorFilter + bgPaint.colorFilter = colorFilter invalidateSelf() } override fun getOpacity(): Int = PixelFormat.TRANSLUCENT + + companion object { + /** Smoothness of the turbulence. Larger numbers yield more detail. */ + private const val NOISE_SIZE = 0.7f + + /** Strength of the sparkles overlaid on the turbulence. */ + private const val SPARKLE_ALPHA = 0.15f + } +} + +private class MagicActionBackgroundShader : RuntimeShader(SHADER) { + + // language=AGSL + companion object { + private const val UNIFORMS = + """ + uniform float in_gridNum; + uniform vec3 in_noiseMove; + uniform vec2 in_size; + uniform float in_aspectRatio; + uniform half in_time; + uniform half in_pixelDensity; + uniform float in_spkarkleAlpha; + layout(color) uniform vec4 in_color; + layout(color) uniform vec4 in_dimColor; + """ + private const val MAIN_SHADER = + """vec4 main(vec2 p) { + vec2 uv = p / in_size.xy; + uv.x *= in_aspectRatio; + vec3 noiseP = vec3(uv + in_noiseMove.xy, in_noiseMove.z) * in_gridNum; + half luma = 1.0 - getLuminosity(half3(simplex3d(noiseP))); + half4 turbulenceColor = mix(in_color, in_dimColor, luma); + float sparkle = sparkles(p - mod(p, in_pixelDensity * 0.8), in_time); + sparkle = min(sparkle * in_spkarkleAlpha, in_spkarkleAlpha); + return turbulenceColor + half4(half3(sparkle), 1.0); + } + """ + private const val SHADER = UNIFORMS + ShaderUtilLibrary.SHADER_LIB + MAIN_SHADER + } +} + +// @Preview +@Composable +fun DrawablePreview() { + AndroidView( + factory = { context -> + FrameLayout(context).apply { + background = + MagicActionBackgroundDrawable( + context = context, + primaryContainer = Color.parseColor("#c5eae2"), + seed = 0f, + ) + } + }, + modifier = Modifier.size(100.dp, 50.dp), + ) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java index c47ed1722bb4..fcc3af6a2134 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java @@ -397,6 +397,13 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh public void onWindowFocusChanged(boolean hasFocus) { super.onWindowFocusChanged(hasFocus); mDelegate.onWindowFocusChanged(this, hasFocus); + if (hasFocus) { + // Update SysUI state to reflect that a dialog is showing. This ensures the state is + // correct when this dialog regains focus after another dialog was closed. + // See b/386871258 + mSysUiState.setFlag(QuickStepContract.SYSUI_STATE_DIALOG_SHOWING, true) + .commitUpdate(mContext.getDisplayId()); + } } public void setShowForAllUsers(boolean show) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt index 4eb69babfadb..a29934fa3a16 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt @@ -39,6 +39,7 @@ import com.android.systemui.statusbar.chips.ui.view.ChipChronometer import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore import com.android.systemui.statusbar.gesture.SwipeStatusBarAwayGestureHandler import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor +import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel import com.android.systemui.statusbar.notification.shared.CallType import com.android.systemui.statusbar.phone.ongoingcall.data.repository.OngoingCallRepository @@ -159,6 +160,7 @@ constructor( notificationIconView = currentInfo.notificationIconView, intent = currentInfo.intent, notificationKey = currentInfo.key, + promotedContent = currentInfo.promotedContent, ) } else { return OngoingCallModel.NoCall @@ -215,6 +217,7 @@ constructor( notifModel.statusBarChipIconView, notifModel.contentIntent, notifModel.uid, + notifModel.promotedContent, isOngoing = true, statusBarSwipedAway = callNotificationInfo?.statusBarSwipedAway ?: false, ) @@ -334,6 +337,11 @@ constructor( val notificationIconView: StatusBarIconView?, val intent: PendingIntent?, val uid: Int, + /** + * If the call notification also meets promoted notification criteria, this field is filled + * in with the content related to promotion. Otherwise null. + */ + val promotedContent: PromotedNotificationContentModel?, /** True if the call is currently ongoing (as opposed to incoming, screening, etc.). */ val isOngoing: Boolean, /** True if the user has swiped away the status bar while in this phone call. */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/StatusBarChipsModernization.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/StatusBarChipsModernization.kt index 2ab2b68bbb2e..9f8b45578903 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/StatusBarChipsModernization.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/StatusBarChipsModernization.kt @@ -1,18 +1,18 @@ /* -* 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. -*/ + * 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.phone.ongoingcall import com.android.systemui.Flags @@ -44,9 +44,16 @@ object StatusBarChipsModernization { RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME) /** + * Called to ensure code is only run when the flag is enabled. This will throw an exception if + * the flag is not enabled to ensure that the refactor author catches issues in testing. + */ + @JvmStatic + inline fun assertInNewMode() = RefactorFlagUtils.assertInNewMode(isEnabled, FLAG_NAME) + + /** * Called to ensure code is only run when the flag is disabled. This will throw an exception if * the flag is enabled to ensure that the refactor author catches issues in testing. */ @JvmStatic inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME) -}
\ No newline at end of file +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractor.kt index 2bfbf4826053..99141f592a2e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractor.kt @@ -165,6 +165,7 @@ constructor( notificationIconView = model.statusBarChipIconView, intent = model.contentIntent, notificationKey = model.key, + promotedContent = model.promotedContent, ) } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallModel.kt index 1a5dcc16f3db..7d00e9d58e5b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallModel.kt @@ -18,6 +18,7 @@ package com.android.systemui.statusbar.phone.ongoingcall.shared.model import android.app.PendingIntent import com.android.systemui.statusbar.StatusBarIconView +import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel /** Represents the state of any ongoing calls. */ sealed interface OngoingCallModel { @@ -25,8 +26,8 @@ sealed interface OngoingCallModel { data object NoCall : OngoingCallModel /** - * There is an ongoing call but the call app is currently visible, so we don't need to show - * the chip. + * There is an ongoing call but the call app is currently visible, so we don't need to show the + * chip. */ data object InCallWithVisibleApp : OngoingCallModel @@ -41,11 +42,14 @@ sealed interface OngoingCallModel { * @property notificationIconView the [android.app.Notification.getSmallIcon] that's set on the * call notification. We may use this icon in the chip instead of the default phone icon. * @property intent the intent associated with the call notification. + * @property promotedContent if the call notification also meets promoted notification criteria, + * this field is filled in with the content related to promotion. Otherwise null. */ data class InCall( val startTimeMs: Long, val notificationIconView: StatusBarIconView?, val intent: PendingIntent?, val notificationKey: String, + val promotedContent: PromotedNotificationContentModel?, ) : OngoingCallModel } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepositoryImpl.kt index b32037992501..dacb859967e8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepositoryImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepositoryImpl.kt @@ -111,7 +111,12 @@ constructor( pw.println("Carrier configs by subId") configs.keyIterator().forEach { pw.println(" subId=$it") - pw.println(" config=${configs.get(it).toStringConsideringDefaults()}") + val config = configs.get(it) + if (config == null) { + pw.println(" config=null (config was removed during dump)") + } else { + pw.println(" config=${config.toStringConsideringDefaults()}") + } } // Finally, print the default config pw.println("Default config:") diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyStateInflater.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyStateInflater.kt index cb26679434ef..520c5632370b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyStateInflater.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyStateInflater.kt @@ -408,6 +408,11 @@ constructor( action.extras.getBoolean(Notification.Action.EXTRA_IS_MAGIC, false) ) { background = MagicActionBackgroundDrawable(parent.context) + val textColor = + parent.context.getColor( + com.android.internal.R.color.materialColorOnPrimaryContainer + ) + setTextColor(textColor) } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegate.kt index 9ff0d18f0e2b..c23508f8ae68 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegate.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegate.kt @@ -16,6 +16,8 @@ package com.android.systemui.statusbar.policy.ui.dialog +import android.app.Dialog +import android.content.Context import android.content.Intent import android.provider.Settings import android.util.Log @@ -36,6 +38,7 @@ import com.android.compose.PlatformOutlinedButton import com.android.compose.theme.PlatformTheme import com.android.internal.annotations.VisibleForTesting import com.android.internal.jank.InteractionJankMonitor +import com.android.settingslib.notification.modes.EnableZenModeDialog import com.android.systemui.animation.DialogCuj import com.android.systemui.animation.DialogTransitionAnimator import com.android.systemui.animation.Expandable @@ -43,6 +46,7 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.dialog.ui.composable.AlertDialogContent import com.android.systemui.plugins.ActivityStarter +import com.android.systemui.qs.tiles.dialog.QSZenModeDialogMetricsLogger import com.android.systemui.res.R import com.android.systemui.shade.domain.interactor.ShadeDialogContextInteractor import com.android.systemui.statusbar.phone.ComponentSystemUIDialog @@ -61,6 +65,7 @@ import kotlinx.coroutines.withContext class ModesDialogDelegate @Inject constructor( + val context: Context, private val sysuiDialogFactory: SystemUIDialogFactory, private val dialogTransitionAnimator: DialogTransitionAnimator, private val activityStarter: ActivityStarter, @@ -72,6 +77,7 @@ constructor( ) : SystemUIDialog.Delegate { // NOTE: This should only be accessed/written from the main thread. @VisibleForTesting var currentDialog: ComponentSystemUIDialog? = null + private val zenDialogMetricsLogger by lazy { QSZenModeDialogMetricsLogger(context) } override fun createDialog(): SystemUIDialog { Assert.isMainThread() @@ -195,6 +201,26 @@ constructor( activityStarter.startActivity(intent, /* dismissShade= */ true, animationController) } + /** + * Special dialog to ask the user for the duration of DND. Not to be confused with the modes + * dialog itself. + */ + fun makeDndDurationDialog(): Dialog { + val dialog = + EnableZenModeDialog( + context, + R.style.Theme_SystemUI_Dialog, + /* cancelIsNeutral= */ true, + zenDialogMetricsLogger, + ) + .createDialog() + SystemUIDialog.applyFlags(dialog) + SystemUIDialog.setShowForAllUsers(dialog, true) + SystemUIDialog.registerDismissListener(dialog) + SystemUIDialog.setDialogSize(dialog) + return dialog + } + companion object { private const val TAG = "ModesDialogDelegate" private val ZEN_MODE_SETTINGS_INTENT = Intent(Settings.ACTION_ZEN_MODE_SETTINGS) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt index 1c13a833ef30..07f1c3470c83 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt @@ -16,20 +16,16 @@ package com.android.systemui.statusbar.policy.ui.dialog.viewmodel -import android.app.Dialog import android.content.Context import android.content.Intent import android.provider.Settings.ACTION_AUTOMATIC_ZEN_RULE_SETTINGS import android.provider.Settings.EXTRA_AUTOMATIC_ZEN_RULE_ID -import com.android.settingslib.notification.modes.EnableZenModeDialog import com.android.settingslib.notification.modes.ZenMode import com.android.settingslib.notification.modes.ZenModeDescriptions import com.android.systemui.common.shared.model.asIcon import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background -import com.android.systemui.qs.tiles.dialog.QSZenModeDialogMetricsLogger import com.android.systemui.res.R -import com.android.systemui.statusbar.phone.SystemUIDialog import com.android.systemui.statusbar.policy.domain.interactor.ZenModeInteractor import com.android.systemui.statusbar.policy.ui.dialog.ModesDialogDelegate import com.android.systemui.statusbar.policy.ui.dialog.ModesDialogEventLogger @@ -54,7 +50,6 @@ constructor( private val dialogDelegate: ModesDialogDelegate, private val dialogEventLogger: ModesDialogEventLogger, ) { - private val zenDialogMetricsLogger = QSZenModeDialogMetricsLogger(context) private val zenModeDescriptions = ZenModeDescriptions(context) // Modes that should be displayed in the dialog @@ -112,7 +107,7 @@ constructor( if (zenModeInteractor.shouldAskForZenDuration(mode)) { dialogEventLogger.logOpenDurationDialog(mode) // NOTE: The dialog handles turning on the mode itself. - val dialog = makeZenModeDialog() + val dialog = dialogDelegate.makeDndDurationDialog() dialog.show() } else { dialogEventLogger.logModeOn(mode) @@ -173,20 +168,4 @@ constructor( modeDescription ?: context.getString(R.string.zen_mode_off) } } - - private fun makeZenModeDialog(): Dialog { - val dialog = - EnableZenModeDialog( - context, - R.style.Theme_SystemUI_Dialog, - /* cancelIsNeutral= */ true, - zenDialogMetricsLogger, - ) - .createDialog() - SystemUIDialog.applyFlags(dialog) - SystemUIDialog.setShowForAllUsers(dialog, true) - SystemUIDialog.registerDismissListener(dialog) - SystemUIDialog.setDialogSize(dialog) - return dialog - } } diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/TouchpadTutorialModule.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/TouchpadTutorialModule.kt index c43f31beb5bc..a2125c8f0955 100644 --- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/TouchpadTutorialModule.kt +++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/TouchpadTutorialModule.kt @@ -39,6 +39,8 @@ import com.android.systemui.touchpad.tutorial.ui.viewmodel.HomeGestureRecognizer import com.android.systemui.touchpad.tutorial.ui.viewmodel.HomeGestureScreenViewModel import com.android.systemui.touchpad.tutorial.ui.viewmodel.RecentAppsGestureRecognizerProvider import com.android.systemui.touchpad.tutorial.ui.viewmodel.RecentAppsGestureScreenViewModel +import com.android.systemui.touchpad.tutorial.ui.viewmodel.SwitchAppsGestureRecognizerProvider +import com.android.systemui.touchpad.tutorial.ui.viewmodel.SwitchAppsGestureScreenViewModel import dagger.Binds import dagger.Module import dagger.Provides @@ -69,6 +71,14 @@ interface TouchpadTutorialModule { } @Provides + fun switchAppsViewModel( + recognizerProvider: SwitchAppsGestureRecognizerProvider, + adapterFactory: GestureRecognizerAdapter.Factory, + ): SwitchAppsGestureScreenViewModel { + return SwitchAppsGestureScreenViewModel(adapterFactory.create(recognizerProvider)) + } + + @Provides fun recentAppsViewModel( recognizerProvider: RecentAppsGestureRecognizerProvider, adapterFactory: GestureRecognizerAdapter.Factory, diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/SwitchAppsGestureTutorialScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/SwitchAppsGestureTutorialScreen.kt new file mode 100644 index 000000000000..3bb0dd779613 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/SwitchAppsGestureTutorialScreen.kt @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2025 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.composable + +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import com.airbnb.lottie.compose.rememberLottieDynamicProperties +import com.android.compose.theme.LocalAndroidColorScheme +import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialScreenConfig +import com.android.systemui.inputdevice.tutorial.ui.composable.rememberColorFilterProperty +import com.android.systemui.res.R +import com.android.systemui.touchpad.tutorial.ui.viewmodel.EasterEggGestureViewModel +import com.android.systemui.touchpad.tutorial.ui.viewmodel.SwitchAppsGestureScreenViewModel + +@Composable +fun SwitchAppsGestureTutorialScreen( + viewModel: SwitchAppsGestureScreenViewModel, + easterEggGestureViewModel: EasterEggGestureViewModel, + onDoneButtonClicked: () -> Unit, + onBack: () -> Unit, +) { + val screenConfig = + TutorialScreenConfig( + colors = rememberScreenColors(), + strings = + TutorialScreenConfig.Strings( + titleResId = R.string.touchpad_switch_apps_gesture_action_title, + bodyResId = R.string.touchpad_switch_apps_gesture_guidance, + titleSuccessResId = R.string.touchpad_switch_apps_gesture_success_title, + bodySuccessResId = R.string.touchpad_switch_apps_gesture_success_body, + titleErrorResId = R.string.gesture_error_title, + bodyErrorResId = R.string.touchpad_switch_gesture_error_body, + ), + // TODO: replace animation + animations = TutorialScreenConfig.Animations(educationResId = R.raw.trackpad_back_edu), + ) + GestureTutorialScreen( + screenConfig = screenConfig, + tutorialStateFlow = viewModel.tutorialState, + motionEventConsumer = { + easterEggGestureViewModel.accept(it) + viewModel.handleEvent(it) + }, + easterEggTriggeredFlow = easterEggGestureViewModel.easterEggTriggered, + onEasterEggFinished = easterEggGestureViewModel::onEasterEggFinished, + onDoneButtonClicked = onDoneButtonClicked, + onBack = onBack, + ) +} + +@Composable +private fun rememberScreenColors(): TutorialScreenConfig.Colors { + val onTertiary = MaterialTheme.colorScheme.onTertiary + val onTertiaryFixed = LocalAndroidColorScheme.current.onTertiaryFixed + val onTertiaryFixedVariant = LocalAndroidColorScheme.current.onTertiaryFixedVariant + val tertiaryFixedDim = LocalAndroidColorScheme.current.tertiaryFixedDim + val dynamicProperties = + rememberLottieDynamicProperties( + rememberColorFilterProperty(".tertiaryFixedDim", tertiaryFixedDim), + rememberColorFilterProperty(".onTertiaryFixed", onTertiaryFixed), + rememberColorFilterProperty(".onTertiary", onTertiary), + rememberColorFilterProperty(".onTertiaryFixedVariant", onTertiaryFixedVariant), + ) + val screenColors = + remember(dynamicProperties) { + TutorialScreenConfig.Colors( + background = onTertiaryFixed, + title = tertiaryFixedDim, + animationColors = dynamicProperties, + ) + } + return screenColors +} 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 c8a58400069e..69b7e892a380 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 @@ -61,6 +61,7 @@ fun TutorialSelectionScreen( onBackTutorialClicked: () -> Unit, onHomeTutorialClicked: () -> Unit, onRecentAppsTutorialClicked: () -> Unit, + onSwitchAppsTutorialClicked: () -> Unit, onDoneButtonClicked: () -> Unit, lastSelectedScreen: Screen, ) { @@ -86,6 +87,7 @@ fun TutorialSelectionScreen( onBackTutorialClicked = onBackTutorialClicked, onHomeTutorialClicked = onHomeTutorialClicked, onRecentAppsTutorialClicked = onRecentAppsTutorialClicked, + onSwitchAppsTutorialClicked = onSwitchAppsTutorialClicked, modifier = Modifier.weight(1f).padding(60.dp), lastSelectedScreen, ) @@ -95,6 +97,7 @@ fun TutorialSelectionScreen( onBackTutorialClicked = onBackTutorialClicked, onHomeTutorialClicked = onHomeTutorialClicked, onRecentAppsTutorialClicked = onRecentAppsTutorialClicked, + onSwitchAppsTutorialClicked = onSwitchAppsTutorialClicked, modifier = Modifier.weight(1f).padding(60.dp), lastSelectedScreen, ) @@ -113,6 +116,7 @@ private fun HorizontalSelectionButtons( onBackTutorialClicked: () -> Unit, onHomeTutorialClicked: () -> Unit, onRecentAppsTutorialClicked: () -> Unit, + onSwitchAppsTutorialClicked: () -> Unit, modifier: Modifier = Modifier, lastSelectedScreen: Screen, ) { @@ -121,10 +125,11 @@ private fun HorizontalSelectionButtons( verticalAlignment = Alignment.CenterVertically, modifier = modifier, ) { - ThreeTutorialButtons( + FourTutorialButtons( onBackTutorialClicked, onHomeTutorialClicked, onRecentAppsTutorialClicked, + onSwitchAppsTutorialClicked, modifier = Modifier.weight(1f).fillMaxSize(), lastSelectedScreen, ) @@ -136,6 +141,7 @@ private fun VerticalSelectionButtons( onBackTutorialClicked: () -> Unit, onHomeTutorialClicked: () -> Unit, onRecentAppsTutorialClicked: () -> Unit, + onSwitchAppsTutorialClicked: () -> Unit, modifier: Modifier = Modifier, lastSelectedScreen: Screen, ) { @@ -144,10 +150,11 @@ private fun VerticalSelectionButtons( horizontalAlignment = Alignment.CenterHorizontally, modifier = modifier, ) { - ThreeTutorialButtons( + FourTutorialButtons( onBackTutorialClicked, onHomeTutorialClicked, onRecentAppsTutorialClicked, + onSwitchAppsTutorialClicked, modifier = Modifier.weight(1f).fillMaxSize(), lastSelectedScreen, ) @@ -155,21 +162,24 @@ private fun VerticalSelectionButtons( } @Composable -private fun ThreeTutorialButtons( +private fun FourTutorialButtons( onBackTutorialClicked: () -> Unit, onHomeTutorialClicked: () -> Unit, onRecentAppsTutorialClicked: () -> Unit, + onSwitchAppsTutorialClicked: () -> Unit, modifier: Modifier = Modifier, lastSelectedScreen: Screen, ) { val homeFocusRequester = remember { FocusRequester() } val backFocusRequester = remember { FocusRequester() } val recentAppsFocusRequester = remember { FocusRequester() } + val switchAppsFocusRequester = remember { FocusRequester() } LaunchedEffect(Unit) { when (lastSelectedScreen) { Screen.HOME_GESTURE -> homeFocusRequester.requestFocus() Screen.BACK_GESTURE -> backFocusRequester.requestFocus() Screen.RECENT_APPS_GESTURE -> recentAppsFocusRequester.requestFocus() + Screen.SWITCH_APPS_GESTURE -> switchAppsFocusRequester.requestFocus() else -> {} // No-Op. } } @@ -197,6 +207,14 @@ private fun ThreeTutorialButtons( backgroundColor = MaterialTheme.colorScheme.secondary, modifier = modifier.focusRequester(recentAppsFocusRequester).focusable(), ) + TutorialButton( + text = stringResource(R.string.touchpad_tutorial_switch_apps_gesture_button), + icon = ImageVector.vectorResource(id = R.drawable.touchpad_tutorial_apps_icon), + iconColor = MaterialTheme.colorScheme.primary, + onClick = onSwitchAppsTutorialClicked, + backgroundColor = MaterialTheme.colorScheme.onPrimary, + modifier = modifier.focusRequester(switchAppsFocusRequester).focusable(), + ) } @Composable @@ -227,7 +245,7 @@ private fun TutorialButton( tint = iconColor, ) Spacer(modifier = Modifier.height(16.dp)) - Text(text = text, style = MaterialTheme.typography.headlineLarge) + Text(text = text, style = MaterialTheme.typography.headlineLarge, color = iconColor) } } } diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/SwitchAppsGestureRecognizer.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/SwitchAppsGestureRecognizer.kt new file mode 100644 index 000000000000..470048bd3b20 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/SwitchAppsGestureRecognizer.kt @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2025 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 + +// TODO: javadoc +class SwitchAppsGestureRecognizer(private val gestureDistanceThresholdPx: Int) : GestureRecognizer { + + private val distanceTracker = DistanceTracker() + private var gestureStateChangedCallback: (GestureState) -> Unit = {} + + override fun addGestureStateCallback(callback: (GestureState) -> Unit) { + gestureStateChangedCallback = callback + } + + override fun clearGestureStateCallback() { + gestureStateChangedCallback = {} + } + + // TODO: recognizer logic + override fun accept(event: MotionEvent) { + if (!isMultifingerTouchpadSwipe(event)) return + } +} diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/view/TouchpadTutorialActivity.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/view/TouchpadTutorialActivity.kt index 3264300ed908..0a139125afa2 100644 --- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/view/TouchpadTutorialActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/view/TouchpadTutorialActivity.kt @@ -37,6 +37,7 @@ import com.android.systemui.res.R import com.android.systemui.touchpad.tutorial.ui.composable.BackGestureTutorialScreen import com.android.systemui.touchpad.tutorial.ui.composable.HomeGestureTutorialScreen import com.android.systemui.touchpad.tutorial.ui.composable.RecentAppsGestureTutorialScreen +import com.android.systemui.touchpad.tutorial.ui.composable.SwitchAppsGestureTutorialScreen import com.android.systemui.touchpad.tutorial.ui.composable.TutorialSelectionScreen import com.android.systemui.touchpad.tutorial.ui.viewmodel.BackGestureScreenViewModel import com.android.systemui.touchpad.tutorial.ui.viewmodel.EasterEggGestureViewModel @@ -45,7 +46,9 @@ import com.android.systemui.touchpad.tutorial.ui.viewmodel.RecentAppsGestureScre import com.android.systemui.touchpad.tutorial.ui.viewmodel.Screen.BACK_GESTURE import com.android.systemui.touchpad.tutorial.ui.viewmodel.Screen.HOME_GESTURE import com.android.systemui.touchpad.tutorial.ui.viewmodel.Screen.RECENT_APPS_GESTURE +import com.android.systemui.touchpad.tutorial.ui.viewmodel.Screen.SWITCH_APPS_GESTURE import com.android.systemui.touchpad.tutorial.ui.viewmodel.Screen.TUTORIAL_SELECTION +import com.android.systemui.touchpad.tutorial.ui.viewmodel.SwitchAppsGestureScreenViewModel import com.android.systemui.touchpad.tutorial.ui.viewmodel.TouchpadTutorialViewModel import javax.inject.Inject @@ -58,6 +61,7 @@ constructor( private val backGestureViewModel: BackGestureScreenViewModel, private val homeGestureViewModel: HomeGestureScreenViewModel, private val recentAppsGestureViewModel: RecentAppsGestureScreenViewModel, + private val switchAppsGestureScreenViewModel: SwitchAppsGestureScreenViewModel, private val easterEggGestureViewModel: EasterEggGestureViewModel, ) : ComponentActivity() { @@ -75,6 +79,7 @@ constructor( backGestureViewModel, homeGestureViewModel, recentAppsGestureViewModel, + switchAppsGestureScreenViewModel, easterEggGestureViewModel, closeTutorial = ::finishTutorial, ) @@ -108,6 +113,7 @@ fun TouchpadTutorialScreen( backGestureViewModel: BackGestureScreenViewModel, homeGestureViewModel: HomeGestureScreenViewModel, recentAppsGestureViewModel: RecentAppsGestureScreenViewModel, + switchAppsGestureScreenViewModel: SwitchAppsGestureScreenViewModel, easterEggGestureViewModel: EasterEggGestureViewModel, closeTutorial: () -> Unit, ) { @@ -128,6 +134,10 @@ fun TouchpadTutorialScreen( lastSelectedScreen = RECENT_APPS_GESTURE vm.goTo(RECENT_APPS_GESTURE) }, + onSwitchAppsTutorialClicked = { + lastSelectedScreen = SWITCH_APPS_GESTURE + vm.goTo(SWITCH_APPS_GESTURE) + }, onDoneButtonClicked = closeTutorial, lastSelectedScreen, ) @@ -152,5 +162,12 @@ fun TouchpadTutorialScreen( onDoneButtonClicked = { vm.goTo(TUTORIAL_SELECTION) }, onBack = { vm.goTo(TUTORIAL_SELECTION) }, ) + SWITCH_APPS_GESTURE -> + SwitchAppsGestureTutorialScreen( + switchAppsGestureScreenViewModel, + easterEggGestureViewModel, + onDoneButtonClicked = { vm.goTo(SWITCH_APPS_GESTURE) }, + onBack = { vm.goTo(TUTORIAL_SELECTION) }, + ) } } diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/SwitchAppsGestureRecognizerProvider.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/SwitchAppsGestureRecognizerProvider.kt new file mode 100644 index 000000000000..b1e163b55377 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/SwitchAppsGestureRecognizerProvider.kt @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2025 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.viewmodel + +import com.android.systemui.touchpad.tutorial.ui.gesture.GestureRecognizer +import com.android.systemui.touchpad.tutorial.ui.gesture.SwitchAppsGestureRecognizer +import com.android.systemui.touchpad.tutorial.ui.gesture.VelocityTracker +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map + +class SwitchAppsGestureRecognizerProvider +@Inject +constructor(val resources: TouchpadGestureResources, val velocityTracker: VelocityTracker) : + GestureRecognizerProvider { + + override val recognizer: Flow<GestureRecognizer> = + resources.distanceThreshold().map { + SwitchAppsGestureRecognizer(gestureDistanceThresholdPx = it) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/SwitchAppsGestureScreenViewModel.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/SwitchAppsGestureScreenViewModel.kt new file mode 100644 index 000000000000..6593db49745d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/SwitchAppsGestureScreenViewModel.kt @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2025 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.viewmodel + +import android.view.MotionEvent +import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState +import com.android.systemui.res.R +import com.android.systemui.touchpad.tutorial.ui.gesture.handleTouchpadMotionEvent +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map + +class SwitchAppsGestureScreenViewModel(private val gestureRecognizer: GestureRecognizerAdapter) : + TouchpadTutorialScreenViewModel { + + // TODO: replace with correct markers and resource + override val tutorialState: Flow<TutorialActionState> = + gestureRecognizer.gestureState + .map { + it to + TutorialAnimationProperties( + progressStartMarker = "drag with gesture", + progressEndMarker = "onPause", + successAnimation = R.raw.trackpad_recent_apps_success, + ) + } + .mapToTutorialState() + + override fun handleEvent(event: MotionEvent): Boolean { + return gestureRecognizer.handleTouchpadMotionEvent(event) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/TouchpadTutorialViewModel.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/TouchpadTutorialViewModel.kt index c56dcf3bf062..c6d5e7ad0a71 100644 --- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/TouchpadTutorialViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/TouchpadTutorialViewModel.kt @@ -27,7 +27,7 @@ import kotlinx.coroutines.flow.StateFlow class TouchpadTutorialViewModel( private val gesturesInteractor: TouchpadGesturesInteractor, - private val logger: InputDeviceTutorialLogger + private val logger: InputDeviceTutorialLogger, ) : ViewModel() { private val _screen = MutableStateFlow(Screen.TUTORIAL_SELECTION) @@ -50,7 +50,7 @@ class TouchpadTutorialViewModel( @Inject constructor( private val gesturesInteractor: TouchpadGesturesInteractor, - private val logger: InputDeviceTutorialLogger + private val logger: InputDeviceTutorialLogger, ) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") @@ -65,4 +65,5 @@ enum class Screen { BACK_GESTURE, HOME_GESTURE, RECENT_APPS_GESTURE, + SWITCH_APPS_GESTURE, } diff --git a/packages/SystemUI/src/com/android/systemui/window/flag/WindowBlurFlag.kt b/packages/SystemUI/src/com/android/systemui/window/flag/WindowBlurFlag.kt deleted file mode 100644 index 8b6c8601f5d2..000000000000 --- a/packages/SystemUI/src/com/android/systemui/window/flag/WindowBlurFlag.kt +++ /dev/null @@ -1,32 +0,0 @@ -/* - * 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.window.flag - -import com.android.systemui.Flags - -/** - * Flag that controls whether the background surface is blurred or not while on the - * lockscreen/shade/bouncer. This makes the background of scrim, bouncer and few other opaque - * surfaces transparent so that we can see the blur effect on the background surface (wallpaper). - */ -object WindowBlurFlag { - /** Whether the blur is enabled or not */ - @JvmStatic - val isEnabled - // Add flags here that require scrims/background surfaces to be transparent. - get() = Flags.notificationShadeBlur() || Flags.bouncerUiRevamp() -} diff --git a/packages/SystemUI/src/com/android/systemui/window/ui/WindowRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/window/ui/WindowRootViewBinder.kt index 2491ca7565c7..d2069cfdfdc6 100644 --- a/packages/SystemUI/src/com/android/systemui/window/ui/WindowRootViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/window/ui/WindowRootViewBinder.kt @@ -19,12 +19,12 @@ package com.android.systemui.window.ui import android.util.Log import android.view.Choreographer import android.view.Choreographer.FrameCallback +import com.android.systemui.Flags import com.android.systemui.lifecycle.WindowLifecycleState import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.lifecycle.viewModel import com.android.systemui.scene.ui.view.WindowRootView import com.android.systemui.statusbar.BlurUtils -import com.android.systemui.window.flag.WindowBlurFlag import com.android.systemui.window.ui.viewmodel.WindowRootViewModel import kotlinx.coroutines.awaitCancellation import kotlinx.coroutines.flow.filter @@ -43,7 +43,7 @@ object WindowRootViewBinder { blurUtils: BlurUtils?, choreographer: Choreographer?, ) { - if (!WindowBlurFlag.isEnabled) return + if (!Flags.bouncerUiRevamp()) return if (blurUtils == null || choreographer == null) return view.repeatWhenAttached { diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java index a17f100904be..e5376d26840d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java @@ -88,8 +88,13 @@ public class RecordingControllerTest extends SysuiTestCase { private ScreenRecordPermissionDialogDelegate.Factory mScreenRecordPermissionDialogDelegateFactory; @Mock + private ScreenRecordPermissionViewBinder.Factory + mScreenRecordPermissionViewBinderFactory; + @Mock private ScreenRecordPermissionDialogDelegate mScreenRecordPermissionDialogDelegate; @Mock + private ScreenRecordPermissionViewBinder mScreenRecordPermissionViewBinder; + @Mock private SystemUIDialog mScreenRecordSystemUIDialog; private RecordingController mController; @@ -106,6 +111,8 @@ public class RecordingControllerTest extends SysuiTestCase { .thenReturn(mScreenCaptureDisabledDialog); when(mScreenRecordPermissionDialogDelegateFactory.create(any(), any(), anyInt(), any())) .thenReturn(mScreenRecordPermissionDialogDelegate); + when(mScreenRecordPermissionViewBinderFactory.create(any(), anyInt(), any(), any())) + .thenReturn(mScreenRecordPermissionViewBinder); when(mScreenRecordPermissionDialogDelegate.createDialog()) .thenReturn(mScreenRecordSystemUIDialog); mController = new RecordingController( @@ -116,7 +123,8 @@ public class RecordingControllerTest extends SysuiTestCase { new RecordingControllerLogger(logcatLogBuffer("RecordingControllerTest")), mMediaProjectionMetricsLogger, mScreenCaptureDisabledDialogDelegate, - mScreenRecordPermissionDialogDelegateFactory + mScreenRecordPermissionDialogDelegateFactory, + mScreenRecordPermissionViewBinderFactory ); mController.addCallback(mCallback); } @@ -238,6 +246,26 @@ public class RecordingControllerTest extends SysuiTestCase { } @Test + public void testCreateScreenRecordPermissionViewBinder() { + ScreenRecordPermissionViewBinder viewBinder = + mController.createScreenRecordPermissionViewBinder( + /* onStartRecordingClicked= */ null); + assertThat(viewBinder).isEqualTo(mScreenRecordPermissionViewBinder); + } + + @Test + public void testScreenCapturingAllowed_returnsFalseIsScreenCaptureDisabled() { + when(mDevicePolicyResolver.isScreenCaptureCompletelyDisabled((any()))).thenReturn(false); + assertFalse(mController.isScreenCaptureDisabled()); + } + + @Test + public void testScreenCapturingNotAllowed_returnsTrueIsScreenCaptureDisabled() { + when(mDevicePolicyResolver.isScreenCaptureCompletelyDisabled((any()))).thenReturn(true); + assertTrue(mController.isScreenCaptureDisabled()); + } + + @Test public void testScreenCapturingAllowed_logsProjectionInitiated() { when(mDevicePolicyResolver.isScreenCaptureCompletelyDisabled((any()))).thenReturn(false); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java index 72d1db3affe8..281ce16b539f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java @@ -22,6 +22,7 @@ import static android.app.Notification.CATEGORY_EVENT; import static android.app.Notification.CATEGORY_MESSAGE; import static android.app.Notification.CATEGORY_REMINDER; import static android.app.Notification.FLAG_FSI_REQUESTED_BUT_DENIED; +import static android.app.Notification.FLAG_PROMOTED_ONGOING; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_AMBIENT; import static com.android.systemui.statusbar.NotificationEntryHelper.modifyRanking; @@ -43,6 +44,8 @@ import android.graphics.drawable.Icon; import android.media.session.MediaSession; import android.os.Bundle; import android.os.UserHandle; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; import android.service.notification.NotificationListenerService.Ranking; import android.service.notification.SnoozeCriterion; import android.service.notification.StatusBarNotification; @@ -54,6 +57,8 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.res.R; import com.android.systemui.statusbar.RankingBuilder; import com.android.systemui.statusbar.SbnBuilder; +import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips; +import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUi; import com.android.systemui.util.time.FakeSystemClock; import org.junit.Before; @@ -280,6 +285,40 @@ public class NotificationEntryTest extends SysuiTestCase { } @Test + @EnableFlags({PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME}) + public void isPromotedOngoing_noFlagOnNotif_false() { + mEntry.getSbn().getNotification().flags &= ~FLAG_PROMOTED_ONGOING; + + assertFalse(mEntry.isPromotedOngoing()); + } + + @Test + @DisableFlags({PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME}) + public void isPromotedOngoing_statusBarNotifChipsFlagAndUiFlagOff_false() { + mEntry.getSbn().getNotification().flags |= FLAG_PROMOTED_ONGOING; + + assertFalse(mEntry.isPromotedOngoing()); + } + + @Test + @EnableFlags(PromotedNotificationUi.FLAG_NAME) + @DisableFlags(StatusBarNotifChips.FLAG_NAME) + public void isPromotedOngoing_uiFlagOnAndNotifHasFlag_true() { + mEntry.getSbn().getNotification().flags |= FLAG_PROMOTED_ONGOING; + + assertTrue(mEntry.isPromotedOngoing()); + } + + @Test + @EnableFlags(StatusBarNotifChips.FLAG_NAME) + @DisableFlags(PromotedNotificationUi.FLAG_NAME) + public void isPromotedOngoing_statusBarNotifChipsFlagOnAndNotifHasFlag_true() { + mEntry.getSbn().getNotification().flags |= FLAG_PROMOTED_ONGOING; + + assertTrue(mEntry.isPromotedOngoing()); + } + + @Test public void testIsNotificationVisibilityPrivate_true() { assertTrue(mEntry.isNotificationVisibilityPrivate()); } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/DismissKeyguardInteractor.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissTransitionInteractor.kt index 82a5311269f0..56a7b4db5455 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/DismissKeyguardInteractor.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissTransitionInteractor.kt @@ -18,10 +18,12 @@ package com.android.systemui.keyguard.domain.interactor import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.testScope val Kosmos.keyguardDismissTransitionInteractor: KeyguardDismissTransitionInteractor by Kosmos.Fixture { KeyguardDismissTransitionInteractor( + scope = testScope, repository = keyguardTransitionRepository, fromLockscreenTransitionInteractor = fromLockscreenTransitionInteractor, fromPrimaryBouncerTransitionInteractor = fromPrimaryBouncerTransitionInteractor, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorKosmos.kt index 3c37101cfe66..13cbddff8803 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorKosmos.kt @@ -20,6 +20,7 @@ import com.android.systemui.kosmos.Kosmos import com.android.systemui.qs.tiles.base.actions.qsTileIntentUserInputHandler import com.android.systemui.statusbar.policy.domain.interactor.zenModeInteractor import com.android.systemui.statusbar.policy.ui.dialog.modesDialogDelegate +import com.android.systemui.statusbar.policy.ui.dialog.modesDialogEventLogger import javax.inject.Provider val Kosmos.modesTileUserActionInteractor: ModesTileUserActionInteractor by @@ -28,5 +29,6 @@ val Kosmos.modesTileUserActionInteractor: ModesTileUserActionInteractor by qsTileIntentUserInputHandler, Provider { modesDialogDelegate }.get(), zenModeInteractor, + modesDialogEventLogger, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallModelBuilder.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallModelBuilder.kt index 766b280334c8..f4e74fe0e6bb 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallModelBuilder.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallModelBuilder.kt @@ -18,6 +18,7 @@ package com.android.systemui.statusbar.phone.ongoingcall.shared.model import android.app.PendingIntent import com.android.systemui.statusbar.StatusBarIconView +import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel /** Helper for building [OngoingCallModel.InCall] instances in tests. */ fun inCallModel( @@ -25,4 +26,5 @@ fun inCallModel( notificationIcon: StatusBarIconView? = null, intent: PendingIntent? = null, notificationKey: String = "test", -) = OngoingCallModel.InCall(startTimeMs, notificationIcon, intent, notificationKey) + promotedContent: PromotedNotificationContentModel? = null, +) = OngoingCallModel.InCall(startTimeMs, notificationIcon, intent, notificationKey, promotedContent) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateKosmos.kt index 6c98d19db5d7..ef043e0177a5 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateKosmos.kt @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.policy.ui.dialog +import android.content.mockedContext import com.android.systemui.animation.dialogTransitionAnimator import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.mainCoroutineContext @@ -30,6 +31,7 @@ val Kosmos.mockModesDialogDelegate by Kosmos.Fixture { mock<ModesDialogDelegate> var Kosmos.modesDialogDelegate: ModesDialogDelegate by Kosmos.Fixture { ModesDialogDelegate( + mockedContext, systemUIDialogFactory, dialogTransitionAnimator, activityStarter, diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java index 79888b051c54..70c4c1311fc9 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java @@ -21,14 +21,14 @@ import static android.view.MotionEvent.ACTION_SCROLL; import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY; import static android.view.WindowManager.LayoutParams.TYPE_MAGNIFICATION_OVERLAY; +import static com.android.hardware.input.Flags.enableTalkbackAndMagnifierKeyGestures; + import android.accessibilityservice.AccessibilityTrace; import android.annotation.MainThread; import android.annotation.NonNull; import android.content.Context; import android.graphics.Region; import android.hardware.input.InputManager; -import android.hardware.input.KeyGestureEvent; -import android.os.IBinder; import android.os.Looper; import android.os.PowerManager; import android.os.SystemClock; @@ -46,15 +46,13 @@ import android.view.MotionEvent.PointerCoords; import android.view.MotionEvent.PointerProperties; import android.view.accessibility.AccessibilityEvent; -import androidx.annotation.Nullable; - import com.android.server.LocalServices; import com.android.server.accessibility.gestures.TouchExplorer; import com.android.server.accessibility.magnification.FullScreenMagnificationController; import com.android.server.accessibility.magnification.FullScreenMagnificationGestureHandler; import com.android.server.accessibility.magnification.FullScreenMagnificationVibrationHelper; -import com.android.server.accessibility.magnification.MagnificationController; import com.android.server.accessibility.magnification.MagnificationGestureHandler; +import com.android.server.accessibility.magnification.MagnificationKeyHandler; import com.android.server.accessibility.magnification.MouseEventHandler; import com.android.server.accessibility.magnification.WindowMagnificationGestureHandler; import com.android.server.accessibility.magnification.WindowMagnificationPromptController; @@ -209,6 +207,8 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo private MouseKeysInterceptor mMouseKeysInterceptor; + private MagnificationKeyHandler mMagnificationKeyHandler; + private boolean mInstalled; private int mUserId; @@ -235,74 +235,6 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo */ private MotionEvent mLastActiveDeviceMotionEvent = null; - private boolean mKeyGestureEventHandlerInstalled = false; - private InputManager.KeyGestureEventHandler mKeyGestureEventHandler = - new InputManager.KeyGestureEventHandler() { - @Override - public boolean handleKeyGestureEvent( - @NonNull KeyGestureEvent event, - @Nullable IBinder focusedToken) { - final boolean complete = - event.getAction() == KeyGestureEvent.ACTION_GESTURE_COMPLETE - && !event.isCancelled(); - - // TODO(b/355499907): Receive and handle held key gestures, which can be used - // for continuous scaling and panning. In addition, handle multiple pan gestures - // at the same time (e.g. user may try to pan diagonally) reasonably, including - // decreasing diagonal movement by sqrt(2) to make it appear the same speed - // as non-diagonal movement. - - if (!complete) { - return false; - } - - final int gestureType = event.getKeyGestureType(); - final int displayId = isDisplayIdValid(event.getDisplayId()) - ? event.getDisplayId() : Display.DEFAULT_DISPLAY; - - switch (gestureType) { - case KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFICATION_ZOOM_IN: - mAms.getMagnificationController().scaleMagnificationByStep( - displayId, MagnificationController.ZOOM_DIRECTION_IN); - return true; - case KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFICATION_ZOOM_OUT: - mAms.getMagnificationController().scaleMagnificationByStep( - displayId, MagnificationController.ZOOM_DIRECTION_OUT); - return true; - case KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFICATION_PAN_LEFT: - mAms.getMagnificationController().panMagnificationByStep( - displayId, MagnificationController.PAN_DIRECTION_LEFT); - return true; - case KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFICATION_PAN_RIGHT: - mAms.getMagnificationController().panMagnificationByStep( - displayId, MagnificationController.PAN_DIRECTION_RIGHT); - return true; - case KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFICATION_PAN_UP: - mAms.getMagnificationController().panMagnificationByStep( - displayId, MagnificationController.PAN_DIRECTION_UP); - return true; - case KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFICATION_PAN_DOWN: - mAms.getMagnificationController().panMagnificationByStep( - displayId, MagnificationController.PAN_DIRECTION_DOWN); - return true; - } - return false; - } - - @Override - public boolean isKeyGestureSupported(int gestureType) { - return switch (gestureType) { - case KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFICATION_ZOOM_IN, - KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFICATION_ZOOM_OUT, - KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFICATION_PAN_LEFT, - KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFICATION_PAN_RIGHT, - KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFICATION_PAN_UP, - KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFICATION_PAN_DOWN -> true; - default -> false; - }; - } - }; - private static MotionEvent cancelMotion(MotionEvent event) { if (event.getActionMasked() == MotionEvent.ACTION_CANCEL || event.getActionMasked() == MotionEvent.ACTION_HOVER_EXIT @@ -787,20 +719,11 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo }); } - if ((mEnabledFeatures & FLAG_FEATURE_CONTROL_SCREEN_MAGNIFIER) != 0 - || ((mEnabledFeatures & FLAG_FEATURE_MAGNIFICATION_SINGLE_FINGER_TRIPLE_TAP) != 0) - || ((mEnabledFeatures & FLAG_FEATURE_MAGNIFICATION_TWO_FINGER_TRIPLE_TAP) != 0) - || ((mEnabledFeatures & FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER) != 0)) { + if (isAnyMagnificationEnabled()) { final MagnificationGestureHandler magnificationGestureHandler = createMagnificationGestureHandler(displayId, displayContext); addFirstEventHandler(displayId, magnificationGestureHandler); mMagnificationGestureHandler.put(displayId, magnificationGestureHandler); - - if (com.android.hardware.input.Flags.enableTalkbackAndMagnifierKeyGestures() - && !mKeyGestureEventHandlerInstalled) { - mInputManager.registerKeyGestureEventHandler(mKeyGestureEventHandler); - mKeyGestureEventHandlerInstalled = true; - } } if ((mEnabledFeatures & FLAG_FEATURE_INJECT_MOTION_EVENTS) != 0) { @@ -817,6 +740,8 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo } if ((mEnabledFeatures & FLAG_FEATURE_FILTER_KEY_EVENTS) != 0) { + // mKeyboardInterceptor does not forward KeyEvents to other EventStreamTransformations, + // so it must be the last EventStreamTransformation for key events in the list. mKeyboardInterceptor = new KeyboardInterceptor(mAms, LocalServices.getService(WindowManagerPolicy.class)); // Since the display id of KeyEvent always would be -1 and it would be dispatched to @@ -832,6 +757,19 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo Display.DEFAULT_DISPLAY); addFirstEventHandler(Display.DEFAULT_DISPLAY, mMouseKeysInterceptor); } + + if (enableTalkbackAndMagnifierKeyGestures() && isAnyMagnificationEnabled()) { + mMagnificationKeyHandler = new MagnificationKeyHandler( + mAms.getMagnificationController()); + addFirstEventHandler(Display.DEFAULT_DISPLAY, mMagnificationKeyHandler); + } + } + + private boolean isAnyMagnificationEnabled() { + return (mEnabledFeatures & FLAG_FEATURE_CONTROL_SCREEN_MAGNIFIER) != 0 + || ((mEnabledFeatures & FLAG_FEATURE_MAGNIFICATION_SINGLE_FINGER_TRIPLE_TAP) != 0) + || ((mEnabledFeatures & FLAG_FEATURE_MAGNIFICATION_TWO_FINGER_TRIPLE_TAP) != 0) + || ((mEnabledFeatures & FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER) != 0); } /** @@ -921,9 +859,9 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo mMouseKeysInterceptor = null; } - if (mKeyGestureEventHandlerInstalled) { - mInputManager.unregisterKeyGestureEventHandler(mKeyGestureEventHandler); - mKeyGestureEventHandlerInstalled = false; + if (mMagnificationKeyHandler != null) { + mMagnificationKeyHandler.onDestroy(); + mMagnificationKeyHandler = null; } } @@ -1365,6 +1303,8 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo joiner.add("AutoclickController"); } else if (next instanceof MotionEventInjector) { joiner.add("MotionEventInjector"); + } else if (next instanceof MagnificationKeyHandler) { + joiner.add("MagnificationKeyHandler"); } next = next.getNext(); } diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java index 2e131b696afc..75ec8ea88ace 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java @@ -84,7 +84,7 @@ import java.util.concurrent.Executor; * is done and before invoking {@link TransitionCallBack#onResult}. */ public class MagnificationController implements MagnificationConnectionManager.Callback, - MagnificationGestureHandler.Callback, + MagnificationGestureHandler.Callback, MagnificationKeyHandler.Callback, FullScreenMagnificationController.MagnificationInfoChangedCallback, WindowManagerInternal.AccessibilityControllerInternal.UiChangesForAccessibilityCallbacks { @@ -347,6 +347,36 @@ public class MagnificationController implements MagnificationConnectionManager.C handleUserInteractionChanged(displayId, mode); } + @Override + public void onPanMagnificationStart(int displayId, + @MagnificationController.PanDirection int direction) { + // TODO(b/355499907): Handle multiple pan gestures at the same time (e.g. user may try to + // pan diagonally) by decreasing diagonal movement by sqrt(2) to make it appear the same + // speed as non-diagonal movement. + panMagnificationByStep(displayId, direction); + } + + @Override + public void onPanMagnificationStop(int displayId, + @MagnificationController.PanDirection int direction) { + // TODO(b/388847283): Handle held key gestures, which can be used + // for continuous scaling and panning, until they are released. + + } + + @Override + public void onScaleMagnificationStart(int displayId, + @MagnificationController.ZoomDirection int direction) { + scaleMagnificationByStep(displayId, direction); + } + + @Override + public void onScaleMagnificationStop(int displayId, + @MagnificationController.ZoomDirection int direction) { + // TODO(b/388847283): Handle held key gestures, which can be used + // for continuous scaling and panning, until they are released. + } + private void handleUserInteractionChanged(int displayId, int mode) { if (mMagnificationCapabilities != Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL) { return; diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationKeyHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationKeyHandler.java new file mode 100644 index 000000000000..a65580c82124 --- /dev/null +++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationKeyHandler.java @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2025 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.accessibility.magnification; + +import android.view.Display; +import android.view.KeyEvent; + +import com.android.server.accessibility.BaseEventStreamTransformation; + +/* + * A class that listens to key presses used to control magnification. + */ +public class MagnificationKeyHandler extends BaseEventStreamTransformation { + + /** Callback interface to report that a user is intending to interact with Magnification. */ + public interface Callback { + /** + * Called when a keyboard shortcut to pan magnification in direction {@code direction} is + * pressed by a user. Note that this can be called for multiple directions if multiple + * arrows are pressed at the same time (e.g. diagonal panning). + * + * @param displayId The logical display ID + * @param direction The direction to start panning + */ + void onPanMagnificationStart(int displayId, + @MagnificationController.PanDirection int direction); + + /** + * Called when a keyboard shortcut to pan magnification in direction {@code direction} is + * unpressed by a user. Note that this can be called for multiple directions if multiple + * arrows had been pressed at the same time (e.g. diagonal panning). + * + * @param displayId The logical display ID + * @param direction The direction in which panning stopped + */ + void onPanMagnificationStop(int displayId, + @MagnificationController.PanDirection int direction); + + /** + * Called when a keyboard shortcut to scale magnification in direction `direction` is + * pressed by a user. + * + * @param displayId The logical display ID + * @param direction The direction in which scaling started + */ + void onScaleMagnificationStart(int displayId, + @MagnificationController.ZoomDirection int direction); + + /** + * Called when a keyboard shortcut to scale magnification in direction `direction` is + * unpressed by a user. + * + * @param displayId The logical display ID + * @param direction The direction in which scaling stopped + */ + void onScaleMagnificationStop(int displayId, + @MagnificationController.ZoomDirection int direction); + } + + protected final MagnificationKeyHandler.Callback mCallback; + + public MagnificationKeyHandler(Callback callback) { + mCallback = callback; + } + + @Override + public void onKeyEvent(KeyEvent event, int policyFlags) { + if (!com.android.hardware.input.Flags.enableTalkbackAndMagnifierKeyGestures()) { + // Send to the rest of the handlers. + super.onKeyEvent(event, policyFlags); + return; + } + boolean modifiersPressed = event.isAltPressed() && event.isMetaPressed(); + if (!modifiersPressed) { + super.onKeyEvent(event, policyFlags); + return; + } + boolean isDown = event.getAction() == KeyEvent.ACTION_DOWN; + int keyCode = event.getKeyCode(); + if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT + || keyCode == KeyEvent.KEYCODE_DPAD_UP || keyCode == KeyEvent.KEYCODE_DPAD_DOWN) { + int panDirection = switch(keyCode) { + case KeyEvent.KEYCODE_DPAD_LEFT -> MagnificationController.PAN_DIRECTION_LEFT; + case KeyEvent.KEYCODE_DPAD_RIGHT -> MagnificationController.PAN_DIRECTION_RIGHT; + case KeyEvent.KEYCODE_DPAD_UP -> MagnificationController.PAN_DIRECTION_UP; + default -> MagnificationController.PAN_DIRECTION_DOWN; + }; + if (isDown) { + mCallback.onPanMagnificationStart(getDisplayId(event), panDirection); + } else { + mCallback.onPanMagnificationStop(getDisplayId(event), panDirection); + } + return; + } else if (keyCode == KeyEvent.KEYCODE_EQUALS || keyCode == KeyEvent.KEYCODE_MINUS) { + int zoomDirection = MagnificationController.ZOOM_DIRECTION_OUT; + if (keyCode == KeyEvent.KEYCODE_EQUALS) { + zoomDirection = MagnificationController.ZOOM_DIRECTION_IN; + } + if (isDown) { + mCallback.onScaleMagnificationStart(getDisplayId(event), zoomDirection); + } else { + mCallback.onScaleMagnificationStop(getDisplayId(event), zoomDirection); + } + return; + } + + // Continue down the eventing chain if this was unused. + super.onKeyEvent(event, policyFlags); + } + + private int getDisplayId(KeyEvent event) { + // Display ID may be invalid, e.g. for external keyboard attached to phone. + // In that case, use the default display. + if (event.getDisplayId() != Display.INVALID_DISPLAY) { + return event.getDisplayId(); + } + return Display.DEFAULT_DISPLAY; + } +} diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java index 5af2346650ed..2143aaaa4cd6 100644 --- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java +++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java @@ -236,12 +236,13 @@ public class UserBackupManagerService { // If an app is busy when we want to do a full-data backup, how long to defer the retry. // This is fuzzed, so there are two parameters; backoff_min + Rand[0, backoff_fuzz) - private static final long BUSY_BACKOFF_MIN_MILLIS = 1000 * 60 * 60; // one hour - private static final int BUSY_BACKOFF_FUZZ = 1000 * 60 * 60 * 2; // two hours + private static final long BUSY_BACKOFF_MIN_MILLIS = 1000 * 60 * 60; // one hour + private static final int BUSY_BACKOFF_FUZZ = 1000 * 60 * 60 * 2; // two hours private static final String SERIAL_ID_FILE = "serial_id"; private final @UserIdInt int mUserId; + private final String mLogIdMsg; // Prepended to Logcat messages. private final BackupAgentTimeoutParameters mAgentTimeoutParameters; private final TransportManager mTransportManager; @@ -259,13 +260,13 @@ public class UserBackupManagerService { private final IBackupManager mBackupManagerBinder; - private boolean mEnabled; // writes to this are synchronized on 'this' + private boolean mEnabled; // writes to this are synchronized on 'this' private boolean mSetupComplete; private boolean mAutoRestore; private final PendingIntent mRunInitIntent; - private final ArraySet<String> mPendingInits = new ArraySet<>(); // transport names + private final ArraySet<String> mPendingInits = new ArraySet<>(); // transport names // map UIDs to the set of participating packages under that UID private final SparseArray<HashSet<String>> mBackupParticipants = new SparseArray<>(); @@ -315,8 +316,7 @@ public class UserBackupManagerService { private final File mBaseStateDir; private final File mDataDir; private final File mJournalDir; - @Nullable - private DataChangedJournal mJournal; + @Nullable private DataChangedJournal mJournal; private final File mFullBackupScheduleFile; // Keep a log of all the apps we've ever backed up. @@ -337,7 +337,7 @@ public class UserBackupManagerService { * includes setting up the directories where we keep our bookkeeping and transport management. * * @see #createAndInitializeService(int, Context, BackupManagerService, HandlerThread, File, - * File, TransportManager) + * File, TransportManager) */ static UserBackupManagerService createAndInitializeService( @UserIdInt int userId, @@ -351,7 +351,7 @@ public class UserBackupManagerService { currentTransport = null; } - Slog.d(TAG, addUserIdToLogMessage(userId, "Starting with transport " + currentTransport)); + Slog.d(TAG, "Starting with transport " + currentTransport + " for user " + userId); TransportManager transportManager = new TransportManager(userId, context, transportWhitelist, currentTransport); @@ -361,7 +361,7 @@ public class UserBackupManagerService { HandlerThread userBackupThread = new HandlerThread("backup-" + userId, Process.THREAD_PRIORITY_BACKGROUND); userBackupThread.start(); - Slog.d(TAG, addUserIdToLogMessage(userId, "Started thread " + userBackupThread.getName())); + Slog.d(TAG, "Started thread " + userBackupThread.getName() + " for user " + userId); return createAndInitializeService( userId, @@ -417,26 +417,32 @@ public class UserBackupManagerService { */ public static boolean getSetupCompleteSettingForUser(Context context, int userId) { return Settings.Secure.getIntForUser( - context.getContentResolver(), - Settings.Secure.USER_SETUP_COMPLETE, - 0, - userId) + context.getContentResolver(), + Settings.Secure.USER_SETUP_COMPLETE, + 0, + userId) != 0; } @VisibleForTesting - UserBackupManagerService(Context context, PackageManager packageManager, - LifecycleOperationStorage operationStorage, TransportManager transportManager, - BackupHandler backupHandler, BackupManagerConstants backupManagerConstants, - IActivityManager activityManager, ActivityManagerInternal activityManagerInternal) { + UserBackupManagerService( + Context context, + PackageManager packageManager, + LifecycleOperationStorage operationStorage, + TransportManager transportManager, + BackupHandler backupHandler, + BackupManagerConstants backupManagerConstants, + IActivityManager activityManager, + ActivityManagerInternal activityManagerInternal) { mContext = context; mUserId = 0; + mLogIdMsg = "[UserID:" + mUserId + "] "; mRegisterTransportsRequestedTime = 0; mPackageManager = packageManager; mOperationStorage = operationStorage; - mBackupAgentConnectionManager = new BackupAgentConnectionManager(mOperationStorage, - mPackageManager, this, mUserId); + mBackupAgentConnectionManager = + new BackupAgentConnectionManager(mOperationStorage, mPackageManager, this, mUserId); mTransportManager = transportManager; mFullBackupQueue = new ArrayList<>(); mBackupHandler = backupHandler; @@ -470,13 +476,14 @@ public class UserBackupManagerService { File dataDir, TransportManager transportManager) { mUserId = userId; + mLogIdMsg = "[UserID:" + mUserId + "] "; mContext = Objects.requireNonNull(context, "context cannot be null"); mPackageManager = context.getPackageManager(); mPackageManagerBinder = AppGlobals.getPackageManager(); mActivityManager = ActivityManager.getService(); mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class); - mScheduledBackupEligibility = getEligibilityRules(mPackageManager, userId, mContext, - BackupDestination.CLOUD); + mScheduledBackupEligibility = + getEligibilityRules(mPackageManager, userId, mContext, BackupDestination.CLOUD); mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE); @@ -484,13 +491,13 @@ public class UserBackupManagerService { Objects.requireNonNull(parent, "parent cannot be null"); mBackupManagerBinder = BackupManagerService.asInterface(parent.asBinder()); - mAgentTimeoutParameters = new - BackupAgentTimeoutParameters(Handler.getMain(), mContext.getContentResolver()); + mAgentTimeoutParameters = + new BackupAgentTimeoutParameters(Handler.getMain(), mContext.getContentResolver()); mAgentTimeoutParameters.start(); mOperationStorage = new LifecycleOperationStorage(mUserId); - mBackupAgentConnectionManager = new BackupAgentConnectionManager(mOperationStorage, - mPackageManager, this, mUserId); + mBackupAgentConnectionManager = + new BackupAgentConnectionManager(mOperationStorage, mPackageManager, this, mUserId); Objects.requireNonNull(userBackupThread, "userBackupThread cannot be null"); mBackupHandler = new BackupHandler(this, mOperationStorage, userBackupThread); @@ -498,8 +505,10 @@ public class UserBackupManagerService { // Set up our bookkeeping final ContentResolver resolver = context.getContentResolver(); mSetupComplete = getSetupCompleteSettingForUser(context, userId); - mAutoRestore = Settings.Secure.getIntForUser(resolver, - Settings.Secure.BACKUP_AUTO_RESTORE, 1, userId) != 0; + mAutoRestore = + Settings.Secure.getIntForUser( + resolver, Settings.Secure.BACKUP_AUTO_RESTORE, 1, userId) + != 0; mSetupObserver = new SetupObserver(this, mBackupHandler); resolver.registerContentObserver( @@ -514,10 +523,7 @@ public class UserBackupManagerService { if (userId == UserHandle.USER_SYSTEM) { mBaseStateDir.mkdirs(); if (!SELinux.restorecon(mBaseStateDir)) { - Slog.w( - TAG, - addUserIdToLogMessage( - userId, "SELinux restorecon failed on " + mBaseStateDir)); + Slog.w(TAG, mLogIdMsg + "SELinux restorecon failed on " + mBaseStateDir); } } @@ -549,8 +555,8 @@ public class UserBackupManagerService { // Set up the backup-request journaling mJournalDir = new File(mBaseStateDir, "pending"); - mJournalDir.mkdirs(); // creates mBaseStateDir along the way - mJournal = null; // will be created on first use + mJournalDir.mkdirs(); // creates mBaseStateDir along the way + mJournal = null; // will be created on first use mConstants = new BackupManagerConstants(mBackupHandler, mContext.getContentResolver()); // We are observing changes to the constants throughout the lifecycle of BMS. This is @@ -580,14 +586,20 @@ public class UserBackupManagerService { // if so delete expired events and do not print them to dumpsys BackupManagerMonitorDumpsysUtils backupManagerMonitorDumpsysUtils = new BackupManagerMonitorDumpsysUtils(); - mBackupHandler.postDelayed(backupManagerMonitorDumpsysUtils::deleteExpiredBMMEvents, + mBackupHandler.postDelayed( + backupManagerMonitorDumpsysUtils::deleteExpiredBMMEvents, INITIALIZATION_DELAY_MILLIS); mBackupPreferences = new UserBackupPreferences(mContext, mBaseStateDir); // Power management - mWakelock = new BackupWakeLock(mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, - "*backup*-" + userId + "-" + userBackupThread.getThreadId()), userId, mConstants); + mWakelock = + new BackupWakeLock( + mPowerManager.newWakeLock( + PowerManager.PARTIAL_WAKE_LOCK, + "*backup*-" + userId + "-" + userBackupThread.getThreadId()), + userId, + mConstants); // Set up the various sorts of package tracking we do mFullBackupScheduleFile = new File(mBaseStateDir, "fb-schedule"); @@ -788,14 +800,13 @@ public class UserBackupManagerService { mPendingInits.clear(); } - public void setRunningFullBackupTask( - PerformFullTransportBackupTask runningFullBackupTask) { + public void setRunningFullBackupTask(PerformFullTransportBackupTask runningFullBackupTask) { mRunningFullBackupTask = runningFullBackupTask; } /** - * Utility: build a new random integer token. The low bits are the ordinal of the operation for - * near-time uniqueness, and the upper bits are random for app-side unpredictability. + * Utility: build a new random integer token. The low bits are the ordinal of the operation for + * near-time uniqueness, and the upper bits are random for app-side unpredictability. */ public int generateRandomIntegerToken() { int token = mTokenGenerator.nextInt(); @@ -815,16 +826,14 @@ public class UserBackupManagerService { public BackupAgent makeMetadataAgentWithEligibilityRules( BackupEligibilityRules backupEligibilityRules) { - PackageManagerBackupAgent pmAgent = new PackageManagerBackupAgent(mPackageManager, mUserId, - backupEligibilityRules); + PackageManagerBackupAgent pmAgent = + new PackageManagerBackupAgent(mPackageManager, mUserId, backupEligibilityRules); pmAgent.attach(mContext); pmAgent.onCreate(UserHandle.of(mUserId)); return pmAgent; } - /** - * Same as {@link #makeMetadataAgent()} but with explicit package-set configuration. - */ + /** Same as {@link #makeMetadataAgent()} but with explicit package-set configuration. */ public PackageManagerBackupAgent makeMetadataAgent(List<PackageInfo> packages) { PackageManagerBackupAgent pmAgent = new PackageManagerBackupAgent(mPackageManager, packages, mUserId); @@ -834,12 +843,12 @@ public class UserBackupManagerService { } private void initPackageTracking() { - if (DEBUG) Slog.v(TAG, addUserIdToLogMessage(mUserId, "` tracking")); + if (DEBUG) Slog.v(TAG, mLogIdMsg + "` tracking"); // Remember our ancestral dataset mTokenFile = new File(mBaseStateDir, "ancestral"); - try (DataInputStream tokenStream = new DataInputStream(new BufferedInputStream( - new FileInputStream(mTokenFile)))) { + try (DataInputStream tokenStream = + new DataInputStream(new BufferedInputStream(new FileInputStream(mTokenFile)))) { int version = tokenStream.readInt(); if (version == CURRENT_ANCESTRAL_RECORD_VERSION) { mAncestralToken = tokenStream.readLong(); @@ -856,9 +865,9 @@ public class UserBackupManagerService { } } catch (FileNotFoundException fnf) { // Probably innocuous - Slog.d(TAG, addUserIdToLogMessage(mUserId, "No ancestral data")); + Slog.d(TAG, mLogIdMsg + "No ancestral data"); } catch (IOException e) { - Slog.w(TAG, addUserIdToLogMessage(mUserId, "Unable to read token file"), e); + Slog.w(TAG, mLogIdMsg + "Unable to read token file", e); } mProcessedPackagesJournal = new ProcessedPackagesJournal(mBaseStateDir); @@ -899,20 +908,20 @@ public class UserBackupManagerService { boolean changed = false; ArrayList<FullBackupEntry> schedule = null; List<PackageInfo> apps = - PackageManagerBackupAgent.getStorableApplications(mPackageManager, mUserId, - mScheduledBackupEligibility); + PackageManagerBackupAgent.getStorableApplications( + mPackageManager, mUserId, mScheduledBackupEligibility); if (mFullBackupScheduleFile.exists()) { try (FileInputStream fstream = new FileInputStream(mFullBackupScheduleFile); - BufferedInputStream bufStream = new BufferedInputStream(fstream); - DataInputStream in = new DataInputStream(bufStream)) { + BufferedInputStream bufStream = new BufferedInputStream(fstream); + DataInputStream in = new DataInputStream(bufStream)) { int version = in.readInt(); if (version != SCHEDULE_FILE_VERSION) { // The file version doesn't match the expected value. // Since this is within a "try" block, this exception will be treated like // any other exception, and caught below. - throw new IllegalArgumentException("Unknown backup schedule version " - + version); + throw new IllegalArgumentException( + "Unknown backup schedule version " + version); } final int numPackages = in.readInt(); @@ -935,12 +944,20 @@ public class UserBackupManagerService { pkg.applicationInfo)) { schedule.add(new FullBackupEntry(pkgName, lastBackup)); } else { - Slog.i(TAG, addUserIdToLogMessage(mUserId, "Package " + pkgName - + " no longer eligible for full backup")); + Slog.i( + TAG, + mLogIdMsg + + "Package " + + pkgName + + " no longer eligible for full backup"); } } catch (NameNotFoundException e) { - Slog.i(TAG, addUserIdToLogMessage(mUserId, "Package " + pkgName - + " not installed; dropping from full backup")); + Slog.i( + TAG, + mLogIdMsg + + "Package " + + pkgName + + " not installed; dropping from full backup"); } } @@ -954,11 +971,10 @@ public class UserBackupManagerService { if (DEBUG) { Slog.i( TAG, - addUserIdToLogMessage( - mUserId, - "New full backup app " - + app.packageName - + " found")); + mLogIdMsg + + "New full backup app " + + app.packageName + + " found"); } schedule.add(new FullBackupEntry(app.packageName, 0)); changed = true; @@ -968,7 +984,7 @@ public class UserBackupManagerService { Collections.sort(schedule); } catch (Exception e) { - Slog.e(TAG, addUserIdToLogMessage(mUserId, "Unable to read backup schedule"), e); + Slog.e(TAG, mLogIdMsg + "Unable to read backup schedule", e); mFullBackupScheduleFile.delete(); schedule = null; } @@ -994,46 +1010,43 @@ public class UserBackupManagerService { return schedule; } - private Runnable mFullBackupScheduleWriter = new Runnable() { - @Override - public void run() { - synchronized (mQueueLock) { - try { - ByteArrayOutputStream bufStream = new ByteArrayOutputStream(4096); - DataOutputStream bufOut = new DataOutputStream(bufStream); - bufOut.writeInt(SCHEDULE_FILE_VERSION); - - // version 1: - // - // [int] # of packages in the queue = N - // N * { - // [utf8] package name - // [long] last backup time for this package - // } - int numPackages = mFullBackupQueue.size(); - bufOut.writeInt(numPackages); - - for (int i = 0; i < numPackages; i++) { - FullBackupEntry entry = mFullBackupQueue.get(i); - bufOut.writeUTF(entry.packageName); - bufOut.writeLong(entry.lastBackup); + private Runnable mFullBackupScheduleWriter = + new Runnable() { + @Override + public void run() { + synchronized (mQueueLock) { + try { + ByteArrayOutputStream bufStream = new ByteArrayOutputStream(4096); + DataOutputStream bufOut = new DataOutputStream(bufStream); + bufOut.writeInt(SCHEDULE_FILE_VERSION); + + // version 1: + // + // [int] # of packages in the queue = N + // N * { + // [utf8] package name + // [long] last backup time for this package + // } + int numPackages = mFullBackupQueue.size(); + bufOut.writeInt(numPackages); + + for (int i = 0; i < numPackages; i++) { + FullBackupEntry entry = mFullBackupQueue.get(i); + bufOut.writeUTF(entry.packageName); + bufOut.writeLong(entry.lastBackup); + } + bufOut.flush(); + + AtomicFile af = new AtomicFile(mFullBackupScheduleFile); + FileOutputStream out = af.startWrite(); + out.write(bufStream.toByteArray()); + af.finishWrite(out); + } catch (Exception e) { + Slog.e(TAG, mLogIdMsg + "Unable to write backup schedule!", e); + } } - bufOut.flush(); - - AtomicFile af = new AtomicFile(mFullBackupScheduleFile); - FileOutputStream out = af.startWrite(); - out.write(bufStream.toByteArray()); - af.finishWrite(out); - } catch (Exception e) { - Slog.e( - TAG, - addUserIdToLogMessage( - mUserId, "Unable to write backup schedule!"), - e); } - } - } - }; + }; private void writeFullBackupScheduleAsync() { mBackupHandler.removeCallbacks(mFullBackupScheduleWriter); @@ -1044,28 +1057,33 @@ public class UserBackupManagerService { ArrayList<DataChangedJournal> journals = DataChangedJournal.listJournals(mJournalDir); journals.removeAll(Collections.singletonList(mJournal)); if (!journals.isEmpty()) { - Slog.i(TAG, addUserIdToLogMessage(mUserId, - "Found " + journals.size() + " stale backup journal(s), scheduling.")); + Slog.i( + TAG, + mLogIdMsg + + "Found " + + journals.size() + + " stale backup journal(s), scheduling."); } Set<String> packageNames = new LinkedHashSet<>(); for (DataChangedJournal journal : journals) { try { - journal.forEach(packageName -> { - if (packageNames.add(packageName)) { - dataChangedImpl(packageName); - } - }); + journal.forEach( + packageName -> { + if (packageNames.add(packageName)) { + dataChangedImpl(packageName); + } + }); } catch (IOException e) { - Slog.e(TAG, addUserIdToLogMessage(mUserId, "Can't read " + journal), e); + Slog.e(TAG, mLogIdMsg + "Can't read " + journal, e); } } if (!packageNames.isEmpty()) { - String msg = "Stale backup journals: Scheduled " + packageNames.size() - + " package(s) total"; + String msg = + "Stale backup journals: Scheduled " + packageNames.size() + " package(s) total"; if (DEBUG) { msg += ": " + packageNames; } - Slog.i(TAG, addUserIdToLogMessage(mUserId, msg)); + Slog.i(TAG, mLogIdMsg + msg); } } @@ -1105,12 +1123,11 @@ public class UserBackupManagerService { if (DEBUG) { Slog.i( TAG, - addUserIdToLogMessage( - mUserId, - "recordInitPending(" - + isPending - + ") on transport " - + transportName)); + mLogIdMsg + + "recordInitPending(" + + isPending + + ") on transport " + + transportName); } File stateDir = new File(mBaseStateDir, transportDirName); @@ -1170,10 +1187,16 @@ public class UserBackupManagerService { private void onTransportRegistered(String transportName, String transportDirName) { long timeMs = SystemClock.elapsedRealtime() - mRegisterTransportsRequestedTime; - Slog.d(TAG, addUserIdToLogMessage(mUserId, - "Transport " + transportName + " registered " + timeMs - + "ms after first request (delay = " + INITIALIZATION_DELAY_MILLIS - + "ms)")); + Slog.d( + TAG, + mLogIdMsg + + "Transport " + + transportName + + " registered " + + timeMs + + "ms after first request (delay = " + + INITIALIZATION_DELAY_MILLIS + + "ms)"); File stateDir = new File(mBaseStateDir, transportDirName); stateDir.mkdirs(); @@ -1185,8 +1208,10 @@ public class UserBackupManagerService { // TODO: pick a better starting time than now + 1 minute long delay = 1000 * 60; // one minute, in milliseconds - mAlarmManager.set(AlarmManager.RTC_WAKEUP, - System.currentTimeMillis() + delay, mRunInitIntent); + mAlarmManager.set( + AlarmManager.RTC_WAKEUP, + System.currentTimeMillis() + delay, + mRunInitIntent); } } } @@ -1195,137 +1220,131 @@ public class UserBackupManagerService { * A {@link BroadcastReceiver} tracking changes to packages and sd cards in order to update our * internal bookkeeping. */ - private BroadcastReceiver mPackageTrackingReceiver = new BroadcastReceiver() { - public void onReceive(Context context, Intent intent) { - if (DEBUG) { - Slog.d(TAG, addUserIdToLogMessage(mUserId, "Received broadcast " + intent)); - } + private BroadcastReceiver mPackageTrackingReceiver = + new BroadcastReceiver() { + public void onReceive(Context context, Intent intent) { + if (DEBUG) { + Slog.d(TAG, mLogIdMsg + "Received broadcast " + intent); + } - String action = intent.getAction(); - boolean replacing = false; - boolean added = false; - boolean changed = false; - Bundle extras = intent.getExtras(); - String[] packageList = null; + String action = intent.getAction(); + boolean replacing = false; + boolean added = false; + boolean changed = false; + Bundle extras = intent.getExtras(); + String[] packageList = null; + + if (Intent.ACTION_PACKAGE_ADDED.equals(action) + || Intent.ACTION_PACKAGE_REMOVED.equals(action) + || Intent.ACTION_PACKAGE_CHANGED.equals(action)) { + Uri uri = intent.getData(); + if (uri == null) { + return; + } - if (Intent.ACTION_PACKAGE_ADDED.equals(action) - || Intent.ACTION_PACKAGE_REMOVED.equals(action) - || Intent.ACTION_PACKAGE_CHANGED.equals(action)) { - Uri uri = intent.getData(); - if (uri == null) { - return; - } + String packageName = uri.getSchemeSpecificPart(); + if (packageName != null) { + packageList = new String[] {packageName}; + } - String packageName = uri.getSchemeSpecificPart(); - if (packageName != null) { - packageList = new String[] {packageName}; - } + changed = Intent.ACTION_PACKAGE_CHANGED.equals(action); + if (changed) { + // Look at new transport states for package changed events. + String[] components = + intent.getStringArrayExtra( + Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST); - changed = Intent.ACTION_PACKAGE_CHANGED.equals(action); - if (changed) { - // Look at new transport states for package changed events. - String[] components = - intent.getStringArrayExtra( - Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST); + if (DEBUG) { + Slog.i(TAG, mLogIdMsg + "Package " + packageName + " changed"); + for (int i = 0; i < components.length; i++) { + Slog.i(TAG, mLogIdMsg + " * " + components[i]); + } + } - if (DEBUG) { - Slog.i( - TAG, - addUserIdToLogMessage( - mUserId, "Package " + packageName + " changed")); - for (int i = 0; i < components.length; i++) { - Slog.i( - TAG, - addUserIdToLogMessage( - mUserId, " * " + components[i])); + mBackupHandler.post( + () -> + mTransportManager.onPackageChanged( + packageName, components)); + return; } - } - mBackupHandler.post( - () -> - mTransportManager.onPackageChanged( - packageName, components)); - return; - } + added = Intent.ACTION_PACKAGE_ADDED.equals(action); + replacing = extras.getBoolean(Intent.EXTRA_REPLACING, false); + } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action)) { + added = true; + packageList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST); + } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) { + added = false; + packageList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST); + } - added = Intent.ACTION_PACKAGE_ADDED.equals(action); - replacing = extras.getBoolean(Intent.EXTRA_REPLACING, false); - } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action)) { - added = true; - packageList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST); - } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) { - added = false; - packageList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST); - } + if (packageList == null || packageList.length == 0) { + return; + } - if (packageList == null || packageList.length == 0) { - return; - } + int uid = extras.getInt(Intent.EXTRA_UID); + if (added) { + synchronized (mBackupParticipants) { + if (replacing) { + // Remove the entry under the old uid and fall through to re-add. If + // an app + // just opted into key/value backup, add it as a known participant. + removePackageParticipantsLocked(packageList, uid); + } + addPackageParticipantsLocked(packageList); + } - int uid = extras.getInt(Intent.EXTRA_UID); - if (added) { - synchronized (mBackupParticipants) { - if (replacing) { - // Remove the entry under the old uid and fall through to re-add. If - // an app - // just opted into key/value backup, add it as a known participant. - removePackageParticipantsLocked(packageList, uid); - } - addPackageParticipantsLocked(packageList); - } + long now = System.currentTimeMillis(); + for (String packageName : packageList) { + try { + PackageInfo app = + mPackageManager.getPackageInfoAsUser( + packageName, /* flags */ 0, mUserId); + if (mScheduledBackupEligibility.appGetsFullBackup(app) + && mScheduledBackupEligibility.appIsEligibleForBackup( + app.applicationInfo)) { + enqueueFullBackup(packageName, now); + scheduleNextFullBackupJob(0); + } else { + // The app might have just transitioned out of full-data into + // doing + // key/value backups, or might have just disabled backups + // entirely. Make + // sure it is no longer in the full-data queue. + synchronized (mQueueLock) { + dequeueFullBackupLocked(packageName); + } + writeFullBackupScheduleAsync(); + } - long now = System.currentTimeMillis(); - for (String packageName : packageList) { - try { - PackageInfo app = - mPackageManager.getPackageInfoAsUser( - packageName, /* flags */ 0, mUserId); - if (mScheduledBackupEligibility.appGetsFullBackup(app) - && mScheduledBackupEligibility.appIsEligibleForBackup( - app.applicationInfo)) { - enqueueFullBackup(packageName, now); - scheduleNextFullBackupJob(0); - } else { - // The app might have just transitioned out of full-data into - // doing - // key/value backups, or might have just disabled backups - // entirely. Make - // sure it is no longer in the full-data queue. - synchronized (mQueueLock) { - dequeueFullBackupLocked(packageName); + mBackupHandler.post( + () -> mTransportManager.onPackageAdded(packageName)); + } catch (NameNotFoundException e) { + Slog.w(TAG, mLogIdMsg + "Can't resolve new app " + packageName); } - writeFullBackupScheduleAsync(); } - mBackupHandler.post( - () -> mTransportManager.onPackageAdded(packageName)); - } catch (NameNotFoundException e) { - Slog.w(TAG, addUserIdToLogMessage(mUserId, - "Can't resolve new app " + packageName)); - } - } + // Whenever a package is added or updated we need to update the package + // metadata + // bookkeeping. + dataChangedImpl(PACKAGE_MANAGER_SENTINEL); + } else { + if (!replacing) { + // Outright removal. In the full-data case, the app will be dropped from + // the + // queue when its (now obsolete) name comes up again for backup. + synchronized (mBackupParticipants) { + removePackageParticipantsLocked(packageList, uid); + } + } - // Whenever a package is added or updated we need to update the package - // metadata - // bookkeeping. - dataChangedImpl(PACKAGE_MANAGER_SENTINEL); - } else { - if (!replacing) { - // Outright removal. In the full-data case, the app will be dropped from - // the - // queue when its (now obsolete) name comes up again for backup. - synchronized (mBackupParticipants) { - removePackageParticipantsLocked(packageList, uid); + for (String packageName : packageList) { + mBackupHandler.post( + () -> mTransportManager.onPackageRemoved(packageName)); + } } } - - for (String packageName : packageList) { - mBackupHandler.post( - () -> mTransportManager.onPackageRemoved(packageName)); - } - } - } - }; + }; // Add the backup agents in the given packages to our set of known backup participants. // If 'packageNames' is null, adds all backup agents in the whole system. @@ -1334,29 +1353,23 @@ public class UserBackupManagerService { List<PackageInfo> targetApps = allAgentPackages(); if (packageNames != null) { if (DEBUG) { - Slog.v( - TAG, - addUserIdToLogMessage( - mUserId, "addPackageParticipantsLocked: #" + packageNames.length)); + Slog.v(TAG, mLogIdMsg + "addPackageParticipantsLocked: #" + packageNames.length); } for (String packageName : packageNames) { addPackageParticipantsLockedInner(packageName, targetApps); } } else { if (DEBUG) { - Slog.v(TAG, addUserIdToLogMessage(mUserId, "addPackageParticipantsLocked: all")); + Slog.v(TAG, mLogIdMsg + "addPackageParticipantsLocked: all"); } addPackageParticipantsLockedInner(null, targetApps); } } - private void addPackageParticipantsLockedInner(String packageName, - List<PackageInfo> targetPkgs) { + private void addPackageParticipantsLockedInner( + String packageName, List<PackageInfo> targetPkgs) { if (DEBUG) { - Slog.v( - TAG, - addUserIdToLogMessage( - mUserId, "Examining " + packageName + " for backup agent")); + Slog.v(TAG, mLogIdMsg + "Examining " + packageName + " for backup agent"); } for (PackageInfo pkg : targetPkgs) { @@ -1368,17 +1381,14 @@ public class UserBackupManagerService { mBackupParticipants.put(uid, set); } set.add(pkg.packageName); - if (DEBUG) Slog.v(TAG, addUserIdToLogMessage(mUserId, "Agent found; added")); + if (DEBUG) Slog.v(TAG, mLogIdMsg + "Agent found; added"); // Schedule a backup for it on general principles if (DEBUG) { - Slog.i( - TAG, - addUserIdToLogMessage( - mUserId, "Scheduling backup for new app " + pkg.packageName)); + Slog.i(TAG, mLogIdMsg + "Scheduling backup for new app " + pkg.packageName); } - Message msg = mBackupHandler - .obtainMessage(MSG_SCHEDULE_BACKUP_PACKAGE, pkg.packageName); + Message msg = + mBackupHandler.obtainMessage(MSG_SCHEDULE_BACKUP_PACKAGE, pkg.packageName); mBackupHandler.sendMessage(msg); } } @@ -1387,19 +1397,18 @@ public class UserBackupManagerService { // Remove the given packages' entries from our known active set. private void removePackageParticipantsLocked(String[] packageNames, int oldUid) { if (packageNames == null) { - Slog.w(TAG, addUserIdToLogMessage(mUserId, "removePackageParticipants with null list")); + Slog.w(TAG, mLogIdMsg + "removePackageParticipants with null list"); return; } if (DEBUG) { Slog.v( TAG, - addUserIdToLogMessage( - mUserId, - "removePackageParticipantsLocked: uid=" - + oldUid - + " #" - + packageNames.length)); + mLogIdMsg + + "removePackageParticipantsLocked: uid=" + + oldUid + + " #" + + packageNames.length); } for (String pkg : packageNames) { // Known previous UID, so we know which package set to check @@ -1408,10 +1417,7 @@ public class UserBackupManagerService { removePackageFromSetLocked(set, pkg); if (set.isEmpty()) { if (DEBUG) { - Slog.v( - TAG, - addUserIdToLogMessage( - mUserId, " last one of this uid; purging set")); + Slog.v(TAG, mLogIdMsg + " last one of this uid; purging set"); } mBackupParticipants.remove(oldUid); } @@ -1419,8 +1425,7 @@ public class UserBackupManagerService { } } - private void removePackageFromSetLocked(final HashSet<String> set, - final String packageName) { + private void removePackageFromSetLocked(final HashSet<String> set, final String packageName) { if (set.contains(packageName)) { // Found it. Remove this one package from the bookkeeping, and // if it's the last participating app under this uid we drop the @@ -1429,9 +1434,7 @@ public class UserBackupManagerService { // bookkeeping so that its current-dataset data will be retrieved // if the app is subsequently reinstalled if (DEBUG) { - Slog.v( - TAG, - addUserIdToLogMessage(mUserId, " removing participant " + packageName)); + Slog.v(TAG, mLogIdMsg + " removing participant " + packageName); } set.remove(packageName); mPendingBackups.remove(packageName); @@ -1456,8 +1459,11 @@ public class UserBackupManagerService { // we will need the shared library path, so look that up and store it here. // This is used implicitly when we pass the PackageInfo object off to // the Activity Manager to launch the app for backup/restore purposes. - app = mPackageManager.getApplicationInfoAsUser(pkg.packageName, - PackageManager.GET_SHARED_LIBRARY_FILES, mUserId); + app = + mPackageManager.getApplicationInfoAsUser( + pkg.packageName, + PackageManager.GET_SHARED_LIBRARY_FILES, + mUserId); pkg.applicationInfo.sharedLibraryFiles = app.sharedLibraryFiles; pkg.applicationInfo.sharedLibraryInfos = app.sharedLibraryInfos; } @@ -1479,8 +1485,8 @@ public class UserBackupManagerService { final Intent notification = new Intent(); notification.setAction(BACKUP_FINISHED_ACTION); notification.setPackage(receiver); - notification.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES - | Intent.FLAG_RECEIVER_FOREGROUND); + notification.addFlags( + Intent.FLAG_INCLUDE_STOPPED_PACKAGES | Intent.FLAG_RECEIVER_FOREGROUND); notification.putExtra(BACKUP_FINISHED_PACKAGE_EXTRA, packageName); mContext.sendBroadcastAsUser(notification, UserHandle.of(mUserId)); } @@ -1506,17 +1512,14 @@ public class UserBackupManagerService { af.writeInt(-1); } else { af.writeInt(mAncestralPackages.size()); - Slog.d( - TAG, - addUserIdToLogMessage( - mUserId, "Ancestral packages: " + mAncestralPackages.size())); + Slog.d(TAG, mLogIdMsg + "Ancestral packages: " + mAncestralPackages.size()); for (String pkgName : mAncestralPackages) { af.writeUTF(pkgName); - if (DEBUG) Slog.v(TAG, addUserIdToLogMessage(mUserId, " " + pkgName)); + if (DEBUG) Slog.v(TAG, mLogIdMsg + " " + pkgName); } } } catch (IOException e) { - Slog.w(TAG, addUserIdToLogMessage(mUserId, "Unable to write token file:"), e); + Slog.w(TAG, mLogIdMsg + "Unable to write token file:", e); } } @@ -1539,45 +1542,39 @@ public class UserBackupManagerService { /** * Clear an application's data, blocking until the operation completes or times out. * - * @param checkFlagAllowClearUserDataOnFailedRestore if {@code true} uses - * {@link ApplicationInfo#PRIVATE_FLAG_ALLOW_CLEAR_USER_DATA_ON_FAILED_RESTORE} to decide if - * clearing data is allowed after a failed restore. - * + * @param checkFlagAllowClearUserDataOnFailedRestore if {@code true} uses {@link + * ApplicationInfo#PRIVATE_FLAG_ALLOW_CLEAR_USER_DATA_ON_FAILED_RESTORE} to decide if + * clearing data is allowed after a failed restore. * @param keepSystemState if {@code true}, we don't clear system state such as already restored - * notification settings, permission grants, etc. + * notification settings, permission grants, etc. */ - private void clearApplicationDataSynchronous(String packageName, - boolean checkFlagAllowClearUserDataOnFailedRestore, boolean keepSystemState) { + private void clearApplicationDataSynchronous( + String packageName, + boolean checkFlagAllowClearUserDataOnFailedRestore, + boolean keepSystemState) { try { - ApplicationInfo applicationInfo = mPackageManager.getPackageInfoAsUser( - packageName, 0, mUserId).applicationInfo; + ApplicationInfo applicationInfo = + mPackageManager.getPackageInfoAsUser(packageName, 0, mUserId).applicationInfo; boolean shouldClearData; if (checkFlagAllowClearUserDataOnFailedRestore && applicationInfo.targetSdkVersion >= Build.VERSION_CODES.Q) { - shouldClearData = (applicationInfo.privateFlags - & ApplicationInfo.PRIVATE_FLAG_ALLOW_CLEAR_USER_DATA_ON_FAILED_RESTORE) != 0; + int clearOnFailedRestoreFlag = + ApplicationInfo.PRIVATE_FLAG_ALLOW_CLEAR_USER_DATA_ON_FAILED_RESTORE; + shouldClearData = (applicationInfo.privateFlags & clearOnFailedRestoreFlag) != 0; } else { shouldClearData = - (applicationInfo.flags & ApplicationInfo.FLAG_ALLOW_CLEAR_USER_DATA) != 0; + (applicationInfo.flags & ApplicationInfo.FLAG_ALLOW_CLEAR_USER_DATA) != 0; } if (!shouldClearData) { if (DEBUG) { - Slog.i( - TAG, - addUserIdToLogMessage( - mUserId, - "Clearing app data is not allowed so not wiping " - + packageName)); + Slog.i(TAG, mLogIdMsg + "Clearing app data is not allowed " + packageName); } return; } } catch (NameNotFoundException e) { - Slog.w( - TAG, - addUserIdToLogMessage( - mUserId, "Tried to clear data for " + packageName + " but not found")); + Slog.w(TAG, mLogIdMsg + "Tried to clear data for " + packageName + " but not found"); return; } @@ -1585,8 +1582,8 @@ public class UserBackupManagerService { synchronized (mClearDataLock) { mClearingData = true; - mActivityManagerInternal.clearApplicationUserData(packageName, keepSystemState, - /*isRestore=*/ true, observer, mUserId); + mActivityManagerInternal.clearApplicationUserData( + packageName, keepSystemState, /* isRestore= */ true, observer, mUserId); // Only wait 30 seconds for the clear data to happen. long timeoutMark = System.currentTimeMillis() + CLEAR_DATA_TIMEOUT_INTERVAL; @@ -1598,20 +1595,16 @@ public class UserBackupManagerService { mClearingData = false; Slog.w( TAG, - addUserIdToLogMessage( - mUserId, - "Interrupted while waiting for " - + packageName - + " data to be cleared"), + mLogIdMsg + + "Interrupted while waiting for " + + packageName + + " data to be cleared", e); } } if (mClearingData) { - Slog.w( - TAG, - addUserIdToLogMessage( - mUserId, "Clearing app data for " + packageName + " timed out")); + Slog.w(TAG, mLogIdMsg + "Clearing app data for " + packageName + " timed out"); } } } @@ -1632,23 +1625,20 @@ public class UserBackupManagerService { * the active set if possible, else the ancestral one. Returns zero if none available. */ public long getAvailableRestoreToken(String packageName) { - mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP, - "getAvailableRestoreToken"); + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.BACKUP, "getAvailableRestoreToken"); long token = mAncestralToken; synchronized (mQueueLock) { if (mCurrentToken != 0 && mProcessedPackagesJournal.hasBeenProcessed(packageName)) { if (DEBUG) { - Slog.i( - TAG, - addUserIdToLogMessage( - mUserId, "App in ever-stored, so using current token")); + Slog.i(TAG, mLogIdMsg + "App in ever-stored, so using current token"); } token = mCurrentToken; } } if (DEBUG) { - Slog.i(TAG, addUserIdToLogMessage(mUserId, "getAvailableRestoreToken() == " + token)); + Slog.i(TAG, mLogIdMsg + "getAvailableRestoreToken() == " + token); } return token; } @@ -1666,35 +1656,40 @@ public class UserBackupManagerService { * Requests a backup for the inputted {@code packages} with a specified {@link * IBackupManagerMonitor} and {@link OperationType}. */ - public int requestBackup(String[] packages, IBackupObserver observer, - IBackupManagerMonitor monitor, int flags) { - BackupManagerMonitorEventSender mBackupManagerMonitorEventSender = + public int requestBackup( + String[] packages, IBackupObserver observer, IBackupManagerMonitor monitor, int flags) { + BackupManagerMonitorEventSender mBackupManagerMonitorEventSender = getBMMEventSender(monitor); mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, "requestBackup"); if (packages == null || packages.length < 1) { - Slog.e(TAG, addUserIdToLogMessage(mUserId, "No packages named for backup request")); + Slog.e(TAG, mLogIdMsg + "No packages named for backup request"); BackupObserverUtils.sendBackupFinished(observer, BackupManager.ERROR_TRANSPORT_ABORTED); mBackupManagerMonitorEventSender.monitorEvent( - BackupManagerMonitor.LOG_EVENT_ID_NO_PACKAGES, - null, BackupManagerMonitor.LOG_EVENT_CATEGORY_TRANSPORT, null); + BackupManagerMonitor.LOG_EVENT_ID_NO_PACKAGES, null, + BackupManagerMonitor.LOG_EVENT_CATEGORY_TRANSPORT, null); throw new IllegalArgumentException("No packages are provided for backup"); } if (!mEnabled || !mSetupComplete) { Slog.i( TAG, - addUserIdToLogMessage(mUserId, "Backup requested but enabled=" + mLogIdMsg + + "Backup requested but enabled=" + mEnabled + " setupComplete=" - + mSetupComplete)); - BackupObserverUtils.sendBackupFinished(observer, - BackupManager.ERROR_BACKUP_NOT_ALLOWED); - final int logTag = mSetupComplete - ? BackupManagerMonitor.LOG_EVENT_ID_BACKUP_DISABLED - : BackupManagerMonitor.LOG_EVENT_ID_DEVICE_NOT_PROVISIONED; - mBackupManagerMonitorEventSender.monitorEvent(logTag, null, - BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY, null); + + mSetupComplete); + BackupObserverUtils.sendBackupFinished( + observer, BackupManager.ERROR_BACKUP_NOT_ALLOWED); + final int logTag = + mSetupComplete + ? BackupManagerMonitor.LOG_EVENT_ID_BACKUP_DISABLED + : BackupManagerMonitor.LOG_EVENT_ID_DEVICE_NOT_PROVISIONED; + mBackupManagerMonitorEventSender.monitorEvent( + logTag, + null, + BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY, + null); return BackupManager.ERROR_BACKUP_NOT_ALLOWED; } @@ -1708,31 +1703,45 @@ public class UserBackupManagerService { transportConnection = mTransportManager.getCurrentTransportClientOrThrow("BMS.requestBackup()"); backupDestination = getBackupDestinationFromTransport(transportConnection); - } catch (TransportNotRegisteredException | TransportNotAvailableException + } catch (TransportNotRegisteredException + | TransportNotAvailableException | RemoteException e) { BackupObserverUtils.sendBackupFinished(observer, BackupManager.ERROR_TRANSPORT_ABORTED); mBackupManagerMonitorEventSender.monitorEvent( - BackupManagerMonitor.LOG_EVENT_ID_TRANSPORT_IS_NULL, - null, BackupManagerMonitor.LOG_EVENT_CATEGORY_TRANSPORT, null); + BackupManagerMonitor.LOG_EVENT_ID_TRANSPORT_IS_NULL, null, + BackupManagerMonitor.LOG_EVENT_CATEGORY_TRANSPORT, null); return BackupManager.ERROR_TRANSPORT_ABORTED; } OnTaskFinishedListener listener = caller -> mTransportManager.disposeOfTransportClient(transportConnection, caller); - BackupEligibilityRules backupEligibilityRules = getEligibilityRulesForOperation( - backupDestination); + BackupEligibilityRules backupEligibilityRules = + getEligibilityRulesForOperation(backupDestination); Message msg = mBackupHandler.obtainMessage(MSG_REQUEST_BACKUP); - msg.obj = getRequestBackupParams(packages, observer, monitor, flags, backupEligibilityRules, - transportConnection, transportDirName, listener); + msg.obj = + getRequestBackupParams( + packages, + observer, + monitor, + flags, + backupEligibilityRules, + transportConnection, + transportDirName, + listener); mBackupHandler.sendMessage(msg); return BackupManager.SUCCESS; } @VisibleForTesting - BackupParams getRequestBackupParams(String[] packages, IBackupObserver observer, - IBackupManagerMonitor monitor, int flags, BackupEligibilityRules backupEligibilityRules, - TransportConnection transportConnection, String transportDirName, + BackupParams getRequestBackupParams( + String[] packages, + IBackupObserver observer, + IBackupManagerMonitor monitor, + int flags, + BackupEligibilityRules backupEligibilityRules, + TransportConnection transportConnection, + String transportDirName, OnTaskFinishedListener listener) { ArrayList<String> fullBackupList = new ArrayList<>(); ArrayList<String> kvBackupList = new ArrayList<>(); @@ -1742,11 +1751,12 @@ public class UserBackupManagerService { continue; } try { - PackageInfo packageInfo = mPackageManager.getPackageInfoAsUser(packageName, - PackageManager.GET_SIGNING_CERTIFICATES, mUserId); + PackageInfo packageInfo = + mPackageManager.getPackageInfoAsUser( + packageName, PackageManager.GET_SIGNING_CERTIFICATES, mUserId); if (!backupEligibilityRules.appIsEligibleForBackup(packageInfo.applicationInfo)) { - BackupObserverUtils.sendBackupOnPackageResult(observer, packageName, - BackupManager.ERROR_BACKUP_NOT_ALLOWED); + BackupObserverUtils.sendBackupOnPackageResult( + observer, packageName, BackupManager.ERROR_BACKUP_NOT_ALLOWED); continue; } if (backupEligibilityRules.appGetsFullBackup(packageInfo)) { @@ -1755,31 +1765,41 @@ public class UserBackupManagerService { kvBackupList.add(packageInfo.packageName); } } catch (NameNotFoundException e) { - BackupObserverUtils.sendBackupOnPackageResult(observer, packageName, - BackupManager.ERROR_PACKAGE_NOT_FOUND); + BackupObserverUtils.sendBackupOnPackageResult( + observer, packageName, BackupManager.ERROR_PACKAGE_NOT_FOUND); } } - EventLog.writeEvent(EventLogTags.BACKUP_REQUESTED, packages.length, kvBackupList.size(), + EventLog.writeEvent( + EventLogTags.BACKUP_REQUESTED, + packages.length, + kvBackupList.size(), fullBackupList.size()); if (DEBUG) { Slog.i( TAG, - addUserIdToLogMessage( - mUserId, - "Backup requested for " - + packages.length - + " packages, of them: " - + fullBackupList.size() - + " full backups, " - + kvBackupList.size() - + " k/v backups")); + mLogIdMsg + + "Backup requested for " + + packages.length + + " packages, of them: " + + fullBackupList.size() + + " full backups, " + + kvBackupList.size() + + " k/v backups"); } boolean nonIncrementalBackup = (flags & BackupManager.FLAG_NON_INCREMENTAL_BACKUP) != 0; - return new BackupParams(transportConnection, transportDirName, kvBackupList, fullBackupList, - observer, monitor, listener, /* userInitiated */ true, nonIncrementalBackup, + return new BackupParams( + transportConnection, + transportDirName, + kvBackupList, + fullBackupList, + observer, + monitor, + listener, /* userInitiated */ + true, + nonIncrementalBackup, backupEligibilityRules); } @@ -1787,7 +1807,7 @@ public class UserBackupManagerService { public void cancelBackups() { mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, "cancelBackups"); if (DEBUG) { - Slog.i(TAG, addUserIdToLogMessage(mUserId, "cancelBackups() called.")); + Slog.i(TAG, mLogIdMsg + "cancelBackups() called."); } final long oldToken = Binder.clearCallingIdentity(); try { @@ -1795,14 +1815,24 @@ public class UserBackupManagerService { mOperationStorage.operationTokensForOpType(OpType.BACKUP); for (Integer token : operationsToCancel) { - mOperationStorage.cancelOperation(token, /* cancelAll */ true, - operationType -> { /* no callback needed here */ }); + mOperationStorage.cancelOperation( + token, /* cancelAll */ + true, + operationType -> { + /* no callback needed here */ + }); } // We don't want the backup jobs to kick in any time soon. // Reschedules them to run in the distant future. - KeyValueBackupJob.schedule(mUserId, mContext, BUSY_BACKOFF_MIN_MILLIS, + KeyValueBackupJob.schedule( + mUserId, + mContext, + BUSY_BACKOFF_MIN_MILLIS, /* userBackupManagerService */ this); - FullBackupJob.schedule(mUserId, mContext, 2 * BUSY_BACKOFF_MIN_MILLIS, + FullBackupJob.schedule( + mUserId, + mContext, + 2 * BUSY_BACKOFF_MIN_MILLIS, /* userBackupManagerService */ this); } finally { Binder.restoreCallingIdentity(oldToken); @@ -1810,35 +1840,34 @@ public class UserBackupManagerService { } /** Schedule a timeout message for the operation identified by {@code token}. */ - public void prepareOperationTimeout(int token, long interval, BackupRestoreTask callback, - int operationType) { + public void prepareOperationTimeout( + int token, long interval, BackupRestoreTask callback, int operationType) { if (operationType != OpType.BACKUP_WAIT && operationType != OpType.RESTORE_WAIT) { Slog.wtf( TAG, - addUserIdToLogMessage( - mUserId, - "prepareOperationTimeout() doesn't support operation " - + Integer.toHexString(token) - + " of type " - + operationType)); + mLogIdMsg + + "prepareOperationTimeout() doesn't support operation " + + Integer.toHexString(token) + + " of type " + + operationType); return; } if (DEBUG) { Slog.v( TAG, - addUserIdToLogMessage( - mUserId, - "starting timeout: token=" - + Integer.toHexString(token) - + " interval=" - + interval - + " callback=" - + callback)); + mLogIdMsg + + "starting timeout: token=" + + Integer.toHexString(token) + + " interval=" + + interval + + " callback=" + + callback); } mOperationStorage.registerOperation(token, OpState.PENDING, callback, operationType); - Message msg = mBackupHandler.obtainMessage(getMessageIdForOperationType(operationType), - token, 0, callback); + Message msg = + mBackupHandler.obtainMessage( + getMessageIdForOperationType(operationType), token, 0, callback); mBackupHandler.sendMessageDelayed(msg, interval); } @@ -1851,19 +1880,20 @@ public class UserBackupManagerService { default: Slog.wtf( TAG, - addUserIdToLogMessage( - mUserId, - "getMessageIdForOperationType called on invalid operation type: " - + operationType)); + mLogIdMsg + + "getMessageIdForOperationType called on invalid operation type: " + + operationType); return -1; } } /** Block until we received an operation complete message (from the agent or cancellation). */ public boolean waitUntilOperationComplete(int token) { - return mOperationStorage.waitUntilOperationComplete(token, operationType -> { - mBackupHandler.removeMessages(getMessageIdForOperationType(operationType)); - }); + return mOperationStorage.waitUntilOperationComplete( + token, + operationType -> { + mBackupHandler.removeMessages(getMessageIdForOperationType(operationType)); + }); } /** Cancel the operation associated with {@code token}. */ @@ -1871,11 +1901,15 @@ public class UserBackupManagerService { // Remove all pending timeout messages of types OpType.BACKUP_WAIT and // OpType.RESTORE_WAIT. On the other hand, OP_TYPE_BACKUP cannot time out and // doesn't require cancellation. - mOperationStorage.cancelOperation(token, cancelAll, operationType -> { - if (operationType == OpType.BACKUP_WAIT || operationType == OpType.RESTORE_WAIT) { - mBackupHandler.removeMessages(getMessageIdForOperationType(operationType)); - } - }); + mOperationStorage.cancelOperation( + token, + cancelAll, + operationType -> { + if (operationType == OpType.BACKUP_WAIT + || operationType == OpType.RESTORE_WAIT) { + mBackupHandler.removeMessages(getMessageIdForOperationType(operationType)); + } + }); } /** Returns {@code true} if a backup is currently running, else returns {@code false}. */ @@ -1885,9 +1919,7 @@ public class UserBackupManagerService { // ----- Full-data backup scheduling ----- - /** - * Schedule a job to tell us when it's a good time to run a full backup - */ + /** Schedule a job to tell us when it's a good time to run a full backup */ public void scheduleNextFullBackupJob(long transportMinLatency) { synchronized (mQueueLock) { if (mFullBackupQueue.size() > 0) { @@ -1899,18 +1931,15 @@ public class UserBackupManagerService { final long interval = mConstants.getFullBackupIntervalMilliseconds(); final long appLatency = (timeSinceLast < interval) ? (interval - timeSinceLast) : 0; final long latency = Math.max(transportMinLatency, appLatency); - FullBackupJob.schedule(mUserId, mContext, latency, - /* userBackupManagerService */ this); + FullBackupJob.schedule( + mUserId, mContext, latency, /* userBackupManagerService */ this); } else { - Slog.i(TAG, - addUserIdToLogMessage(mUserId, "Full backup queue empty; not scheduling")); + Slog.i(TAG, mLogIdMsg + "Full backup queue empty; not scheduling"); } } } - /** - * Remove a package from the full-data queue. - */ + /** Remove a package from the full-data queue. */ @GuardedBy("mQueueLock") private void dequeueFullBackupLocked(String packageName) { final int numPackages = mFullBackupQueue.size(); @@ -1922,9 +1951,7 @@ public class UserBackupManagerService { } } - /** - * Enqueue full backup for the given app, with a note about when it last ran. - */ + /** Enqueue full backup for the given app, with a note about when it last ran. */ public void enqueueFullBackup(String packageName, long lastBackedUp) { FullBackupEntry newEntry = new FullBackupEntry(packageName, lastBackedUp); synchronized (mQueueLock) { @@ -1957,10 +1984,7 @@ public class UserBackupManagerService { private boolean fullBackupAllowable(String transportName) { if (!mTransportManager.isTransportRegistered(transportName)) { - Slog.w( - TAG, - addUserIdToLogMessage( - mUserId, "Transport not registered; full data backup not performed")); + Slog.w(TAG, mLogIdMsg + "Transport not registered; full data backup not performed"); return false; } @@ -1971,15 +1995,11 @@ public class UserBackupManagerService { File stateDir = new File(mBaseStateDir, transportDirName); File pmState = new File(stateDir, PACKAGE_MANAGER_SENTINEL); if (pmState.length() <= 0) { - Slog.i(TAG, addUserIdToLogMessage(mUserId, - "Full backup requested but dataset not yet initialized")); + Slog.i(TAG, mLogIdMsg + "Full backup requested but dataset not yet initialized"); return false; } } catch (Exception e) { - Slog.w( - TAG, - addUserIdToLogMessage( - mUserId, "Unable to get transport name: " + e.getMessage())); + Slog.w(TAG, mLogIdMsg + "Unable to get transport name: " + e.getMessage()); return false; } @@ -1987,14 +2007,14 @@ public class UserBackupManagerService { } /** - * Conditions are right for a full backup operation, so run one. The model we use is - * to perform one app backup per scheduled job execution, and to reschedule the job - * with zero latency as long as conditions remain right and we still have work to do. + * Conditions are right for a full backup operation, so run one. The model we use is to perform + * one app backup per scheduled job execution, and to reschedule the job with zero latency as + * long as conditions remain right and we still have work to do. * * <p>This is the "start a full backup operation" entry point called by the scheduled job. * - * @return Whether ongoing work will continue. The return value here will be passed - * along as the return value to the scheduled job's onStartJob() callback. + * @return Whether ongoing work will continue. The return value here will be passed along as the + * return value to the scheduled job's onStartJob() callback. */ public boolean beginFullBackup(FullBackupJob scheduledJob) { final long now = System.currentTimeMillis(); @@ -2012,33 +2032,34 @@ public class UserBackupManagerService { // the job driving automatic backups; that job will be scheduled again when // the user enables backup. if (DEBUG) { - Slog.i(TAG, addUserIdToLogMessage(mUserId, "beginFullBackup but enabled=" + mEnabled - + " setupComplete=" + mSetupComplete + "; ignoring")); + Slog.i( + TAG, + mLogIdMsg + + "beginFullBackup but enabled=" + + mEnabled + + " setupComplete=" + + mSetupComplete + + "; ignoring"); } return false; } // Don't run the backup if we're in battery saver mode, but reschedule // to try again in the not-so-distant future. - final PowerSaveState result = - mPowerManager.getPowerSaveState(ServiceType.FULL_BACKUP); + final PowerSaveState result = mPowerManager.getPowerSaveState(ServiceType.FULL_BACKUP); if (result.batterySaverEnabled) { - Slog.i(TAG, addUserIdToLogMessage(mUserId, - "Deferring scheduled full backups in battery saver mode")); - FullBackupJob.schedule(mUserId, mContext, keyValueBackupInterval, - /* userBackupManagerService */ this); + Slog.i(TAG, mLogIdMsg + "Deferring scheduled full backups in battery saver mode"); + FullBackupJob.schedule( + mUserId, mContext, keyValueBackupInterval, /* userBackupManagerService */ this); return false; } - Slog.i(TAG, addUserIdToLogMessage(mUserId, "Beginning scheduled full backup operation")); + Slog.i(TAG, mLogIdMsg + "Beginning scheduled full backup operation"); // Great; we're able to run full backup jobs now. See if we have any work to do. synchronized (mQueueLock) { if (mRunningFullBackupTask != null) { - Slog.e( - TAG, - addUserIdToLogMessage( - mUserId, "Backup triggered but one already/still running!")); + Slog.e(TAG, mLogIdMsg + "Backup triggered but one already/still running!"); return false; } @@ -2053,8 +2074,7 @@ public class UserBackupManagerService { // have emptied the queue. if (mFullBackupQueue.size() == 0) { // no work to do so just bow out - Slog.i(TAG, - addUserIdToLogMessage(mUserId, "Backup queue empty; doing nothing")); + Slog.i(TAG, mLogIdMsg + "Backup queue empty; doing nothing"); runBackup = false; break; } @@ -2064,10 +2084,7 @@ public class UserBackupManagerService { String transportName = mTransportManager.getCurrentTransportName(); if (!fullBackupAllowable(transportName)) { if (DEBUG) { - Slog.i( - TAG, - addUserIdToLogMessage( - mUserId, "Preconditions not met; not running full backup")); + Slog.i(TAG, mLogIdMsg + "Preconditions not met; not running full backup"); } runBackup = false; // Typically this means we haven't run a key/value backup yet. Back off @@ -2085,18 +2102,16 @@ public class UserBackupManagerService { if (DEBUG) { Slog.i( TAG, - addUserIdToLogMessage( - mUserId, - "Device ready but too early to back up next app")); + mLogIdMsg + "Device ready but too early to back up next app"); } // Wait until the next app in the queue falls due for a full data backup latency = fullBackupInterval - timeSinceRun; - break; // we know we aren't doing work yet, so bail. + break; // we know we aren't doing work yet, so bail. } try { - PackageInfo appInfo = mPackageManager.getPackageInfoAsUser( - entry.packageName, 0, mUserId); + PackageInfo appInfo = + mPackageManager.getPackageInfoAsUser(entry.packageName, 0, mUserId); if (!mScheduledBackupEligibility.appGetsFullBackup(appInfo)) { // The head app isn't supposed to get full-data backups [any more]; // so we cull it and force a loop around to consider the new head @@ -2104,12 +2119,11 @@ public class UserBackupManagerService { if (DEBUG) { Slog.i( TAG, - addUserIdToLogMessage( - mUserId, - "Culling package " - + entry.packageName - + " in full-backup queue but not" - + " eligible")); + mLogIdMsg + + "Culling package " + + entry.packageName + + " in full-backup queue but not" + + " eligible"); } mFullBackupQueue.remove(0); headBusy = true; // force the while() condition @@ -2117,19 +2131,24 @@ public class UserBackupManagerService { } final int privFlags = appInfo.applicationInfo.privateFlags; - headBusy = (privFlags & PRIVATE_FLAG_BACKUP_IN_FOREGROUND) == 0 - && mActivityManagerInternal.isAppForeground( - appInfo.applicationInfo.uid); + headBusy = + (privFlags & PRIVATE_FLAG_BACKUP_IN_FOREGROUND) == 0 + && mActivityManagerInternal.isAppForeground( + appInfo.applicationInfo.uid); if (headBusy) { - final long nextEligible = System.currentTimeMillis() - + BUSY_BACKOFF_MIN_MILLIS - + mTokenGenerator.nextInt(BUSY_BACKOFF_FUZZ); + final long nextEligible = + System.currentTimeMillis() + + BUSY_BACKOFF_MIN_MILLIS + + mTokenGenerator.nextInt(BUSY_BACKOFF_FUZZ); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); - Slog.i(TAG, addUserIdToLogMessage(mUserId, - "Full backup time but " + entry.packageName - + " is busy; deferring to " + sdf.format( - new Date(nextEligible)))); + Slog.i( + TAG, + mLogIdMsg + + "Full backup time but " + + entry.packageName + + " is busy; deferring to " + + sdf.format(new Date(nextEligible))); // This relocates the app's entry from the head of the queue to // its order-appropriate position further down, so upon looping // a new candidate will be considered at the head. @@ -2147,21 +2166,22 @@ public class UserBackupManagerService { if (runBackup) { CountDownLatch latch = new CountDownLatch(1); - String[] pkg = new String[]{entry.packageName}; + String[] pkg = new String[] {entry.packageName}; try { - mRunningFullBackupTask = PerformFullTransportBackupTask.newWithCurrentTransport( - this, - mOperationStorage, - /* observer */ null, - pkg, - /* updateSchedule */ true, - scheduledJob, - latch, - /* backupObserver */ null, - /* monitor */ null, - /* userInitiated */ false, - "BMS.beginFullBackup()", - getEligibilityRulesForOperation(BackupDestination.CLOUD)); + mRunningFullBackupTask = + PerformFullTransportBackupTask.newWithCurrentTransport( + this, + mOperationStorage, + /* observer */ null, + pkg, + /* updateSchedule */ true, + scheduledJob, + latch, + /* backupObserver */ null, + /* monitor */ null, + /* userInitiated */ false, + "BMS.beginFullBackup()", + getEligibilityRulesForOperation(BackupDestination.CLOUD)); } catch (IllegalStateException e) { Slog.w(TAG, "Failed to start backup", e); runBackup = false; @@ -2169,12 +2189,15 @@ public class UserBackupManagerService { } if (!runBackup) { - Slog.i(TAG, addUserIdToLogMessage(mUserId, - "Nothing pending full backup or failed to start the " - + "operation; rescheduling +" + latency)); - final long deferTime = latency; // pin for the closure - FullBackupJob.schedule(mUserId, mContext, deferTime, - /* userBackupManagerService */ this); + Slog.i( + TAG, + mLogIdMsg + + "Nothing pending full backup or failed to start the " + + "operation; rescheduling +" + + latency); + final long deferTime = latency; // pin for the closure + FullBackupJob.schedule( + mUserId, mContext, deferTime, /* userBackupManagerService */ this); return false; } @@ -2195,21 +2218,22 @@ public class UserBackupManagerService { public void endFullBackup() { // offload the mRunningFullBackupTask.handleCancel() call to another thread, // as we might have to wait for mCancelLock - Runnable endFullBackupRunnable = new Runnable() { - @Override - public void run() { - PerformFullTransportBackupTask pftbt = null; - synchronized (mQueueLock) { - if (mRunningFullBackupTask != null) { - pftbt = mRunningFullBackupTask; + Runnable endFullBackupRunnable = + new Runnable() { + @Override + public void run() { + PerformFullTransportBackupTask pftbt = null; + synchronized (mQueueLock) { + if (mRunningFullBackupTask != null) { + pftbt = mRunningFullBackupTask; + } + } + if (pftbt != null) { + Slog.i(TAG, mLogIdMsg + "Telling running backup to stop"); + pftbt.handleCancel(true); + } } - } - if (pftbt != null) { - Slog.i(TAG, addUserIdToLogMessage(mUserId, "Telling running backup to stop")); - pftbt.handleCancel(true); - } - } - }; + }; new Thread(endFullBackupRunnable, "end-full-backup").start(); } @@ -2217,7 +2241,7 @@ public class UserBackupManagerService { public void restoreWidgetData(String packageName, byte[] widgetData) { // Apply the restored widget state and generate the ID update for the app if (DEBUG) { - Slog.i(TAG, addUserIdToLogMessage(mUserId, "Incorporating restored widget data")); + Slog.i(TAG, mLogIdMsg + "Incorporating restored widget data"); } AppWidgetBackupBridge.restoreWidgetState(packageName, widgetData, mUserId); } @@ -2235,13 +2259,12 @@ public class UserBackupManagerService { if (targets == null) { Slog.w( TAG, - addUserIdToLogMessage( - mUserId, - "dataChanged but no participant pkg='" - + packageName - + "'" - + " uid=" - + Binder.getCallingUid())); + mLogIdMsg + + "dataChanged but no participant pkg='" + + packageName + + "'" + + " uid=" + + Binder.getCallingUid()); return; } @@ -2253,10 +2276,7 @@ public class UserBackupManagerService { BackupRequest req = new BackupRequest(packageName); if (mPendingBackups.put(packageName, req) == null) { if (DEBUG) { - Slog.d( - TAG, - addUserIdToLogMessage( - mUserId, "Now staging backup of " + packageName)); + Slog.d(TAG, mLogIdMsg + "Now staging backup of " + packageName); } // Journal this request in case of crash. The put() @@ -2268,16 +2288,18 @@ public class UserBackupManagerService { } // ...and schedule a backup pass if necessary - KeyValueBackupJob.schedule(mUserId, mContext, - /* userBackupManagerService */ this); + KeyValueBackupJob.schedule(mUserId, mContext, /* userBackupManagerService */ this); } // Note: packageName is currently unused, but may be in the future private HashSet<String> dataChangedTargets(String packageName) { // If the caller does not hold the BACKUP permission, it can only request a // backup of its own data. - if ((mContext.checkPermission(android.Manifest.permission.BACKUP, Binder.getCallingPid(), - Binder.getCallingUid())) == PackageManager.PERMISSION_DENIED) { + if ((mContext.checkPermission( + android.Manifest.permission.BACKUP, + Binder.getCallingPid(), + Binder.getCallingUid())) + == PackageManager.PERMISSION_DENIED) { synchronized (mBackupParticipants) { return mBackupParticipants.get(Binder.getCallingUid()); } @@ -2298,10 +2320,7 @@ public class UserBackupManagerService { if (mJournal == null) mJournal = DataChangedJournal.newJournal(mJournalDir); mJournal.addPackage(str); } catch (IOException e) { - Slog.e( - TAG, - addUserIdToLogMessage(mUserId, "Can't write " + str + " to backup journal"), - e); + Slog.e(TAG, mLogIdMsg + "Can't write " + str + " to backup journal", e); mJournal = null; } } @@ -2314,31 +2333,28 @@ public class UserBackupManagerService { if (targets == null) { Slog.w( TAG, - addUserIdToLogMessage( - mUserId, - "dataChanged but no participant pkg='" - + packageName - + "'" - + " uid=" - + Binder.getCallingUid())); + mLogIdMsg + + "dataChanged but no participant pkg='" + + packageName + + "'" + + " uid=" + + Binder.getCallingUid()); return; } - mBackupHandler.post(new Runnable() { - public void run() { - dataChangedImpl(packageName, targets); - } - }); + mBackupHandler.post( + new Runnable() { + public void run() { + dataChangedImpl(packageName, targets); + } + }); } /** Run an initialize operation for the given transport. */ public void initializeTransports(String[] transportNames, IBackupObserver observer) { - mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP, - "initializeTransport"); - Slog.d( - TAG, - addUserIdToLogMessage( - mUserId, "initializeTransport(): " + Arrays.asList(transportNames))); + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.BACKUP, "initializeTransport"); + Slog.d(TAG, mLogIdMsg + "initializeTransport(): " + Arrays.asList(transportNames)); final long oldId = Binder.clearCallingIdentity(); try { @@ -2351,32 +2367,23 @@ public class UserBackupManagerService { } } - /** - * Sets the work profile serial number of the ancestral work profile. - */ + /** Sets the work profile serial number of the ancestral work profile. */ public void setAncestralSerialNumber(long ancestralSerialNumber) { - mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, - "setAncestralSerialNumber"); - Slog.d( - TAG, - addUserIdToLogMessage( - mUserId, "Setting ancestral work profile id to " + ancestralSerialNumber)); + mContext.enforceCallingPermission( + android.Manifest.permission.BACKUP, "setAncestralSerialNumber"); + Slog.d(TAG, mLogIdMsg + "Setting ancestral work profile id to " + ancestralSerialNumber); try (RandomAccessFile af = new RandomAccessFile(getAncestralSerialNumberFile(), /* mode */ "rwd")) { af.writeLong(ancestralSerialNumber); } catch (IOException e) { - Slog.w( - TAG, - addUserIdToLogMessage( - mUserId, "Unable to write to work profile serial mapping file:"), - e); + Slog.w(TAG, mLogIdMsg + "Unable to write to work profile serial mapping file:", e); } } /** - * Returns the work profile serial number of the ancestral device. This will be set by - * {@link #setAncestralSerialNumber(long)}. Will return {@code -1} if not set. + * Returns the work profile serial number of the ancestral device. This will be set by {@link + * #setAncestralSerialNumber(long)}. Will return {@code -1} if not set. */ public long getAncestralSerialNumber() { try (RandomAccessFile af = @@ -2385,20 +2392,15 @@ public class UserBackupManagerService { } catch (FileNotFoundException e) { // It's OK not to have the file present, so we just return -1 to indicate no value. } catch (IOException e) { - Slog.w( - TAG, - addUserIdToLogMessage( - mUserId, "Unable to read work profile serial number file:"), - e); + Slog.w(TAG, mLogIdMsg + "Unable to read work profile serial number file:", e); } return -1; } private File getAncestralSerialNumberFile() { if (mAncestralSerialNumberFile == null) { - mAncestralSerialNumberFile = new File( - UserBackupManagerFiles.getBaseStateDir(getUserId()), - SERIAL_ID_FILE); + mAncestralSerialNumberFile = + new File(UserBackupManagerFiles.getBaseStateDir(getUserId()), SERIAL_ID_FILE); } return mAncestralSerialNumberFile; } @@ -2408,39 +2410,36 @@ public class UserBackupManagerService { mAncestralSerialNumberFile = ancestralSerialNumberFile; } - /** Clear the given package's backup data from the current transport. */ public void clearBackupData(String transportName, String packageName) { - Slog.d(TAG, addUserIdToLogMessage(mUserId, - "clearBackupData() of " + packageName + " on " + transportName)); + Slog.d(TAG, mLogIdMsg + "clearBackupData() of " + packageName + " on " + transportName); PackageInfo info; try { - info = mPackageManager.getPackageInfoAsUser(packageName, - PackageManager.GET_SIGNING_CERTIFICATES, mUserId); + info = + mPackageManager.getPackageInfoAsUser( + packageName, PackageManager.GET_SIGNING_CERTIFICATES, mUserId); } catch (NameNotFoundException e) { Slog.d( TAG, - addUserIdToLogMessage( - mUserId, - "No such package '" + packageName + "' - not clearing backup data")); + mLogIdMsg + "No such package '" + packageName + "' - not clearing backup data"); return; } // If the caller does not hold the BACKUP permission, it can only request a // wipe of its own backed-up data. Set<String> apps; - if ((mContext.checkPermission(android.Manifest.permission.BACKUP, Binder.getCallingPid(), - Binder.getCallingUid())) == PackageManager.PERMISSION_DENIED) { + if ((mContext.checkPermission( + android.Manifest.permission.BACKUP, + Binder.getCallingPid(), + Binder.getCallingUid())) + == PackageManager.PERMISSION_DENIED) { apps = mBackupParticipants.get(Binder.getCallingUid()); } else { // a caller with full permission can ask to back up any participating app // !!! TODO: allow data-clear of ANY app? if (DEBUG) { - Slog.v( - TAG, - addUserIdToLogMessage( - mUserId, "Privileged caller, allowing clear of other apps")); + Slog.v(TAG, mLogIdMsg + "Privileged caller, allowing clear of other apps"); } apps = mProcessedPackagesJournal.getPackagesCopy(); } @@ -2448,30 +2447,33 @@ public class UserBackupManagerService { if (apps.contains(packageName)) { // found it; fire off the clear request if (DEBUG) { - Slog.v( - TAG, - addUserIdToLogMessage(mUserId, "Found the app - running clear process")); + Slog.v(TAG, mLogIdMsg + "Found the app - running clear process"); } mBackupHandler.removeMessages(MSG_RETRY_CLEAR); synchronized (mQueueLock) { TransportConnection transportConnection = - mTransportManager - .getTransportClient(transportName, "BMS.clearBackupData()"); + mTransportManager.getTransportClient( + transportName, "BMS.clearBackupData()"); if (transportConnection == null) { // transport is currently unregistered -- make sure to retry - Message msg = mBackupHandler.obtainMessage(MSG_RETRY_CLEAR, - new ClearRetryParams(transportName, packageName)); + Message msg = + mBackupHandler.obtainMessage( + MSG_RETRY_CLEAR, + new ClearRetryParams(transportName, packageName)); mBackupHandler.sendMessageDelayed(msg, TRANSPORT_RETRY_INTERVAL); return; } final long oldId = Binder.clearCallingIdentity(); try { - OnTaskFinishedListener listener = caller -> mTransportManager - .disposeOfTransportClient(transportConnection, caller); + OnTaskFinishedListener listener = + caller -> + mTransportManager.disposeOfTransportClient( + transportConnection, caller); mWakelock.acquire(); - Message msg = mBackupHandler.obtainMessage( - MSG_RUN_CLEAR, - new ClearParams(transportConnection, info, listener)); + Message msg = + mBackupHandler.obtainMessage( + MSG_RUN_CLEAR, + new ClearParams(transportConnection, info, listener)); mBackupHandler.sendMessage(msg); } finally { Binder.restoreCallingIdentity(oldId); @@ -2492,31 +2494,24 @@ public class UserBackupManagerService { final PowerSaveState result = mPowerManager.getPowerSaveState(ServiceType.KEYVALUE_BACKUP); if (result.batterySaverEnabled) { - Slog.d(TAG, addUserIdToLogMessage(mUserId, - "Not running backup while in battery save mode")); + Slog.d(TAG, mLogIdMsg + "Not running backup while in battery save mode"); // Try again in several hours. - KeyValueBackupJob.schedule(mUserId, mContext, - /* userBackupManagerService */ this); + KeyValueBackupJob.schedule(mUserId, mContext, /* userBackupManagerService */ this); } else { - Slog.d(TAG, addUserIdToLogMessage(mUserId, "Scheduling immediate backup pass")); + Slog.d(TAG, mLogIdMsg + "Scheduling immediate backup pass"); synchronized (getQueueLock()) { if (getPendingInits().size() > 0) { // If there are pending init operations, we process those and then settle // into the usual periodic backup schedule. if (DEBUG) { - Slog.v( - TAG, - addUserIdToLogMessage( - mUserId, "Init pending at scheduled backup")); + Slog.v(TAG, mLogIdMsg + "Init pending at scheduled backup"); } try { getAlarmManager().cancel(mRunInitIntent); mRunInitIntent.send(); } catch (PendingIntent.CanceledException ce) { - Slog.w( - TAG, - addUserIdToLogMessage(mUserId, "Run init intent cancelled")); + Slog.w(TAG, mLogIdMsg + "Run init intent cancelled"); } return; } @@ -2526,8 +2521,11 @@ public class UserBackupManagerService { if (!isEnabled() || !isSetupComplete()) { Slog.w( TAG, - addUserIdToLogMessage(mUserId, "Backup pass but enabled=" + isEnabled() - + " setupComplete=" + isSetupComplete())); + mLogIdMsg + + "Backup pass but enabled=" + + isEnabled() + + " setupComplete=" + + isSetupComplete()); return; } @@ -2548,9 +2546,17 @@ public class UserBackupManagerService { * return to the caller until the backup has been completed. It requires on-screen confirmation * by the user. */ - public void adbBackup(ParcelFileDescriptor fd, boolean includeApks, - boolean includeObbs, boolean includeShared, boolean doWidgets, boolean doAllApps, - boolean includeSystem, boolean compress, boolean doKeyValue, String[] pkgList) { + public void adbBackup( + ParcelFileDescriptor fd, + boolean includeApks, + boolean includeObbs, + boolean includeShared, + boolean doWidgets, + boolean doAllApps, + boolean includeSystem, + boolean compress, + boolean doKeyValue, + String[] pkgList) { mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, "adbBackup"); final int callingUserHandle = UserHandle.getCallingUserId(); @@ -2574,47 +2580,66 @@ public class UserBackupManagerService { final long oldId = Binder.clearCallingIdentity(); try { if (!mSetupComplete) { - Slog.i(TAG, addUserIdToLogMessage(mUserId, "Backup not supported before setup")); + Slog.i(TAG, mLogIdMsg + "Backup not supported before setup"); return; } - Slog.d(TAG, addUserIdToLogMessage(mUserId, - "Requesting backup: apks=" + includeApks + " obb=" + includeObbs + " shared=" - + includeShared + " all=" + doAllApps + " system=" + includeSystem - + " includekeyvalue=" + doKeyValue + " pkgs=" + Arrays.toString( - pkgList))); - Slog.i(TAG, addUserIdToLogMessage(mUserId, "Beginning adb backup...")); - - BackupEligibilityRules eligibilityRules = getEligibilityRulesForOperation( - BackupDestination.ADB_BACKUP); - AdbBackupParams params = new AdbBackupParams(fd, includeApks, includeObbs, - includeShared, doWidgets, doAllApps, includeSystem, compress, doKeyValue, - pkgList, eligibilityRules); + Slog.d( + TAG, + mLogIdMsg + + "Requesting backup: apks=" + + includeApks + + " obb=" + + includeObbs + + " shared=" + + includeShared + + " all=" + + doAllApps + + " system=" + + includeSystem + + " includekeyvalue=" + + doKeyValue + + " pkgs=" + + Arrays.toString(pkgList)); + Slog.i(TAG, mLogIdMsg + "Beginning adb backup..."); + + BackupEligibilityRules eligibilityRules = + getEligibilityRulesForOperation(BackupDestination.ADB_BACKUP); + AdbBackupParams params = + new AdbBackupParams( + fd, + includeApks, + includeObbs, + includeShared, + doWidgets, + doAllApps, + includeSystem, + compress, + doKeyValue, + pkgList, + eligibilityRules); final int token = generateRandomIntegerToken(); synchronized (mAdbBackupRestoreConfirmations) { mAdbBackupRestoreConfirmations.put(token, params); } // start up the confirmation UI - Slog.d(TAG, addUserIdToLogMessage(mUserId, "Starting backup confirmation UI")); + Slog.d(TAG, mLogIdMsg + "Starting backup confirmation UI"); if (!startConfirmationUi(token, FullBackup.FULL_BACKUP_INTENT_ACTION)) { - Slog.e( - TAG, - addUserIdToLogMessage(mUserId, "Unable to launch backup confirmation UI")); + Slog.e(TAG, mLogIdMsg + "Unable to launch backup confirmation UI"); mAdbBackupRestoreConfirmations.delete(token); return; } // make sure the screen is lit for the user interaction - mPowerManager.userActivity(SystemClock.uptimeMillis(), - PowerManager.USER_ACTIVITY_EVENT_OTHER, - 0); + mPowerManager.userActivity( + SystemClock.uptimeMillis(), PowerManager.USER_ACTIVITY_EVENT_OTHER, 0); // start the confirmation countdown startConfirmationTimeout(token, params); // wait for the backup to be performed - Slog.d(TAG, addUserIdToLogMessage(mUserId, "Waiting for backup completion...")); + Slog.d(TAG, mLogIdMsg + "Waiting for backup completion..."); waitForCompletion(params); } finally { try { @@ -2622,19 +2647,17 @@ public class UserBackupManagerService { } catch (IOException e) { Slog.e( TAG, - addUserIdToLogMessage( - mUserId, - "IO error closing output for adb backup: " + e.getMessage())); + mLogIdMsg + "IO error closing output for adb backup: " + e.getMessage()); } Binder.restoreCallingIdentity(oldId); - Slog.d(TAG, addUserIdToLogMessage(mUserId, "Adb backup processing complete.")); + Slog.d(TAG, mLogIdMsg + "Adb backup processing complete."); } } /** Run a full backup pass for the given packages. Used by 'adb shell bmgr'. */ public void fullTransportBackup(String[] pkgNames) { - mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, - "fullTransportBackup"); + mContext.enforceCallingPermission( + android.Manifest.permission.BACKUP, "fullTransportBackup"); final int callingUserHandle = UserHandle.getCallingUserId(); // TODO: http://b/22388012 if (callingUserHandle != UserHandle.USER_SYSTEM) { @@ -2643,30 +2666,27 @@ public class UserBackupManagerService { String transportName = mTransportManager.getCurrentTransportName(); if (!fullBackupAllowable(transportName)) { - Slog.i( - TAG, - addUserIdToLogMessage( - mUserId, - "Full backup not currently possible -- key/value backup not yet run?")); + Slog.i(TAG, mLogIdMsg + "Full backup not possible. Key/value backup not yet run?"); } else { - Slog.d(TAG, addUserIdToLogMessage(mUserId, "fullTransportBackup()")); + Slog.d(TAG, mLogIdMsg + "fullTransportBackup()"); final long oldId = Binder.clearCallingIdentity(); try { CountDownLatch latch = new CountDownLatch(1); - Runnable task = PerformFullTransportBackupTask.newWithCurrentTransport( - this, - mOperationStorage, - /* observer */ null, - pkgNames, - /* updateSchedule */ false, - /* runningJob */ null, - latch, - /* backupObserver */ null, - /* monitor */ null, - /* userInitiated */ false, - "BMS.fullTransportBackup()", - getEligibilityRulesForOperation(BackupDestination.CLOUD)); + Runnable task = + PerformFullTransportBackupTask.newWithCurrentTransport( + this, + mOperationStorage, + /* observer */ null, + pkgNames, + /* updateSchedule */ false, + /* runningJob */ null, + latch, + /* backupObserver */ null, + /* monitor */ null, + /* userInitiated */ false, + "BMS.fullTransportBackup()", + getEligibilityRulesForOperation(BackupDestination.CLOUD)); // Acquiring wakelock for PerformFullTransportBackupTask before its start. mWakelock.acquire(); (new Thread(task, "full-transport-master")).start(); @@ -2692,7 +2712,7 @@ public class UserBackupManagerService { } } - Slog.d(TAG, addUserIdToLogMessage(mUserId, "Done with full transport backup.")); + Slog.d(TAG, mLogIdMsg + "Done with full transport backup."); } /** @@ -2711,13 +2731,11 @@ public class UserBackupManagerService { try { if (!mSetupComplete) { - Slog.i( - TAG, - addUserIdToLogMessage(mUserId, "Full restore not permitted before setup")); + Slog.i(TAG, mLogIdMsg + "Full restore not permitted before setup"); return; } - Slog.i(TAG, addUserIdToLogMessage(mUserId, "Beginning restore...")); + Slog.i(TAG, mLogIdMsg + "Beginning restore..."); AdbRestoreParams params = new AdbRestoreParams(fd); final int token = generateRandomIntegerToken(); @@ -2726,38 +2744,31 @@ public class UserBackupManagerService { } // start up the confirmation UI - Slog.d(TAG, addUserIdToLogMessage(mUserId, - "Starting restore confirmation UI, token=" + token)); + Slog.d(TAG, mLogIdMsg + "Starting restore confirmation UI, token=" + token); if (!startConfirmationUi(token, FullBackup.FULL_RESTORE_INTENT_ACTION)) { - Slog.e( - TAG, - addUserIdToLogMessage(mUserId, "Unable to launch restore confirmation")); + Slog.e(TAG, mLogIdMsg + "Unable to launch restore confirmation"); mAdbBackupRestoreConfirmations.delete(token); return; } // make sure the screen is lit for the user interaction - mPowerManager.userActivity(SystemClock.uptimeMillis(), - PowerManager.USER_ACTIVITY_EVENT_OTHER, - 0); + mPowerManager.userActivity( + SystemClock.uptimeMillis(), PowerManager.USER_ACTIVITY_EVENT_OTHER, 0); // start the confirmation countdown startConfirmationTimeout(token, params); // wait for the restore to be performed - Slog.d(TAG, addUserIdToLogMessage(mUserId, "Waiting for restore completion...")); + Slog.d(TAG, mLogIdMsg + "Waiting for restore completion..."); waitForCompletion(params); } finally { try { fd.close(); } catch (IOException e) { - Slog.w( - TAG, - addUserIdToLogMessage( - mUserId, "Error trying to close fd after adb restore: " + e)); + Slog.w(TAG, mLogIdMsg + "Error trying to close fd after adb restore: " + e); } Binder.restoreCallingIdentity(oldId); - Slog.i(TAG, addUserIdToLogMessage(mUserId, "adb restore processing complete.")); + Slog.i(TAG, mLogIdMsg + "adb restore processing complete."); } } @@ -2766,13 +2777,14 @@ public class UserBackupManagerService { * to the backup agent during restore. */ public void excludeKeysFromRestore(String packageName, List<String> keys) { - mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP, - "excludeKeysFromRestore"); + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.BACKUP, "excludeKeysFromRestore"); mBackupPreferences.addExcludedKeys(packageName, keys); } - public void reportDelayedRestoreResult(String packageName, - List<BackupRestoreEventLogger.DataTypeResult> results) { + /** See {@link BackupManager#reportDelayedRestoreResult(BackupRestoreEventLogger)}. */ + public void reportDelayedRestoreResult( + String packageName, List<BackupRestoreEventLogger.DataTypeResult> results) { String transport = mTransportManager.getCurrentTransportName(); if (transport == null) { Slog.w(TAG, "Failed to send delayed restore logs as no transport selected"); @@ -2781,26 +2793,34 @@ public class UserBackupManagerService { TransportConnection transportConnection = null; try { - PackageInfo packageInfo = getPackageManager().getPackageInfoAsUser(packageName, - PackageManager.PackageInfoFlags.of(/* value */ 0), getUserId()); + PackageInfo packageInfo = + getPackageManager() + .getPackageInfoAsUser( + packageName, + PackageManager.PackageInfoFlags.of(/* value */ 0), + getUserId()); - transportConnection = mTransportManager.getTransportClientOrThrow( - transport, /* caller */"BMS.reportDelayedRestoreResult"); - BackupTransportClient transportClient = transportConnection.connectOrThrow( - /* caller */ "BMS.reportDelayedRestoreResult"); + transportConnection = + mTransportManager.getTransportClientOrThrow( + transport, /* caller */ "BMS.reportDelayedRestoreResult"); + BackupTransportClient transportClient = + transportConnection.connectOrThrow( + /* caller */ "BMS.reportDelayedRestoreResult"); IBackupManagerMonitor monitor = transportClient.getBackupManagerMonitor(); - BackupManagerMonitorEventSender mBackupManagerMonitorEventSender = + BackupManagerMonitorEventSender mBackupManagerMonitorEventSender = getBMMEventSender(monitor); - mBackupManagerMonitorEventSender.sendAgentLoggingResults(packageInfo, results, - BackupAnnotations.OperationType.RESTORE); - } catch (NameNotFoundException | TransportNotAvailableException - | TransportNotRegisteredException | RemoteException e) { + mBackupManagerMonitorEventSender.sendAgentLoggingResults( + packageInfo, results, BackupAnnotations.OperationType.RESTORE); + } catch (NameNotFoundException + | TransportNotAvailableException + | TransportNotRegisteredException + | RemoteException e) { Slog.w(TAG, "Failed to send delayed restore logs: " + e); } finally { if (transportConnection != null) { - mTransportManager.disposeOfTransportClient(transportConnection, - /* caller */"BMS.reportDelayedRestoreResult"); + mTransportManager.disposeOfTransportClient( + transportConnection, /* caller */ "BMS.reportDelayedRestoreResult"); } } } @@ -2808,7 +2828,8 @@ public class UserBackupManagerService { private boolean startConfirmationUi(int token, String action) { try { Intent confIntent = new Intent(action); - confIntent.setClassName("com.android.backupconfirm", + confIntent.setClassName( + "com.android.backupconfirm", "com.android.backupconfirm.BackupRestoreConfirmation"); confIntent.putExtra(FullBackup.CONF_TOKEN_INTENT_EXTRA, token); confIntent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); @@ -2821,11 +2842,14 @@ public class UserBackupManagerService { private void startConfirmationTimeout(int token, AdbParams params) { if (DEBUG) { - Slog.d(TAG, addUserIdToLogMessage(mUserId, "Posting conf timeout msg after " - + TIMEOUT_FULL_CONFIRMATION + " millis")); + Slog.d( + TAG, + mLogIdMsg + + "Posting conf timeout msg after " + + TIMEOUT_FULL_CONFIRMATION + + " millis"); } - Message msg = mBackupHandler.obtainMessage(MSG_FULL_CONFIRMATION_TIMEOUT, - token, 0, params); + Message msg = mBackupHandler.obtainMessage(MSG_FULL_CONFIRMATION_TIMEOUT, token, 0, params); mBackupHandler.sendMessageDelayed(msg, TIMEOUT_FULL_CONFIRMATION); } @@ -2834,7 +2858,9 @@ public class UserBackupManagerService { while (!params.latch.get()) { try { params.latch.wait(); - } catch (InterruptedException e) { /* never interrupted */ } + } catch (InterruptedException e) { + /* never interrupted */ + } } } } @@ -2851,15 +2877,20 @@ public class UserBackupManagerService { * Confirm that the previously-requested full backup/restore operation can proceed. This is used * to require a user-facing disclosure about the operation. */ - public void acknowledgeAdbBackupOrRestore(int token, boolean allow, - String curPassword, String encPpassword, IFullBackupRestoreObserver observer) { - Slog.d(TAG, addUserIdToLogMessage(mUserId, - "acknowledgeAdbBackupOrRestore : token=" + token + " allow=" + allow)); + public void acknowledgeAdbBackupOrRestore( + int token, + boolean allow, + String curPassword, + String encPpassword, + IFullBackupRestoreObserver observer) { + Slog.d( + TAG, + mLogIdMsg + "acknowledgeAdbBackupOrRestore : token=" + token + " allow=" + allow); // TODO: possibly require not just this signature-only permission, but even // require that the specific designated confirmation-UI app uid is the caller? - mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, - "acknowledgeAdbBackupOrRestore"); + mContext.enforceCallingPermission( + android.Manifest.permission.BACKUP, "acknowledgeAdbBackupOrRestore"); final long oldId = Binder.clearCallingIdentity(); try { @@ -2872,9 +2903,10 @@ public class UserBackupManagerService { mAdbBackupRestoreConfirmations.delete(token); if (allow) { - final int verb = params instanceof AdbBackupParams - ? MSG_RUN_ADB_BACKUP - : MSG_RUN_ADB_RESTORE; + final int verb = + params instanceof AdbBackupParams + ? MSG_RUN_ADB_BACKUP + : MSG_RUN_ADB_RESTORE; params.observer = observer; params.curPassword = curPassword; @@ -2882,28 +2914,20 @@ public class UserBackupManagerService { params.encryptPassword = encPpassword; if (DEBUG) { - Slog.d( - TAG, - addUserIdToLogMessage( - mUserId, "Sending conf message with verb " + verb)); + Slog.d(TAG, mLogIdMsg + "Sending conf message with verb " + verb); } mWakelock.acquire(); Message msg = mBackupHandler.obtainMessage(verb, params); mBackupHandler.sendMessage(msg); } else { - Slog.w( - TAG, - addUserIdToLogMessage( - mUserId, "User rejected full backup/restore operation")); + Slog.w(TAG, mLogIdMsg + "User rejected full backup/restore operation"); // indicate completion without having actually transferred any data signalAdbBackupRestoreCompletion(params); } } else { Slog.w( TAG, - addUserIdToLogMessage( - mUserId, - "Attempted to ack full backup/restore with invalid token")); + mLogIdMsg + "Attempted to ack full backup/restore with invalid token"); } } } finally { @@ -2922,10 +2946,10 @@ public class UserBackupManagerService { } private void setBackupEnabled(boolean enable, boolean persistToDisk) { - mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP, - "setBackupEnabled"); + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.BACKUP, "setBackupEnabled"); - Slog.i(TAG, addUserIdToLogMessage(mUserId, "Backup enabled => " + enable)); + Slog.i(TAG, mLogIdMsg + "Backup enabled => " + enable); final long oldId = Binder.clearCallingIdentity(); try { @@ -2944,23 +2968,25 @@ public class UserBackupManagerService { } synchronized void setFrameworkSchedulingEnabled(boolean isEnabled) { - mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP, - "setFrameworkSchedulingEnabled"); + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.BACKUP, "setFrameworkSchedulingEnabled"); boolean wasEnabled = isFrameworkSchedulingEnabled(); if (wasEnabled == isEnabled) { return; } - Slog.i(TAG, addUserIdToLogMessage(mUserId, - (isEnabled ? "Enabling" : "Disabling") + " backup scheduling")); + Slog.i(TAG, mLogIdMsg + (isEnabled ? "Enabling" : "Disabling") + " backup scheduling"); final long oldId = Binder.clearCallingIdentity(); try { // TODO(b/264889098): Consider at a later point if we should us a sentinel file as // setBackupEnabled. - Settings.Secure.putIntForUser(mContext.getContentResolver(), - Settings.Secure.BACKUP_SCHEDULING_ENABLED, isEnabled ? 1 : 0, mUserId); + Settings.Secure.putIntForUser( + mContext.getContentResolver(), + Settings.Secure.BACKUP_SCHEDULING_ENABLED, + isEnabled ? 1 : 0, + mUserId); if (!isEnabled) { KeyValueBackupJob.cancel(mUserId, mContext); @@ -2977,8 +3003,12 @@ public class UserBackupManagerService { synchronized boolean isFrameworkSchedulingEnabled() { // By default scheduling is enabled final int defaultSetting = 1; - int isEnabled = Settings.Secure.getIntForUser(mContext.getContentResolver(), - Settings.Secure.BACKUP_SCHEDULING_ENABLED, defaultSetting, mUserId); + int isEnabled = + Settings.Secure.getIntForUser( + mContext.getContentResolver(), + Settings.Secure.BACKUP_SCHEDULING_ENABLED, + defaultSetting, + mUserId); return isEnabled == 1; } @@ -2992,7 +3022,7 @@ public class UserBackupManagerService { } else if (!enable) { // No longer enabled, so stop running backups if (DEBUG) { - Slog.i(TAG, addUserIdToLogMessage(mUserId, "Opting out of backup")); + Slog.i(TAG, mLogIdMsg + "Opting out of backup"); } KeyValueBackupJob.cancel(mUserId, mContext); @@ -3012,11 +3042,7 @@ public class UserBackupManagerService { dirName = mTransportManager.getTransportDirName(name); } catch (TransportNotRegisteredException e) { // Should never happen - Slog.e( - TAG, - addUserIdToLogMessage( - mUserId, "Unexpected unregistered transport"), - e); + Slog.e(TAG, mLogIdMsg + "Unexpected unregistered transport", e); return; } transportNames.add(name); @@ -3025,13 +3051,10 @@ public class UserBackupManagerService { // build the set of transports for which we are posting an init for (int i = 0; i < transportNames.size(); i++) { - recordInitPending( - true, - transportNames.get(i), - transportDirNames.get(i)); + recordInitPending(true, transportNames.get(i), transportDirNames.get(i)); } - mAlarmManager.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis(), - mRunInitIntent); + mAlarmManager.set( + AlarmManager.RTC_WAKEUP, System.currentTimeMillis(), mRunInitIntent); } } } @@ -3049,16 +3072,19 @@ public class UserBackupManagerService { /** Enable/disable automatic restore of app data at install time. */ public void setAutoRestore(boolean doAutoRestore) { - mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP, - "setAutoRestore"); + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.BACKUP, "setAutoRestore"); - Slog.i(TAG, addUserIdToLogMessage(mUserId, "Auto restore => " + doAutoRestore)); + Slog.i(TAG, mLogIdMsg + "Auto restore => " + doAutoRestore); final long oldId = Binder.clearCallingIdentity(); try { synchronized (this) { - Settings.Secure.putIntForUser(mContext.getContentResolver(), - Settings.Secure.BACKUP_AUTO_RESTORE, doAutoRestore ? 1 : 0, mUserId); + Settings.Secure.putIntForUser( + mContext.getContentResolver(), + Settings.Secure.BACKUP_AUTO_RESTORE, + doAutoRestore ? 1 : 0, + mUserId); mAutoRestore = doAutoRestore; } } finally { @@ -3068,21 +3094,18 @@ public class UserBackupManagerService { /** Report whether the backup mechanism is currently enabled. */ public boolean isBackupEnabled() { - mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP, - "isBackupEnabled"); - return mEnabled; // no need to synchronize just to read it + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.BACKUP, "isBackupEnabled"); + return mEnabled; // no need to synchronize just to read it } /** Report the name of the currently active transport. */ public String getCurrentTransport() { - mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP, - "getCurrentTransport"); + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.BACKUP, "getCurrentTransport"); String currentTransport = mTransportManager.getCurrentTransportName(); if (DEBUG) { - Slog.v( - TAG, - addUserIdToLogMessage( - mUserId, "... getCurrentTransport() returning " + currentTransport)); + Slog.v(TAG, mLogIdMsg + "... getCurrentTransport() returning " + currentTransport); } return currentTransport; } @@ -3107,16 +3130,16 @@ public class UserBackupManagerService { /** Report all known, available backup transports by name. */ public String[] listAllTransports() { - mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP, - "listAllTransports"); + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.BACKUP, "listAllTransports"); return mTransportManager.getRegisteredTransportNames(); } /** Report all known, available backup transports by component. */ public ComponentName[] listAllTransportComponents() { - mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP, - "listAllTransportComponents"); + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.BACKUP, "listAllTransportComponents"); return mTransportManager.getRegisteredTransportComponents(); } @@ -3129,18 +3152,17 @@ public class UserBackupManagerService { * @param transportComponent The identity of the transport being described. * @param name A {@link String} with the new name for the transport. This is NOT for * identification. MUST NOT be {@code null}. - * @param configurationIntent An {@link Intent} that can be passed to - * {@link Context#startActivity} in order to launch the transport's configuration UI. It may - * be {@code null} if the transport does not offer any user-facing configuration UI. + * @param configurationIntent An {@link Intent} that can be passed to {@link + * Context#startActivity} in order to launch the transport's configuration UI. It may be + * {@code null} if the transport does not offer any user-facing configuration UI. * @param currentDestinationString A {@link String} describing the destination to which the * transport is currently sending data. MUST NOT be {@code null}. - * @param dataManagementIntent An {@link Intent} that can be passed to - * {@link Context#startActivity} in order to launch the transport's data-management UI. It - * may be {@code null} if the transport does not offer any user-facing data - * management UI. + * @param dataManagementIntent An {@link Intent} that can be passed to {@link + * Context#startActivity} in order to launch the transport's data-management UI. It may be + * {@code null} if the transport does not offer any user-facing data management UI. * @param dataManagementLabel A {@link CharSequence} to be used as the label for the transport's - * data management affordance. This MUST be {@code null} when dataManagementIntent is - * {@code null} and MUST NOT be {@code null} when dataManagementIntent is not {@code null}. + * data management affordance. This MUST be {@code null} when dataManagementIntent is {@code + * null} and MUST NOT be {@code null} when dataManagementIntent is not {@code null}. * @throws SecurityException If the UID of the calling process differs from the package UID of * {@code transportComponent} or if the caller does NOT have BACKUP permission. */ @@ -3175,8 +3197,7 @@ public class UserBackupManagerService { Objects.requireNonNull(transportComponent, "transportComponent can't be null"); Objects.requireNonNull(name, "name can't be null"); - Objects.requireNonNull( - currentDestinationString, "currentDestinationString can't be null"); + Objects.requireNonNull(currentDestinationString, "currentDestinationString can't be null"); Preconditions.checkArgument( (dataManagementIntent == null) == (dataManagementLabel == null), "dataManagementLabel should be null iff dataManagementIntent is null"); @@ -3211,7 +3232,7 @@ public class UserBackupManagerService { * selected transport. Returns {@code null} if the transport is not registered. * * @deprecated Use {@link #selectBackupTransportAsync(ComponentName, - * ISelectBackupTransportCallback)} instead. + * ISelectBackupTransportCallback)} instead. */ @Deprecated @Nullable @@ -3224,11 +3245,10 @@ public class UserBackupManagerService { if (!mTransportManager.isTransportRegistered(transportName)) { Slog.d( TAG, - addUserIdToLogMessage( - mUserId, - "Could not select transport " - + transportName - + ", as the transport is not registered.")); + mLogIdMsg + + "Could not select transport " + + transportName + + ", as the transport is not registered."); return null; } @@ -3236,12 +3256,11 @@ public class UserBackupManagerService { updateStateForTransport(transportName); Slog.d( TAG, - addUserIdToLogMessage( - mUserId, - "selectBackupTransport(transport = " - + transportName - + "): previous transport = " - + previousTransportName)); + mLogIdMsg + + "selectBackupTransport(transport = " + + transportName + + "): previous transport = " + + previousTransportName); return previousTransportName; } finally { Binder.restoreCallingIdentity(oldId); @@ -3262,9 +3281,7 @@ public class UserBackupManagerService { String transportString = transportComponent.flattenToShortString(); Slog.d( TAG, - addUserIdToLogMessage( - mUserId, - "selectBackupTransportAsync(transport = " + transportString + ")")); + mLogIdMsg + "selectBackupTransportAsync(transport = " + transportString + ")"); mBackupHandler.post( () -> { String transportName = null; @@ -3276,10 +3293,7 @@ public class UserBackupManagerService { mTransportManager.getTransportName(transportComponent); updateStateForTransport(transportName); } catch (TransportNotRegisteredException e) { - Slog.e( - TAG, - addUserIdToLogMessage( - mUserId, "Transport got unregistered")); + Slog.e(TAG, mLogIdMsg + "Transport got unregistered"); result = BackupManager.ERROR_TRANSPORT_UNAVAILABLE; } } @@ -3293,10 +3307,9 @@ public class UserBackupManagerService { } catch (RemoteException e) { Slog.e( TAG, - addUserIdToLogMessage( - mUserId, - "ISelectBackupTransportCallback listener not" - + " available")); + mLogIdMsg + + "ISelectBackupTransportCallback listener not" + + " available"); } }); } finally { @@ -3306,8 +3319,11 @@ public class UserBackupManagerService { private void updateStateForTransport(String newTransportName) { // Publish the name change - Settings.Secure.putStringForUser(mContext.getContentResolver(), - Settings.Secure.BACKUP_TRANSPORT, newTransportName, mUserId); + Settings.Secure.putStringForUser( + mContext.getContentResolver(), + Settings.Secure.BACKUP_TRANSPORT, + newTransportName, + mUserId); // And update our current-dataset bookkeeping String callerLogString = "BMS.updateStateForTransport()"; @@ -3315,8 +3331,8 @@ public class UserBackupManagerService { mTransportManager.getTransportClient(newTransportName, callerLogString); if (transportConnection != null) { try { - BackupTransportClient transport = transportConnection.connectOrThrow( - callerLogString); + BackupTransportClient transport = + transportConnection.connectOrThrow(callerLogString); mCurrentToken = transport.getCurrentRestoreSet(); } catch (Exception e) { // Oops. We can't know the current dataset token, so reset and figure it out @@ -3324,21 +3340,19 @@ public class UserBackupManagerService { mCurrentToken = 0; Slog.w( TAG, - addUserIdToLogMessage( - mUserId, - "Transport " - + newTransportName - + " not available: current token = 0")); + mLogIdMsg + + "Transport " + + newTransportName + + " not available: current token = 0"); } mTransportManager.disposeOfTransportClient(transportConnection, callerLogString); } else { Slog.w( TAG, - addUserIdToLogMessage( - mUserId, - "Transport " - + newTransportName - + " not registered: current token = 0")); + mLogIdMsg + + "Transport " + + newTransportName + + " not registered: current token = 0"); // The named transport isn't registered, so we can't know what its current dataset token // is. Reset as above. mCurrentToken = 0; @@ -3351,24 +3365,20 @@ public class UserBackupManagerService { * returns {@code null}. */ public Intent getConfigurationIntent(String transportName) { - mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP, - "getConfigurationIntent"); + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.BACKUP, "getConfigurationIntent"); try { Intent intent = mTransportManager.getTransportConfigurationIntent(transportName); if (DEBUG) { - Slog.d( - TAG, - addUserIdToLogMessage( - mUserId, "getConfigurationIntent() returning intent " + intent)); + Slog.d(TAG, mLogIdMsg + "getConfigurationIntent() returning intent " + intent); } return intent; } catch (TransportNotRegisteredException e) { Slog.e( TAG, - addUserIdToLogMessage( - mUserId, - "Unable to get configuration intent from transport: " - + e.getMessage())); + mLogIdMsg + + "Unable to get configuration intent from transport: " + + e.getMessage()); return null; } } @@ -3389,42 +3399,36 @@ public class UserBackupManagerService { try { String string = mTransportManager.getTransportCurrentDestinationString(transportName); if (DEBUG) { - Slog.d( - TAG, - addUserIdToLogMessage( - mUserId, "getDestinationString() returning " + string)); + Slog.d(TAG, mLogIdMsg + "getDestinationString() returning " + string); } return string; } catch (TransportNotRegisteredException e) { Slog.e( TAG, - addUserIdToLogMessage( - mUserId, - "Unable to get destination string from transport: " + e.getMessage())); + mLogIdMsg + + "Unable to get destination string from transport: " + + e.getMessage()); return null; } } /** Supply the manage-data intent for the given transport. */ public Intent getDataManagementIntent(String transportName) { - mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP, - "getDataManagementIntent"); + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.BACKUP, "getDataManagementIntent"); try { Intent intent = mTransportManager.getTransportDataManagementIntent(transportName); if (DEBUG) { - Slog.d( - TAG, - addUserIdToLogMessage( - mUserId, "getDataManagementIntent() returning intent " + intent)); + Slog.d(TAG, mLogIdMsg + "getDataManagementIntent() returning intent " + intent); } return intent; } catch (TransportNotRegisteredException e) { Slog.e( TAG, - addUserIdToLogMessage( - mUserId, - "Unable to get management intent from transport: " + e.getMessage())); + mLogIdMsg + + "Unable to get management intent from transport: " + + e.getMessage()); return null; } } @@ -3434,24 +3438,19 @@ public class UserBackupManagerService { * transport. */ public CharSequence getDataManagementLabel(String transportName) { - mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP, - "getDataManagementLabel"); + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.BACKUP, "getDataManagementLabel"); try { CharSequence label = mTransportManager.getTransportDataManagementLabel(transportName); if (DEBUG) { - Slog.d( - TAG, - addUserIdToLogMessage( - mUserId, "getDataManagementLabel() returning " + label)); + Slog.d(TAG, mLogIdMsg + "getDataManagementLabel() returning " + label); } return label; } catch (TransportNotRegisteredException e) { Slog.e( TAG, - addUserIdToLogMessage( - mUserId, - "Unable to get management label from transport: " + e.getMessage())); + mLogIdMsg + "Unable to get management label from transport: " + e.getMessage()); return null; } } @@ -3464,57 +3463,64 @@ public class UserBackupManagerService { if (Binder.getCallingUid() != Process.SYSTEM_UID) { Slog.w( TAG, - addUserIdToLogMessage( - mUserId, - "Non-system process uid=" - + Binder.getCallingUid() - + " attemping install-time restore")); + mLogIdMsg + + "Non-system process uid=" + + Binder.getCallingUid() + + " attemping install-time restore"); return; } boolean skip = false; long restoreSet = getAvailableRestoreToken(packageName); - Slog.d(TAG, addUserIdToLogMessage(mUserId, - "restoreAtInstall pkg=" + packageName + " token=" + Integer.toHexString(token) - + " restoreSet=" + Long.toHexString(restoreSet))); + Slog.d( + TAG, + mLogIdMsg + + "restoreAtInstall pkg=" + + packageName + + " token=" + + Integer.toHexString(token) + + " restoreSet=" + + Long.toHexString(restoreSet)); if (restoreSet == 0) { - if (DEBUG) Slog.i(TAG, addUserIdToLogMessage(mUserId, "No restore set")); + if (DEBUG) Slog.i(TAG, mLogIdMsg + "No restore set"); skip = true; } - BackupManagerMonitorEventSender mBMMEventSender = - getBMMEventSender(/*monitor=*/ null); + BackupManagerMonitorEventSender mBMMEventSender = getBMMEventSender(/* monitor= */ null); PackageInfo packageInfo = getPackageInfoForBMMLogging(packageName); TransportConnection transportConnection = mTransportManager.getCurrentTransportClient("BMS.restoreAtInstall()"); if (transportConnection == null) { - Slog.w(TAG, addUserIdToLogMessage(mUserId, "No transport client")); + Slog.w(TAG, mLogIdMsg + "No transport client"); skip = true; } else if (Flags.enableIncreasedBmmLoggingForRestoreAtInstall()) { try { - BackupTransportClient transportClient = transportConnection.connectOrThrow( - "BMS.restoreAtInstall"); + BackupTransportClient transportClient = + transportConnection.connectOrThrow("BMS.restoreAtInstall"); mBMMEventSender.setMonitor(transportClient.getBackupManagerMonitor()); } catch (TransportNotAvailableException | RemoteException e) { mBMMEventSender.monitorEvent( - BackupManagerMonitor.LOG_EVENT_ID_TRANSPORT_IS_NULL, packageInfo, - BackupManagerMonitor.LOG_EVENT_CATEGORY_TRANSPORT, null); + BackupManagerMonitor.LOG_EVENT_ID_TRANSPORT_IS_NULL, + packageInfo, + BackupManagerMonitor.LOG_EVENT_CATEGORY_TRANSPORT, + null); } } if (Flags.enableIncreasedBmmLoggingForRestoreAtInstall()) { mBMMEventSender.monitorEvent( - BackupManagerMonitor.LOG_EVENT_ID_RESTORE_AT_INSTALL_INVOKED, packageInfo, + BackupManagerMonitor.LOG_EVENT_ID_RESTORE_AT_INSTALL_INVOKED, + packageInfo, BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY, - mBMMEventSender.putMonitoringExtra(/*extras=*/null, + mBMMEventSender.putMonitoringExtra( + /* extras= */ null, BackupManagerMonitor.EXTRA_LOG_OPERATION_TYPE, BackupAnnotations.OperationType.RESTORE)); } if (!mAutoRestore) { - Slog.w(TAG, - addUserIdToLogMessage(mUserId, "Non-restorable state: auto=" + mAutoRestore)); + Slog.w(TAG, mLogIdMsg + "Non-restorable state: auto=" + mAutoRestore); skip = true; } @@ -3526,15 +3532,14 @@ public class UserBackupManagerService { mWakelock.acquire(); - OnTaskFinishedListener listener = caller -> { - mTransportManager.disposeOfTransportClient(transportConnection, caller); - mWakelock.release(); - }; + OnTaskFinishedListener listener = + caller -> { + mTransportManager.disposeOfTransportClient(transportConnection, caller); + mWakelock.release(); + }; if (DEBUG) { - Slog.d( - TAG, - addUserIdToLogMessage(mUserId, "Restore at install of " + packageName)); + Slog.d(TAG, mLogIdMsg + "Restore at install of " + packageName); } Message msg = mBackupHandler.obtainMessage(MSG_RUN_RESTORE); msg.obj = @@ -3550,10 +3555,7 @@ public class UserBackupManagerService { mBackupHandler.sendMessage(msg); } catch (Exception e) { // Calling into the transport broke; back off and proceed with the installation. - Slog.e( - TAG, - addUserIdToLogMessage( - mUserId, "Unable to contact transport: " + e.getMessage())); + Slog.e(TAG, mLogIdMsg + "Unable to contact transport: " + e.getMessage()); skip = true; } } @@ -3563,9 +3565,11 @@ public class UserBackupManagerService { if (Flags.enableIncreasedBmmLoggingForRestoreAtInstall()) { mBMMEventSender.monitorEvent( - BackupManagerMonitor.LOG_EVENT_ID_SKIP_RESTORE_AT_INSTALL, packageInfo, + BackupManagerMonitor.LOG_EVENT_ID_SKIP_RESTORE_AT_INSTALL, + packageInfo, BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY, - mBMMEventSender.putMonitoringExtra(/*extras=*/null, + mBMMEventSender.putMonitoringExtra( + /* extras= */ null, BackupManagerMonitor.EXTRA_LOG_OPERATION_TYPE, BackupAnnotations.OperationType.RESTORE)); } @@ -3576,10 +3580,12 @@ public class UserBackupManagerService { } // Tell the PackageManager to proceed with the post-install handling for this package. - Slog.d(TAG, addUserIdToLogMessage(mUserId, "Finishing install immediately")); + Slog.d(TAG, mLogIdMsg + "Finishing install immediately"); try { mPackageManagerBinder.finishPackageInstall(token, false); - } catch (RemoteException e) { /* can't happen */ } + } catch (RemoteException e) { + /* can't happen */ + } } } @@ -3592,8 +3598,9 @@ public class UserBackupManagerService { /** Hand off a restore session. */ public IRestoreSession beginRestoreSession(String packageName, String transport) { - Slog.d(TAG, addUserIdToLogMessage(mUserId, - "beginRestoreSession: pkg=" + packageName + " transport=" + transport)); + Slog.d( + TAG, + mLogIdMsg + "beginRestoreSession: pkg=" + packageName + " transport=" + transport); boolean needPermission = true; if (transport == null) { @@ -3604,10 +3611,7 @@ public class UserBackupManagerService { try { app = mPackageManager.getPackageInfoAsUser(packageName, 0, mUserId); } catch (NameNotFoundException nnf) { - Slog.w( - TAG, - addUserIdToLogMessage( - mUserId, "Asked to restore nonexistent pkg " + packageName)); + Slog.w(TAG, mLogIdMsg + "Asked to restore nonexistent pkg " + packageName); throw new IllegalArgumentException("Package " + packageName + " not found"); } @@ -3624,46 +3628,45 @@ public class UserBackupManagerService { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.BACKUP, "beginRestoreSession"); } else { - Slog.d(TAG, addUserIdToLogMessage(mUserId, - "restoring self on current transport; no permission needed")); + Slog.d(TAG, mLogIdMsg + "restoring self on current transport; no permission needed"); } int backupDestination; TransportConnection transportConnection = null; try { - transportConnection = mTransportManager.getTransportClientOrThrow( - transport, /* caller */"BMS.beginRestoreSession"); + transportConnection = + mTransportManager.getTransportClientOrThrow( + transport, /* caller */ "BMS.beginRestoreSession"); backupDestination = getBackupDestinationFromTransport(transportConnection); - } catch (TransportNotAvailableException | TransportNotRegisteredException + } catch (TransportNotAvailableException + | TransportNotRegisteredException | RemoteException e) { Slog.w(TAG, "Failed to get operation type from transport: " + e); return null; } finally { if (transportConnection != null) { - mTransportManager.disposeOfTransportClient(transportConnection, - /* caller */"BMS.beginRestoreSession"); + mTransportManager.disposeOfTransportClient( + transportConnection, /* caller */ "BMS.beginRestoreSession"); } } synchronized (this) { if (mActiveRestoreSession != null) { - Slog.i( - TAG, - addUserIdToLogMessage( - mUserId, "Restore session requested but one already active")); + Slog.i(TAG, mLogIdMsg + "Restore session requested but one already active"); return null; } if (mBackupRunning) { - Slog.i( - TAG, - addUserIdToLogMessage( - mUserId, - "Restore session requested but currently running backups")); + Slog.i(TAG, mLogIdMsg + "Restore session requested but currently running backups"); return null; } - mActiveRestoreSession = new ActiveRestoreSession(this, packageName, transport, - getEligibilityRulesForOperation(backupDestination)); - mBackupHandler.sendEmptyMessageDelayed(MSG_RESTORE_SESSION_TIMEOUT, + mActiveRestoreSession = + new ActiveRestoreSession( + this, + packageName, + transport, + getEligibilityRulesForOperation(backupDestination)); + mBackupHandler.sendEmptyMessageDelayed( + MSG_RESTORE_SESSION_TIMEOUT, mAgentTimeoutParameters.getRestoreSessionTimeoutMillis()); } return mActiveRestoreSession; @@ -3673,10 +3676,9 @@ public class UserBackupManagerService { public void clearRestoreSession(ActiveRestoreSession currentSession) { synchronized (this) { if (currentSession != mActiveRestoreSession) { - Slog.e(TAG, addUserIdToLogMessage(mUserId, "ending non-current restore session")); + Slog.e(TAG, mLogIdMsg + "ending non-current restore session"); } else { - Slog.d(TAG, addUserIdToLogMessage(mUserId, - "Clearing restore session and halting timeout")); + Slog.d(TAG, mLogIdMsg + "Clearing restore session and halting timeout"); mActiveRestoreSession = null; mBackupHandler.removeMessages(MSG_RESTORE_SESSION_TIMEOUT); } @@ -3688,11 +3690,14 @@ public class UserBackupManagerService { * outstanding asynchronous backup/restore operation. */ public void opComplete(int token, long result) { - mOperationStorage.onOperationComplete(token, result, callback -> { - Pair<BackupRestoreTask, Long> callbackAndResult = Pair.create(callback, result); - Message msg = mBackupHandler.obtainMessage(MSG_OP_COMPLETE, callbackAndResult); - mBackupHandler.sendMessage(msg); - }); + mOperationStorage.onOperationComplete( + token, + result, + callback -> { + Pair<BackupRestoreTask, Long> callbackAndResult = Pair.create(callback, result); + Message msg = mBackupHandler.obtainMessage(MSG_OP_COMPLETE, callbackAndResult); + mBackupHandler.sendMessage(msg); + }); } /** Checks if the package is eligible for backup. */ @@ -3748,10 +3753,16 @@ public class UserBackupManagerService { return getEligibilityRules(mPackageManager, mUserId, mContext, backupDestination); } - private static BackupEligibilityRules getEligibilityRules(PackageManager packageManager, - int userId, Context context, @BackupDestination int backupDestination) { - return new BackupEligibilityRules(packageManager, - LocalServices.getService(PackageManagerInternal.class), userId, context, + private static BackupEligibilityRules getEligibilityRules( + PackageManager packageManager, + int userId, + Context context, + @BackupDestination int backupDestination) { + return new BackupEligibilityRules( + packageManager, + LocalServices.getService(PackageManagerInternal.class), + userId, + context, backupDestination); } @@ -3793,20 +3804,20 @@ public class UserBackupManagerService { } private void dumpBMMEvents(PrintWriter pw) { - BackupManagerMonitorDumpsysUtils bm = - new BackupManagerMonitorDumpsysUtils(); + BackupManagerMonitorDumpsysUtils bm = new BackupManagerMonitorDumpsysUtils(); if (bm.deleteExpiredBMMEvents()) { pw.println("BACKUP MANAGER MONITOR EVENTS HAVE EXPIRED"); return; } File events = bm.getBMMEventsFile(); - if (events.length() == 0){ + if (events.length() == 0) { // We have not recorded BMMEvents yet. pw.println("NO BACKUP MANAGER MONITOR EVENTS"); return; - } else if (bm.isFileLargerThanSizeLimit(events)){ - pw.println("BACKUP MANAGER MONITOR EVENTS FILE OVER SIZE LIMIT - " - + "future events will not be recorded"); + } else if (bm.isFileLargerThanSizeLimit(events)) { + pw.println( + "BACKUP MANAGER MONITOR EVENTS FILE OVER SIZE LIMIT - " + + "future events will not be recorded"); } pw.println("START OF BACKUP MANAGER MONITOR EVENTS"); try (BufferedReader reader = new BufferedReader(new FileReader(events))) { @@ -3826,16 +3837,27 @@ public class UserBackupManagerService { // Add prefix for only non-system users so that system user dumpsys is the same as before String userPrefix = mUserId == UserHandle.USER_SYSTEM ? "" : "User " + mUserId + ":"; synchronized (mQueueLock) { - pw.println(userPrefix + "Backup Manager is " + (mEnabled ? "enabled" : "disabled") - + " / " + (!mSetupComplete ? "not " : "") + "setup complete / " - + (this.mPendingInits.size() == 0 ? "not " : "") + "pending init"); + pw.println( + userPrefix + + "Backup Manager is " + + (mEnabled ? "enabled" : "disabled") + + " / " + + (!mSetupComplete ? "not " : "") + + "setup complete / " + + (this.mPendingInits.size() == 0 ? "not " : "") + + "pending init"); pw.println("Auto-restore is " + (mAutoRestore ? "enabled" : "disabled")); if (mBackupRunning) pw.println("Backup currently running"); pw.println(isBackupOperationInProgress() ? "Backup in progress" : "No backups running"); - pw.println("Framework scheduling is " - + (isFrameworkSchedulingEnabled() ? "enabled" : "disabled")); - pw.println("Last backup pass started: " + mLastBackupPass - + " (now = " + System.currentTimeMillis() + ')'); + pw.println( + "Framework scheduling is " + + (isFrameworkSchedulingEnabled() ? "enabled" : "disabled")); + pw.println( + "Last backup pass started: " + + mLastBackupPass + + " (now = " + + System.currentTimeMillis() + + ')'); pw.println(" next scheduled: " + KeyValueBackupJob.nextScheduled(mUserId)); pw.println(userPrefix + "Transport whitelist:"); @@ -3848,21 +3870,27 @@ public class UserBackupManagerService { final String[] transports = listAllTransports(); if (transports != null) { for (String t : transports) { - pw.println((t.equals(mTransportManager.getCurrentTransportName()) ? " * " - : " ") + t); + pw.println( + (t.equals(mTransportManager.getCurrentTransportName()) + ? " * " + : " ") + + t); try { - File dir = new File(mBaseStateDir, - mTransportManager.getTransportDirName(t)); - pw.println(" destination: " - + mTransportManager.getTransportCurrentDestinationString(t)); - pw.println(" intent: " - + mTransportManager.getTransportConfigurationIntent(t)); + File dir = + new File(mBaseStateDir, mTransportManager.getTransportDirName(t)); + pw.println( + " destination: " + + mTransportManager.getTransportCurrentDestinationString( + t)); + pw.println( + " intent: " + + mTransportManager.getTransportConfigurationIntent(t)); for (File f : dir.listFiles()) { pw.println( " " + f.getName() + " - " + f.length() + " state bytes"); } } catch (Exception e) { - Slog.e(TAG, addUserIdToLogMessage(mUserId, "Error in transport"), e); + Slog.e(TAG, mLogIdMsg + "Error in transport", e); pw.println(" Error: " + e); } } @@ -3892,8 +3920,10 @@ public class UserBackupManagerService { } } - pw.println(userPrefix + "Ancestral packages: " - + (mAncestralPackages == null ? "none" : mAncestralPackages.size())); + pw.println( + userPrefix + + "Ancestral packages: " + + (mAncestralPackages == null ? "none" : mAncestralPackages.size())); if (mAncestralPackages != null) { for (String pkg : mAncestralPackages) { pw.println(" " + pkg); @@ -3919,29 +3949,35 @@ public class UserBackupManagerService { pw.println(entry.packageName); } pw.println(userPrefix + "Agent timeouts:"); - pw.println(" KvBackupAgentTimeoutMillis: " - + mAgentTimeoutParameters.getKvBackupAgentTimeoutMillis()); - pw.println(" FullBackupAgentTimeoutMillis: " - + mAgentTimeoutParameters.getFullBackupAgentTimeoutMillis()); - pw.println(" SharedBackupAgentTimeoutMillis: " - + mAgentTimeoutParameters.getSharedBackupAgentTimeoutMillis()); - pw.println(" RestoreAgentTimeoutMillis (system): " - + mAgentTimeoutParameters.getRestoreAgentTimeoutMillis( - Process.FIRST_APPLICATION_UID - 1)); - pw.println(" RestoreAgentTimeoutMillis: " - + mAgentTimeoutParameters.getRestoreAgentTimeoutMillis( - Process.FIRST_APPLICATION_UID)); - pw.println(" RestoreAgentFinishedTimeoutMillis: " - + mAgentTimeoutParameters.getRestoreAgentFinishedTimeoutMillis()); - pw.println(" QuotaExceededTimeoutMillis: " - + mAgentTimeoutParameters.getQuotaExceededTimeoutMillis()); - + pw.println( + " KvBackupAgentTimeoutMillis: " + + mAgentTimeoutParameters.getKvBackupAgentTimeoutMillis()); + pw.println( + " FullBackupAgentTimeoutMillis: " + + mAgentTimeoutParameters.getFullBackupAgentTimeoutMillis()); + pw.println( + " SharedBackupAgentTimeoutMillis: " + + mAgentTimeoutParameters.getSharedBackupAgentTimeoutMillis()); + pw.println( + " RestoreAgentTimeoutMillis (system): " + + mAgentTimeoutParameters.getRestoreAgentTimeoutMillis( + Process.FIRST_APPLICATION_UID - 1)); + pw.println( + " RestoreAgentTimeoutMillis: " + + mAgentTimeoutParameters.getRestoreAgentTimeoutMillis( + Process.FIRST_APPLICATION_UID)); + pw.println( + " RestoreAgentFinishedTimeoutMillis: " + + mAgentTimeoutParameters.getRestoreAgentFinishedTimeoutMillis()); + pw.println( + " QuotaExceededTimeoutMillis: " + + mAgentTimeoutParameters.getQuotaExceededTimeoutMillis()); } } @VisibleForTesting - @BackupDestination int getBackupDestinationFromTransport( - TransportConnection transportConnection) + @BackupDestination + int getBackupDestinationFromTransport(TransportConnection transportConnection) throws TransportNotAvailableException, RemoteException { if (!shouldUseNewBackupEligibilityRules()) { // Return the default to stick to the legacy behaviour. @@ -3950,8 +3986,9 @@ public class UserBackupManagerService { final long oldCallingId = Binder.clearCallingIdentity(); try { - BackupTransportClient transport = transportConnection.connectOrThrow( - /* caller */ "BMS.getBackupDestinationFromTransport"); + BackupTransportClient transport = + transportConnection.connectOrThrow( + /* caller */ "BMS.getBackupDestinationFromTransport"); if ((transport.getTransportFlags() & BackupAgent.FLAG_DEVICE_TO_DEVICE_TRANSFER) != 0) { return BackupDestination.DEVICE_TRANSFER; } else { @@ -3964,15 +4001,10 @@ public class UserBackupManagerService { @VisibleForTesting boolean shouldUseNewBackupEligibilityRules() { - return FeatureFlagUtils.isEnabled(mContext, - FeatureFlagUtils.SETTINGS_USE_NEW_BACKUP_ELIGIBILITY_RULES); + return FeatureFlagUtils.isEnabled( + mContext, FeatureFlagUtils.SETTINGS_USE_NEW_BACKUP_ELIGIBILITY_RULES); } - private static String addUserIdToLogMessage(int userId, String message) { - return "[UserID:" + userId + "] " + message; - } - - public IBackupManager getBackupManagerBinder() { return mBackupManagerBinder; } 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 f401e6b66093..c385fbad02a5 100644 --- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java +++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java @@ -490,7 +490,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub ? mParams.getBlockedActivities() : mParams.getAllowedActivities()); - if (Flags.vdmCustomIme() && mParams.getInputMethodComponent() != null) { + if (mParams.getInputMethodComponent() != null) { final String imeId = mParams.getInputMethodComponent().flattenToShortString(); Slog.d(TAG, "Setting custom input method " + imeId + " as default for virtual device " + deviceId); @@ -807,7 +807,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub } // Clear any previously set custom IME components. - if (Flags.vdmCustomIme() && mParams.getInputMethodComponent() != null) { + if (mParams.getInputMethodComponent() != null) { InputMethodManagerInternal.get().setVirtualDeviceInputMethodForAllUsers( mDeviceId, null); } diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java index d76c04ac7f31..27e9e44f1090 100644 --- a/services/core/java/com/android/server/am/UserController.java +++ b/services/core/java/com/android/server/am/UserController.java @@ -4062,8 +4062,7 @@ class UserController implements Handler.Callback { synchronized (mUserSwitchingDialogLock) { dismissUserSwitchingDialog(null); mUserSwitchingDialog = new UserSwitchingDialog(mService.mContext, fromUser, toUser, - switchingFromSystemUserMessage, switchingToSystemUserMessage, - getWindowManager()); + switchingFromSystemUserMessage, switchingToSystemUserMessage); mUserSwitchingDialog.show(onShown); } } diff --git a/services/core/java/com/android/server/am/UserSwitchingDialog.java b/services/core/java/com/android/server/am/UserSwitchingDialog.java index 2d7456471be4..d1fcb9d1ca37 100644 --- a/services/core/java/com/android/server/am/UserSwitchingDialog.java +++ b/services/core/java/com/android/server/am/UserSwitchingDialog.java @@ -39,7 +39,6 @@ import android.os.SystemProperties; import android.os.Trace; import android.os.UserHandle; import android.os.UserManager; -import android.provider.Settings; import android.util.Slog; import android.util.TypedValue; import android.view.View; @@ -53,7 +52,6 @@ import android.widget.TextView; import com.android.internal.R; import com.android.internal.util.ObjectUtils; import com.android.internal.util.UserIcons; -import com.android.server.wm.WindowManagerService; import java.util.concurrent.atomic.AtomicBoolean; @@ -80,14 +78,11 @@ class UserSwitchingDialog extends Dialog { protected final UserInfo mNewUser; private final String mSwitchingFromSystemUserMessage; private final String mSwitchingToSystemUserMessage; - private final WindowManagerService mWindowManager; protected final Context mContext; private final int mTraceCookie; - private final boolean mNeedToFreezeScreen; UserSwitchingDialog(Context context, UserInfo oldUser, UserInfo newUser, - String switchingFromSystemUserMessage, String switchingToSystemUserMessage, - WindowManagerService windowManager) { + String switchingFromSystemUserMessage, String switchingToSystemUserMessage) { super(context, R.style.Theme_Material_NoActionBar_Fullscreen); mContext = context; @@ -97,8 +92,6 @@ class UserSwitchingDialog extends Dialog { mSwitchingToSystemUserMessage = switchingToSystemUserMessage; mDisableAnimations = SystemProperties.getBoolean( "debug.usercontroller.disable_user_switching_dialog_animations", false); - mWindowManager = windowManager; - mNeedToFreezeScreen = !mDisableAnimations && !isUserSetupComplete(newUser); mTraceCookie = UserHandle.MAX_SECONDARY_USER_ID * oldUser.id + newUser.id; inflateContent(); @@ -183,11 +176,6 @@ class UserSwitchingDialog extends Dialog { : res.getString(R.string.user_switching_message, mNewUser.name); } - private boolean isUserSetupComplete(UserInfo user) { - return Settings.Secure.getIntForUser(mContext.getContentResolver(), - Settings.Secure.USER_SETUP_COMPLETE, /* default= */ 0, user.id) == 1; - } - @Override public void show() { asyncTraceBegin("dialog", 0); @@ -197,7 +185,6 @@ class UserSwitchingDialog extends Dialog { @Override public void dismiss() { super.dismiss(); - stopFreezingScreen(); asyncTraceEnd("dialog", 0); } @@ -205,7 +192,6 @@ class UserSwitchingDialog extends Dialog { if (DEBUG) Slog.d(TAG, "show called"); show(); startShowAnimation(() -> { - startFreezingScreen(); onShown.run(); }); } @@ -223,24 +209,6 @@ class UserSwitchingDialog extends Dialog { } } - private void startFreezingScreen() { - if (!mNeedToFreezeScreen) { - return; - } - traceBegin("startFreezingScreen"); - mWindowManager.startFreezingScreen(0, 0); - traceEnd("startFreezingScreen"); - } - - private void stopFreezingScreen() { - if (!mNeedToFreezeScreen) { - return; - } - traceBegin("stopFreezingScreen"); - mWindowManager.stopFreezingScreen(); - traceEnd("stopFreezingScreen"); - } - private void startShowAnimation(Runnable onAnimationEnd) { if (mDisableAnimations) { onAnimationEnd.run(); @@ -260,7 +228,7 @@ class UserSwitchingDialog extends Dialog { } private void startDismissAnimation(Runnable onAnimationEnd) { - if (mDisableAnimations || mNeedToFreezeScreen) { + if (mDisableAnimations) { // animations are disabled or screen is frozen, no need to play an animation onAnimationEnd.run(); return; @@ -352,14 +320,4 @@ class UserSwitchingDialog extends Dialog { Trace.asyncTraceEnd(TRACE_TAG, TAG + subTag, mTraceCookie + subCookie); if (DEBUG) Slog.d(TAG, "asyncTraceEnd-" + subTag); } - - private void traceBegin(String msg) { - if (DEBUG) Slog.d(TAG, "traceBegin-" + msg); - Trace.traceBegin(TRACE_TAG, msg); - } - - private void traceEnd(String msg) { - Trace.traceEnd(TRACE_TAG); - if (DEBUG) Slog.d(TAG, "traceEnd-" + msg); - } } diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index b9b06701a11b..c125d2d2e8cd 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -256,6 +256,7 @@ import com.android.internal.os.BackgroundThread; import com.android.internal.os.SomeArgs; import com.android.internal.util.DumpUtils; import com.android.internal.util.Preconditions; +import com.android.modules.expresslog.Counter; import com.android.server.EventLogTags; import com.android.server.LocalManagerRegistry; import com.android.server.LocalServices; @@ -838,9 +839,49 @@ public class AudioService extends IAudioService.Stub new AudioServiceUserRestrictionsListener(); private final IAudioManagerNative mNativeShim = new IAudioManagerNative.Stub() { + static final String METRIC_COUNTERS_PLAYBACK_PARTIAL = + "media_audio.value_audio_playback_hardening_partial_restriction"; + static final String METRIC_COUNTERS_PLAYBACK_STRICT = + "media_audio.value_audio_playback_hardening_strict_would_restrict"; + + String getPackNameForUid(int uid) { + final long token = Binder.clearCallingIdentity(); + try { + final String[] names = AudioService.this.mContext. + getPackageManager().getPackagesForUid(uid); + if (names == null + || names.length == 0 + || TextUtils.isEmpty(names[0])) { + return "[" + uid + "]"; + } + return names[0]; + } finally { + Binder.restoreCallingIdentity(token); + } + } + // oneway @Override public void playbackHardeningEvent(int uid, byte type, boolean bypassed) { + if (Binder.getCallingUid() != Process.AUDIOSERVER_UID) { + return; + } + if (type == HardeningType.PARTIAL) { + Counter.logIncrementWithUid(METRIC_COUNTERS_PLAYBACK_PARTIAL, uid); + } else if (type == HardeningType.FULL) { + Counter.logIncrementWithUid(METRIC_COUNTERS_PLAYBACK_STRICT, uid); + } else { + Slog.wtf(TAG, "Unexpected hardening type" + type); + return; + } + String msg = "AudioHardening background playback " + + (bypassed ? "would be " : "") + + "muted for " + + getPackNameForUid(uid) + " (" + uid + "), " + + "level: " + (type == HardeningType.PARTIAL ? "partial" : "full"); + + AudioService.this.mHardeningLogger.enqueueAndSlog(msg, + bypassed ? EventLogger.Event.ALOGI : EventLogger.Event.ALOGW, TAG); } @Override @@ -1544,7 +1585,8 @@ public class AudioService extends IAudioService.Stub mMusicFxHelper = new MusicFxHelper(mContext, mAudioHandler); mHardeningEnforcer = new HardeningEnforcer(mContext, isPlatformAutomotive(), mAppOps, - context.getPackageManager()); + context.getPackageManager(), + mHardeningLogger); } private void initVolumeStreamStates() { @@ -12691,6 +12733,7 @@ public class AudioService extends IAudioService.Stub static final int LOG_NB_EVENTS_DYN_POLICY = 10; static final int LOG_NB_EVENTS_SPATIAL = 30; static final int LOG_NB_EVENTS_SOUND_DOSE = 50; + static final int LOG_NB_EVENTS_HARDENING = 50; static final int LOG_NB_EVENTS_LOUDNESS_CODEC = 30; @@ -12729,6 +12772,9 @@ public class AudioService extends IAudioService.Stub mDynPolicyLogger = new EventLogger(LOG_NB_EVENTS_DYN_POLICY, "dynamic policy events (logged when command received by AudioService)"); + private final EventLogger mHardeningLogger = new EventLogger( + LOG_NB_EVENTS_HARDENING, "Hardening enforcement"); + private static final String[] RINGER_MODE_NAMES = new String[] { "SILENT", "VIBRATE", @@ -12803,7 +12849,7 @@ public class AudioService extends IAudioService.Stub pw.println("\nMessage handler is null"); } dumpFlags(pw); - mHardeningEnforcer.dump(pw); + mHardeningLogger.dump(pw); mMediaFocusControl.dump(pw); dumpStreamStates(pw); dumpVolumeGroups(pw); diff --git a/services/core/java/com/android/server/audio/HardeningEnforcer.java b/services/core/java/com/android/server/audio/HardeningEnforcer.java index 661111070aae..f69a810b314f 100644 --- a/services/core/java/com/android/server/audio/HardeningEnforcer.java +++ b/services/core/java/com/android/server/audio/HardeningEnforcer.java @@ -54,8 +54,7 @@ public class HardeningEnforcer { final ActivityManager mActivityManager; final PackageManager mPackageManager; - final EventLogger mEventLogger = new EventLogger(LOG_NB_EVENTS, - "Hardening enforcement"); + final EventLogger mEventLogger; // capacity = 4 for each of the focus request types static final SparseArray<String> METRIC_COUNTERS_FOCUS_DENIAL = new SparseArray<>(4); @@ -108,17 +107,13 @@ public class HardeningEnforcer { public static final int METHOD_AUDIO_MANAGER_REQUEST_AUDIO_FOCUS = 300; public HardeningEnforcer(Context ctxt, boolean isAutomotive, AppOpsManager appOps, - PackageManager pm) { + PackageManager pm, EventLogger logger) { mContext = ctxt; mIsAutomotive = isAutomotive; mAppOps = appOps; mActivityManager = ctxt.getSystemService(ActivityManager.class); mPackageManager = pm; - } - - protected void dump(PrintWriter pw) { - // log - mEventLogger.dump(pw); + mEventLogger = logger; } /** diff --git a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java index 1c01fb9f19e0..e2e06b63c7d6 100644 --- a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java +++ b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java @@ -1745,7 +1745,6 @@ public final class PlaybackActivityMonitor eventValues[0] = eventValue; sEventLogger.enqueue( new PlayerEvent(piid, PLAYER_UPDATE_MUTED, eventValues)); - final AudioPlaybackConfiguration apc = mPlayers.get(piid); if (apc == null || !apc.handleMutedEvent(eventValue)) { break; // do not dispatch diff --git a/services/core/java/com/android/server/display/ColorFade.java b/services/core/java/com/android/server/display/ColorFade.java index 93d9b8d30a2e..25a2f60b85b2 100644 --- a/services/core/java/com/android/server/display/ColorFade.java +++ b/services/core/java/com/android/server/display/ColorFade.java @@ -643,8 +643,9 @@ final class ColorFade { .setSecure(isSecure) .setBLASTLayer(); mBLASTSurfaceControl = b.build(); - mBLASTBufferQueue = new BLASTBufferQueue("ColorFade", mBLASTSurfaceControl, - mDisplayWidth, mDisplayHeight, PixelFormat.TRANSLUCENT); + mBLASTBufferQueue = new BLASTBufferQueue("ColorFade", /*updateDestinationFrame*/ true); + mBLASTBufferQueue.update(mBLASTSurfaceControl, mDisplayWidth, mDisplayHeight, + PixelFormat.TRANSLUCENT); mSurface = mBLASTBufferQueue.createSurface(); } return true; diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java index d37dd3018fde..b49c01b3e2a8 100644 --- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java +++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java @@ -83,6 +83,8 @@ final class LocalDisplayAdapter extends DisplayAdapter { private static final String PROPERTY_EMULATOR_CIRCULAR = "ro.boot.emulator.circular"; + private static final double DEFAULT_DISPLAY_SIZE = 24.0; + private final LongSparseArray<LocalDisplayDevice> mDevices = new LongSparseArray<>(); private final Injector mInjector; @@ -526,6 +528,21 @@ final class LocalDisplayAdapter extends DisplayAdapter { private int getLogicalDensity() { DensityMapping densityMapping = getDisplayDeviceConfig().getDensityMapping(); if (densityMapping == null) { + if (getFeatureFlags().isBaseDensityForExternalDisplaysEnabled() + && !mStaticDisplayInfo.isInternal) { + double dpi; + + if (mActiveSfDisplayMode.xDpi > 0 && mActiveSfDisplayMode.yDpi > 0) { + dpi = Math.sqrt((Math.pow(mActiveSfDisplayMode.xDpi, 2) + + Math.pow(mActiveSfDisplayMode.yDpi, 2)) / 2); + } else { + // xDPI and yDPI is missing, calculate DPI from display resolution and + // default display size + dpi = Math.sqrt(Math.pow(mInfo.width, 2) + Math.pow(mInfo.height, 2)) + / DEFAULT_DISPLAY_SIZE; + } + return (int) (dpi + 0.5); + } return (int) (mStaticDisplayInfo.density * 160 + 0.5); } diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java index 0c04be10d06d..67b1ec305d7f 100644 --- a/services/core/java/com/android/server/dreams/DreamManagerService.java +++ b/services/core/java/com/android/server/dreams/DreamManagerService.java @@ -20,6 +20,7 @@ import static android.Manifest.permission.BIND_DREAM_SERVICE; import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT; import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM; import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; +import static android.service.dreams.Flags.allowDreamWhenPostured; import static android.service.dreams.Flags.cleanupDreamSettingsOnUninstall; import static android.service.dreams.Flags.dreamHandlesBeingObscured; @@ -110,12 +111,13 @@ public final class DreamManagerService extends SystemService { /** Constants for the when to activate dreams. */ @Retention(RetentionPolicy.SOURCE) - @IntDef({DREAM_ON_DOCK, DREAM_ON_CHARGE, DREAM_ON_DOCK_OR_CHARGE}) + @IntDef({DREAM_DISABLED, DREAM_ON_DOCK, DREAM_ON_CHARGE, DREAM_ON_POSTURED}) public @interface WhenToDream {} - private static final int DREAM_DISABLED = 0x0; - private static final int DREAM_ON_DOCK = 0x1; - private static final int DREAM_ON_CHARGE = 0x2; - private static final int DREAM_ON_DOCK_OR_CHARGE = 0x3; + + private static final int DREAM_DISABLED = 0; + private static final int DREAM_ON_DOCK = 1 << 0; + private static final int DREAM_ON_CHARGE = 1 << 1; + private static final int DREAM_ON_POSTURED = 1 << 2; private final Object mLock = new Object(); @@ -137,6 +139,7 @@ public final class DreamManagerService extends SystemService { private final boolean mDreamsEnabledByDefaultConfig; private final boolean mDreamsActivatedOnChargeByDefault; private final boolean mDreamsActivatedOnDockByDefault; + private final boolean mDreamsActivatedOnPosturedByDefault; private final boolean mKeepDreamingWhenUnpluggingDefault; private final boolean mDreamsDisabledByAmbientModeSuppressionConfig; @@ -152,6 +155,7 @@ public final class DreamManagerService extends SystemService { @WhenToDream private int mWhenToDream; private boolean mIsDocked; private boolean mIsCharging; + private boolean mIsPostured; // A temporary dream component that, when present, takes precedence over user configured dream // component. @@ -270,6 +274,8 @@ public final class DreamManagerService extends SystemService { com.android.internal.R.bool.config_dreamsActivatedOnSleepByDefault); mDreamsActivatedOnDockByDefault = mContext.getResources().getBoolean( com.android.internal.R.bool.config_dreamsActivatedOnDockByDefault); + mDreamsActivatedOnPosturedByDefault = mContext.getResources().getBoolean( + com.android.internal.R.bool.config_dreamsActivatedOnPosturedByDefault); mSettingsObserver = new SettingsObserver(mHandler); mKeepDreamingWhenUnpluggingDefault = mContext.getResources().getBoolean( com.android.internal.R.bool.config_keepDreamingWhenUnplugging); @@ -328,6 +334,9 @@ public final class DreamManagerService extends SystemService { Settings.Secure.SCREENSAVER_ACTIVATE_ON_DOCK), false, mSettingsObserver, UserHandle.USER_ALL); mContext.getContentResolver().registerContentObserver(Settings.Secure.getUriFor( + Settings.Secure.SCREENSAVER_ACTIVATE_ON_POSTURED), + false, mSettingsObserver, UserHandle.USER_ALL); + mContext.getContentResolver().registerContentObserver(Settings.Secure.getUriFor( Settings.Secure.SCREENSAVER_ENABLED), false, mSettingsObserver, UserHandle.USER_ALL); @@ -392,6 +401,8 @@ public final class DreamManagerService extends SystemService { pw.println("mDreamsEnabledSetting=" + mDreamsEnabledSetting); pw.println("mDreamsActivatedOnDockByDefault=" + mDreamsActivatedOnDockByDefault); pw.println("mDreamsActivatedOnChargeByDefault=" + mDreamsActivatedOnChargeByDefault); + pw.println("mDreamsActivatedOnPosturedByDefault=" + + mDreamsActivatedOnPosturedByDefault); pw.println("mIsDocked=" + mIsDocked); pw.println("mIsCharging=" + mIsCharging); pw.println("mWhenToDream=" + mWhenToDream); @@ -409,15 +420,28 @@ public final class DreamManagerService extends SystemService { synchronized (mLock) { final ContentResolver resolver = mContext.getContentResolver(); - final int activateWhenCharging = (Settings.Secure.getIntForUser(resolver, + mWhenToDream = DREAM_DISABLED; + + if ((Settings.Secure.getIntForUser(resolver, Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP, mDreamsActivatedOnChargeByDefault ? 1 : 0, - UserHandle.USER_CURRENT) != 0) ? DREAM_ON_CHARGE : DREAM_DISABLED; - final int activateWhenDocked = (Settings.Secure.getIntForUser(resolver, + UserHandle.USER_CURRENT) != 0)) { + mWhenToDream |= DREAM_ON_CHARGE; + } + + if (Settings.Secure.getIntForUser(resolver, Settings.Secure.SCREENSAVER_ACTIVATE_ON_DOCK, mDreamsActivatedOnDockByDefault ? 1 : 0, - UserHandle.USER_CURRENT) != 0) ? DREAM_ON_DOCK : DREAM_DISABLED; - mWhenToDream = activateWhenCharging + activateWhenDocked; + UserHandle.USER_CURRENT) != 0) { + mWhenToDream |= DREAM_ON_DOCK; + } + + if (Settings.Secure.getIntForUser(resolver, + Settings.Secure.SCREENSAVER_ACTIVATE_ON_POSTURED, + mDreamsActivatedOnPosturedByDefault ? 1 : 0, + UserHandle.USER_CURRENT) != 0) { + mWhenToDream |= DREAM_ON_POSTURED; + } mDreamsEnabledSetting = (Settings.Secure.getIntForUser(resolver, Settings.Secure.SCREENSAVER_ENABLED, @@ -508,6 +532,10 @@ public final class DreamManagerService extends SystemService { return mIsDocked; } + if ((mWhenToDream & DREAM_ON_POSTURED) == DREAM_ON_POSTURED) { + return mIsPostured; + } + return false; } } @@ -646,6 +674,14 @@ public final class DreamManagerService extends SystemService { } } + private void setDevicePosturedInternal(boolean isPostured) { + Slog.d(TAG, "Device postured: " + isPostured); + synchronized (mLock) { + mIsPostured = isPostured; + mHandler.post(() -> mPowerManagerInternal.setDevicePostured(isPostured)); + } + } + /** * If doze is true, returns the doze component for the user. * Otherwise, returns the system dream component, if present. @@ -1294,6 +1330,22 @@ public final class DreamManagerService extends SystemService { } } + @Override + public void setDevicePostured(boolean isPostured) { + if (!allowDreamWhenPostured()) { + return; + } + + checkPermission(android.Manifest.permission.WRITE_DREAM_STATE); + + final long ident = Binder.clearCallingIdentity(); + try { + setDevicePosturedInternal(isPostured); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + boolean canLaunchDreamActivity(String dreamPackageName, String packageName, int callingUid) { if (dreamPackageName == null || packageName == null) { diff --git a/services/core/java/com/android/server/hdmi/DeviceSelectActionFromTv.java b/services/core/java/com/android/server/hdmi/DeviceSelectActionFromTv.java index ff1a74af02e2..9118c46e3b22 100644 --- a/services/core/java/com/android/server/hdmi/DeviceSelectActionFromTv.java +++ b/services/core/java/com/android/server/hdmi/DeviceSelectActionFromTv.java @@ -61,6 +61,11 @@ final class DeviceSelectActionFromTv extends HdmiCecFeatureAction { @VisibleForTesting static final int STATE_WAIT_FOR_DEVICE_POWER_ON = 3; + // State in which we wait for device to complete a possible power state change triggered by + // <Set Stream Path>. + @VisibleForTesting + static final int STATE_WAIT_FOR_POWER_STATE_CHANGE = 4; + private final HdmiDeviceInfo mTarget; private final HdmiCecMessage mGivePowerStatus; private final boolean mIsCec20; @@ -100,7 +105,12 @@ final class DeviceSelectActionFromTv extends HdmiCecFeatureAction { // Wake-up on <Set Stream Path> was not mandatory before CEC 2.0. // The message is re-sent at the end of the action for devices that don't support 2.0. sendSetStreamPath(); + mState = STATE_WAIT_FOR_POWER_STATE_CHANGE; + addTimer(mState, HdmiConfig.TIMEOUT_MS); + return true; + } + private void checkForPowerStateChange() { if (!mIsCec20) { queryDevicePowerStatus(); } else { @@ -114,12 +124,11 @@ final class DeviceSelectActionFromTv extends HdmiCecFeatureAction { queryDevicePowerStatus(); } else if (targetPowerStatus == HdmiControlManager.POWER_STATUS_ON) { finishWithCallback(HdmiControlManager.RESULT_SUCCESS); - return true; + return; } } mState = STATE_WAIT_FOR_REPORT_POWER_STATUS; addTimer(mState, HdmiConfig.TIMEOUT_MS); - return true; } private void queryDevicePowerStatus() { @@ -210,6 +219,9 @@ final class DeviceSelectActionFromTv extends HdmiCecFeatureAction { mState = STATE_WAIT_FOR_REPORT_POWER_STATUS; addTimer(mState, HdmiConfig.TIMEOUT_MS); break; + case STATE_WAIT_FOR_POWER_STATE_CHANGE: + checkForPowerStateChange(); + break; } } diff --git a/services/core/java/com/android/server/input/InputGestureManager.java b/services/core/java/com/android/server/input/InputGestureManager.java index 93fdbc787ed0..fd755e3cefe2 100644 --- a/services/core/java/com/android/server/input/InputGestureManager.java +++ b/services/core/java/com/android/server/input/InputGestureManager.java @@ -87,7 +87,20 @@ final class InputGestureManager { createKeyTrigger(KeyEvent.KEYCODE_V, KeyEvent.META_CTRL_ON), createKeyTrigger(KeyEvent.KEYCODE_X, KeyEvent.META_CTRL_ON), createKeyTrigger(KeyEvent.KEYCODE_Z, KeyEvent.META_CTRL_ON), - createKeyTrigger(KeyEvent.KEYCODE_Y, KeyEvent.META_CTRL_ON) + createKeyTrigger(KeyEvent.KEYCODE_Y, KeyEvent.META_CTRL_ON), + // Used for magnification viewport control. + createKeyTrigger(KeyEvent.KEYCODE_MINUS, + KeyEvent.META_META_ON | KeyEvent.META_ALT_ON), + createKeyTrigger(KeyEvent.KEYCODE_EQUALS, + KeyEvent.META_META_ON | KeyEvent.META_ALT_ON), + createKeyTrigger(KeyEvent.KEYCODE_DPAD_LEFT, + KeyEvent.META_META_ON | KeyEvent.META_ALT_ON), + createKeyTrigger(KeyEvent.KEYCODE_DPAD_RIGHT, + KeyEvent.META_META_ON | KeyEvent.META_ALT_ON), + createKeyTrigger(KeyEvent.KEYCODE_DPAD_UP, + KeyEvent.META_META_ON | KeyEvent.META_ALT_ON), + createKeyTrigger(KeyEvent.KEYCODE_DPAD_DOWN, + KeyEvent.META_META_ON | KeyEvent.META_ALT_ON) )); public InputGestureManager(Context context) { @@ -216,24 +229,6 @@ final class InputGestureManager { systemShortcuts.add(createKeyGesture(KeyEvent.KEYCODE_T, KeyEvent.META_META_ON | KeyEvent.META_ALT_ON, KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_TALKBACK)); - systemShortcuts.add(createKeyGesture(KeyEvent.KEYCODE_MINUS, - KeyEvent.META_META_ON | KeyEvent.META_ALT_ON, - KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFICATION_ZOOM_OUT)); - systemShortcuts.add(createKeyGesture(KeyEvent.KEYCODE_EQUALS, - KeyEvent.META_META_ON | KeyEvent.META_ALT_ON, - KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFICATION_ZOOM_IN)); - systemShortcuts.add(createKeyGesture(KeyEvent.KEYCODE_DPAD_LEFT, - KeyEvent.META_META_ON | KeyEvent.META_ALT_ON, - KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFICATION_PAN_LEFT)); - systemShortcuts.add(createKeyGesture(KeyEvent.KEYCODE_DPAD_RIGHT, - KeyEvent.META_META_ON | KeyEvent.META_ALT_ON, - KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFICATION_PAN_RIGHT)); - systemShortcuts.add(createKeyGesture(KeyEvent.KEYCODE_DPAD_UP, - KeyEvent.META_META_ON | KeyEvent.META_ALT_ON, - KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFICATION_PAN_UP)); - systemShortcuts.add(createKeyGesture(KeyEvent.KEYCODE_DPAD_DOWN, - KeyEvent.META_META_ON | KeyEvent.META_ALT_ON, - KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFICATION_PAN_DOWN)); systemShortcuts.add(createKeyGesture(KeyEvent.KEYCODE_M, KeyEvent.META_META_ON | KeyEvent.META_ALT_ON, KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MAGNIFICATION)); diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index 45c7cffd462b..7b81fc92e83d 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -2189,7 +2189,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. if (mVdmInternal == null) { mVdmInternal = LocalServices.getService(VirtualDeviceManagerInternal.class); } - if (mVdmInternal == null || !android.companion.virtual.flags.Flags.vdmCustomIme()) { + if (mVdmInternal == null) { return currentMethodId; } diff --git a/services/core/java/com/android/server/media/quality/MediaQualityService.java b/services/core/java/com/android/server/media/quality/MediaQualityService.java index efc1b9959c0f..e47cbdc3546f 100644 --- a/services/core/java/com/android/server/media/quality/MediaQualityService.java +++ b/services/core/java/com/android/server/media/quality/MediaQualityService.java @@ -16,18 +16,33 @@ package com.android.server.media.quality; +import static android.media.quality.AmbientBacklightEvent.AMBIENT_BACKLIGHT_EVENT_ENABLED; +import static android.media.quality.AmbientBacklightEvent.AMBIENT_BACKLIGHT_EVENT_DISABLED; +import static android.media.quality.AmbientBacklightEvent.AMBIENT_BACKLIGHT_EVENT_METADATA_AVAILABLE; +import static android.media.quality.AmbientBacklightEvent.AMBIENT_BACKLIGHT_EVENT_INTERRUPTED; + +import android.annotation.NonNull; import android.content.ContentValues; import android.content.Context; import android.content.pm.PackageManager; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; +import android.hardware.tv.mediaquality.AmbientBacklightColorFormat; import android.hardware.tv.mediaquality.IMediaQuality; +import android.hardware.tv.mediaquality.PictureParameter; +import android.hardware.tv.mediaquality.PictureParameters; +import android.hardware.tv.mediaquality.SoundParameter; +import android.hardware.tv.mediaquality.SoundParameters; +import android.media.quality.AmbientBacklightEvent; +import android.media.quality.AmbientBacklightMetadata; import android.media.quality.AmbientBacklightSettings; import android.media.quality.IAmbientBacklightCallback; import android.media.quality.IMediaQualityManager; import android.media.quality.IPictureProfileCallback; import android.media.quality.ISoundProfileCallback; import android.media.quality.MediaQualityContract.BaseParameters; +import android.media.quality.MediaQualityContract.PictureQuality; +import android.media.quality.MediaQualityContract.SoundQuality; import android.media.quality.MediaQualityManager; import android.media.quality.ParameterCapability; import android.media.quality.PictureProfile; @@ -42,6 +57,7 @@ import android.os.RemoteCallbackList; import android.os.RemoteException; import android.os.ServiceManager; import android.os.UserHandle; +import android.text.TextUtils; import android.util.Log; import android.util.Pair; import android.util.Slog; @@ -60,6 +76,7 @@ import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.NoSuchElementException; import java.util.UUID; import java.util.stream.Collectors; @@ -76,13 +93,16 @@ public class MediaQualityService extends SystemService { private final MediaQualityDbHelper mMediaQualityDbHelper; private final BiMap<Long, String> mPictureProfileTempIdMap; private final BiMap<Long, String> mSoundProfileTempIdMap; + private IMediaQuality mMediaQuality; + private final HalAmbientBacklightCallback mHalAmbientBacklightCallback; + private final Map<String, AmbientBacklightCallbackRecord> mCallbackRecords = new HashMap<>(); private final PackageManager mPackageManager; private final SparseArray<UserState> mUserStates = new SparseArray<>(); - private IMediaQuality mMediaQuality; public MediaQualityService(Context context) { super(context); mContext = context; + mHalAmbientBacklightCallback = new HalAmbientBacklightCallback(); mPackageManager = mContext.getPackageManager(); mPictureProfileTempIdMap = new BiMap<>(); mSoundProfileTempIdMap = new BiMap<>(); @@ -97,6 +117,13 @@ public class MediaQualityService extends SystemService { if (binder != null) { Slogf.d(TAG, "binder is not null"); mMediaQuality = IMediaQuality.Stub.asInterface(binder); + if (mMediaQuality != null) { + try { + mMediaQuality.setAmbientBacklightCallback(mHalAmbientBacklightCallback); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to set ambient backlight detector callback", e); + } + } } publishBinderService(Context.MEDIA_QUALITY_SERVICE, new BinderService()); @@ -282,10 +309,208 @@ public class MediaQualityService extends SystemService { notifyOnPictureProfileError(profileId, PictureProfile.ERROR_NO_PERMISSION, Binder.getCallingUid(), Binder.getCallingPid()); } - // TODO: pass the profile ID to MediaQuality HAL when ready. + + PictureProfile pictureProfile = getPictureProfile( + mPictureProfileTempIdMap.getKey(profileId)); + PersistableBundle params = pictureProfile.getParameters(); + + try { + if (mMediaQuality != null) { + PictureParameter[] pictureParameters = + convertPersistableBundleToPictureParameterList(params); + + PictureParameters pp = new PictureParameters(); + pp.pictureParameters = pictureParameters; + + mMediaQuality.sendDefaultPictureParameters(pp); + return true; + } + } catch (RemoteException e) { + Slog.e(TAG, "Failed to set default picture profile", e); + } return false; } + private PictureParameter[] convertPersistableBundleToPictureParameterList( + PersistableBundle params) { + List<PictureParameter> pictureParams = new ArrayList<>(); + if (params.containsKey(PictureQuality.PARAMETER_BRIGHTNESS)) { + pictureParams.add(PictureParameter.brightness(params.getLong( + PictureQuality.PARAMETER_BRIGHTNESS))); + } + if (params.containsKey(PictureQuality.PARAMETER_CONTRAST)) { + pictureParams.add(PictureParameter.contrast(params.getInt( + PictureQuality.PARAMETER_CONTRAST))); + } + if (params.containsKey(PictureQuality.PARAMETER_SHARPNESS)) { + pictureParams.add(PictureParameter.sharpness(params.getInt( + PictureQuality.PARAMETER_SHARPNESS))); + } + if (params.containsKey(PictureQuality.PARAMETER_SATURATION)) { + pictureParams.add(PictureParameter.saturation(params.getInt( + PictureQuality.PARAMETER_SATURATION))); + } + if (params.containsKey(PictureQuality.PARAMETER_HUE)) { + pictureParams.add(PictureParameter.hue(params.getInt( + PictureQuality.PARAMETER_HUE))); + } + if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_BRIGHTNESS)) { + pictureParams.add(PictureParameter.colorTunerBrightness(params.getInt( + PictureQuality.PARAMETER_COLOR_TUNER_BRIGHTNESS))); + } + if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_SATURATION)) { + pictureParams.add(PictureParameter.colorTunerSaturation(params.getInt( + PictureQuality.PARAMETER_COLOR_TUNER_SATURATION))); + } + if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_HUE)) { + pictureParams.add(PictureParameter.colorTunerHue(params.getInt( + PictureQuality.PARAMETER_COLOR_TUNER_HUE))); + } + if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_RED_OFFSET)) { + pictureParams.add(PictureParameter.colorTunerRedOffset(params.getInt( + PictureQuality.PARAMETER_COLOR_TUNER_RED_OFFSET))); + } + if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_GREEN_OFFSET)) { + pictureParams.add(PictureParameter.colorTunerGreenOffset(params.getInt( + PictureQuality.PARAMETER_COLOR_TUNER_GREEN_OFFSET))); + } + if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_BLUE_OFFSET)) { + pictureParams.add(PictureParameter.colorTunerBlueOffset(params.getInt( + PictureQuality.PARAMETER_COLOR_TUNER_BLUE_OFFSET))); + } + if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_RED_GAIN)) { + pictureParams.add(PictureParameter.colorTunerRedGain(params.getInt( + PictureQuality.PARAMETER_COLOR_TUNER_RED_GAIN))); + } + if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_GREEN_GAIN)) { + pictureParams.add(PictureParameter.colorTunerGreenGain(params.getInt( + PictureQuality.PARAMETER_COLOR_TUNER_GREEN_GAIN))); + } + if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_BLUE_GAIN)) { + pictureParams.add(PictureParameter.colorTunerBlueGain(params.getInt( + PictureQuality.PARAMETER_COLOR_TUNER_BLUE_GAIN))); + } + if (params.containsKey(PictureQuality.PARAMETER_NOISE_REDUCTION)) { + pictureParams.add(PictureParameter.noiseReduction( + (byte) params.getInt(PictureQuality.PARAMETER_NOISE_REDUCTION))); + } + if (params.containsKey(PictureQuality.PARAMETER_MPEG_NOISE_REDUCTION)) { + pictureParams.add(PictureParameter.mpegNoiseReduction( + (byte) params.getInt(PictureQuality.PARAMETER_MPEG_NOISE_REDUCTION))); + } + if (params.containsKey(PictureQuality.PARAMETER_FLESH_TONE)) { + pictureParams.add(PictureParameter.fleshTone( + (byte) params.getInt(PictureQuality.PARAMETER_FLESH_TONE))); + } + if (params.containsKey(PictureQuality.PARAMETER_DECONTOUR)) { + pictureParams.add(PictureParameter.deContour( + (byte) params.getInt(PictureQuality.PARAMETER_DECONTOUR))); + } + if (params.containsKey(PictureQuality.PARAMETER_DYNAMIC_LUMA_CONTROL)) { + pictureParams.add(PictureParameter.dynamicLumaControl( + (byte) params.getInt(PictureQuality.PARAMETER_DYNAMIC_LUMA_CONTROL))); + } + if (params.containsKey(PictureQuality.PARAMETER_FILM_MODE)) { + pictureParams.add(PictureParameter.filmMode(params.getBoolean( + PictureQuality.PARAMETER_FILM_MODE))); + } + if (params.containsKey(PictureQuality.PARAMETER_BLUE_STRETCH)) { + pictureParams.add(PictureParameter.blueStretch(params.getBoolean( + PictureQuality.PARAMETER_BLUE_STRETCH))); + } + if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNE)) { + pictureParams.add(PictureParameter.colorTune(params.getBoolean( + PictureQuality.PARAMETER_COLOR_TUNE))); + } + if (params.containsKey(PictureQuality.PARAMETER_COLOR_TEMPERATURE)) { + pictureParams.add(PictureParameter.colorTemperature( + (byte) params.getInt( + PictureQuality.PARAMETER_COLOR_TEMPERATURE))); + } + if (params.containsKey(PictureQuality.PARAMETER_GLOBAL_DIMMING)) { + pictureParams.add(PictureParameter.globeDimming(params.getBoolean( + PictureQuality.PARAMETER_GLOBAL_DIMMING))); + } + if (params.containsKey(PictureQuality.PARAMETER_AUTO_PICTURE_QUALITY_ENABLED)) { + pictureParams.add(PictureParameter.autoPictureQualityEnabled(params.getBoolean( + PictureQuality.PARAMETER_AUTO_PICTURE_QUALITY_ENABLED))); + } + if (params.containsKey(PictureQuality.PARAMETER_AUTO_SUPER_RESOLUTION_ENABLED)) { + pictureParams.add(PictureParameter.autoSuperResolutionEnabled(params.getBoolean( + PictureQuality.PARAMETER_AUTO_SUPER_RESOLUTION_ENABLED))); + } + if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_RED_GAIN)) { + pictureParams.add(PictureParameter.colorTemperatureRedGain(params.getInt( + PictureQuality.PARAMETER_COLOR_TUNER_RED_GAIN))); + } + if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_GREEN_GAIN)) { + pictureParams.add(PictureParameter.colorTemperatureGreenGain(params.getInt( + PictureQuality.PARAMETER_COLOR_TUNER_GREEN_GAIN))); + } + if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_BLUE_GAIN)) { + pictureParams.add(PictureParameter.colorTemperatureBlueGain(params.getInt( + PictureQuality.PARAMETER_COLOR_TUNER_BLUE_GAIN))); + } + + /** + * TODO: add conversion for following after adding to MediaQualityContract + * + * PictureParameter.levelRange + * PictureParameter.gamutMapping + * PictureParameter.pcMode + * PictureParameter.lowLatency + * PictureParameter.vrr + * PictureParameter.cvrr + * PictureParameter.hdmiRgbRange + * PictureParameter.colorSpace + * PictureParameter.panelInitMaxLuminceNits + * PictureParameter.panelInitMaxLuminceValid + * PictureParameter.gamma + * PictureParameter.colorTemperatureRedOffset + * PictureParameter.colorTemperatureGreenOffset + * PictureParameter.colorTemperatureBlueOffset + * PictureParameter.elevenPointRed + * PictureParameter.elevenPointGreen + * PictureParameter.elevenPointBlue + * PictureParameter.lowBlueLight + * PictureParameter.LdMode + * PictureParameter.osdRedGain + * PictureParameter.osdGreenGain + * PictureParameter.osdBlueGain + * PictureParameter.osdRedOffset + * PictureParameter.osdGreenOffset + * PictureParameter.osdBlueOffset + * PictureParameter.osdHue + * PictureParameter.osdSaturation + * PictureParameter.osdContrast + * PictureParameter.colorTunerSwitch + * PictureParameter.colorTunerHueRed + * PictureParameter.colorTunerHueGreen + * PictureParameter.colorTunerHueBlue + * PictureParameter.colorTunerHueCyan + * PictureParameter.colorTunerHueMagenta + * PictureParameter.colorTunerHueYellow + * PictureParameter.colorTunerHueFlesh + * PictureParameter.colorTunerSaturationRed + * PictureParameter.colorTunerSaturationGreen + * PictureParameter.colorTunerSaturationBlue + * PictureParameter.colorTunerSaturationCyan + * PictureParameter.colorTunerSaturationMagenta + * PictureParameter.colorTunerSaturationYellow + * PictureParameter.colorTunerSaturationFlesh + * PictureParameter.colorTunerLuminanceRed + * PictureParameter.colorTunerLuminanceGreen + * PictureParameter.colorTunerLuminanceBlue + * PictureParameter.colorTunerLuminanceCyan + * PictureParameter.colorTunerLuminanceMagenta + * PictureParameter.colorTunerLuminanceYellow + * PictureParameter.colorTunerLuminanceFlesh + * PictureParameter.activeProfile + * PictureParameter.pictureQualityEventType + */ + return (PictureParameter[]) pictureParams.toArray(); + } + @Override public List<String> getPictureProfilePackageNames(UserHandle user) { if (!hasGlobalPictureQualityServicePermission()) { @@ -503,10 +728,77 @@ public class MediaQualityService extends SystemService { notifyOnSoundProfileError(profileId, SoundProfile.ERROR_NO_PERMISSION, Binder.getCallingUid(), Binder.getCallingPid()); } - // TODO: pass the profile ID to MediaQuality HAL when ready. + + SoundProfile soundProfile = getSoundProfile(mSoundProfileTempIdMap.getKey(profileId)); + PersistableBundle params = soundProfile.getParameters(); + + try { + if (mMediaQuality != null) { + SoundParameter[] soundParameters = + convertPersistableBundleToSoundParameterList(params); + + SoundParameters sp = new SoundParameters(); + sp.soundParameters = soundParameters; + + mMediaQuality.sendDefaultSoundParameters(sp); + return true; + } + } catch (RemoteException e) { + Slog.e(TAG, "Failed to set default sound profile", e); + } return false; } + private SoundParameter[] convertPersistableBundleToSoundParameterList( + PersistableBundle params) { + List<SoundParameter> soundParams = new ArrayList<>(); + if (params.containsKey(SoundQuality.PARAMETER_BALANCE)) { + soundParams.add(SoundParameter.balance(params.getInt( + SoundQuality.PARAMETER_BALANCE))); + } + if (params.containsKey(SoundQuality.PARAMETER_BASS)) { + soundParams.add(SoundParameter.bass(params.getInt(SoundQuality.PARAMETER_BASS))); + } + if (params.containsKey(SoundQuality.PARAMETER_TREBLE)) { + soundParams.add(SoundParameter.treble(params.getInt( + SoundQuality.PARAMETER_TREBLE))); + } + if (params.containsKey(SoundQuality.PARAMETER_SURROUND_SOUND)) { + soundParams.add(SoundParameter.surroundSoundEnabled(params.getBoolean( + SoundQuality.PARAMETER_SURROUND_SOUND))); + } + if (params.containsKey(SoundQuality.PARAMETER_SPEAKERS)) { + soundParams.add(SoundParameter.speakersEnabled(params.getBoolean( + SoundQuality.PARAMETER_SPEAKERS))); + } + if (params.containsKey(SoundQuality.PARAMETER_SPEAKERS_DELAY_MILLIS)) { + soundParams.add(SoundParameter.speakersDelayMs(params.getInt( + SoundQuality.PARAMETER_SPEAKERS_DELAY_MILLIS))); + } + if (params.containsKey(SoundQuality.PARAMETER_AUTO_VOLUME_CONTROL)) { + soundParams.add(SoundParameter.autoVolumeControl(params.getBoolean( + SoundQuality.PARAMETER_AUTO_VOLUME_CONTROL))); + } + if (params.containsKey(SoundQuality.PARAMETER_DTS_DRC)) { + soundParams.add(SoundParameter.dtsDrc(params.getBoolean( + SoundQuality.PARAMETER_DTS_DRC))); + } + if (params.containsKey(SoundQuality.PARAMETER_DIGITAL_OUTPUT_DELAY_MILLIS)) { + soundParams.add(SoundParameter.surroundSoundEnabled(params.getBoolean( + SoundQuality.PARAMETER_DIGITAL_OUTPUT_DELAY_MILLIS))); + } + //TODO: equalizerDetail + //TODO: downmixMode + //TODO: enhancedAudioReturnChannelEnabled + //TODO: dolbyAudioProcessing + //TODO: dolbyDialogueEnhancer + //TODO: dtsVirtualX + //TODO: digitalOutput + //TODO: activeProfile + //TODO: soundStyle + return (SoundParameter[]) soundParams.toArray(); + } + @Override public List<String> getSoundProfilePackageNames(UserHandle user) { if (!hasGlobalSoundQualityServicePermission()) { @@ -905,24 +1197,86 @@ public class MediaQualityService extends SystemService { @Override public void registerAmbientBacklightCallback(IAmbientBacklightCallback callback) { + if (DEBUG) { + Slogf.d(TAG, "registerAmbientBacklightCallback"); + } + if (!hasReadColorZonesPermission()) { //TODO: error handling } + + String callingPackageName = getPackageOfCallingUid(); + + synchronized (mCallbackRecords) { + AmbientBacklightCallbackRecord record = mCallbackRecords.get(callingPackageName); + if (record != null) { + if (record.mCallback.asBinder().equals(callback.asBinder())) { + Slog.w(TAG, "AmbientBacklight Callback already registered"); + return; + } + record.release(); + mCallbackRecords.remove(callingPackageName); + } + mCallbackRecords.put(callingPackageName, + new AmbientBacklightCallbackRecord(callingPackageName, callback)); + } } @Override public void setAmbientBacklightSettings( AmbientBacklightSettings settings, UserHandle user) { + if (DEBUG) { + Slogf.d(TAG, "setAmbientBacklightSettings " + settings); + } + if (!hasReadColorZonesPermission()) { //TODO: error handling } + + try { + if (mMediaQuality != null) { + android.hardware.tv.mediaquality.AmbientBacklightSettings halSettings = + new android.hardware.tv.mediaquality.AmbientBacklightSettings(); + halSettings.uid = Binder.getCallingUid(); + halSettings.source = (byte) settings.getSource(); + halSettings.maxFramerate = settings.getMaxFps(); + halSettings.colorFormat = (byte) settings.getColorFormat(); + halSettings.hZonesNumber = settings.getHorizontalZonesCount(); + halSettings.vZonesNumber = settings.getVerticalZonesCount(); + halSettings.hasLetterbox = settings.isLetterboxOmitted(); + halSettings.colorThreshold = settings.getThreshold(); + + mMediaQuality.setAmbientBacklightDetector(halSettings); + + mHalAmbientBacklightCallback.setAmbientBacklightClientPackageName( + getPackageOfCallingUid()); + + if (DEBUG) { + Slogf.d(TAG, "set ambient settings package: " + halSettings.uid); + } + } + } catch (RemoteException e) { + Slog.e(TAG, "Failed to set ambient backlight settings", e); + } } @Override public void setAmbientBacklightEnabled(boolean enabled, UserHandle user) { + if (DEBUG) { + Slogf.d(TAG, "setAmbientBacklightEnabled " + enabled); + } if (!hasReadColorZonesPermission()) { //TODO: error handling } + try { + if (mMediaQuality != null) { + mMediaQuality.setAmbientBacklightDetectionEnabled(enabled); + } + } catch (UnsupportedOperationException e) { + Slog.e(TAG, "The current device is not supported"); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to set ambient backlight enabled", e); + } } @Override @@ -979,10 +1333,10 @@ public class MediaQualityService extends SystemService { try { if (mMediaQuality != null) { - mMediaQuality.setAutoPqEnabled(enabled); + if (mMediaQuality.isAutoPqSupported()) { + mMediaQuality.setAutoPqEnabled(enabled); + } } - } catch (UnsupportedOperationException e) { - Slog.e(TAG, "The current device is not supported"); } catch (RemoteException e) { Slog.e(TAG, "Failed to set auto picture quality", e); } @@ -992,10 +1346,10 @@ public class MediaQualityService extends SystemService { public boolean isAutoPictureQualityEnabled(UserHandle user) { try { if (mMediaQuality != null) { - return mMediaQuality.getAutoPqEnabled(); + if (mMediaQuality.isAutoPqSupported()) { + mMediaQuality.getAutoPqEnabled(); + } } - } catch (UnsupportedOperationException e) { - Slog.e(TAG, "The current device is not supported"); } catch (RemoteException e) { Slog.e(TAG, "Failed to get auto picture quality", e); } @@ -1011,12 +1365,12 @@ public class MediaQualityService extends SystemService { try { if (mMediaQuality != null) { - mMediaQuality.setAutoSrEnabled(enabled); + if (mMediaQuality.isAutoSrSupported()) { + mMediaQuality.setAutoSrEnabled(enabled); + } } - } catch (UnsupportedOperationException e) { - Slog.e(TAG, "The current device is not supported"); } catch (RemoteException e) { - Slog.e(TAG, "Failed to set auto super resolution", e); + Slog.e(TAG, "Failed to set super resolution", e); } } @@ -1024,12 +1378,12 @@ public class MediaQualityService extends SystemService { public boolean isSuperResolutionEnabled(UserHandle user) { try { if (mMediaQuality != null) { - return mMediaQuality.getAutoSrEnabled(); + if (mMediaQuality.isAutoSrSupported()) { + mMediaQuality.getAutoSrEnabled(); + } } - } catch (UnsupportedOperationException e) { - Slog.e(TAG, "The current device is not supported"); } catch (RemoteException e) { - Slog.e(TAG, "Failed to get auto super resolution", e); + Slog.e(TAG, "Failed to get super resolution", e); } return false; } @@ -1043,12 +1397,12 @@ public class MediaQualityService extends SystemService { try { if (mMediaQuality != null) { - mMediaQuality.setAutoAqEnabled(enabled); + if (mMediaQuality.isAutoAqSupported()) { + mMediaQuality.setAutoAqEnabled(enabled); + } } - } catch (UnsupportedOperationException e) { - Slog.e(TAG, "The current device is not supported"); } catch (RemoteException e) { - Slog.e(TAG, "Failed to set auto audio quality", e); + Slog.e(TAG, "Failed to set auto sound quality", e); } } @@ -1056,12 +1410,12 @@ public class MediaQualityService extends SystemService { public boolean isAutoSoundQualityEnabled(UserHandle user) { try { if (mMediaQuality != null) { - return mMediaQuality.getAutoAqEnabled(); + if (mMediaQuality.isAutoAqSupported()) { + mMediaQuality.getAutoAqEnabled(); + } } - } catch (UnsupportedOperationException e) { - Slog.e(TAG, "The current device is not supported"); } catch (RemoteException e) { - Slog.e(TAG, "Failed to get auto audio quality", e); + Slog.e(TAG, "Failed to get auto sound quality", e); } return false; } @@ -1119,4 +1473,167 @@ public class MediaQualityService extends SystemService { private UserState getUserStateLocked(int userId) { return mUserStates.get(userId); } + + private final class AmbientBacklightCallbackRecord implements IBinder.DeathRecipient { + final String mPackageName; + final IAmbientBacklightCallback mCallback; + + AmbientBacklightCallbackRecord(@NonNull String pkgName, + @NonNull IAmbientBacklightCallback cb) { + mPackageName = pkgName; + mCallback = cb; + try { + mCallback.asBinder().linkToDeath(this, 0); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to link to death", e); + } + } + + void release() { + try { + mCallback.asBinder().unlinkToDeath(this, 0); + } catch (NoSuchElementException e) { + Slog.e(TAG, "Failed to unlink to death", e); + } + } + + @Override + public void binderDied() { + synchronized (mCallbackRecords) { + mCallbackRecords.remove(mPackageName); + } + } + } + + private final class HalAmbientBacklightCallback + extends android.hardware.tv.mediaquality.IMediaQualityCallback.Stub { + private final Object mLock = new Object(); + private String mAmbientBacklightClientPackageName; + + void setAmbientBacklightClientPackageName(@NonNull String packageName) { + synchronized (mLock) { + if (TextUtils.equals(mAmbientBacklightClientPackageName, packageName)) { + return; + } + handleAmbientBacklightInterrupted(); + mAmbientBacklightClientPackageName = packageName; + } + } + + void handleAmbientBacklightInterrupted() { + synchronized (mCallbackRecords) { + if (mAmbientBacklightClientPackageName == null) { + Slog.e(TAG, "Invalid package name in interrupted event"); + return; + } + AmbientBacklightCallbackRecord record = mCallbackRecords.get( + mAmbientBacklightClientPackageName); + if (record == null) { + Slog.e(TAG, "Callback record not found for ambient backlight"); + return; + } + AmbientBacklightEvent event = + new AmbientBacklightEvent( + AMBIENT_BACKLIGHT_EVENT_INTERRUPTED, null); + try { + record.mCallback.onAmbientBacklightEvent(event); + } catch (RemoteException e) { + Slog.e(TAG, "Deliver ambient backlight interrupted event failed", e); + } + } + } + + void handleAmbientBacklightEnabled(boolean enabled) { + AmbientBacklightEvent event = + new AmbientBacklightEvent( + enabled ? AMBIENT_BACKLIGHT_EVENT_ENABLED : + AMBIENT_BACKLIGHT_EVENT_DISABLED, null); + synchronized (mCallbackRecords) { + for (AmbientBacklightCallbackRecord record : mCallbackRecords.values()) { + try { + record.mCallback.onAmbientBacklightEvent(event); + } catch (RemoteException e) { + Slog.e(TAG, "Deliver ambient backlight enabled event failed", e); + } + } + } + } + + void handleAmbientBacklightMetadataEvent( + @NonNull android.hardware.tv.mediaquality.AmbientBacklightMetadata + halMetadata) { + String halPackageName = mContext.getPackageManager() + .getNameForUid(halMetadata.settings.uid); + if (!TextUtils.equals(mAmbientBacklightClientPackageName, halPackageName)) { + Slog.e(TAG, "Invalid package name in metadata event"); + return; + } + + AmbientBacklightColorFormat[] zonesColorsUnion = halMetadata.zonesColors; + int[] zonesColorsInt = new int[zonesColorsUnion.length]; + + for (int i = 0; i < zonesColorsUnion.length; i++) { + zonesColorsInt[i] = zonesColorsUnion[i].RGB888; + } + + AmbientBacklightMetadata metadata = + new AmbientBacklightMetadata( + halPackageName, + halMetadata.compressAlgorithm, + halMetadata.settings.source, + halMetadata.settings.colorFormat, + halMetadata.settings.hZonesNumber, + halMetadata.settings.vZonesNumber, + zonesColorsInt); + AmbientBacklightEvent event = + new AmbientBacklightEvent( + AMBIENT_BACKLIGHT_EVENT_METADATA_AVAILABLE, metadata); + + synchronized (mCallbackRecords) { + AmbientBacklightCallbackRecord record = mCallbackRecords + .get(halPackageName); + if (record == null) { + Slog.e(TAG, "Callback record not found for ambient backlight metadata"); + return; + } + + try { + record.mCallback.onAmbientBacklightEvent(event); + } catch (RemoteException e) { + Slog.e(TAG, "Deliver ambient backlight metadata event failed", e); + } + } + } + + @Override + public void notifyAmbientBacklightEvent( + android.hardware.tv.mediaquality.AmbientBacklightEvent halEvent) { + synchronized (mLock) { + if (halEvent.getTag() == android.hardware.tv.mediaquality + .AmbientBacklightEvent.Tag.enabled) { + boolean enabled = halEvent.getEnabled(); + if (enabled) { + handleAmbientBacklightEnabled(true); + } else { + handleAmbientBacklightEnabled(false); + } + } else if (halEvent.getTag() == android.hardware.tv.mediaquality + .AmbientBacklightEvent.Tag.metadata) { + handleAmbientBacklightMetadataEvent(halEvent.getMetadata()); + } else { + Slog.e(TAG, "Invalid event type in ambient backlight event"); + } + } + } + + @Override + public synchronized String getInterfaceHash() throws android.os.RemoteException { + return android.hardware.tv.mediaquality.IMediaQualityCallback.Stub.HASH; + } + + @Override + public int getInterfaceVersion() throws android.os.RemoteException { + return android.hardware.tv.mediaquality.IMediaQualityCallback.Stub.VERSION; + } + } } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 837003f87598..ef39f1811876 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -762,6 +762,7 @@ public class NotificationManagerService extends SystemService { private int mWarnRemoteViewsSizeBytes; private int mStripRemoteViewsSizeBytes; + private String[] mDefaultUnsupportedAdjustments; @VisibleForTesting protected boolean mShowReviewPermissionsNotification; @@ -2938,6 +2939,9 @@ public class NotificationManagerService extends SystemService { mShowReviewPermissionsNotification = getContext().getResources().getBoolean( R.bool.config_notificationReviewPermissions); + mDefaultUnsupportedAdjustments = getContext().getResources().getStringArray( + R.array.config_notificationDefaultUnsupportedAdjustments); + init(handler, new RankingHandlerWorker(mRankingThread.getLooper()), AppGlobals.getPackageManager(), getContext().getPackageManager(), getLocalService(LightsManager.class), @@ -3033,10 +3037,9 @@ public class NotificationManagerService extends SystemService { switch(atomTag) { case PACKAGE_NOTIFICATION_PREFERENCES: if (notificationClassificationUi()) { - Set<String> pkgs = mAssistants.getPackagesWithKeyTypeAdjustmentSettings(); mPreferencesHelper.pullPackagePreferencesStats(data, getAllUsersNotificationPermissions(), - getPackageSpecificAdjustmentKeyTypes(pkgs)); + new ArrayMap<>()); } else { mPreferencesHelper.pullPackagePreferencesStats(data, getAllUsersNotificationPermissions()); @@ -4397,16 +4400,16 @@ public class NotificationManagerService extends SystemService { @Override @FlaggedApi(android.app.Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI) - public @NonNull int[] getAllowedAdjustmentKeyTypesForPackage(String pkg) { + public @NonNull String[] getTypeAdjustmentDeniedPackages() { checkCallerIsSystemOrSystemUiOrShell(); - return mAssistants.getAllowedAdjustmentKeyTypesForPackage(pkg); + return mAssistants.getTypeAdjustmentDeniedPackages(); } + @Override @FlaggedApi(android.app.Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI) - public void setAssistantAdjustmentKeyTypeStateForPackage(String pkg, int type, - boolean enabled) { + public void setTypeAdjustmentForPackageState(String pkg, boolean enabled) { checkCallerIsSystemOrSystemUiOrShell(); - mAssistants.setAssistantAdjustmentKeyTypeStateForPackage(pkg, type, enabled); + mAssistants.setTypeAdjustmentForPackageState(pkg, enabled); handleSavePolicyFile(); } @@ -7215,7 +7218,7 @@ public class NotificationManagerService extends SystemService { toRemove.add(potentialKey); } else if (notificationClassificationUi() && !mAssistants.isTypeAdjustmentAllowedForPackage( - r.getSbn().getPackageName(), adjustments.getInt(KEY_TYPE))) { + r.getSbn().getPackageName())) { toRemove.add(potentialKey); } } @@ -7552,24 +7555,6 @@ public class NotificationManagerService extends SystemService { return allPermissions; } - @VisibleForTesting - @FlaggedApi(android.app.Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI) - protected @NonNull Map<String, Set<Integer>> getPackageSpecificAdjustmentKeyTypes( - Set<String> pkgs) { - ArrayMap<String, Set<Integer>> pkgToAllowedTypes = new ArrayMap<>(); - for (String pkg : pkgs) { - int[] allowedTypesArray = mAssistants.getAllowedAdjustmentKeyTypesForPackage(pkg); - if (allowedTypesArray != null) { - Set<Integer> allowedTypes = new ArraySet<Integer>(); - for (int i : allowedTypesArray) { - allowedTypes.add(i); - } - pkgToAllowedTypes.append(pkg, allowedTypes); - } - } - return pkgToAllowedTypes; - } - private void dumpJson(PrintWriter pw, @NonNull DumpFilter filter, ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> pkgPermissions) { JSONObject dump = new JSONObject(); @@ -11885,11 +11870,7 @@ public class NotificationManagerService extends SystemService { private static final String ATT_DENIED = "denied_adjustments"; private static final String ATT_ENABLED_TYPES = "enabled_key_types"; private static final String ATT_NAS_UNSUPPORTED = "unsupported_adjustments"; - // Encapsulates a list of packages and the bundle types enabled for each package. - private static final String TAG_TYPES_ENABLED_FOR_APPS = "types_enabled_for_apps"; - // Encapsulates the bundle types enabled for a package. - private static final String ATT_APP_ENABLED_TYPES = "app_enabled_types"; - private static final String ATT_PACKAGE = "package"; + private static final String ATT_TYPES_DENIED_APPS = "types_denied_apps"; private final Object mLock = new Object(); @@ -11905,14 +11886,8 @@ public class NotificationManagerService extends SystemService { @GuardedBy("mLock") private Map<Integer, HashSet<String>> mNasUnsupported = new ArrayMap<>(); - // Types of classifications (aka bundles) enabled/allowed for this package. - // If the set is NULL (or package is not in the list), default classification allow list - // (the global one) should be used. - // If the set is empty, that indicates the package explicitly has all classifications - // disallowed. @GuardedBy("mLock") - private Map<String, Set<Integer>> mClassificationTypePackagesEnabledTypes = - new ArrayMap<>(); + private Set<String> mClassificationTypeDeniedPackages = new ArraySet<>(); protected ComponentName mDefaultFromConfig = null; @@ -11990,6 +11965,9 @@ public class NotificationManagerService extends SystemService { } } else { mAllowedAdjustmentKeyTypes.addAll(List.of(DEFAULT_ALLOWED_ADJUSTMENT_KEY_TYPES)); + if (mDefaultUnsupportedAdjustments != null) { + mAllowedAdjustments.removeAll(List.of(mDefaultUnsupportedAdjustments)); + } } } @@ -12113,104 +12091,41 @@ public class NotificationManagerService extends SystemService { } } - /** - * Returns whether the type adjustment is allowed for this particular package. - * If no package-specific restrictions have been set, defaults to the same value as - * isAdjustmentKeyTypeAllowed(type). - */ @FlaggedApi(android.app.Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI) - protected boolean isTypeAdjustmentAllowedForPackage(String pkg, - @Adjustment.Types int type) { + protected @NonNull boolean isTypeAdjustmentAllowedForPackage(String pkg) { synchronized (mLock) { if (notificationClassificationUi()) { - if (mClassificationTypePackagesEnabledTypes.containsKey(pkg)) { - Set<Integer> enabled = mClassificationTypePackagesEnabledTypes.get(pkg); - if (enabled != null) { - return enabled.contains(type); - } - } - // If mClassificationTypePackagesEnabledTypes does not contain the pkg, or - // the stored set is null, return the default. - return isAdjustmentKeyTypeAllowed(type); - } - } - return false; - } - - @FlaggedApi(android.app.Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI) - protected @NonNull Set<String> getPackagesWithKeyTypeAdjustmentSettings() { - if (notificationClassificationUi()) { - Set<String> packagesWithModifications = new ArraySet<String>(); - synchronized (mLock) { - for (String pkg : mClassificationTypePackagesEnabledTypes.keySet()) { - if (mClassificationTypePackagesEnabledTypes.get(pkg) != null) { - packagesWithModifications.add(pkg); - } - } + return !mClassificationTypeDeniedPackages.contains(pkg); } - return packagesWithModifications; } - return new ArraySet<String>(); + return true; } @FlaggedApi(android.app.Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI) - protected @NonNull int[] getAllowedAdjustmentKeyTypesForPackage(String pkg) { + protected @NonNull String[] getTypeAdjustmentDeniedPackages() { synchronized (mLock) { if (notificationClassificationUi()) { - if (mClassificationTypePackagesEnabledTypes.containsKey(pkg)) { - Set<Integer> enabled = mClassificationTypePackagesEnabledTypes.get(pkg); - if (enabled != null) { - // Convert Set to int[] for return. - int[] returnEnabled = new int[enabled.size()]; - int i = 0; - for (int val: enabled) { - returnEnabled[i] = val; - i++; - } - return returnEnabled; - } - } - // If package is not in the map, or the value is null, return the default. - return getAllowedAdjustmentKeyTypes(); + return mClassificationTypeDeniedPackages.toArray(new String[0]); } } - return new int[]{}; + return new String[]{}; } /** * Set whether a particular package can have its notification channels adjusted to have a * different type by NotificationAssistants. - * Note: once this method is called to enable or disable a specific type for a package, - * the global default is set as the starting point, and the type is enabled/disabled from - * there. Future changes to the global default will not apply automatically to this package. */ @FlaggedApi(android.app.Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI) - public void setAssistantAdjustmentKeyTypeStateForPackage(String pkg, - @Adjustment.Types int type, - boolean enabled) { + public void setTypeAdjustmentForPackageState(String pkg, boolean enabled) { if (!notificationClassificationUi()) { return; } synchronized (mLock) { - Set<Integer> enabledTypes = null; - if (mClassificationTypePackagesEnabledTypes.containsKey(pkg)) { - enabledTypes = mClassificationTypePackagesEnabledTypes.get(pkg); - } - if (enabledTypes == null) { - // Use global default to start. - enabledTypes = new ArraySet<Integer>(); - // Convert from int[] to Set<Integer> - for (int value : getAllowedAdjustmentKeyTypes()) { - enabledTypes.add(value); - } - } - if (enabled) { - enabledTypes.add(type); + mClassificationTypeDeniedPackages.remove(pkg); } else { - enabledTypes.remove(type); + mClassificationTypeDeniedPackages.add(pkg); } - mClassificationTypePackagesEnabledTypes.put(pkg, enabledTypes); } } @@ -12677,25 +12592,16 @@ public class NotificationManagerService extends SystemService { TextUtils.join(",", mAllowedAdjustmentKeyTypes)); out.endTag(null, ATT_ENABLED_TYPES); if (notificationClassificationUi()) { - out.startTag(null, TAG_TYPES_ENABLED_FOR_APPS); - for (String pkg: mClassificationTypePackagesEnabledTypes.keySet()) { - Set<Integer> allowedTypes = - mClassificationTypePackagesEnabledTypes.get(pkg); - if (allowedTypes != null) { - out.startTag(null, ATT_APP_ENABLED_TYPES); - out.attribute(null, ATT_PACKAGE, pkg); - out.attribute(null, ATT_TYPES, TextUtils.join(",", allowedTypes)); - out.endTag(null, ATT_APP_ENABLED_TYPES); - } - } - out.endTag(null, TAG_TYPES_ENABLED_FOR_APPS); + out.startTag(null, ATT_TYPES_DENIED_APPS); + out.attribute(null, ATT_TYPES, + TextUtils.join(",", mClassificationTypeDeniedPackages)); + out.endTag(null, ATT_TYPES_DENIED_APPS); } } } @Override - protected void readExtraTag(String tag, TypedXmlPullParser parser) throws IOException, - XmlPullParserException { + protected void readExtraTag(String tag, TypedXmlPullParser parser) throws IOException { if (!notificationClassification()) { return; } @@ -12722,25 +12628,12 @@ public class NotificationManagerService extends SystemService { } } } - } else if (TAG_TYPES_ENABLED_FOR_APPS.equals(tag)) { - final int appsOuterDepth = parser.getDepth(); + } else if (notificationClassificationUi() && ATT_TYPES_DENIED_APPS.equals(tag)) { + final String apps = XmlUtils.readStringAttribute(parser, ATT_TYPES); synchronized (mLock) { - mClassificationTypePackagesEnabledTypes.clear(); - while (XmlUtils.nextElementWithin(parser, appsOuterDepth)) { - if (!ATT_APP_ENABLED_TYPES.equals(parser.getName())) { - continue; - } - final String app = XmlUtils.readStringAttribute(parser, ATT_PACKAGE); - Set<Integer> allowedTypes = new ArraySet<>(); - final String typesString = XmlUtils.readStringAttribute(parser, ATT_TYPES); - if (!TextUtils.isEmpty(typesString)) { - allowedTypes = Arrays.stream(typesString.split(",")) - .map(Integer::valueOf) - .collect(Collectors.toSet()); - } - // Empty type list is allowed, because empty type list signifies the user - // has manually cleared the package of allowed types. - mClassificationTypePackagesEnabledTypes.put(app, allowedTypes); + mClassificationTypeDeniedPackages.clear(); + if (!TextUtils.isEmpty(apps)) { + mClassificationTypeDeniedPackages.addAll(Arrays.asList(apps.split(","))); } } } diff --git a/services/core/java/com/android/server/notification/ZenLog.java b/services/core/java/com/android/server/notification/ZenLog.java index 1aa5ac046ae9..7e853d9d2d0b 100644 --- a/services/core/java/com/android/server/notification/ZenLog.java +++ b/services/core/java/com/android/server/notification/ZenLog.java @@ -16,6 +16,7 @@ package com.android.server.notification; +import android.annotation.Nullable; import android.app.NotificationManager; import android.content.ComponentName; import android.media.AudioManager; @@ -26,6 +27,7 @@ import android.provider.Settings.Global; import android.service.notification.IConditionProvider; import android.service.notification.NotificationListenerService; import android.service.notification.ZenModeConfig; +import android.service.notification.ZenModeConfig.ConfigOrigin; import android.service.notification.ZenModeDiff; import android.util.LocalLog; @@ -119,16 +121,17 @@ public class ZenLog { append(TYPE_UNSUBSCRIBE, uri + "," + subscribeResult(provider, e)); } - public static void traceConfig(String reason, ComponentName triggeringComponent, - ZenModeConfig oldConfig, ZenModeConfig newConfig, int callingUid) { + public static void traceConfig(@ConfigOrigin int origin, String reason, + @Nullable ComponentName triggeringComponent, ZenModeConfig oldConfig, + ZenModeConfig newConfig, int callingUid) { ZenModeDiff.ConfigDiff diff = new ZenModeDiff.ConfigDiff(oldConfig, newConfig); - if (diff == null || !diff.hasDiff()) { - append(TYPE_CONFIG, reason + " no changes"); + if (!diff.hasDiff()) { + append(TYPE_CONFIG, reason + " (" + originToString(origin) + ") no changes"); } else { - append(TYPE_CONFIG, reason - + " - " + triggeringComponent + " : " + callingUid - + ",\n" + (newConfig != null ? newConfig.toString() : null) - + ",\n" + diff); + append(TYPE_CONFIG, reason + " (" + originToString(origin) + ") from uid " + callingUid + + (triggeringComponent != null ? " - " + triggeringComponent : "") + ",\n" + + (newConfig != null ? newConfig.toString() : null) + ",\n" + + diff); } } @@ -241,7 +244,22 @@ public class ZenLog { } } - private static String componentToString(ComponentName component) { + private static String originToString(@ConfigOrigin int origin) { + return switch (origin) { + case ZenModeConfig.ORIGIN_UNKNOWN -> "ORIGIN_UNKNOWN"; + case ZenModeConfig.ORIGIN_INIT -> "ORIGIN_INIT"; + case ZenModeConfig.ORIGIN_INIT_USER -> "ORIGIN_INIT_USER"; + case ZenModeConfig.ORIGIN_USER_IN_SYSTEMUI -> "ORIGIN_USER_IN_SYSTEMUI"; + case ZenModeConfig.ORIGIN_APP -> "ORIGIN_APP"; + case ZenModeConfig.ORIGIN_SYSTEM -> "ORIGIN_SYSTEM"; + case ZenModeConfig.ORIGIN_RESTORE_BACKUP -> "ORIGIN_RESTORE_BACKUP"; + case ZenModeConfig.ORIGIN_USER_IN_APP -> "ORIGIN_USER_IN_APP"; + default -> origin + "??"; + }; + } + + @Nullable + private static String componentToString(@Nullable ComponentName component) { return component != null ? component.toShortString() : null; } diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java index 0a63f3fb36d0..f089c3acdd70 100644 --- a/services/core/java/com/android/server/notification/ZenModeHelper.java +++ b/services/core/java/com/android/server/notification/ZenModeHelper.java @@ -659,7 +659,8 @@ public class ZenModeHelper { mContext.getString(R.string.zen_mode_implicit_deactivated), STATE_FALSE); setAutomaticZenRuleStateLocked(newConfig, Collections.singletonList(rule), - deactivated, ORIGIN_APP, callingUid); + deactivated, ORIGIN_APP, + "applyGlobalZenModeAsImplicitZenRule: " + callingPkg, callingUid); } } else { // Either create a new rule with a default ZenPolicy, or update an existing rule's @@ -971,26 +972,27 @@ public class ZenModeHelper { if (Flags.modesApi()) { if (rule != null && canManageAutomaticZenRule(rule, callingUid)) { setAutomaticZenRuleStateLocked(newConfig, Collections.singletonList(rule), - condition, origin, callingUid); + condition, origin, "setAzrState: " + rule.id, callingUid); } } else { ArrayList<ZenRule> rules = new ArrayList<>(); rules.add(rule); // rule may be null and throw NPE in the next method. - setAutomaticZenRuleStateLocked(newConfig, rules, condition, origin, callingUid); + setAutomaticZenRuleStateLocked(newConfig, rules, condition, origin, + "setAzrState: " + (rule != null ? rule.id : "null!"), callingUid); } } } - void setAutomaticZenRuleStateFromConditionProvider(UserHandle user, Uri ruleDefinition, + void setAutomaticZenRuleStateFromConditionProvider(UserHandle user, Uri ruleConditionId, Condition condition, @ConfigOrigin int origin, int callingUid) { - checkSetRuleStateOrigin("setAutomaticZenRuleState(Uri ruleDefinition)", origin); + checkSetRuleStateOrigin("setAutomaticZenRuleStateFromConditionProvider", origin); ZenModeConfig newConfig; synchronized (mConfigLock) { ZenModeConfig config = getConfigLocked(user); if (config == null) return; newConfig = config.copy(); - List<ZenRule> matchingRules = findMatchingRules(newConfig, ruleDefinition, condition); + List<ZenRule> matchingRules = findMatchingRules(newConfig, ruleConditionId, condition); if (Flags.modesApi()) { for (int i = matchingRules.size() - 1; i >= 0; i--) { if (!canManageAutomaticZenRule(matchingRules.get(i), callingUid)) { @@ -998,13 +1000,14 @@ public class ZenModeHelper { } } } - setAutomaticZenRuleStateLocked(newConfig, matchingRules, condition, origin, callingUid); + setAutomaticZenRuleStateLocked(newConfig, matchingRules, condition, origin, + "setAzrStateFromCps: " + ruleConditionId, callingUid); } } @GuardedBy("mConfigLock") private void setAutomaticZenRuleStateLocked(ZenModeConfig config, List<ZenRule> rules, - Condition condition, @ConfigOrigin int origin, int callingUid) { + Condition condition, @ConfigOrigin int origin, String reason, int callingUid) { if (rules == null || rules.isEmpty()) return; if (!Flags.modesUi()) { @@ -1015,7 +1018,7 @@ public class ZenModeHelper { for (ZenRule rule : rules) { applyConditionAndReconsiderOverride(rule, condition, origin); - setConfigLocked(config, rule.component, origin, "conditionChanged", callingUid); + setConfigLocked(config, rule.component, origin, reason, callingUid); } } @@ -2111,13 +2114,14 @@ public class ZenModeHelper { } @GuardedBy("mConfigLock") - private boolean setConfigLocked(ZenModeConfig config, ComponentName triggeringComponent, - @ConfigOrigin int origin, String reason, int callingUid) { + private boolean setConfigLocked(ZenModeConfig config, + @Nullable ComponentName triggeringComponent, @ConfigOrigin int origin, String reason, + int callingUid) { return setConfigLocked(config, origin, reason, triggeringComponent, true /*setRingerMode*/, callingUid); } - void setConfig(ZenModeConfig config, ComponentName triggeringComponent, + void setConfig(ZenModeConfig config, @Nullable ComponentName triggeringComponent, @ConfigOrigin int origin, String reason, int callingUid) { synchronized (mConfigLock) { setConfigLocked(config, triggeringComponent, origin, reason, callingUid); @@ -2126,7 +2130,7 @@ public class ZenModeHelper { @GuardedBy("mConfigLock") private boolean setConfigLocked(ZenModeConfig config, @ConfigOrigin int origin, - String reason, ComponentName triggeringComponent, boolean setRingerMode, + String reason, @Nullable ComponentName triggeringComponent, boolean setRingerMode, int callingUid) { final long identity = Binder.clearCallingIdentity(); try { @@ -2149,7 +2153,7 @@ public class ZenModeHelper { mConfigs.put(config.user, config); } if (DEBUG) Log.d(TAG, "setConfigLocked reason=" + reason, new Throwable()); - ZenLog.traceConfig(reason, triggeringComponent, mConfig, config, callingUid); + ZenLog.traceConfig(origin, reason, triggeringComponent, mConfig, config, callingUid); // send some broadcasts Policy newPolicy = getNotificationPolicy(config); diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index f60e086e7c5d..61429a41370c 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -4255,8 +4255,6 @@ public class PackageManagerService implements PackageSender, TestUtilityService CarrierAppUtils.disableCarrierAppsUntilPrivileged( mContext.getOpPackageName(), UserHandle.USER_SYSTEM, mContext); - disableSkuSpecificApps(); - // Read the compatibilty setting when the system is ready. boolean compatibilityModeEnabled = android.provider.Settings.Global.getInt( mContext.getContentResolver(), @@ -4390,29 +4388,6 @@ public class PackageManagerService implements PackageSender, TestUtilityService } } - //TODO: b/111402650 - private void disableSkuSpecificApps() { - String[] apkList = mContext.getResources().getStringArray( - R.array.config_disableApksUnlessMatchedSku_apk_list); - String[] skuArray = mContext.getResources().getStringArray( - R.array.config_disableApkUnlessMatchedSku_skus_list); - if (ArrayUtils.isEmpty(apkList)) { - return; - } - String sku = SystemProperties.get("ro.boot.hardware.sku"); - if (!TextUtils.isEmpty(sku) && ArrayUtils.contains(skuArray, sku)) { - return; - } - final Computer snapshot = snapshotComputer(); - for (String packageName : apkList) { - setSystemAppHiddenUntilInstalled(snapshot, packageName, true); - final List<UserInfo> users = mInjector.getUserManagerInternal().getUsers(false); - for (int i = 0; i < users.size(); i++) { - setSystemAppInstallState(snapshot, packageName, false, users.get(i).id); - } - } - } - public PackageFreezer freezePackage(String packageName, int userId, String killReason, int exitInfoReason, InstallRequest request) { return freezePackage(packageName, userId, killReason, exitInfoReason, request, diff --git a/services/core/java/com/android/server/policy/PermissionPolicyService.java b/services/core/java/com/android/server/policy/PermissionPolicyService.java index deaa8d8feae1..44d787f790cf 100644 --- a/services/core/java/com/android/server/policy/PermissionPolicyService.java +++ b/services/core/java/com/android/server/policy/PermissionPolicyService.java @@ -356,7 +356,7 @@ public final class PermissionPolicyService extends SystemService { try { manager = new PermissionControllerManager( getUserContext(getContext(), user), PermissionThread.getHandler()); - } catch (IllegalArgumentException exception) { + } catch (IllegalStateException exception) { // There's a possible race condition when a user is being removed Log.e(LOG_TAG, "Could not create PermissionControllerManager for user" + user, exception); diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java index f9e4022f04a0..090707db50a5 100644 --- a/services/core/java/com/android/server/power/PowerManagerService.java +++ b/services/core/java/com/android/server/power/PowerManagerService.java @@ -30,6 +30,7 @@ import static android.os.PowerManagerInternal.WAKEFULNESS_DOZING; import static android.os.PowerManagerInternal.WAKEFULNESS_DREAMING; import static android.os.PowerManagerInternal.isInteractive; import static android.os.PowerManagerInternal.wakefulnessToString; +import static android.service.dreams.Flags.allowDreamWhenPostured; import static com.android.internal.util.LatencyTracker.ACTION_TURN_ON_SCREEN; import static com.android.server.deviceidle.Flags.disableWakelocksInLightIdle; @@ -216,6 +217,8 @@ public final class PowerManagerService extends SystemService private static final int DIRTY_ATTENTIVE = 1 << 14; // Dirty bit: display group wakefulness has changed private static final int DIRTY_DISPLAY_GROUP_WAKEFULNESS = 1 << 16; + // Dirty bit: device postured state has changed + private static final int DIRTY_POSTURED_STATE = 1 << 17; // Summarizes the state of all active wakelocks. static final int WAKE_LOCK_CPU = 1 << 0; @@ -500,6 +503,11 @@ public final class PowerManagerService extends SystemService // The current dock state. private int mDockState = Intent.EXTRA_DOCK_STATE_UNDOCKED; + /** + * Whether the device is upright and stationary. + */ + private boolean mDevicePostured; + // True to decouple auto-suspend mode from the display state. private boolean mDecoupleHalAutoSuspendModeFromDisplayConfig; @@ -530,6 +538,9 @@ public final class PowerManagerService extends SystemService // Default value for dreams activate-on-dock private boolean mDreamsActivatedOnDockByDefaultConfig; + /** Default value for whether dreams are activated when postured (stationary + upright) */ + private boolean mDreamsActivatedWhilePosturedByDefaultConfig; + // True if dreams can run while not plugged in. private boolean mDreamsEnabledOnBatteryConfig; @@ -558,6 +569,9 @@ public final class PowerManagerService extends SystemService // True if dreams should be activated on dock. private boolean mDreamsActivateOnDockSetting; + /** Whether dreams should be activated when device is postured (stationary and upright) */ + private boolean mDreamsActivateWhilePosturedSetting; + // True if doze should not be started until after the screen off transition. private boolean mDozeAfterScreenOff; @@ -1471,6 +1485,9 @@ public final class PowerManagerService extends SystemService resolver.registerContentObserver(Settings.Secure.getUriFor( Settings.Secure.SCREENSAVER_ACTIVATE_ON_DOCK), false, mSettingsObserver, UserHandle.USER_ALL); + resolver.registerContentObserver(Settings.Secure.getUriFor( + Settings.Secure.SCREENSAVER_ACTIVATE_ON_POSTURED), + false, mSettingsObserver, UserHandle.USER_ALL); resolver.registerContentObserver(Settings.System.getUriFor( Settings.System.SCREEN_OFF_TIMEOUT), false, mSettingsObserver, UserHandle.USER_ALL); @@ -1549,6 +1566,8 @@ public final class PowerManagerService extends SystemService com.android.internal.R.bool.config_dreamsActivatedOnSleepByDefault); mDreamsActivatedOnDockByDefaultConfig = resources.getBoolean( com.android.internal.R.bool.config_dreamsActivatedOnDockByDefault); + mDreamsActivatedWhilePosturedByDefaultConfig = resources.getBoolean( + com.android.internal.R.bool.config_dreamsActivatedOnPosturedByDefault); mDreamsEnabledOnBatteryConfig = resources.getBoolean( com.android.internal.R.bool.config_dreamsEnabledOnBattery); mDreamsBatteryLevelMinimumWhenPoweredConfig = resources.getInteger( @@ -1589,6 +1608,10 @@ public final class PowerManagerService extends SystemService Settings.Secure.SCREENSAVER_ACTIVATE_ON_DOCK, mDreamsActivatedOnDockByDefaultConfig ? 1 : 0, UserHandle.USER_CURRENT) != 0); + mDreamsActivateWhilePosturedSetting = (Settings.Secure.getIntForUser(resolver, + Settings.Secure.SCREENSAVER_ACTIVATE_ON_POSTURED, + mDreamsActivatedWhilePosturedByDefaultConfig ? 1 : 0, + UserHandle.USER_CURRENT) != 0); mScreenOffTimeoutSetting = Settings.System.getIntForUser(resolver, Settings.System.SCREEN_OFF_TIMEOUT, DEFAULT_SCREEN_OFF_TIMEOUT, UserHandle.USER_CURRENT); @@ -3336,7 +3359,7 @@ public final class PowerManagerService extends SystemService if ((dirty & (DIRTY_WAKE_LOCKS | DIRTY_USER_ACTIVITY | DIRTY_BOOT_COMPLETED | DIRTY_WAKEFULNESS | DIRTY_STAY_ON | DIRTY_PROXIMITY_POSITIVE | DIRTY_DOCK_STATE | DIRTY_ATTENTIVE | DIRTY_SETTINGS - | DIRTY_SCREEN_BRIGHTNESS_BOOST)) == 0) { + | DIRTY_SCREEN_BRIGHTNESS_BOOST | DIRTY_POSTURED_STATE)) == 0) { return changed; } final long time = mClock.uptimeMillis(); @@ -3375,7 +3398,8 @@ public final class PowerManagerService extends SystemService private boolean shouldNapAtBedTimeLocked() { return mDreamsActivateOnSleepSetting || (mDreamsActivateOnDockSetting - && mDockState != Intent.EXTRA_DOCK_STATE_UNDOCKED); + && mDockState != Intent.EXTRA_DOCK_STATE_UNDOCKED) + || (mDreamsActivateWhilePosturedSetting && mDevicePostured); } /** @@ -4489,6 +4513,17 @@ public final class PowerManagerService extends SystemService } } + private void setDevicePosturedInternal(boolean isPostured) { + if (!allowDreamWhenPostured()) { + return; + } + synchronized (mLock) { + mDevicePostured = isPostured; + mDirty |= DIRTY_POSTURED_STATE; + updatePowerStateLocked(); + } + } + private void setUserActivityTimeoutOverrideFromWindowManagerInternal(long timeoutMillis) { synchronized (mLock) { if (mUserActivityTimeoutOverrideFromWindowManager != timeoutMillis) { @@ -4794,6 +4829,8 @@ public final class PowerManagerService extends SystemService + mDreamsActivatedOnSleepByDefaultConfig); pw.println(" mDreamsActivatedOnDockByDefaultConfig=" + mDreamsActivatedOnDockByDefaultConfig); + pw.println(" mDreamsActivatedWhilePosturedByDefaultConfig=" + + mDreamsActivatedWhilePosturedByDefaultConfig); pw.println(" mDreamsEnabledOnBatteryConfig=" + mDreamsEnabledOnBatteryConfig); pw.println(" mDreamsBatteryLevelMinimumWhenPoweredConfig=" @@ -4805,6 +4842,8 @@ public final class PowerManagerService extends SystemService pw.println(" mDreamsEnabledSetting=" + mDreamsEnabledSetting); pw.println(" mDreamsActivateOnSleepSetting=" + mDreamsActivateOnSleepSetting); pw.println(" mDreamsActivateOnDockSetting=" + mDreamsActivateOnDockSetting); + pw.println(" mDreamsActivateWhilePosturedSetting=" + + mDreamsActivateWhilePosturedSetting); pw.println(" mDozeAfterScreenOff=" + mDozeAfterScreenOff); pw.println(" mBrightWhenDozingConfig=" + mBrightWhenDozingConfig); pw.println(" mMinimumScreenOffTimeoutConfig=" + mMinimumScreenOffTimeoutConfig); @@ -7388,6 +7427,11 @@ public final class PowerManagerService extends SystemService public boolean isAmbientDisplaySuppressed() { return mAmbientDisplaySuppressionController.isSuppressed(); } + + @Override + public void setDevicePostured(boolean isPostured) { + setDevicePosturedInternal(isPostured); + } } /** diff --git a/services/core/java/com/android/server/security/authenticationpolicy/AuthenticationPolicyService.java b/services/core/java/com/android/server/security/authenticationpolicy/AuthenticationPolicyService.java index 6798a6146ae0..2452dc59bea5 100644 --- a/services/core/java/com/android/server/security/authenticationpolicy/AuthenticationPolicyService.java +++ b/services/core/java/com/android/server/security/authenticationpolicy/AuthenticationPolicyService.java @@ -17,6 +17,7 @@ package com.android.server.security.authenticationpolicy; import static android.Manifest.permission.MANAGE_SECURE_LOCK_DEVICE; +import static android.security.Flags.disableAdaptiveAuthCounterLock; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST; @@ -39,6 +40,7 @@ import android.os.IBinder; import android.os.Looper; import android.os.Message; import android.os.SystemClock; +import android.provider.Settings; import android.security.authenticationpolicy.AuthenticationPolicyManager; import android.security.authenticationpolicy.DisableSecureLockDeviceParams; import android.security.authenticationpolicy.EnableSecureLockDeviceParams; @@ -251,6 +253,17 @@ public class AuthenticationPolicyService extends SystemService { return; } + if (disableAdaptiveAuthCounterLock() && Build.IS_DEBUGGABLE) { + final boolean disabled = Settings.Secure.getIntForUser( + getContext().getContentResolver(), + Settings.Secure.DISABLE_ADAPTIVE_AUTH_LIMIT_LOCK, + 0 /* default */, userId) != 0; + if (disabled) { + Slog.d(TAG, "not locking (disabled by user)"); + return; + } + } + //TODO: additionally consider the trust signal before locking device lockDevice(userId); } diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java index 40ea9319c6be..7f2c68ff60b1 100644 --- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java +++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java @@ -118,6 +118,7 @@ import android.hardware.biometrics.BiometricsProtoEnums; import android.hardware.display.DisplayManager; import android.hardware.face.FaceManager; import android.hardware.fingerprint.FingerprintManager; +import android.health.connect.HealthConnectManager; import android.media.AudioManager; import android.media.MediaDrm; import android.media.UnsupportedSchemeException; @@ -4115,7 +4116,7 @@ public class StatsPullAtomService extends SystemService { int nOps = opsList.size(); for (int i = 0; i < nOps; i++) { AppOpEntry entry = opsList.get(i); - if (entry.mHash >= samplingRate) { + if (entry.mHash >= samplingRate || isHealthAppOp(entry.mOp.getOpCode())) { continue; } StatsEvent e; @@ -4301,6 +4302,11 @@ public class StatsPullAtomService extends SystemService { return StatsManager.PULL_SUCCESS; } + if (isHealthAppOp(AppOpsManager.strOpToOp(message.getOp()))) { + // Not log sensitive health app ops. + return StatsManager.PULL_SKIP; + } + pulledData.add(FrameworkStatsLog.buildStatsEvent(atomTag, message.getUid(), message.getPackageName(), "", message.getAttributionTag() == null ? "" : message.getAttributionTag(), @@ -4893,7 +4899,7 @@ public class StatsPullAtomService extends SystemService { Slog.e(TAG, "Disconnected from keystore service. Cannot pull.", e); return StatsManager.PULL_SKIP; } catch (ServiceSpecificException e) { - Slog.e(TAG, "pulling keystore metrics failed", e); + Slog.e(TAG, "Pulling keystore atom with tag " + atomTag + " failed", e); return StatsManager.PULL_SKIP; } finally { Binder.restoreCallingIdentity(callingToken); @@ -5370,6 +5376,11 @@ public class StatsPullAtomService extends SystemService { } } + private boolean isHealthAppOp(int opCode) { + String permission = AppOpsManager.opToPermission(opCode); + return permission != null && HealthConnectManager.isHealthPermission(mContext, permission); + } + // Thermal event received from vendor thermal management subsystem private static final class ThermalEventListener extends IThermalEventListener.Stub { @Override diff --git a/services/core/java/com/android/server/wm/AppCompatController.java b/services/core/java/com/android/server/wm/AppCompatController.java index a94f6252cd68..cc9cd90fae06 100644 --- a/services/core/java/com/android/server/wm/AppCompatController.java +++ b/services/core/java/com/android/server/wm/AppCompatController.java @@ -39,7 +39,7 @@ class AppCompatController { @NonNull private final AppCompatOverrides mAppCompatOverrides; @NonNull - private final AppCompatDeviceStateQuery mAppCompatDeviceStateQuery; + private final AppCompatDeviceStateQuery mDeviceStateQuery; @NonNull private final AppCompatLetterboxPolicy mAppCompatLetterboxPolicy; @NonNull @@ -50,11 +50,11 @@ class AppCompatController { final PackageManager packageManager = wmService.mContext.getPackageManager(); final OptPropFactory optPropBuilder = new OptPropFactory(packageManager, activityRecord.packageName); - mAppCompatDeviceStateQuery = new AppCompatDeviceStateQuery(activityRecord); + mDeviceStateQuery = new AppCompatDeviceStateQuery(activityRecord); mTransparentPolicy = new TransparentPolicy(activityRecord, wmService.mAppCompatConfiguration); mAppCompatOverrides = new AppCompatOverrides(activityRecord, packageManager, - wmService.mAppCompatConfiguration, optPropBuilder, mAppCompatDeviceStateQuery); + wmService.mAppCompatConfiguration, optPropBuilder, mDeviceStateQuery); mOrientationPolicy = new AppCompatOrientationPolicy(activityRecord, mAppCompatOverrides); mAspectRatioPolicy = new AppCompatAspectRatioPolicy(activityRecord, mTransparentPolicy, mAppCompatOverrides); @@ -129,8 +129,8 @@ class AppCompatController { } @NonNull - AppCompatDeviceStateQuery getAppCompatDeviceStateQuery() { - return mAppCompatDeviceStateQuery; + AppCompatDeviceStateQuery getDeviceStateQuery() { + return mDeviceStateQuery; } @NonNull diff --git a/services/core/java/com/android/server/wm/AppCompatReachabilityPolicy.java b/services/core/java/com/android/server/wm/AppCompatReachabilityPolicy.java index 087edc184b6f..a7c52bd1fc38 100644 --- a/services/core/java/com/android/server/wm/AppCompatReachabilityPolicy.java +++ b/services/core/java/com/android/server/wm/AppCompatReachabilityPolicy.java @@ -107,7 +107,7 @@ class AppCompatReachabilityPolicy { return; } final AppCompatDeviceStateQuery deviceStateQuery = mActivityRecord.mAppCompatController - .getAppCompatDeviceStateQuery(); + .getDeviceStateQuery(); final boolean isInFullScreenBookMode = deviceStateQuery .isDisplayFullScreenAndSeparatingHinge() && mAppCompatConfiguration.getIsAutomaticReachabilityInBookModeEnabled(); @@ -153,7 +153,7 @@ class AppCompatReachabilityPolicy { return; } final AppCompatDeviceStateQuery deviceStateQuery = mActivityRecord.mAppCompatController - .getAppCompatDeviceStateQuery(); + .getDeviceStateQuery(); final boolean isInFullScreenTabletopMode = deviceStateQuery .isDisplayFullScreenAndSeparatingHinge(); final int letterboxPositionForVerticalReachability = mAppCompatConfiguration diff --git a/services/core/java/com/android/server/wm/AppWarnings.java b/services/core/java/com/android/server/wm/AppWarnings.java index 601b17c46c03..576e5d5d0cd2 100644 --- a/services/core/java/com/android/server/wm/AppWarnings.java +++ b/services/core/java/com/android/server/wm/AppWarnings.java @@ -51,6 +51,7 @@ import android.util.Pair; import android.util.Slog; import android.util.SparseArray; import android.util.Xml; +import android.view.ContextThemeWrapper; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.ArrayUtils; @@ -498,10 +499,21 @@ class AppWarnings { } } if (!hasPackageFlag(userId, ar.packageName, FLAG_HIDE_PAGE_SIZE_MISMATCH)) { + Context context = getUiContextForActivity(ar); + // PageSizeMismatchDialog has link in message which should open in browser. + // Starting activity from non-activity context is not allowed and flag + // FLAG_ACTIVITY_NEW_TASK is needed to start activity. + context = new ContextThemeWrapper(context, context.getThemeResId()) { + @Override + public void startActivity(Intent intent) { + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + super.startActivity(intent); + } + }; pageSizeMismatchDialog = new PageSizeMismatchDialog( AppWarnings.this, - getUiContextForActivity(ar), + context, ar.info.applicationInfo, userId, warning); diff --git a/services/core/java/com/android/server/wm/DisplayAreaPolicy.java b/services/core/java/com/android/server/wm/DisplayAreaPolicy.java index 5bec4424269a..4b30a43db5d9 100644 --- a/services/core/java/com/android/server/wm/DisplayAreaPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayAreaPolicy.java @@ -53,6 +53,12 @@ import java.util.List; * Policy that manages {@link DisplayArea}. */ public abstract class DisplayAreaPolicy { + /** + * No corresponding use case yet (see b/154719717). The current implementation still uses + * {@link WindowState#shouldMagnify}. + */ + static final boolean USE_DISPLAY_AREA_FOR_FULLSCREEN_MAGNIFICATION = false; + protected final WindowManagerService mWmService; /** @@ -161,14 +167,17 @@ public abstract class DisplayAreaPolicy { TYPE_STATUS_BAR, TYPE_NOTIFICATION_SHADE, TYPE_WALLPAPER) .build()); } + if (USE_DISPLAY_AREA_FOR_FULLSCREEN_MAGNIFICATION) { + rootHierarchy.addFeature( + new Feature.Builder(wmService.mPolicy, "FullscreenMagnification", + FEATURE_FULLSCREEN_MAGNIFICATION) + .all() + .except(TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY, TYPE_INPUT_METHOD, + TYPE_INPUT_METHOD_DIALOG, TYPE_MAGNIFICATION_OVERLAY, + TYPE_NAVIGATION_BAR, TYPE_NAVIGATION_BAR_PANEL) + .build()); + } rootHierarchy - .addFeature(new Feature.Builder(wmService.mPolicy, "FullscreenMagnification", - FEATURE_FULLSCREEN_MAGNIFICATION) - .all() - .except(TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY, TYPE_INPUT_METHOD, - TYPE_INPUT_METHOD_DIALOG, TYPE_MAGNIFICATION_OVERLAY, - TYPE_NAVIGATION_BAR, TYPE_NAVIGATION_BAR_PANEL) - .build()) .addFeature(new Feature.Builder(wmService.mPolicy, "ImePlaceholder", FEATURE_IME_PLACEHOLDER) .and(TYPE_INPUT_METHOD, TYPE_INPUT_METHOD_DIALOG) diff --git a/services/core/java/com/android/server/wm/DragDropController.java b/services/core/java/com/android/server/wm/DragDropController.java index fa748d3a22a5..c418349e6158 100644 --- a/services/core/java/com/android/server/wm/DragDropController.java +++ b/services/core/java/com/android/server/wm/DragDropController.java @@ -28,9 +28,11 @@ import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import android.annotation.NonNull; import android.content.ClipData; import android.content.Context; +import android.hardware.display.DisplayTopology; import android.hardware.input.InputManagerGlobal; import android.os.Binder; import android.os.Handler; +import android.os.HandlerExecutor; import android.os.IBinder; import android.os.Looper; import android.os.Message; @@ -50,12 +52,14 @@ import android.window.IUnhandledDragCallback; import com.android.internal.annotations.VisibleForTesting; import com.android.server.wm.WindowManagerInternal.IDragDropCallback; +import com.android.window.flags.Flags; import java.util.Objects; import java.util.Random; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Consumer; /** * Managing drag and drop operations initiated by View#startDragAndDrop. @@ -83,6 +87,8 @@ class DragDropController { private WindowManagerService mService; private final Handler mHandler; + private final Consumer<DisplayTopology> mDisplayTopologyListener = + this::handleDisplayTopologyChange; // The global drag listener for handling cross-window drags private IGlobalDragListener mGlobalDragListener; @@ -108,6 +114,10 @@ class DragDropController { DragDropController(WindowManagerService service, Looper looper) { mService = service; mHandler = new DragHandler(service, looper); + if (Flags.enableConnectedDisplaysDnd()) { + mService.mDisplayManager.registerTopologyListener( + new HandlerExecutor(mService.mH), mDisplayTopologyListener); + } } @VisibleForTesting @@ -481,6 +491,19 @@ class DragDropController { } } + @VisibleForTesting + void handleDisplayTopologyChange(DisplayTopology unused) { + synchronized (mService.mGlobalLock) { + if (mDragState == null) { + return; + } + if (DEBUG_DRAG) { + Slog.d(TAG_WM, "DisplayTopology changed, cancelling DragAndDrop"); + } + cancelDragAndDrop(mDragState.mToken, true /* skipAnimation */); + } + } + /** * Handles motion events. * @param keepHandling Whether if the drag operation is continuing or this is the last motion diff --git a/services/core/java/com/android/server/wm/EmulatorDisplayOverlay.java b/services/core/java/com/android/server/wm/EmulatorDisplayOverlay.java index 02a7db19f405..0fbf56d120a8 100644 --- a/services/core/java/com/android/server/wm/EmulatorDisplayOverlay.java +++ b/services/core/java/com/android/server/wm/EmulatorDisplayOverlay.java @@ -77,8 +77,9 @@ class EmulatorDisplayOverlay { mOverlay = context.getDrawable( com.android.internal.R.drawable.emulator_circular_window_overlay); - mBlastBufferQueue = new BLASTBufferQueue(TITLE, mSurfaceControl, mScreenSize.x, - mScreenSize.y, PixelFormat.RGBA_8888); + mBlastBufferQueue = new BLASTBufferQueue(TITLE, /* updateDestinationFrame */ true); + mBlastBufferQueue.update(mSurfaceControl, mScreenSize.x, mScreenSize.y, + PixelFormat.RGBA_8888); mSurface = mBlastBufferQueue.createSurface(); } diff --git a/services/core/java/com/android/server/wm/PageSizeMismatchDialog.java b/services/core/java/com/android/server/wm/PageSizeMismatchDialog.java index 29922f0f85c5..24235ef2d585 100644 --- a/services/core/java/com/android/server/wm/PageSizeMismatchDialog.java +++ b/services/core/java/com/android/server/wm/PageSizeMismatchDialog.java @@ -24,8 +24,10 @@ import android.content.pm.ApplicationInfo; import android.content.pm.PackageItemInfo; import android.content.pm.PackageManager; import android.text.Html; +import android.text.method.LinkMovementMethod; import android.view.Window; import android.view.WindowManager; +import android.widget.TextView; import com.android.internal.R; @@ -69,6 +71,14 @@ class PageSizeMismatchDialog extends AppWarnings.BaseDialog { mDialog.create(); final Window window = mDialog.getWindow(); - window.setType(WindowManager.LayoutParams.TYPE_PHONE); + window.setType(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY); + } + + @Override + public void show() { + super.show(); + // Make the links in dialog clickable + final TextView msgTxt = (TextView) mDialog.findViewById(android.R.id.message); + msgTxt.setMovementMethod(LinkMovementMethod.getInstance()); } } diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java index 44f000da3d73..b9550feeab8a 100644 --- a/services/core/java/com/android/server/wm/RecentTasks.java +++ b/services/core/java/com/android/server/wm/RecentTasks.java @@ -40,7 +40,6 @@ import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW; import static android.view.WindowManager.LayoutParams.LAST_APPLICATION_WINDOW; import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_TASKS; -import static com.android.launcher3.Flags.enableUseTopVisibleActivityForExcludeFromRecentTask; import static com.android.server.wm.ActivityRecord.State.RESUMED; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_RECENTS; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_RECENTS_TRIM_TASKS; @@ -1528,12 +1527,7 @@ class RecentTasks { } // The Recents is only supported on default display now, we should only keep the // most recent task of home display. - boolean isMostRecentTask; - if (enableUseTopVisibleActivityForExcludeFromRecentTask()) { - isMostRecentTask = task.getTopVisibleActivity() != null; - } else { - isMostRecentTask = taskIndex == 0; - } + boolean isMostRecentTask = task.getTopVisibleActivity() != null; return (task.isOnHomeDisplay() && isMostRecentTask); } } diff --git a/services/core/java/com/android/server/wm/StrictModeFlash.java b/services/core/java/com/android/server/wm/StrictModeFlash.java index cdf6b08b1c57..b6365ad47535 100644 --- a/services/core/java/com/android/server/wm/StrictModeFlash.java +++ b/services/core/java/com/android/server/wm/StrictModeFlash.java @@ -63,8 +63,9 @@ class StrictModeFlash { mSurfaceControl = ctrl; mDrawNeeded = true; - mBlastBufferQueue = new BLASTBufferQueue(TITLE, mSurfaceControl, 1 /* width */, - 1 /* height */, PixelFormat.RGBA_8888); + mBlastBufferQueue = new BLASTBufferQueue(TITLE, /* updateDestinationFrame */ true); + mBlastBufferQueue.update(mSurfaceControl, 1 /* width */, 1 /* height */, + PixelFormat.RGBA_8888); mSurface = mBlastBufferQueue.createSurface(); } diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 295759c2fc7e..7a88338d8ac5 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -5297,40 +5297,29 @@ class Task extends TaskFragment { return mRootWindowContainer.resumeHomeActivity(prev, reason, getDisplayArea()); } - void startActivityLocked(ActivityRecord r, @Nullable Task topTask, boolean newTask, - boolean isTaskSwitch, ActivityOptions options, @Nullable ActivityRecord sourceRecord) { - Task rTask = r.getTask(); + void startActivityLocked(@NonNull ActivityRecord r, @Nullable Task topTask, boolean newTask, + boolean isTaskSwitch, @Nullable ActivityOptions options, + @Nullable ActivityRecord sourceRecord) { final boolean allowMoveToFront = options == null || !options.getAvoidMoveToFront(); - final boolean isOrhasTask = rTask == this || hasChild(rTask); + final Task activityTask = r.getTask(); + final boolean isThisOrHasChildTask = activityTask == this || hasChild(activityTask); + // mLaunchTaskBehind tasks get placed at the back of the task stack. - if (!r.mLaunchTaskBehind && allowMoveToFront && (!isOrhasTask || newTask)) { + if (!r.mLaunchTaskBehind && allowMoveToFront && (!isThisOrHasChildTask || newTask)) { // Last activity in task had been removed or ActivityManagerService is reusing task. // Insert or replace. // Might not even be in. - positionChildAtTop(rTask); + positionChildAtTop(activityTask); } - Task task = null; - if (!newTask && isOrhasTask && !r.shouldBeVisible()) { + + if (!newTask && isThisOrHasChildTask && !r.shouldBeVisible()) { ActivityOptions.abort(options); return; } - // Place a new activity at top of root task, so it is next to interact with the user. - - // If we are not placing the new activity frontmost, we do not want to deliver the - // onUserLeaving callback to the actual frontmost activity - final Task activityTask = r.getTask(); - if (task == activityTask && mChildren.indexOf(task) != (getChildCount() - 1)) { - mTaskSupervisor.mUserLeaving = false; - if (DEBUG_USER_LEAVING) Slog.v(TAG_USER_LEAVING, - "startActivity() behind front, mUserLeaving=false"); - } - - task = activityTask; - // Slot the activity into the history root task and proceed - ProtoLog.i(WM_DEBUG_ADD_REMOVE, "Adding activity %s to task %s " - + "callers: %s", r, task, new RuntimeException("here").fillInStackTrace()); + ProtoLog.i(WM_DEBUG_ADD_REMOVE, "Adding activity %s to task %s callers: %s", r, + activityTask, new RuntimeException("here").fillInStackTrace()); if (isActivityTypeHomeOrRecents() && getActivityBelow(r) == null) { // If this is the first activity, don't do any fancy animations, @@ -5346,15 +5335,15 @@ class Task extends TaskFragment { return; } - final DisplayContent dc = mDisplayContent; - if (DEBUG_TRANSITION) Slog.v(TAG_TRANSITION, - "Prepare open transition: starting " + r); + if (DEBUG_TRANSITION) Slog.v(TAG_TRANSITION, "Prepare open transition: starting " + r); + + // Place a new activity at top of root task, so it is next to interact with the user. if ((r.intent.getFlags() & Intent.FLAG_ACTIVITY_NO_ANIMATION) != 0) { - dc.prepareAppTransition(TRANSIT_NONE); + mDisplayContent.prepareAppTransition(TRANSIT_NONE); mTaskSupervisor.mNoAnimActivities.add(r); mTransitionController.setNoAnimation(r); } else { - dc.prepareAppTransition(TRANSIT_OPEN); + mDisplayContent.prepareAppTransition(TRANSIT_OPEN); mTaskSupervisor.mNoAnimActivities.remove(r); } if (newTask && !r.mLaunchTaskBehind) { @@ -5405,8 +5394,7 @@ class Task extends TaskFragment { // "has the same starting icon" as the next one. This allows the // window manager to keep the previous window it had previously // created, if it still had one. - Task baseTask = r.getTask(); - final ActivityRecord prev = baseTask.getActivity( + final ActivityRecord prev = activityTask.getActivity( a -> a.mStartingData != null && a.showToCurrentUser()); mWmService.mStartingSurfaceController.showStartingWindow(r, prev, newTask, isTaskSwitch, sourceRecord); diff --git a/services/core/java/com/android/server/wm/Watermark.java b/services/core/java/com/android/server/wm/Watermark.java index 9780d3317e11..eb6eeb31e8fb 100644 --- a/services/core/java/com/android/server/wm/Watermark.java +++ b/services/core/java/com/android/server/wm/Watermark.java @@ -126,8 +126,9 @@ class Watermark { } catch (OutOfResourcesException e) { } mSurfaceControl = ctrl; - mBlastBufferQueue = new BLASTBufferQueue(TITLE, mSurfaceControl, 1 /* width */, - 1 /* height */, PixelFormat.RGBA_8888); + mBlastBufferQueue = new BLASTBufferQueue(TITLE, /* updateDestinationFrame */ true); + mBlastBufferQueue.update(mSurfaceControl, 1 /* width */, 1 /* height */, + PixelFormat.RGBA_8888); mSurface = mBlastBufferQueue.createSurface(); } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 36d52ddb40e6..1754d7346220 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -753,8 +753,6 @@ public class WindowManagerService extends IWindowManager.Stub final static int WINDOWS_FREEZING_SCREENS_TIMEOUT = 2; int mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_NONE; - /** Indicates that the system server is actively demanding the screen be frozen. */ - boolean mClientFreezingScreen = false; int mAppsFreezingScreen = 0; @VisibleForTesting @@ -3354,60 +3352,6 @@ public class WindowManagerService extends IWindowManager.Stub return getDefaultDisplayContentLocked().mAppTransition.isIdle(); } - - // ------------------------------------------------------------- - // Misc IWindowSession methods - // ------------------------------------------------------------- - - /** Freeze the screen during a user-switch event. Called by UserController. */ - @Override - public void startFreezingScreen(int exitAnim, int enterAnim) { - if (!checkCallingPermission(android.Manifest.permission.FREEZE_SCREEN, - "startFreezingScreen()")) { - throw new SecurityException("Requires FREEZE_SCREEN permission"); - } - - synchronized (mGlobalLock) { - if (!mClientFreezingScreen) { - mClientFreezingScreen = true; - final long origId = Binder.clearCallingIdentity(); - try { - startFreezingDisplay(exitAnim, enterAnim); - mH.removeMessages(H.CLIENT_FREEZE_TIMEOUT); - mH.sendEmptyMessageDelayed(H.CLIENT_FREEZE_TIMEOUT, 5000); - } finally { - Binder.restoreCallingIdentity(origId); - } - } - } - } - - /** - * No longer actively demand that the screen remain frozen. - * Called by UserController after a user-switch. - * This doesn't necessarily immediately unlock the screen; it just allows it if we're ready. - */ - @Override - public void stopFreezingScreen() { - if (!checkCallingPermission(android.Manifest.permission.FREEZE_SCREEN, - "stopFreezingScreen()")) { - throw new SecurityException("Requires FREEZE_SCREEN permission"); - } - - synchronized (mGlobalLock) { - if (mClientFreezingScreen) { - mClientFreezingScreen = false; - mLastFinishedFreezeSource = "client"; - final long origId = Binder.clearCallingIdentity(); - try { - stopFreezingDisplayLocked(); - } finally { - Binder.restoreCallingIdentity(origId); - } - } - } - } - @Override public void disableKeyguard(IBinder token, String tag, int userId) { userId = mAmInternal.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), @@ -5669,7 +5613,6 @@ public class WindowManagerService extends IWindowManager.Stub public static final int WAITING_FOR_DRAWN_TIMEOUT = 24; public static final int SHOW_STRICT_MODE_VIOLATION = 25; - public static final int CLIENT_FREEZE_TIMEOUT = 30; public static final int NOTIFY_ACTIVITY_DRAWN = 32; public static final int NEW_ANIMATOR_SCALE = 34; @@ -5759,17 +5702,6 @@ public class WindowManagerService extends IWindowManager.Stub break; } - case CLIENT_FREEZE_TIMEOUT: { - synchronized (mGlobalLock) { - if (mClientFreezingScreen) { - mClientFreezingScreen = false; - mLastFinishedFreezeSource = "client-timeout"; - stopFreezingDisplayLocked(); - } - } - break; - } - case REPORT_WINDOWS_CHANGE: { if (mWindowsChanged) { synchronized (mGlobalLock) { @@ -6552,14 +6484,14 @@ public class WindowManagerService extends IWindowManager.Stub } if (waitingForConfig || waitingForRemoteDisplayChange || mAppsFreezingScreen > 0 || mWindowsFreezingScreen == WINDOWS_FREEZING_SCREENS_ACTIVE - || mClientFreezingScreen || numOpeningApps > 0) { + || numOpeningApps > 0) { ProtoLog.d(WM_DEBUG_ORIENTATION, "stopFreezingDisplayLocked: Returning " + "waitingForConfig=%b, waitingForRemoteDisplayChange=%b, " + "mAppsFreezingScreen=%d, mWindowsFreezingScreen=%d, " - + "mClientFreezingScreen=%b, mOpeningApps.size()=%d", + + "mOpeningApps.size()=%d", waitingForConfig, waitingForRemoteDisplayChange, mAppsFreezingScreen, mWindowsFreezingScreen, - mClientFreezingScreen, numOpeningApps); + numOpeningApps); return; } @@ -6589,7 +6521,6 @@ public class WindowManagerService extends IWindowManager.Stub } ProtoLog.i(WM_ERROR, "%s", sb.toString()); mH.removeMessages(H.APP_FREEZE_TIMEOUT); - mH.removeMessages(H.CLIENT_FREEZE_TIMEOUT); if (PROFILE_ORIENTATION) { Debug.stopMethodTracing(); } @@ -7096,7 +7027,6 @@ public class WindowManagerService extends IWindowManager.Stub pw.print(" mTransactionSequence="); pw.println(mTransactionSequence); pw.print(" mDisplayFrozen="); pw.print(mDisplayFrozen); pw.print(" windows="); pw.print(mWindowsFreezingScreen); - pw.print(" client="); pw.print(mClientFreezingScreen); pw.print(" apps="); pw.println(mAppsFreezingScreen); final DisplayContent defaultDisplayContent = getDefaultDisplayContentLocked(); pw.print(" mRotation="); pw.println(defaultDisplayContent.getRotation()); diff --git a/services/credentials/java/com/android/server/credentials/RequestSession.java b/services/credentials/java/com/android/server/credentials/RequestSession.java index c0bc8e094a39..2aa0c6b6dd0b 100644 --- a/services/credentials/java/com/android/server/credentials/RequestSession.java +++ b/services/credentials/java/com/android/server/credentials/RequestSession.java @@ -258,12 +258,33 @@ abstract class RequestSession<T, U, V> implements CredentialManagerUi.Credential if (propagateCancellation) { mProviders.values().forEach(ProviderSession::cancelProviderRemoteSession); } - mRequestSessionMetric.logApiCalledAtFinish(apiStatus); mRequestSessionStatus = RequestSessionStatus.COMPLETE; + if (Flags.fixMetricDuplicationEmits()) { + logTrackOneCandidatesAndPrepareFinalPhaseLogs(apiStatus); + } + mRequestSessionMetric.logApiCalledAtFinish(apiStatus); mProviders.clear(); clearRequestSessionLocked(); } + /** + * Ensures all logging done in final phase methods only occur within the 'finishSession'. + */ + private void logTrackOneCandidatesAndPrepareFinalPhaseLogs(int apiStatus) { + mRequestSessionMetric.logCandidateAggregateMetrics(mProviders); + if (isRespondingWithError(apiStatus)) { + mRequestSessionMetric.collectFinalPhaseProviderMetricStatus( + /*hasException=*/ true, ProviderStatusForMetrics.FINAL_FAILURE); + } else if (isRespondingWithUserCanceledError(apiStatus)) { + mRequestSessionMetric.collectFinalPhaseProviderMetricStatus( + /*hasException=*/false, ProviderStatusForMetrics.FINAL_FAILURE + ); + } else if (isRespondingWithSuccess(apiStatus)) { + mRequestSessionMetric.collectFinalPhaseProviderMetricStatus(/*hasException=*/ false, + ProviderStatusForMetrics.FINAL_SUCCESS); + } + } + void cancelExistingPendingIntent() { if (mPendingIntent != null) { try { @@ -343,9 +364,11 @@ abstract class RequestSession<T, U, V> implements CredentialManagerUi.Credential * @param response the response associated with the API call that just completed */ protected void respondToClientWithResponseAndFinish(V response) { - mRequestSessionMetric.logCandidateAggregateMetrics(mProviders); - mRequestSessionMetric.collectFinalPhaseProviderMetricStatus(/*has_exception=*/ false, - ProviderStatusForMetrics.FINAL_SUCCESS); + if (!Flags.fixMetricDuplicationEmits()) { + mRequestSessionMetric.logCandidateAggregateMetrics(mProviders); + mRequestSessionMetric.collectFinalPhaseProviderMetricStatus(/*hasException=*/ false, + ProviderStatusForMetrics.FINAL_SUCCESS); + } if (mRequestSessionStatus == RequestSessionStatus.COMPLETE) { Slog.w(TAG, "Request has already been completed. This is strange."); return; @@ -360,8 +383,10 @@ abstract class RequestSession<T, U, V> implements CredentialManagerUi.Credential finishSession(/*propagateCancellation=*/false, ApiStatus.SUCCESS.getMetricCode()); } catch (RemoteException e) { - mRequestSessionMetric.collectFinalPhaseProviderMetricStatus( - /*has_exception=*/ true, ProviderStatusForMetrics.FINAL_FAILURE); + if (!Flags.fixMetricDuplicationEmits()) { + mRequestSessionMetric.collectFinalPhaseProviderMetricStatus( + /*hasException=*/ true, ProviderStatusForMetrics.FINAL_FAILURE); + } Slog.e(TAG, "Issue while responding to client with a response : " + e); finishSession(/*propagateCancellation=*/false, ApiStatus.FAILURE.getMetricCode()); } @@ -374,9 +399,11 @@ abstract class RequestSession<T, U, V> implements CredentialManagerUi.Credential * @param errorMsg the error message given back in the flow */ protected void respondToClientWithErrorAndFinish(String errorType, String errorMsg) { - mRequestSessionMetric.logCandidateAggregateMetrics(mProviders); - mRequestSessionMetric.collectFinalPhaseProviderMetricStatus( - /*has_exception=*/ true, ProviderStatusForMetrics.FINAL_FAILURE); + if (!Flags.fixMetricDuplicationEmits()) { + mRequestSessionMetric.logCandidateAggregateMetrics(mProviders); + mRequestSessionMetric.collectFinalPhaseProviderMetricStatus( + /*hasException=*/ true, ProviderStatusForMetrics.FINAL_FAILURE); + } if (mRequestSessionStatus == RequestSessionStatus.COMPLETE) { Slog.w(TAG, "Request has already been completed. This is strange."); return; @@ -385,7 +412,6 @@ abstract class RequestSession<T, U, V> implements CredentialManagerUi.Credential finishSession(/*propagateCancellation=*/true, ApiStatus.CLIENT_CANCELED.getMetricCode()); return; } - try { invokeClientCallbackError(errorType, errorMsg); } catch (RemoteException e) { @@ -393,7 +419,9 @@ abstract class RequestSession<T, U, V> implements CredentialManagerUi.Credential } boolean isUserCanceled = errorType.contains(MetricUtilities.USER_CANCELED_SUBSTRING); if (isUserCanceled) { - mRequestSessionMetric.setHasExceptionFinalPhase(/* has_exception */ false); + if (!Flags.fixMetricDuplicationEmits()) { + mRequestSessionMetric.setHasExceptionFinalPhase(/* hasException */ false); + } finishSession(/*propagateCancellation=*/false, ApiStatus.USER_CANCELED.getMetricCode()); } else { @@ -421,4 +449,26 @@ abstract class RequestSession<T, U, V> implements CredentialManagerUi.Credential finishSession(isUiWaitingForData(), ApiStatus.CLIENT_CANCELED.getMetricCode()); } } + + /** + * This captures the final state of the apiStatus as presented in 'finishSession'. + */ + private boolean isRespondingWithError(int apiStatus) { + return apiStatus == ApiStatus.FAILURE.getMetricCode() + || apiStatus == ApiStatus.CLIENT_CANCELED.getMetricCode(); + } + + /** + * A unique failure case, where we do not set the exception bit to be true. + */ + private boolean isRespondingWithUserCanceledError(int apiStatus) { + return apiStatus == ApiStatus.USER_CANCELED.getMetricCode(); + } + + /** + * This captures the final state of the apiStatus as presented in 'finishSession'. + */ + private boolean isRespondingWithSuccess(int apiStatus) { + return apiStatus == ApiStatus.SUCCESS.getMetricCode(); + } } diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index fadab1f8832e..25e9f8a38f89 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -1527,6 +1527,8 @@ public final class SystemServer implements Dumpable { boolean disableCameraService = SystemProperties.getBoolean("config.disable_cameraservice", false); + boolean isDesktop = context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_PC); + boolean isWatch = RoSystemFeatures.hasFeatureWatch(context); boolean isArc = context.getPackageManager().hasSystemFeature( @@ -1656,7 +1658,7 @@ public final class SystemServer implements Dumpable { t.traceEnd(); } - if (!isTv) { + if (!isTv && !isDesktop) { t.traceBegin("StartVibratorManagerService"); mSystemServiceManager.startService(VibratorManagerService.Lifecycle.class); t.traceEnd(); diff --git a/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java index f5bed999d5a0..5393e20889c0 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java @@ -455,8 +455,9 @@ public class LocalDisplayAdapterTest { * Confirm that external display uses physical density. */ @Test - public void testDpiValues() throws Exception { + public void testDpiValues_baseDensityForExternalDisplaysDisabled() throws Exception { // needs default one always + doReturn(false).when(mFlags).isBaseDensityForExternalDisplaysEnabled(); setUpDisplay(new FakeDisplay(PORT_A)); setUpDisplay(new FakeDisplay(PORT_B)); updateAvailableDisplays(); @@ -472,6 +473,25 @@ public class LocalDisplayAdapterTest { 16000); } + @Test + public void testDpiValues_baseDensityForExternalDisplaysEnabled() throws Exception { + // needs default one always + doReturn(true).when(mFlags).isBaseDensityForExternalDisplaysEnabled(); + setUpDisplay(new FakeDisplay(PORT_A)); + setUpDisplay(new FakeDisplay(PORT_B)); + updateAvailableDisplays(); + mAdapter.registerLocked(); + + waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS); + + assertDisplayDpi( + mListener.addedDisplays.get(0).getDisplayDeviceInfoLocked(), PORT_A, 100, 100, + 100); + assertDisplayDpi( + mListener.addedDisplays.get(1).getDisplayDeviceInfoLocked(), PORT_B, 100, 100, + 100); + } + private static class DisplayModeWrapper { public SurfaceControl.DisplayMode mode; public float[] expectedAlternativeRefreshRates; diff --git a/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java index d9256247b835..6b138b986fe7 100644 --- a/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java +++ b/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java @@ -27,6 +27,7 @@ import static android.os.PowerManagerInternal.WAKEFULNESS_ASLEEP; import static android.os.PowerManagerInternal.WAKEFULNESS_AWAKE; import static android.os.PowerManagerInternal.WAKEFULNESS_DOZING; import static android.os.PowerManagerInternal.WAKEFULNESS_DREAMING; +import static android.service.dreams.Flags.FLAG_ALLOW_DREAM_WHEN_POSTURED; import static com.android.server.deviceidle.Flags.FLAG_DISABLE_WAKELOCKS_IN_LIGHT_IDLE; @@ -81,8 +82,8 @@ import android.os.BatterySaverPolicyConfig; import android.os.Binder; import android.os.Handler; import android.os.IBinder; -import android.os.IWakeLockCallback; import android.os.IScreenTimeoutPolicyListener; +import android.os.IWakeLockCallback; import android.os.Looper; import android.os.PowerManager; import android.os.PowerManagerInternal; @@ -120,8 +121,8 @@ import com.android.server.power.PowerManagerService.WakeLock; import com.android.server.power.batterysaver.BatterySaverController; import com.android.server.power.batterysaver.BatterySaverPolicy; import com.android.server.power.batterysaver.BatterySaverStateMachine; -import com.android.server.power.feature.flags.Flags; import com.android.server.power.feature.PowerManagerFlags; +import com.android.server.power.feature.flags.Flags; import com.android.server.testutils.OffsettableClock; import com.google.testing.junit.testparameterinjector.TestParameter; @@ -279,6 +280,8 @@ public class PowerManagerServiceTest { Settings.Global.STAY_ON_WHILE_PLUGGED_IN, 0); Settings.Secure.putInt(mContextSpy.getContentResolver(), Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP, 0); + Settings.Secure.putInt(mContextSpy.getContentResolver(), + Settings.Secure.SCREENSAVER_ACTIVATE_ON_POSTURED, 0); mClock = new OffsettableClock.Stopped(); mTestLooper = new TestLooper(mClock::now); @@ -1215,6 +1218,52 @@ public class PowerManagerServiceTest { assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DREAMING); } + @EnableFlags(FLAG_ALLOW_DREAM_WHEN_POSTURED) + @Test + public void testDreamActivateWhilePosturedEnabled_postured_afterTimeout_goesToDreaming() { + when(mBatteryManagerInternalMock.isPowered(anyInt())).thenReturn(true); + Settings.Secure.putInt(mContextSpy.getContentResolver(), + Settings.Secure.SCREENSAVER_ACTIVATE_ON_POSTURED, 1); + + doAnswer(inv -> { + when(mDreamManagerInternalMock.isDreaming()).thenReturn(true); + return null; + }).when(mDreamManagerInternalMock).startDream(anyBoolean(), anyString()); + + setMinimumScreenOffTimeoutConfig(5); + createService(); + startSystem(); + mService.getLocalServiceInstance().setDevicePostured(true); + + assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE); + + advanceTime(15000); + assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DREAMING); + } + + @EnableFlags(FLAG_ALLOW_DREAM_WHEN_POSTURED) + @Test + public void testDreamActivateWhilePosturedEnabled_notPostured_afterTimeout_goesToDozing() { + when(mBatteryManagerInternalMock.isPowered(anyInt())).thenReturn(true); + Settings.Secure.putInt(mContextSpy.getContentResolver(), + Settings.Secure.SCREENSAVER_ACTIVATE_ON_POSTURED, 1); + + doAnswer(inv -> { + when(mDreamManagerInternalMock.isDreaming()).thenReturn(true); + return null; + }).when(mDreamManagerInternalMock).startDream(anyBoolean(), anyString()); + + setMinimumScreenOffTimeoutConfig(5); + createService(); + startSystem(); + mService.getLocalServiceInstance().setDevicePostured(false); + + assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE); + + advanceTime(15000); + assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DOZING); + } + @EnableFlags(android.companion.virtualdevice.flags.Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER) @SuppressWarnings("GuardedBy") @Test diff --git a/services/tests/servicestests/src/com/android/server/BinaryTransparencyServiceTest.java b/services/tests/servicestests/src/com/android/server/BinaryTransparencyServiceTest.java index cc5be7ebba62..1522954c123f 100644 --- a/services/tests/servicestests/src/com/android/server/BinaryTransparencyServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/BinaryTransparencyServiceTest.java @@ -46,17 +46,14 @@ import android.os.RemoteException; import android.os.ResultReceiver; import android.os.SystemProperties; import android.provider.DeviceConfig; -import android.util.Log; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; -import com.android.internal.util.FrameworkStatsLog; import com.android.internal.os.IBinaryTransparencyService; -import com.android.server.pm.BackgroundInstallControlService; +import com.android.internal.util.FrameworkStatsLog; import com.android.server.pm.BackgroundInstallControlCallbackHelper; -import com.android.server.pm.pkg.AndroidPackage; -import com.android.server.pm.pkg.AndroidPackageSplit; +import com.android.server.pm.BackgroundInstallControlService; import com.android.server.pm.pkg.PackageStateInternal; import org.junit.After; @@ -82,7 +79,7 @@ public class BinaryTransparencyServiceTest { private Context mContext; private BinaryTransparencyService mBinaryTransparencyService; private BinaryTransparencyService.BinaryTransparencyServiceImpl mTestInterface; - private DeviceConfig.Properties mOriginalBiometricsFlags; + private String mOriginalBiometricsFlag; @Mock private BinaryTransparencyService.BiometricLogger mBiometricLogger; @@ -117,17 +114,15 @@ public class BinaryTransparencyServiceTest { mBinaryTransparencyService = new BinaryTransparencyService(mContext, mBiometricLogger); mTestInterface = mBinaryTransparencyService.new BinaryTransparencyServiceImpl(); - mOriginalBiometricsFlags = DeviceConfig.getProperties(DeviceConfig.NAMESPACE_BIOMETRICS); + mOriginalBiometricsFlag = DeviceConfig.getProperty(DeviceConfig.NAMESPACE_BIOMETRICS, + BinaryTransparencyService.KEY_ENABLE_BIOMETRIC_PROPERTY_VERIFICATION); } @After - public void tearDown() throws Exception { - try { - DeviceConfig.setProperties(mOriginalBiometricsFlags); - } catch (DeviceConfig.BadConfigException e) { - Log.e(TAG, "Failed to reset biometrics flags to the original values before test. " - + e); - } + public void tearDown() { + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_BIOMETRICS, + BinaryTransparencyService.KEY_ENABLE_BIOMETRIC_PROPERTY_VERIFICATION, + mOriginalBiometricsFlag, false /* makeDefault */); LocalServices.removeServiceForTest(PackageManagerInternal.class); } diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInputFilterTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInputFilterTest.java index 464fee2bfc11..fb31cfe762f2 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInputFilterTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInputFilterTest.java @@ -20,6 +20,7 @@ import static android.view.Display.DEFAULT_DISPLAY; import static android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS; import static android.view.WindowManagerPolicyConstants.FLAG_PASS_TO_USER; +import static com.android.hardware.input.Flags.FLAG_ENABLE_TALKBACK_AND_MAGNIFIER_KEY_GESTURES; import static com.android.server.accessibility.AccessibilityInputFilter.FLAG_FEATURE_AUTOCLICK; import static com.android.server.accessibility.AccessibilityInputFilter.FLAG_FEATURE_CONTROL_SCREEN_MAGNIFIER; import static com.android.server.accessibility.AccessibilityInputFilter.FLAG_FEATURE_FILTER_KEY_EVENTS; @@ -39,6 +40,10 @@ import android.content.Context; import android.hardware.display.DisplayManagerGlobal; import android.os.Looper; import android.os.SystemClock; +import android.platform.test.annotations.RequiresFlagsDisabled; +import android.platform.test.annotations.RequiresFlagsEnabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.provider.Settings; import android.util.SparseArray; import android.view.Display; @@ -55,12 +60,14 @@ import com.android.server.LocalServices; import com.android.server.accessibility.gestures.TouchExplorer; import com.android.server.accessibility.magnification.FullScreenMagnificationGestureHandler; import com.android.server.accessibility.magnification.MagnificationGestureHandler; +import com.android.server.accessibility.magnification.MagnificationKeyHandler; import com.android.server.accessibility.magnification.MagnificationProcessor; import com.android.server.accessibility.magnification.WindowMagnificationGestureHandler; import com.android.server.wm.WindowManagerInternal; import org.junit.After; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -88,8 +95,16 @@ public class AccessibilityInputFilterTest { | FLAG_FEATURE_INJECT_MOTION_EVENTS | FLAG_FEATURE_FILTER_KEY_EVENTS; + @Rule + public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + // The expected order of EventStreamTransformations. private final Class[] mExpectedEventHandlerTypes = + {MagnificationKeyHandler.class, KeyboardInterceptor.class, MotionEventInjector.class, + FullScreenMagnificationGestureHandler.class, TouchExplorer.class, + AutoclickController.class, AccessibilityInputFilter.class}; + + private final Class[] mExpectedEventHandlerTypesWithoutMagKeyboard = {KeyboardInterceptor.class, MotionEventInjector.class, FullScreenMagnificationGestureHandler.class, TouchExplorer.class, AutoclickController.class, AccessibilityInputFilter.class}; @@ -176,6 +191,7 @@ public class AccessibilityInputFilterTest { } @Test + @RequiresFlagsEnabled(FLAG_ENABLE_TALKBACK_AND_MAGNIFIER_KEY_GESTURES) public void testEventHandler_shouldIncreaseAndHaveCorrectOrderAfterOnDisplayAdded() { prepareLooper(); @@ -191,9 +207,9 @@ public class AccessibilityInputFilterTest { EventStreamTransformation next = mEventHandler.get(SECOND_DISPLAY); assertNotNull(next); - // Start from index 1 because KeyboardInterceptor only exists in EventHandler for - // DEFAULT_DISPLAY. - for (int i = 1; next != null; i++) { + // Start from index 2 because KeyboardInterceptor and MagnificationKeyHandler only exist in + // EventHandler for DEFAULT_DISPLAY. + for (int i = 2; next != null; i++) { assertEquals(next.getClass(), mExpectedEventHandlerTypes[i]); next = next.getNext(); } @@ -232,6 +248,7 @@ public class AccessibilityInputFilterTest { } @Test + @RequiresFlagsEnabled(FLAG_ENABLE_TALKBACK_AND_MAGNIFIER_KEY_GESTURES) public void testEventHandler_shouldHaveCorrectOrderForEventStreamTransformation() { prepareLooper(); @@ -248,10 +265,36 @@ public class AccessibilityInputFilterTest { } next = mEventHandler.get(SECOND_DISPLAY); + // Start from index 2 because KeyboardInterceptor and MagnificationKeyHandler only exist + // in EventHandler for DEFAULT_DISPLAY. + for (int i = 2; next != null; i++) { + assertEquals(next.getClass(), mExpectedEventHandlerTypes[i]); + next = next.getNext(); + } + } + + @Test + @RequiresFlagsDisabled(FLAG_ENABLE_TALKBACK_AND_MAGNIFIER_KEY_GESTURES) + public void testEventHandler_shouldHaveCorrectOrderForEventStreamTransformation_noMagKeys() { + prepareLooper(); + + setDisplayCount(2); + mA11yInputFilter.setUserAndEnabledFeatures(0, mFeatures); + assertEquals(2, mEventHandler.size()); + + // Check if mEventHandler for each display has correct order of the + // EventStreamTransformations. + EventStreamTransformation next = mEventHandler.get(DEFAULT_DISPLAY); + for (int i = 0; next != null; i++) { + assertEquals(next.getClass(), mExpectedEventHandlerTypesWithoutMagKeyboard[i]); + next = next.getNext(); + } + + next = mEventHandler.get(SECOND_DISPLAY); // Start from index 1 because KeyboardInterceptor only exists in EventHandler for // DEFAULT_DISPLAY. for (int i = 1; next != null; i++) { - assertEquals(next.getClass(), mExpectedEventHandlerTypes[i]); + assertEquals(next.getClass(), mExpectedEventHandlerTypesWithoutMagKeyboard[i]); next = next.getNext(); } } @@ -387,7 +430,6 @@ public class AccessibilityInputFilterTest { assertNotNull(handler); assertEquals(WindowMagnificationGestureHandler.class, handler.getClass()); assertEquals(nextEventStream.getClass(), handler.getNext().getClass()); - } @Test public void @@ -412,6 +454,32 @@ public class AccessibilityInputFilterTest { assertEquals(nextEventStream.getClass(), handler.getNext().getClass()); } + @Test + @RequiresFlagsEnabled(FLAG_ENABLE_TALKBACK_AND_MAGNIFIER_KEY_GESTURES) + public void testEnabledFeatures_windowMagnificationMode_expectedMagnificationKeyHandler() { + prepareLooper(); + doReturn(Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW).when( + mAms).getMagnificationMode(DEFAULT_DISPLAY); + + mA11yInputFilter.setUserAndEnabledFeatures(0, mFeatures); + + MagnificationKeyHandler handler = getMagnificationKeyHandlerFromEventHandler(); + assertNotNull(handler); + } + + @Test + @RequiresFlagsEnabled(FLAG_ENABLE_TALKBACK_AND_MAGNIFIER_KEY_GESTURES) + public void testEnabledFeatures_fullscreenMagnificationMode_expectedMagnificationKeyHandler() { + prepareLooper(); + doReturn(Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN).when( + mAms).getMagnificationMode(DEFAULT_DISPLAY); + + mA11yInputFilter.setUserAndEnabledFeatures(0, mFeatures); + + MagnificationKeyHandler handler = getMagnificationKeyHandlerFromEventHandler(); + assertNotNull(handler); + } + private static void prepareLooper() { if (Looper.myLooper() == null) { Looper.prepare(); @@ -458,4 +526,16 @@ public class AccessibilityInputFilterTest { } return null; } + + @Nullable + private MagnificationKeyHandler getMagnificationKeyHandlerFromEventHandler() { + EventStreamTransformation next = mEventHandler.get(DEFAULT_DISPLAY); + while (next != null) { + if (next instanceof MagnificationKeyHandler) { + return (MagnificationKeyHandler) next; + } + next = next.getNext(); + } + return null; + } } diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationKeyHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationKeyHandlerTest.java new file mode 100644 index 000000000000..d1ef33d8fb70 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationKeyHandlerTest.java @@ -0,0 +1,256 @@ +/* + * Copyright (C) 2025 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.accessibility.magnification; + +import static com.android.hardware.input.Flags.FLAG_ENABLE_TALKBACK_AND_MAGNIFIER_KEY_GESTURES; +import static com.android.server.accessibility.magnification.MagnificationController.PAN_DIRECTION_DOWN; +import static com.android.server.accessibility.magnification.MagnificationController.PAN_DIRECTION_LEFT; +import static com.android.server.accessibility.magnification.MagnificationController.PAN_DIRECTION_RIGHT; +import static com.android.server.accessibility.magnification.MagnificationController.PAN_DIRECTION_UP; +import static com.android.server.accessibility.magnification.MagnificationController.ZOOM_DIRECTION_IN; +import static com.android.server.accessibility.magnification.MagnificationController.ZOOM_DIRECTION_OUT; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import android.platform.test.annotations.RequiresFlagsEnabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; +import android.view.Display; +import android.view.KeyEvent; + +import androidx.test.runner.AndroidJUnit4; + +import com.android.server.accessibility.EventStreamTransformation; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Tests for {@link MagnificationKeyHandler}. + */ +@RunWith(AndroidJUnit4.class) +@RequiresFlagsEnabled(FLAG_ENABLE_TALKBACK_AND_MAGNIFIER_KEY_GESTURES) +public class MagnificationKeyHandlerTest { + + @Rule + public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + + private MagnificationKeyHandler mMkh; + + @Mock + MagnificationKeyHandler.Callback mCallback; + + @Mock + EventStreamTransformation mNextHandler; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mMkh = new MagnificationKeyHandler(mCallback); + mMkh.setNext(mNextHandler); + } + + @Test + public void onKeyEvent_unusedKeyPress_sendToNext() { + final KeyEvent event = new KeyEvent(0, 0, KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_L, 0, 0); + mMkh.onKeyEvent(event, 0); + + // No callbacks were called. + verify(mCallback, times(0)).onPanMagnificationStart(anyInt(), anyInt()); + verify(mCallback, times(0)).onPanMagnificationStop(anyInt(), anyInt()); + verify(mCallback, times(0)).onScaleMagnificationStart(anyInt(), anyInt()); + verify(mCallback, times(0)).onScaleMagnificationStop(anyInt(), anyInt()); + + // The event was passed on. + verify(mNextHandler, times(1)).onKeyEvent(event, 0); + } + + @Test + public void onKeyEvent_arrowKeyPressWithIncorrectModifiers_sendToNext() { + final KeyEvent event = + new KeyEvent(0, 0, KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_LEFT, + 0, KeyEvent.META_ALT_ON); + mMkh.onKeyEvent(event, 0); + + // No callbacks were called. + verify(mCallback, times(0)).onPanMagnificationStart(anyInt(), anyInt()); + verify(mCallback, times(0)).onPanMagnificationStop(anyInt(), anyInt()); + verify(mCallback, times(0)).onScaleMagnificationStart(anyInt(), anyInt()); + verify(mCallback, times(0)).onScaleMagnificationStop(anyInt(), anyInt()); + + // The event was passed on. + verify(mNextHandler, times(1)).onKeyEvent(event, 0); + } + + @Test + public void onKeyEvent_unusedKeyPressWithCorrectModifiers_sendToNext() { + final KeyEvent event = + new KeyEvent(0, 0, KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_J, 0, + KeyEvent.META_META_ON | KeyEvent.META_ALT_ON); + mMkh.onKeyEvent(event, 0); + + // No callbacks were called. + verify(mCallback, times(0)).onPanMagnificationStart(anyInt(), anyInt()); + verify(mCallback, times(0)).onPanMagnificationStop(anyInt(), anyInt()); + verify(mCallback, times(0)).onScaleMagnificationStart(anyInt(), anyInt()); + verify(mCallback, times(0)).onScaleMagnificationStop(anyInt(), anyInt()); + + // The event was passed on. + verify(mNextHandler, times(1)).onKeyEvent(event, 0); + } + + @Test + public void onKeyEvent_panStartAndEnd_left() { + testPanMagnification(KeyEvent.KEYCODE_DPAD_LEFT, PAN_DIRECTION_LEFT); + } + + @Test + public void onKeyEvent_panStartAndEnd_right() { + testPanMagnification(KeyEvent.KEYCODE_DPAD_RIGHT, PAN_DIRECTION_RIGHT); + } + + @Test + public void onKeyEvent_panStartAndEnd_up() { + testPanMagnification(KeyEvent.KEYCODE_DPAD_UP, PAN_DIRECTION_UP); + } + + @Test + public void onKeyEvent_panStartAndEnd_down() { + testPanMagnification(KeyEvent.KEYCODE_DPAD_DOWN, PAN_DIRECTION_DOWN); + } + + @Test + public void onKeyEvent_scaleStartAndEnd_zoomIn() { + testScaleMagnification(KeyEvent.KEYCODE_EQUALS, ZOOM_DIRECTION_IN); + } + + @Test + public void onKeyEvent_scaleStartAndEnd_zoomOut() { + testScaleMagnification(KeyEvent.KEYCODE_MINUS, ZOOM_DIRECTION_OUT); + } + + @Test + public void onKeyEvent_panStartAndStop_diagonal() { + final KeyEvent downLeftEvent = new KeyEvent(0, 0, KeyEvent.ACTION_DOWN, + KeyEvent.KEYCODE_DPAD_LEFT, 0, KeyEvent.META_META_ON | KeyEvent.META_ALT_ON); + mMkh.onKeyEvent(downLeftEvent, 0); + verify(mCallback, times(1)).onPanMagnificationStart(Display.DEFAULT_DISPLAY, + PAN_DIRECTION_LEFT); + verify(mCallback, times(0)).onPanMagnificationStop(anyInt(), anyInt()); + + // Also press the down arrow key. + final KeyEvent downDownEvent = new KeyEvent(0, 0, KeyEvent.ACTION_DOWN, + KeyEvent.KEYCODE_DPAD_DOWN, 0, KeyEvent.META_META_ON | KeyEvent.META_ALT_ON); + mMkh.onKeyEvent(downDownEvent, 0); + verify(mCallback, times(1)).onPanMagnificationStart(Display.DEFAULT_DISPLAY, + PAN_DIRECTION_LEFT); + verify(mCallback, times(1)).onPanMagnificationStart(Display.DEFAULT_DISPLAY, + PAN_DIRECTION_DOWN); + verify(mCallback, times(0)).onPanMagnificationStop(anyInt(), anyInt()); + + // Lift the left arrow key. + final KeyEvent upLeftEvent = new KeyEvent(0, 0, KeyEvent.ACTION_UP, + KeyEvent.KEYCODE_DPAD_LEFT, 0, KeyEvent.META_META_ON | KeyEvent.META_ALT_ON); + mMkh.onKeyEvent(upLeftEvent, 0); + verify(mCallback, times(1)).onPanMagnificationStart(Display.DEFAULT_DISPLAY, + PAN_DIRECTION_LEFT); + verify(mCallback, times(1)).onPanMagnificationStart(Display.DEFAULT_DISPLAY, + PAN_DIRECTION_DOWN); + verify(mCallback, times(1)).onPanMagnificationStop(Display.DEFAULT_DISPLAY, + PAN_DIRECTION_LEFT); + verify(mCallback, times(0)).onPanMagnificationStop(Display.DEFAULT_DISPLAY, + PAN_DIRECTION_DOWN); + + // Lift the down arrow key. + final KeyEvent upDownEvent = new KeyEvent(0, 0, KeyEvent.ACTION_UP, + KeyEvent.KEYCODE_DPAD_DOWN, 0, KeyEvent.META_META_ON | KeyEvent.META_ALT_ON); + mMkh.onKeyEvent(upDownEvent, 0); + verify(mCallback, times(1)).onPanMagnificationStart(Display.DEFAULT_DISPLAY, + PAN_DIRECTION_LEFT); + verify(mCallback, times(1)).onPanMagnificationStart(Display.DEFAULT_DISPLAY, + PAN_DIRECTION_DOWN); + verify(mCallback, times(1)).onPanMagnificationStop(Display.DEFAULT_DISPLAY, + PAN_DIRECTION_LEFT); + verify(mCallback, times(1)).onPanMagnificationStop(Display.DEFAULT_DISPLAY, + PAN_DIRECTION_DOWN); + + // The event was not passed on. + verify(mNextHandler, times(0)).onKeyEvent(any(), anyInt()); + } + + private void testPanMagnification(int keyCode, int panDirection) { + final KeyEvent downEvent = new KeyEvent(0, 0, KeyEvent.ACTION_DOWN, keyCode, 0, + KeyEvent.META_META_ON | KeyEvent.META_ALT_ON); + mMkh.onKeyEvent(downEvent, 0); + + // Pan started. + verify(mCallback, times(1)).onPanMagnificationStart(Display.DEFAULT_DISPLAY, panDirection); + verify(mCallback, times(0)).onPanMagnificationStop(anyInt(), anyInt()); + + final KeyEvent upEvent = new KeyEvent(0, 0, KeyEvent.ACTION_UP, keyCode, 0, + KeyEvent.META_META_ON | KeyEvent.META_ALT_ON); + mMkh.onKeyEvent(upEvent, 0); + + // Pan ended. + verify(mCallback, times(1)).onPanMagnificationStart(Display.DEFAULT_DISPLAY, panDirection); + verify(mCallback, times(1)).onPanMagnificationStop(Display.DEFAULT_DISPLAY, panDirection); + + // Scale callbacks were not called. + verify(mCallback, times(0)).onScaleMagnificationStart(anyInt(), anyInt()); + verify(mCallback, times(0)).onScaleMagnificationStop(anyInt(), anyInt()); + + // The events were not passed on. + verify(mNextHandler, times(0)).onKeyEvent(any(), anyInt()); + } + + private void testScaleMagnification(int keyCode, int zoomDirection) { + final KeyEvent downEvent = new KeyEvent(0, 0, KeyEvent.ACTION_DOWN, keyCode, 0, + KeyEvent.META_META_ON | KeyEvent.META_ALT_ON); + mMkh.onKeyEvent(downEvent, 0); + + // Scale started. + verify(mCallback, times(1)).onScaleMagnificationStart(Display.DEFAULT_DISPLAY, + zoomDirection); + verify(mCallback, times(0)).onScaleMagnificationStop(anyInt(), anyInt()); + + final KeyEvent upEvent = new KeyEvent(0, 0, KeyEvent.ACTION_UP, keyCode, 0, + KeyEvent.META_META_ON | KeyEvent.META_ALT_ON); + mMkh.onKeyEvent(upEvent, 0); + + // Scale ended. + verify(mCallback, times(1)).onScaleMagnificationStart(Display.DEFAULT_DISPLAY, + zoomDirection); + verify(mCallback, times(1)).onScaleMagnificationStop(Display.DEFAULT_DISPLAY, + zoomDirection); + + // Pan callbacks were not called. + verify(mCallback, times(0)).onPanMagnificationStart(anyInt(), anyInt()); + verify(mCallback, times(0)).onPanMagnificationStop(anyInt(), anyInt()); + + // The events were not passed on. + verify(mNextHandler, times(0)).onKeyEvent(any(), anyInt()); + + } + +} diff --git a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java index eb4a628e14e5..792faab5b196 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java @@ -25,6 +25,7 @@ import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_1; import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_2; import static com.android.server.hdmi.Constants.ADDR_TV; import static com.android.server.hdmi.DeviceSelectActionFromTv.STATE_WAIT_FOR_DEVICE_POWER_ON; +import static com.android.server.hdmi.DeviceSelectActionFromTv.STATE_WAIT_FOR_POWER_STATE_CHANGE; import static com.android.server.hdmi.DeviceSelectActionFromTv.STATE_WAIT_FOR_REPORT_POWER_STATUS; import static com.google.common.truth.Truth.assertThat; @@ -230,11 +231,15 @@ public class DeviceSelectActionFromTvTest { "testDeviceSelect"); action.start(); mTestLooper.dispatchAll(); + assertThat(mNativeWrapper.getResultMessages()).contains(SET_STREAM_PATH); mNativeWrapper.clearResultMessages(); + assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_POWER_STATE_CHANGE); + action.handleTimerEvent(STATE_WAIT_FOR_POWER_STATE_CHANGE); assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_REPORT_POWER_STATUS); action.processCommand(REPORT_POWER_STATUS_ON); mTestLooper.dispatchAll(); + assertThat(mNativeWrapper.getResultMessages()).contains(SET_STREAM_PATH); assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_SUCCESS); } @@ -249,10 +254,14 @@ public class DeviceSelectActionFromTvTest { /*isCec20=*/false); action.start(); mTestLooper.dispatchAll(); + assertThat(mNativeWrapper.getResultMessages()).contains(SET_STREAM_PATH); + assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_POWER_STATE_CHANGE); + action.handleTimerEvent(STATE_WAIT_FOR_POWER_STATE_CHANGE); assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_REPORT_POWER_STATUS); action.processCommand(REPORT_POWER_STATUS_STANDBY); mTestLooper.dispatchAll(); + HdmiCecMessage userControlPressed = HdmiCecMessageBuilder.buildUserControlPressed( ADDR_TV, ADDR_PLAYBACK_1, HdmiCecKeycode.CEC_KEYCODE_POWER); assertThat(mNativeWrapper.getResultMessages()).contains(userControlPressed); @@ -261,6 +270,7 @@ public class DeviceSelectActionFromTvTest { action.handleTimerEvent(STATE_WAIT_FOR_DEVICE_POWER_ON); action.processCommand(REPORT_POWER_STATUS_ON); mTestLooper.dispatchAll(); + assertThat(mNativeWrapper.getResultMessages()).contains(SET_STREAM_PATH); assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_SUCCESS); } @@ -275,8 +285,11 @@ public class DeviceSelectActionFromTvTest { /*isCec20=*/false); action.start(); mTestLooper.dispatchAll(); + assertThat(mNativeWrapper.getResultMessages()).contains(SET_STREAM_PATH); mNativeWrapper.clearResultMessages(); + assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_POWER_STATE_CHANGE); + action.handleTimerEvent(STATE_WAIT_FOR_POWER_STATE_CHANGE); assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_REPORT_POWER_STATUS); action.processCommand(REPORT_POWER_STATUS_STANDBY); assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_DEVICE_POWER_ON); @@ -288,6 +301,7 @@ public class DeviceSelectActionFromTvTest { assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_REPORT_POWER_STATUS); action.processCommand(REPORT_POWER_STATUS_ON); mTestLooper.dispatchAll(); + assertThat(mNativeWrapper.getResultMessages()).contains(SET_STREAM_PATH); assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_SUCCESS); } @@ -302,8 +316,11 @@ public class DeviceSelectActionFromTvTest { /*isCec20=*/false); action.start(); mTestLooper.dispatchAll(); + assertThat(mNativeWrapper.getResultMessages()).contains(SET_STREAM_PATH); mNativeWrapper.clearResultMessages(); + assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_POWER_STATE_CHANGE); + action.handleTimerEvent(STATE_WAIT_FOR_POWER_STATE_CHANGE); assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_REPORT_POWER_STATUS); action.processCommand(REPORT_POWER_STATUS_STANDBY); assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_DEVICE_POWER_ON); @@ -316,6 +333,7 @@ public class DeviceSelectActionFromTvTest { action.handleTimerEvent(STATE_WAIT_FOR_REPORT_POWER_STATUS); // Give up getting power status, and just send <Set Stream Path> mTestLooper.dispatchAll(); + assertThat(mNativeWrapper.getResultMessages()).contains(SET_STREAM_PATH); assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_SUCCESS); } @@ -332,7 +350,10 @@ public class DeviceSelectActionFromTvTest { "testDeviceSelect"); action.start(); mTestLooper.dispatchAll(); + assertThat(mNativeWrapper.getResultMessages()).contains(SET_STREAM_PATH); + assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_POWER_STATE_CHANGE); + action.handleTimerEvent(STATE_WAIT_FOR_POWER_STATE_CHANGE); assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_SUCCESS); } @@ -348,11 +369,15 @@ public class DeviceSelectActionFromTvTest { "testDeviceSelect"); action.start(); mTestLooper.dispatchAll(); + assertThat(mNativeWrapper.getResultMessages()).contains(SET_STREAM_PATH); mNativeWrapper.clearResultMessages(); + assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_POWER_STATE_CHANGE); + action.handleTimerEvent(STATE_WAIT_FOR_POWER_STATE_CHANGE); assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_REPORT_POWER_STATUS); action.processCommand(REPORT_POWER_STATUS_ON); mTestLooper.dispatchAll(); + assertThat(mNativeWrapper.getResultMessages()).doesNotContain(SET_STREAM_PATH); assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_SUCCESS); } @@ -369,10 +394,14 @@ public class DeviceSelectActionFromTvTest { /*isCec20=*/true); action.start(); mTestLooper.dispatchAll(); + assertThat(mNativeWrapper.getResultMessages()).contains(SET_STREAM_PATH); + assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_POWER_STATE_CHANGE); + action.handleTimerEvent(STATE_WAIT_FOR_POWER_STATE_CHANGE); assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_REPORT_POWER_STATUS); action.processCommand(REPORT_POWER_STATUS_STANDBY); mTestLooper.dispatchAll(); + HdmiCecMessage userControlPressed = HdmiCecMessageBuilder.buildUserControlPressed( ADDR_TV, ADDR_PLAYBACK_1, HdmiCecKeycode.CEC_KEYCODE_POWER); assertThat(mNativeWrapper.getResultMessages()).doesNotContain(userControlPressed); @@ -381,6 +410,7 @@ public class DeviceSelectActionFromTvTest { action.handleTimerEvent(STATE_WAIT_FOR_DEVICE_POWER_ON); action.processCommand(REPORT_POWER_STATUS_ON); mTestLooper.dispatchAll(); + assertThat(mNativeWrapper.getResultMessages()).doesNotContain(SET_STREAM_PATH); assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_SUCCESS); } diff --git a/services/tests/servicestests/src/com/android/server/security/authenticationpolicy/AuthenticationPolicyServiceTest.java b/services/tests/servicestests/src/com/android/server/security/authenticationpolicy/AuthenticationPolicyServiceTest.java index ee8eb9b35088..b76e0bc8cd14 100644 --- a/services/tests/servicestests/src/com/android/server/security/authenticationpolicy/AuthenticationPolicyServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/security/authenticationpolicy/AuthenticationPolicyServiceTest.java @@ -42,8 +42,10 @@ import android.hardware.biometrics.BiometricSourceType; import android.hardware.biometrics.events.AuthenticationFailedInfo; import android.hardware.biometrics.events.AuthenticationSucceededInfo; import android.os.RemoteException; +import android.platform.test.annotations.EnableFlags; import android.platform.test.annotations.Presubmit; import android.platform.test.flag.junit.SetFlagsRule; +import android.provider.Settings; import androidx.test.InstrumentationRegistry; import androidx.test.core.app.ApplicationProvider; @@ -151,6 +153,8 @@ public class AuthenticationPolicyServiceTest { when(mSecureLockDeviceService.disableSecureLockDevice(any())) .thenReturn(ERROR_UNSUPPORTED); } + + toggleAdaptiveAuthSettingsOverride(PRIMARY_USER_ID, false /* disable */); } @After @@ -252,8 +256,24 @@ public class AuthenticationPolicyServiceTest { } @Test - public void testReportAuthAttempt_biometricAuthFailed_multiple_deviceCurrentlyNotLocked() + @EnableFlags({android.security.Flags.FLAG_DISABLE_ADAPTIVE_AUTH_COUNTER_LOCK}) + public void testReportAuthAttempt_biometricAuthFailed_multiple_deviceCurrentlyNotLocked_deviceLockEnabled() + throws RemoteException { + testReportAuthAttempt_biometricAuthFailed_multiple_deviceCurrentlyNotLocked( + true /* enabled */); + } + + @Test + @EnableFlags({android.security.Flags.FLAG_DISABLE_ADAPTIVE_AUTH_COUNTER_LOCK}) + public void testReportAuthAttempt_biometricAuthFailed_multiple_deviceCurrentlyNotLocked_deviceLockDisabled() throws RemoteException { + toggleAdaptiveAuthSettingsOverride(PRIMARY_USER_ID, true /* disabled */); + testReportAuthAttempt_biometricAuthFailed_multiple_deviceCurrentlyNotLocked( + false /* enabled */); + } + + private void testReportAuthAttempt_biometricAuthFailed_multiple_deviceCurrentlyNotLocked( + boolean enabled) throws RemoteException { // Device is currently not locked and Keyguard is not showing when(mKeyguardManager.isDeviceLocked(PRIMARY_USER_ID)).thenReturn(false); when(mKeyguardManager.isKeyguardLocked()).thenReturn(false); @@ -264,7 +284,11 @@ public class AuthenticationPolicyServiceTest { } waitForAuthCompletion(); - verifyLockDevice(PRIMARY_USER_ID); + if (enabled) { + verifyLockDevice(PRIMARY_USER_ID); + } else { + verifyNotLockDevice(MAX_ALLOWED_FAILED_AUTH_ATTEMPTS, PRIMARY_USER_ID); + } } @Test @@ -300,8 +324,24 @@ public class AuthenticationPolicyServiceTest { } @Test - public void testReportAuthAttempt_primaryAuthAndBiometricAuthFailed_primaryUser() + @EnableFlags({android.security.Flags.FLAG_DISABLE_ADAPTIVE_AUTH_COUNTER_LOCK}) + public void testReportAuthAttempt_primaryAuthAndBiometricAuthFailed_primaryUser_deviceLockEnabled() throws RemoteException { + testReportAuthAttempt_primaryAuthAndBiometricAuthFailed_primaryUser( + true /* enabled */); + } + + @Test + @EnableFlags({android.security.Flags.FLAG_DISABLE_ADAPTIVE_AUTH_COUNTER_LOCK}) + public void testReportAuthAttempt_primaryAuthAndBiometricAuthFailed_primaryUser_deviceLockDisabled() + throws RemoteException { + toggleAdaptiveAuthSettingsOverride(PRIMARY_USER_ID, true /* disabled */); + testReportAuthAttempt_primaryAuthAndBiometricAuthFailed_primaryUser( + false /* enabled */); + } + + private void testReportAuthAttempt_primaryAuthAndBiometricAuthFailed_primaryUser( + boolean enabled) throws RemoteException { // Three failed primary auth attempts for (int i = 0; i < 3; i++) { mLockSettingsStateListenerCaptor.getValue().onAuthenticationFailed(PRIMARY_USER_ID); @@ -313,7 +353,11 @@ public class AuthenticationPolicyServiceTest { } waitForAuthCompletion(); - verifyLockDevice(PRIMARY_USER_ID); + if (enabled) { + verifyLockDevice(PRIMARY_USER_ID); + } else { + verifyNotLockDevice(MAX_ALLOWED_FAILED_AUTH_ATTEMPTS, PRIMARY_USER_ID); + } } @Test @@ -366,10 +410,13 @@ public class AuthenticationPolicyServiceTest { REASON_UNKNOWN, true, userId).build(); } - private AuthenticationFailedInfo authFailedInfo(int userId) { return new AuthenticationFailedInfo.Builder(BiometricSourceType.FINGERPRINT, REASON_UNKNOWN, userId).build(); } + private void toggleAdaptiveAuthSettingsOverride(int userId, boolean disable) { + Settings.Secure.putIntForUser(mContext.getContentResolver(), + Settings.Secure.DISABLE_ADAPTIVE_AUTH_LIMIT_LOCK, disable ? 1 : 0, userId); + } } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java index 1df8e3deb84b..d2f8d14c5007 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java @@ -19,13 +19,9 @@ import static android.os.UserHandle.USER_ALL; import static android.service.notification.Adjustment.KEY_IMPORTANCE; import static android.service.notification.Adjustment.TYPE_CONTENT_RECOMMENDATION; import static android.service.notification.Adjustment.TYPE_NEWS; -import static android.service.notification.Adjustment.TYPE_OTHER; import static android.service.notification.Adjustment.TYPE_PROMOTION; -import static android.service.notification.Adjustment.TYPE_SOCIAL_MEDIA; -import static android.service.notification.Flags.notificationClassification; import static com.android.server.notification.NotificationManagerService.DEFAULT_ALLOWED_ADJUSTMENTS; -import static com.android.server.notification.NotificationManagerService.DEFAULT_ALLOWED_ADJUSTMENT_KEY_TYPES; import static com.google.common.truth.Truth.assertThat; @@ -160,17 +156,6 @@ public class NotificationAssistantsTest extends UiServiceTestCase { mAssistants.readXml(parser, mNm::canUseManagedServices, false, USER_ALL); } - private void setDefaultAllowedAdjustmentKeyTypes(NotificationAssistants assistants) { - assistants.setAssistantAdjustmentKeyTypeState(TYPE_OTHER, false); - assistants.setAssistantAdjustmentKeyTypeState(TYPE_PROMOTION, false); - assistants.setAssistantAdjustmentKeyTypeState(TYPE_SOCIAL_MEDIA, false); - assistants.setAssistantAdjustmentKeyTypeState(TYPE_NEWS, false); - assistants.setAssistantAdjustmentKeyTypeState(TYPE_CONTENT_RECOMMENDATION, false); - - for (int type : DEFAULT_ALLOWED_ADJUSTMENT_KEY_TYPES) { - assistants.setAssistantAdjustmentKeyTypeState(type, true); - } - } @Before public void setUp() throws Exception { @@ -181,9 +166,6 @@ public class NotificationAssistantsTest extends UiServiceTestCase { com.android.internal.R.string.config_defaultAssistantAccessComponent, mCn.flattenToString()); mAssistants = spy(mNm.new NotificationAssistants(mContext, mLock, mUserProfiles, miPm)); - if (notificationClassification()) { - setDefaultAllowedAdjustmentKeyTypes(mAssistants); - } when(mNm.getBinderService()).thenReturn(mINm); mContext.ensureTestableResources(); @@ -727,7 +709,7 @@ public class NotificationAssistantsTest extends UiServiceTestCase { mAssistants.setAssistantAdjustmentKeyTypeState(TYPE_CONTENT_RECOMMENDATION, true); assertThat(mAssistants.getAllowedAdjustmentKeyTypes()).asList() - .containsExactly(TYPE_PROMOTION, TYPE_CONTENT_RECOMMENDATION); + .containsExactlyElementsIn(List.of(TYPE_PROMOTION, TYPE_CONTENT_RECOMMENDATION)); } @Test @@ -748,7 +730,7 @@ public class NotificationAssistantsTest extends UiServiceTestCase { writeXmlAndReload(USER_ALL); assertThat(mAssistants.getAllowedAdjustmentKeyTypes()).asList() - .containsExactly(TYPE_NEWS, TYPE_CONTENT_RECOMMENDATION); + .containsExactlyElementsIn(List.of(TYPE_NEWS, TYPE_CONTENT_RECOMMENDATION)); } @Test @@ -765,168 +747,98 @@ public class NotificationAssistantsTest extends UiServiceTestCase { @Test @EnableFlags({android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION, android.app.Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI}) - public void testGetPackagesWithKeyTypeAdjustmentSettings() throws Exception { + public void testGetTypeAdjustmentDeniedPackages() throws Exception { String pkg = "my.package"; String pkg2 = "my.package.2"; - setDefaultAllowedAdjustmentKeyTypes(mAssistants); - assertThat(mAssistants.isTypeAdjustmentAllowedForPackage(pkg, TYPE_PROMOTION)).isTrue(); - assertThat(mAssistants.getPackagesWithKeyTypeAdjustmentSettings()).isEmpty(); + assertThat(mAssistants.isTypeAdjustmentAllowedForPackage(pkg)).isTrue(); + assertThat(mAssistants.getTypeAdjustmentDeniedPackages()).isEmpty(); - mAssistants.setAssistantAdjustmentKeyTypeStateForPackage(pkg, TYPE_PROMOTION, true); - assertThat(mAssistants.getPackagesWithKeyTypeAdjustmentSettings()) + mAssistants.setTypeAdjustmentForPackageState(pkg, true); + assertThat(mAssistants.getTypeAdjustmentDeniedPackages()).isEmpty(); + mAssistants.setTypeAdjustmentForPackageState(pkg, false); + assertThat(mAssistants.getTypeAdjustmentDeniedPackages()).asList() .containsExactly(pkg); - mAssistants.setAssistantAdjustmentKeyTypeStateForPackage(pkg, TYPE_PROMOTION, false); - assertThat(mAssistants.getPackagesWithKeyTypeAdjustmentSettings()) + mAssistants.setTypeAdjustmentForPackageState(pkg2, true); + assertThat(mAssistants.getTypeAdjustmentDeniedPackages()).asList() .containsExactly(pkg); - mAssistants.setAssistantAdjustmentKeyTypeStateForPackage(pkg2, TYPE_NEWS, true); - mAssistants.setAssistantAdjustmentKeyTypeStateForPackage(pkg2, TYPE_PROMOTION, false); - assertThat(mAssistants.getPackagesWithKeyTypeAdjustmentSettings()) + mAssistants.setTypeAdjustmentForPackageState(pkg2, false); + assertThat(mAssistants.getTypeAdjustmentDeniedPackages()).asList() .containsExactly(pkg, pkg2); } @Test @EnableFlags(android.app.Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI) - public void testSetAssistantAdjustmentKeyTypeStateForPackage_usesGlobalDefault() { - String pkg = "my.package"; - setDefaultAllowedAdjustmentKeyTypes(mAssistants); - assertThat(mAssistants.isTypeAdjustmentAllowedForPackage(pkg, TYPE_PROMOTION)).isTrue(); - assertThat(mAssistants.isTypeAdjustmentAllowedForPackage(pkg, TYPE_NEWS)).isFalse(); - assertThat(mAssistants.getAllowedAdjustmentKeyTypesForPackage(pkg)).asList() - .containsExactlyElementsIn(DEFAULT_ALLOWED_ADJUSTMENT_KEY_TYPES); - } - - @Test - @EnableFlags(android.app.Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI) - public void testSetAssistantAdjustmentKeyTypeStateForPackage_allowsAndDenies() { - setDefaultAllowedAdjustmentKeyTypes(mAssistants); - // Given that a package is set to have a type adjustment allowed, - String pkg = "my.package"; - mAssistants.setAssistantAdjustmentKeyTypeStateForPackage(pkg, TYPE_NEWS, true); + public void testSetTypeAdjustmentForPackageState_allowsAndDenies() { + // Given that a package is allowed to have its type adjusted, + String allowedPackage = "allowed.package"; + assertThat(mAssistants.getTypeAdjustmentDeniedPackages()).isEmpty(); + mAssistants.setTypeAdjustmentForPackageState(allowedPackage, true); - // The newly set state is the combination of the global default and the newly set type. - assertThat(mAssistants.getAllowedAdjustmentKeyTypesForPackage(pkg)).asList() - .containsExactly(TYPE_NEWS, TYPE_PROMOTION); - assertThat(mAssistants.isTypeAdjustmentAllowedForPackage(pkg, TYPE_NEWS)).isTrue(); + assertThat(mAssistants.getTypeAdjustmentDeniedPackages()).isEmpty(); + assertTrue(mAssistants.isTypeAdjustmentAllowedForPackage(allowedPackage)); // Set type adjustment disallowed for this package - mAssistants.setAssistantAdjustmentKeyTypeStateForPackage(pkg, TYPE_NEWS, false); - mAssistants.setAssistantAdjustmentKeyTypeStateForPackage(pkg, TYPE_PROMOTION, false); + mAssistants.setTypeAdjustmentForPackageState(allowedPackage, false); // Then the package is marked as denied - assertThat(mAssistants.getAllowedAdjustmentKeyTypesForPackage(pkg)).isEmpty(); - assertThat(mAssistants.isTypeAdjustmentAllowedForPackage(pkg, TYPE_NEWS)).isFalse(); + assertThat(mAssistants.getTypeAdjustmentDeniedPackages()).asList() + .containsExactly(allowedPackage); + assertFalse(mAssistants.isTypeAdjustmentAllowedForPackage(allowedPackage)); // Set type adjustment allowed again - mAssistants.setAssistantAdjustmentKeyTypeStateForPackage(pkg, TYPE_NEWS, true); - mAssistants.setAssistantAdjustmentKeyTypeStateForPackage(pkg, TYPE_PROMOTION, true); + mAssistants.setTypeAdjustmentForPackageState(allowedPackage, true); // Then the package is marked as allowed again - assertThat(mAssistants.getAllowedAdjustmentKeyTypesForPackage(pkg)).asList() - .containsExactly(TYPE_NEWS, TYPE_PROMOTION); - assertThat(mAssistants.isTypeAdjustmentAllowedForPackage(pkg, TYPE_NEWS)).isTrue(); - assertThat(mAssistants.isTypeAdjustmentAllowedForPackage(pkg, TYPE_PROMOTION)).isTrue(); - - // Set type adjustment promotions false, - mAssistants.setAssistantAdjustmentKeyTypeStateForPackage(pkg, TYPE_PROMOTION, false); - assertThat(mAssistants.getAllowedAdjustmentKeyTypesForPackage(pkg)).asList() - .containsExactly(TYPE_NEWS); - assertThat(mAssistants.isTypeAdjustmentAllowedForPackage(pkg, TYPE_NEWS)).isTrue(); - assertThat(mAssistants.isTypeAdjustmentAllowedForPackage(pkg, TYPE_PROMOTION)).isFalse(); + assertThat(mAssistants.getTypeAdjustmentDeniedPackages()).isEmpty(); + assertTrue(mAssistants.isTypeAdjustmentAllowedForPackage(allowedPackage)); } @Test @EnableFlags(android.app.Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI) - public void testSetAssistantAdjustmentKeyTypeStateForPackage_allowsMultiplePkgs() { - setDefaultAllowedAdjustmentKeyTypes(mAssistants); - // Given packages allowed to have their type adjusted to TYPE_NEWS, - String allowedPkg1 = "allowed.Pkg1"; - String allowedPkg2 = "allowed.Pkg2"; - String allowedPkg3 = "allowed.Pkg3"; - // Set type adjustment allowed for these packages - mAssistants.setAssistantAdjustmentKeyTypeStateForPackage(allowedPkg1, TYPE_NEWS, true); - mAssistants.setAssistantAdjustmentKeyTypeStateForPackage(allowedPkg2, TYPE_NEWS, true); - mAssistants.setAssistantAdjustmentKeyTypeStateForPackage(allowedPkg3, TYPE_NEWS, true); - - // The newly set state is the combination of the global default and the newly set type. - assertThat(mAssistants.getAllowedAdjustmentKeyTypesForPackage(allowedPkg1)).asList() - .containsExactly(TYPE_NEWS, TYPE_PROMOTION); - assertThat(mAssistants.getAllowedAdjustmentKeyTypesForPackage(allowedPkg2)).asList() - .containsExactly(TYPE_NEWS, TYPE_PROMOTION); - assertThat(mAssistants.getAllowedAdjustmentKeyTypesForPackage(allowedPkg3)).asList() - .containsExactly(TYPE_NEWS, TYPE_PROMOTION); - assertThat(mAssistants.isTypeAdjustmentAllowedForPackage(allowedPkg1, TYPE_NEWS)).isTrue(); - assertThat(mAssistants.isTypeAdjustmentAllowedForPackage(allowedPkg2, TYPE_NEWS)).isTrue(); - assertThat(mAssistants.isTypeAdjustmentAllowedForPackage(allowedPkg3, TYPE_NEWS)).isTrue(); - - // And when we deny some of them, - mAssistants.setAssistantAdjustmentKeyTypeStateForPackage(allowedPkg2, TYPE_NEWS, false); - mAssistants.setAssistantAdjustmentKeyTypeStateForPackage(allowedPkg2, TYPE_PROMOTION, - false); - mAssistants.setAssistantAdjustmentKeyTypeStateForPackage(allowedPkg3, TYPE_PROMOTION, - false); - - // Then the rest of the original packages are still marked as allowed. - assertThat(mAssistants.getAllowedAdjustmentKeyTypesForPackage(allowedPkg1)).asList() - .containsExactly(TYPE_NEWS, TYPE_PROMOTION); - assertThat(mAssistants.getAllowedAdjustmentKeyTypesForPackage(allowedPkg2)).isEmpty(); - assertThat(mAssistants.getAllowedAdjustmentKeyTypesForPackage(allowedPkg3)).asList() - .containsExactly(TYPE_NEWS); - assertThat(mAssistants.isTypeAdjustmentAllowedForPackage(allowedPkg1, TYPE_NEWS)).isTrue(); - assertThat(mAssistants.isTypeAdjustmentAllowedForPackage(allowedPkg2, TYPE_NEWS)).isFalse(); - assertThat(mAssistants.isTypeAdjustmentAllowedForPackage(allowedPkg3, TYPE_NEWS)).isTrue(); + public void testSetAssistantAdjustmentKeyTypeStateForPackage_deniesMultiple() { + // Given packages not allowed to have their type adjusted, + String deniedPkg1 = "denied.Pkg1"; + String deniedPkg2 = "denied.Pkg2"; + String deniedPkg3 = "denied.Pkg3"; + // Set type adjustment disallowed for these packages + mAssistants.setTypeAdjustmentForPackageState(deniedPkg1, false); + mAssistants.setTypeAdjustmentForPackageState(deniedPkg2, false); + mAssistants.setTypeAdjustmentForPackageState(deniedPkg3, false); + + // Then the packages are marked as denied + assertThat(mAssistants.getTypeAdjustmentDeniedPackages()).asList() + .containsExactlyElementsIn(List.of(deniedPkg1, deniedPkg2, deniedPkg3)); + assertFalse(mAssistants.isTypeAdjustmentAllowedForPackage(deniedPkg1)); + assertFalse(mAssistants.isTypeAdjustmentAllowedForPackage(deniedPkg2)); + assertFalse(mAssistants.isTypeAdjustmentAllowedForPackage(deniedPkg3)); + + // And when we re-allow one of them, + mAssistants.setTypeAdjustmentForPackageState(deniedPkg2, true); + + // Then the rest of the original packages are still marked as denied. + assertThat(mAssistants.getTypeAdjustmentDeniedPackages()).asList() + .containsExactlyElementsIn(List.of(deniedPkg1, deniedPkg3)); + assertFalse(mAssistants.isTypeAdjustmentAllowedForPackage(deniedPkg1)); + assertTrue(mAssistants.isTypeAdjustmentAllowedForPackage(deniedPkg2)); + assertFalse(mAssistants.isTypeAdjustmentAllowedForPackage(deniedPkg3)); } @Test @EnableFlags(android.app.Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI) public void testSetAssistantAdjustmentKeyTypeStateForPackage_readWriteXml() throws Exception { - setDefaultAllowedAdjustmentKeyTypes(mAssistants); mAssistants.loadDefaultsFromConfig(true); String deniedPkg1 = "denied.Pkg1"; String allowedPkg2 = "allowed.Pkg2"; - String allowedPkg3 = "allowed.Pkg3"; + String deniedPkg3 = "denied.Pkg3"; // Set type adjustment disallowed or allowed for these packages - mAssistants.setAssistantAdjustmentKeyTypeStateForPackage(deniedPkg1, TYPE_PROMOTION, false); - mAssistants.setAssistantAdjustmentKeyTypeStateForPackage(allowedPkg2, TYPE_NEWS, true); - mAssistants.setAssistantAdjustmentKeyTypeStateForPackage(allowedPkg3, TYPE_NEWS, true); - mAssistants.setAssistantAdjustmentKeyTypeStateForPackage(allowedPkg3, TYPE_SOCIAL_MEDIA, - true); + mAssistants.setTypeAdjustmentForPackageState(deniedPkg1, false); + mAssistants.setTypeAdjustmentForPackageState(allowedPkg2, true); + mAssistants.setTypeAdjustmentForPackageState(deniedPkg3, false); writeXmlAndReload(USER_ALL); - assertThat(mAssistants.getAllowedAdjustmentKeyTypesForPackage(deniedPkg1)).isEmpty(); - assertThat(mAssistants.getAllowedAdjustmentKeyTypesForPackage(allowedPkg2)).asList() - .containsExactly(TYPE_NEWS, TYPE_PROMOTION); - assertThat(mAssistants.getAllowedAdjustmentKeyTypesForPackage(allowedPkg3)).asList() - .containsExactly(TYPE_NEWS, TYPE_SOCIAL_MEDIA, TYPE_PROMOTION); - } - - @Test - @EnableFlags(android.app.Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI) - public void testSetAssistantAdjustmentKeyTypeStateForPackage_noGlobalImpact() throws Exception { - setDefaultAllowedAdjustmentKeyTypes(mAssistants); - // When the global state is changed, - mAssistants.setAssistantAdjustmentKeyTypeState(TYPE_NEWS, true); - - // The package state reflects the global state. - String pkg = "my.package"; - assertThat(mAssistants.isTypeAdjustmentAllowedForPackage(pkg, TYPE_PROMOTION)).isTrue(); - assertThat(mAssistants.isTypeAdjustmentAllowedForPackage(pkg, TYPE_NEWS)).isTrue(); - assertThat(mAssistants.getAllowedAdjustmentKeyTypesForPackage(pkg)).asList() - .containsExactly(TYPE_NEWS, TYPE_PROMOTION); - - // Once the package specific state is modified, - mAssistants.setAssistantAdjustmentKeyTypeStateForPackage(pkg, TYPE_SOCIAL_MEDIA, true); - - // The package specific state combines the global state with those modifications - assertThat(mAssistants.isTypeAdjustmentAllowedForPackage(pkg, TYPE_SOCIAL_MEDIA)).isTrue(); - assertThat(mAssistants.getAllowedAdjustmentKeyTypesForPackage(pkg)).asList() - .containsExactly(TYPE_NEWS, TYPE_PROMOTION, TYPE_SOCIAL_MEDIA); - - // And further changes to the global state are ignored. - mAssistants.setAssistantAdjustmentKeyTypeState(TYPE_NEWS, false); - assertThat(mAssistants.isTypeAdjustmentAllowedForPackage(pkg, TYPE_NEWS)).isTrue(); - assertThat(mAssistants.getAllowedAdjustmentKeyTypesForPackage(pkg)).asList() - .containsExactly(TYPE_NEWS, TYPE_PROMOTION, TYPE_SOCIAL_MEDIA); + assertThat(mAssistants.getTypeAdjustmentDeniedPackages()).asList() + .containsExactlyElementsIn(List.of(deniedPkg1, deniedPkg3)); } @Test diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index fdb6a6802b7e..85c592084acf 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -369,9 +369,6 @@ import org.mockito.MockitoAnnotations; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; -import platform.test.runner.parameterized.ParameterizedAndroidJunit4; -import platform.test.runner.parameterized.Parameters; - import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.ByteArrayInputStream; @@ -387,6 +384,9 @@ import java.util.Map; import java.util.concurrent.CountDownLatch; import java.util.function.Consumer; +import platform.test.runner.parameterized.ParameterizedAndroidJunit4; +import platform.test.runner.parameterized.Parameters; + @SmallTest @RunWith(ParameterizedAndroidJunit4.class) @RunWithLooper @@ -7662,7 +7662,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { when(mAssistants.isSameUser(any(), anyInt())).thenReturn(true); when(mAssistants.isServiceTokenValidLocked(any())).thenReturn(true); when(mAssistants.isAdjustmentKeyTypeAllowed(anyInt())).thenReturn(true); - when(mAssistants.isTypeAdjustmentAllowedForPackage(anyString(), anyInt())).thenReturn(true); + when(mAssistants.isTypeAdjustmentAllowedForPackage(anyString())).thenReturn(true); // Set up notifications that will be adjusted final NotificationRecord r1 = spy(generateNotificationRecord( @@ -17512,7 +17512,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { NotificationManagerService.WorkerHandler.class); mService.setHandler(handler); when(mAssistants.isAdjustmentKeyTypeAllowed(anyInt())).thenReturn(true); - when(mAssistants.isTypeAdjustmentAllowedForPackage(anyString(), anyInt())).thenReturn(true); + when(mAssistants.isTypeAdjustmentAllowedForPackage(anyString())).thenReturn(true); Bundle signals = new Bundle(); signals.putInt(KEY_TYPE, TYPE_NEWS); @@ -17556,11 +17556,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { NotificationManagerService.WorkerHandler.class); mService.setHandler(handler); when(mAssistants.isAdjustmentKeyTypeAllowed(anyInt())).thenReturn(true); - when(mAssistants.isTypeAdjustmentAllowedForPackage(anyString(), eq(TYPE_NEWS))) - .thenReturn(true); - // Blocking adjustments for a different type does nothing - when(mAssistants.isTypeAdjustmentAllowedForPackage(anyString(), eq(TYPE_PROMOTION))) - .thenReturn(false); + when(mAssistants.isTypeAdjustmentAllowedForPackage(anyString())).thenReturn(true); Bundle signals = new Bundle(); signals.putInt(KEY_TYPE, TYPE_NEWS); @@ -17575,9 +17571,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { assertThat(r.getChannel().getId()).isEqualTo(NEWS_ID); - // When we block adjustments for this package/type - when(mAssistants.isTypeAdjustmentAllowedForPackage(anyString(), eq(TYPE_PROMOTION))) - .thenReturn(false); + // When we block adjustments for this package + when(mAssistants.isTypeAdjustmentAllowedForPackage(anyString())).thenReturn(false); signals.putInt(KEY_TYPE, TYPE_PROMOTION); mBinderService.applyAdjustmentFromAssistant(null, adjustment); @@ -17907,7 +17902,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { when(mAssistants.isSameUser(any(), anyInt())).thenReturn(true); when(mAssistants.isServiceTokenValidLocked(any())).thenReturn(true); when(mAssistants.isAdjustmentKeyTypeAllowed(anyInt())).thenReturn(true); - when(mAssistants.isTypeAdjustmentAllowedForPackage(anyString(), anyInt())).thenReturn(true); + when(mAssistants.isTypeAdjustmentAllowedForPackage(anyString())).thenReturn(true); // Post a single notification final boolean hasOriginalSummary = false; @@ -17947,7 +17942,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { when(mAssistants.isSameUser(any(), anyInt())).thenReturn(true); when(mAssistants.isServiceTokenValidLocked(any())).thenReturn(true); when(mAssistants.isAdjustmentKeyTypeAllowed(anyInt())).thenReturn(true); - when(mAssistants.isTypeAdjustmentAllowedForPackage(anyString(), anyInt())).thenReturn(true); + when(mAssistants.isTypeAdjustmentAllowedForPackage(anyString())).thenReturn(true); // Post grouped notifications final String originalGroupName = "originalGroup"; @@ -17996,7 +17991,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { when(mAssistants.isSameUser(any(), anyInt())).thenReturn(true); when(mAssistants.isServiceTokenValidLocked(any())).thenReturn(true); when(mAssistants.isAdjustmentKeyTypeAllowed(anyInt())).thenReturn(true); - when(mAssistants.isTypeAdjustmentAllowedForPackage(anyString(), anyInt())).thenReturn(true); + when(mAssistants.isTypeAdjustmentAllowedForPackage(anyString())).thenReturn(true); // Post grouped notifications final String originalGroupName = "originalGroup"; @@ -18047,7 +18042,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { when(mAssistants.isSameUser(any(), anyInt())).thenReturn(true); when(mAssistants.isServiceTokenValidLocked(any())).thenReturn(true); when(mAssistants.isAdjustmentKeyTypeAllowed(anyInt())).thenReturn(true); - when(mAssistants.isTypeAdjustmentAllowedForPackage(anyString(), anyInt())).thenReturn(true); + when(mAssistants.isTypeAdjustmentAllowedForPackage(anyString())).thenReturn(true); // Post a single notification final boolean hasOriginalSummary = false; diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java index 6ef078b6da8a..96fddf13cdd0 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java @@ -211,7 +211,9 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; +import java.io.PrintWriter; import java.io.Reader; +import java.io.StringWriter; import java.time.Instant; import java.time.ZoneOffset; import java.time.temporal.ChronoUnit; @@ -7406,6 +7408,43 @@ public class ZenModeHelperTest extends UiServiceTestCase { .isEqualTo(mZenModeHelper.getDefaultZenPolicy()); } + @Test + public void setAutomaticZenRuleState_logsOriginToZenLog() { + AutomaticZenRule azr = new AutomaticZenRule.Builder("Rule", Uri.parse("cond")) + .setPackage(mPkg) + .build(); + String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, mPkg, azr, + ORIGIN_APP, "adding", CUSTOM_PKG_UID); + ZenLog.clear(); + + // User enables manually from QS: + mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleId, + new Condition(azr.getConditionId(), "", STATE_TRUE), ORIGIN_USER_IN_SYSTEMUI, + 123456); + + assertThat(getZenLog()).contains( + "config: setAzrState: " + ruleId + " (ORIGIN_USER_IN_SYSTEMUI) from uid " + 1234); + } + + @Test + public void setAutomaticZenRuleStateFromConditionProvider_logsOriginToZenLog() { + AutomaticZenRule azr = new AutomaticZenRule.Builder("Rule", Uri.parse("cond/cond")) + .setOwner(new ComponentName(CUSTOM_PKG_NAME, "SomeConditionProvider")) + .setPackage(mPkg) + .build(); + String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, mPkg, azr, + ORIGIN_APP, "adding", CUSTOM_PKG_UID); + ZenLog.clear(); + + // App enables rule through CPS + mZenModeHelper.setAutomaticZenRuleStateFromConditionProvider(UserHandle.CURRENT, + Uri.parse("cond/cond"), new Condition(azr.getConditionId(), "", STATE_TRUE), + ORIGIN_APP, CUSTOM_PKG_UID); + + assertThat(getZenLog()).contains( + "config: setAzrStateFromCps: cond/cond (ORIGIN_APP) from uid " + CUSTOM_PKG_UID); + } + private static void addZenRule(ZenModeConfig config, String id, String ownerPkg, int zenMode, @Nullable ZenPolicy zenPolicy) { ZenRule rule = new ZenRule(); @@ -7530,6 +7569,12 @@ public class ZenModeHelperTest extends UiServiceTestCase { assertThat(dndProto.getNotificationList().getNumber()).isEqualTo(STATE_ALLOW); } + private static String getZenLog() { + StringWriter zenLogWriter = new StringWriter(); + ZenLog.dump(new PrintWriter(zenLogWriter), ""); + return zenLogWriter.toString(); + } + private static void withoutWtfCrash(Runnable test) { Log.TerribleFailureHandler oldHandler = Log.setWtfHandler((tag, what, system) -> { }); diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaPolicyBuilderTest.java b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaPolicyBuilderTest.java index 22e411ea1500..3f3b24a21d22 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaPolicyBuilderTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaPolicyBuilderTest.java @@ -202,7 +202,7 @@ public class DisplayAreaPolicyBuilderTest { } @Test - public void testBuilder_defaultPolicy_hasWindowedMagnificationFeature() { + public void testBuilder_defaultPolicy_hasMagnificationFeature() { final DisplayAreaPolicy.Provider defaultProvider = DisplayAreaPolicy.Provider.fromResources( resourcesWithProvider("")); final DisplayAreaPolicyBuilder.Result defaultPolicy = @@ -210,28 +210,16 @@ public class DisplayAreaPolicyBuilderTest { mRoot, mImeContainer); final List<Feature> features = defaultPolicy.getFeatures(); boolean hasWindowedMagnificationFeature = false; - for (Feature feature : features) { - hasWindowedMagnificationFeature |= feature.getId() == FEATURE_WINDOWED_MAGNIFICATION; - } - - assertThat(hasWindowedMagnificationFeature).isTrue(); - } - - @Test - public void testBuilder_defaultPolicy_hasFullscreenMagnificationFeature() { - final DisplayAreaPolicy.Provider defaultProvider = DisplayAreaPolicy.Provider.fromResources( - resourcesWithProvider("")); - final DisplayAreaPolicyBuilder.Result defaultPolicy = - (DisplayAreaPolicyBuilder.Result) defaultProvider.instantiate(mWms, mDisplayContent, - mRoot, mImeContainer); - final List<Feature> features = defaultPolicy.getFeatures(); boolean hasFullscreenMagnificationFeature = false; for (Feature feature : features) { + hasWindowedMagnificationFeature |= feature.getId() == FEATURE_WINDOWED_MAGNIFICATION; hasFullscreenMagnificationFeature |= feature.getId() == FEATURE_FULLSCREEN_MAGNIFICATION; } - assertThat(hasFullscreenMagnificationFeature).isTrue(); + assertThat(hasWindowedMagnificationFeature).isTrue(); + assertThat(hasFullscreenMagnificationFeature).isEqualTo( + DisplayAreaPolicy.USE_DISPLAY_AREA_FOR_FULLSCREEN_MAGNIFICATION); } @Test diff --git a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java index f509706d1692..523b7235140a 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java @@ -36,6 +36,7 @@ import static com.android.server.wm.DragDropController.MSG_UNHANDLED_DROP_LISTEN import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; @@ -337,12 +338,13 @@ public class DragDropControllerTests extends WindowTestsBase { // Verify the drop event is only sent for the global intercept window assertTrue(nonLocalWindowDragEvents.isEmpty()); - assertTrue(last(localWindowDragEvents).getAction() != ACTION_DROP); - assertTrue(last(globalInterceptWindowDragEvents).getAction() == ACTION_DROP); + assertNotEquals(ACTION_DROP, localWindowDragEvents.getLast().getAction()); + assertEquals(ACTION_DROP, + globalInterceptWindowDragEvents.getLast().getAction()); // Verify that item extras were not sent with the drop event - assertNull(last(localWindowDragEvents).getClipData()); - assertFalse(last(globalInterceptWindowDragEvents).getClipData() + assertNull(localWindowDragEvents.getLast().getClipData()); + assertFalse(globalInterceptWindowDragEvents.getLast().getClipData() .willParcelWithActivityInfo()); }); } @@ -384,7 +386,7 @@ public class DragDropControllerTests extends WindowTestsBase { } @Test - public void testDragEventCoordinates() { + public void testDragEventCoordinatesOverlappingWindows() { int dragStartX = mWindow.getBounds().centerX(); int dragStartY = mWindow.getBounds().centerY(); int startOffsetPx = 10; @@ -429,7 +431,7 @@ public class DragDropControllerTests extends WindowTestsBase { // Verify only window2 received the DROP event and coords are sent as-is. assertEquals(1, dragEvents.size()); assertEquals(2, dragEvents2.size()); - final DragEvent dropEvent = last(dragEvents2); + final DragEvent dropEvent = dragEvents2.getLast(); assertEquals(ACTION_DROP, dropEvent.getAction()); assertEquals(dropCoordsPx, dropEvent.getX(), 0.0 /* delta */); assertEquals(dropCoordsPx, dropEvent.getY(), 0.0 /* delta */); @@ -437,10 +439,10 @@ public class DragDropControllerTests extends WindowTestsBase { mTarget.reportDropResult(iwindow2, true); // Verify both windows received ACTION_DRAG_ENDED event. - assertEquals(ACTION_DRAG_ENDED, last(dragEvents).getAction()); - assertEquals(window2.getDisplayId(), last(dragEvents).getDisplayId()); - assertEquals(ACTION_DRAG_ENDED, last(dragEvents2).getAction()); - assertEquals(window2.getDisplayId(), last(dragEvents2).getDisplayId()); + assertEquals(ACTION_DRAG_ENDED, dragEvents.getLast().getAction()); + assertEquals(window2.getDisplayId(), dragEvents.getLast().getDisplayId()); + assertEquals(ACTION_DRAG_ENDED, dragEvents2.getLast().getAction()); + assertEquals(window2.getDisplayId(), dragEvents2.getLast().getDisplayId()); } finally { mTarget.continueDragStateClose(); } @@ -493,7 +495,7 @@ public class DragDropControllerTests extends WindowTestsBase { // Verify only window2 received the DROP event and coords are sent as-is assertEquals(1, dragEvents.size()); assertEquals(2, dragEvents2.size()); - final DragEvent dropEvent = last(dragEvents2); + final DragEvent dropEvent = dragEvents2.getLast(); assertEquals(ACTION_DROP, dropEvent.getAction()); assertEquals(dropCoordsPx, dropEvent.getX(), 0.0 /* delta */); assertEquals(dropCoordsPx, dropEvent.getY(), 0.0 /* delta */); @@ -501,10 +503,12 @@ public class DragDropControllerTests extends WindowTestsBase { mTarget.reportDropResult(iwindow2, true); // Verify both windows received ACTION_DRAG_ENDED event. - assertEquals(ACTION_DRAG_ENDED, last(dragEvents).getAction()); - assertEquals(testDisplay.getDisplayId(), last(dragEvents).getDisplayId()); - assertEquals(ACTION_DRAG_ENDED, last(dragEvents2).getAction()); - assertEquals(testDisplay.getDisplayId(), last(dragEvents2).getDisplayId()); + assertEquals(ACTION_DRAG_ENDED, dragEvents.getLast().getAction()); + assertEquals(testDisplay.getDisplayId(), + dragEvents.getLast().getDisplayId()); + assertEquals(ACTION_DRAG_ENDED, dragEvents2.getLast().getAction()); + assertEquals(testDisplay.getDisplayId(), + dragEvents2.getLast().getDisplayId()); } finally { mTarget.continueDragStateClose(); } @@ -561,8 +565,23 @@ public class DragDropControllerTests extends WindowTestsBase { }); } - private DragEvent last(ArrayList<DragEvent> list) { - return list.get(list.size() - 1); + @Test + @EnableFlags(Flags.FLAG_ENABLE_CONNECTED_DISPLAYS_DND) + public void testDragCancelledOnTopologyChange() { + // Necessary for now since DragState.sendDragStartedLocked() will recycle drag events + // immediately after dispatching, which is a problem when using mockito arguments captor + // because it returns and modifies the same drag event. + TestIWindow iwindow = (TestIWindow) mWindow.mClient; + final ArrayList<DragEvent> dragEvents = new ArrayList<>(); + iwindow.setDragEventJournal(dragEvents); + + startDrag(0, 0, View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_GLOBAL_URI_READ, + ClipData.newPlainText("label", "text"), (surface) -> { + // Simulate display topology change to trigger drag-and-drop cancellation. + mTarget.handleDisplayTopologyChange(null /* displayTopology */); + assertEquals(2, dragEvents.size()); + assertEquals(ACTION_DRAG_ENDED, dragEvents.getLast().getAction()); + }); } @Test diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java index 25b9f4b8035b..8a7e7434e604 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java @@ -70,8 +70,6 @@ import android.os.Bundle; import android.os.RemoteException; import android.os.SystemClock; import android.os.UserManager; -import android.platform.test.annotations.DisableFlags; -import android.platform.test.annotations.EnableFlags; import android.platform.test.annotations.Presubmit; import android.platform.test.flag.junit.SetFlagsRule; import android.util.ArraySet; @@ -82,11 +80,9 @@ import android.window.TaskSnapshot; import androidx.test.filters.MediumTest; -import com.android.launcher3.Flags; import com.android.server.wm.RecentTasks.Callbacks; import org.junit.Before; -import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -717,18 +713,7 @@ public class RecentTasksTest extends WindowTestsBase { } @Test - @DisableFlags(Flags.FLAG_ENABLE_USE_TOP_VISIBLE_ACTIVITY_FOR_EXCLUDE_FROM_RECENT_TASK) - public void testVisibleTasks_excludedFromRecents() { - testVisibleTasks_excludedFromRecents_internal(); - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_USE_TOP_VISIBLE_ACTIVITY_FOR_EXCLUDE_FROM_RECENT_TASK) public void testVisibleTasks_excludedFromRecents_withRefactorFlag() { - testVisibleTasks_excludedFromRecents_internal(); - } - - private void testVisibleTasks_excludedFromRecents_internal() { mRecentTasks.setParameters(-1 /* min */, 4 /* max */, -1 /* ms */); Task invisibleExcludedTask = createTaskBuilder(".ExcludedTask1") @@ -766,19 +751,7 @@ public class RecentTasksTest extends WindowTestsBase { } @Test - @Ignore("b/342627272") - @DisableFlags(Flags.FLAG_ENABLE_USE_TOP_VISIBLE_ACTIVITY_FOR_EXCLUDE_FROM_RECENT_TASK) - public void testVisibleTasks_excludedFromRecents_visibleTaskNotFirstTask() { - testVisibleTasks_excludedFromRecents_visibleTaskNotFirstTask_internal(); - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_USE_TOP_VISIBLE_ACTIVITY_FOR_EXCLUDE_FROM_RECENT_TASK) public void testVisibleTasks_excludedFromRecents_visibleTaskNotFirstTask_withRefactorFlag() { - testVisibleTasks_excludedFromRecents_visibleTaskNotFirstTask_internal(); - } - - private void testVisibleTasks_excludedFromRecents_visibleTaskNotFirstTask_internal() { mRecentTasks.setParameters(-1 /* min */, 4 /* max */, -1 /* ms */); Task invisibleExcludedTask = createTaskBuilder(".ExcludedTask1") @@ -816,18 +789,7 @@ public class RecentTasksTest extends WindowTestsBase { } @Test - @DisableFlags(Flags.FLAG_ENABLE_USE_TOP_VISIBLE_ACTIVITY_FOR_EXCLUDE_FROM_RECENT_TASK) - public void testVisibleTasks_excludedFromRecents_firstTaskNotVisible() { - testVisibleTasks_excludedFromRecents_firstTaskNotVisible_internal(); - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_USE_TOP_VISIBLE_ACTIVITY_FOR_EXCLUDE_FROM_RECENT_TASK) public void testVisibleTasks_excludedFromRecents_firstTaskNotVisible_withRefactorFlag() { - testVisibleTasks_excludedFromRecents_firstTaskNotVisible_internal(); - } - - private void testVisibleTasks_excludedFromRecents_firstTaskNotVisible_internal() { // Create some set of tasks, some of which are visible and some are not Task homeTask = createTaskBuilder("com.android.pkg1", ".HomeTask") .setParentTask(mTaskContainer.getRootHomeTask()) diff --git a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java index a95093d7e113..59335d3bb135 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java +++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java @@ -264,6 +264,7 @@ public class SystemServicesTestRule implements TestRule { spyOn(dmg); doNothing().when(dmg).registerDisplayListener( any(), any(Executor.class), anyLong(), anyString()); + doNothing().when(dmg).registerTopologyListener(any(Executor.class), any(), anyString()); } private void setUpLocalServices() { diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskStackChangedListenerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskStackChangedListenerTest.java index c876663dd749..8a068cc7837a 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskStackChangedListenerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskStackChangedListenerTest.java @@ -16,9 +16,6 @@ package com.android.server.wm; -import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY; -import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION; -import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC; import static android.os.Build.HW_TIMEOUT_MULTIPLIER; import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; @@ -41,10 +38,7 @@ import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; import android.graphics.Color; -import android.graphics.PixelFormat; -import android.hardware.display.DisplayManager; import android.hardware.display.VirtualDisplay; -import android.media.ImageReader; import android.os.Bundle; import android.os.RemoteException; import android.os.SystemClock; @@ -57,13 +51,14 @@ import android.widget.LinearLayout; import androidx.test.filters.MediumTest; import com.android.server.wm.utils.CommonUtils; +import com.android.server.wm.utils.VirtualDisplayTestRule; import org.junit.After; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import java.util.ArrayList; -import java.util.Arrays; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -76,9 +71,9 @@ import java.util.function.Predicate; @MediumTest public class TaskStackChangedListenerTest { + @Rule + public VirtualDisplayTestRule mVirtualDisplayTestRule = new VirtualDisplayTestRule(); private ITaskStackListener mTaskStackListener; - private VirtualDisplay mVirtualDisplay; - private ImageReader mImageReader; private final ArrayList<Activity> mStartedActivities = new ArrayList<>(); private static final int WAIT_TIMEOUT_MS = 5000 * HW_TIMEOUT_MULTIPLIER; @@ -94,10 +89,6 @@ public class TaskStackChangedListenerTest { if (mTaskStackListener != null) { ActivityTaskManager.getService().unregisterTaskStackListener(mTaskStackListener); } - if (mVirtualDisplay != null) { - mVirtualDisplay.release(); - mImageReader.close(); - } // Finish from bottom to top. final int size = mStartedActivities.size(); for (int i = 0; i < size; i++) { @@ -116,21 +107,9 @@ public class TaskStackChangedListenerTest { private VirtualDisplay createVirtualDisplay() { final int width = 800; final int height = 600; - final int density = 160; - final DisplayManager displayManager = getInstrumentation().getContext().getSystemService( - DisplayManager.class); - mImageReader = ImageReader.newInstance(width, height, PixelFormat.RGBA_8888, - 2 /* maxImages */); - final int flags = VIRTUAL_DISPLAY_FLAG_PRESENTATION | VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY - | VIRTUAL_DISPLAY_FLAG_PUBLIC; final String name = getClass().getSimpleName() + "_VirtualDisplay"; - mVirtualDisplay = displayManager.createVirtualDisplay(name, width, height, density, - mImageReader.getSurface(), flags); - mVirtualDisplay.setSurface(mImageReader.getSurface()); - assertNotNull("display must be registered", - Arrays.stream(displayManager.getDisplays()).filter( - d -> d.getName().equals(name)).findAny()); - return mVirtualDisplay; + return mVirtualDisplayTestRule.createDisplayManagerAttachedVirtualDisplay(name, width, + height); } @Test diff --git a/services/tests/wmtests/src/com/android/server/wm/utils/VirtualDisplayTestRule.java b/services/tests/wmtests/src/com/android/server/wm/utils/VirtualDisplayTestRule.java new file mode 100644 index 000000000000..e92e6846e161 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/utils/VirtualDisplayTestRule.java @@ -0,0 +1,92 @@ +/* + * Copyright 2025 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.utils; + +import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY; +import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION; +import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC; + +import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; + +import static org.junit.Assert.assertNotNull; + +import android.graphics.PixelFormat; +import android.hardware.display.DisplayManager; +import android.hardware.display.VirtualDisplay; +import android.media.ImageReader; + +import org.junit.rules.TestRule; +import org.junit.runner.Description; +import org.junit.runners.model.Statement; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** Provides wrapper test rule for creating and managing the cleanup for VirtualDisplay */ +public class VirtualDisplayTestRule implements TestRule { + private static final int DISPLAY_DENSITY = 160; + + private final List<VirtualDisplay> mVirtualDisplays = new ArrayList<>(); + private final List<ImageReader> mImageReaders = new ArrayList<>(); + private final DisplayManager mDisplayManager; + + public VirtualDisplayTestRule() { + mDisplayManager = getInstrumentation().getTargetContext().getSystemService( + DisplayManager.class); + } + + @Override + public Statement apply(Statement base, Description description) { + return new Statement() { + @Override + public void evaluate() throws Throwable { + try { + base.evaluate(); + } finally { + tearDown(); + } + } + }; + } + + private void tearDown() { + mVirtualDisplays.forEach(VirtualDisplay::release); + mImageReaders.forEach(ImageReader::close); + } + + /** + * The virtual display in WindowTestsBase#createMockSimulatedDisplay is only attached to WM + * DisplayWindowSettingsProvider. DisplayManager is not aware of mock simulated display and + * therefore couldn't be used for actual Display-related testing (e.g. display listeners). + * This method creates real VirtualDisplay through DisplayManager. + */ + public VirtualDisplay createDisplayManagerAttachedVirtualDisplay(String name, int width, + int height) { + final ImageReader imageReader = ImageReader.newInstance(width, height, + PixelFormat.RGBA_8888, 2 /* maxImages */); + mImageReaders.add(imageReader); + final int flags = VIRTUAL_DISPLAY_FLAG_PRESENTATION | VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY + | VIRTUAL_DISPLAY_FLAG_PUBLIC; + final VirtualDisplay virtualDisplay = mDisplayManager.createVirtualDisplay(name, width, + height, DISPLAY_DENSITY, imageReader.getSurface(), flags); + mVirtualDisplays.add(virtualDisplay); + assertNotNull("display must be registered", Arrays.stream( + mDisplayManager.getDisplays()).filter(d -> d.getName().equals(name)).findAny()); + return virtualDisplay; + } +} diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbACTerminal.java b/services/usb/java/com/android/server/usb/descriptors/UsbACTerminal.java index 819e73df955b..6dda7ea3eb59 100644 --- a/services/usb/java/com/android/server/usb/descriptors/UsbACTerminal.java +++ b/services/usb/java/com/android/server/usb/descriptors/UsbACTerminal.java @@ -48,6 +48,13 @@ public abstract class UsbACTerminal extends UsbACInterface { return mAssocTerminal; } + public boolean isInputTerminal() { + return mTerminalType == UsbTerminalTypes.TERMINAL_IN_MIC + || mTerminalType == UsbTerminalTypes.TERMINAL_BIDIR_HEADSET + || mTerminalType == UsbTerminalTypes.TERMINAL_BIDIR_UNDEFINED + || mTerminalType == UsbTerminalTypes.TERMINAL_EXTERN_LINE; + } + @Override public int parseRawDescriptors(ByteStream stream) { mTerminalID = stream.getByte(); diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java b/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java index ba178845a536..bfa4ecd71f5a 100644 --- a/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java +++ b/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java @@ -524,27 +524,21 @@ public final class UsbDescriptorParser { * @hide */ public boolean hasMic() { - boolean hasMic = false; - ArrayList<UsbDescriptor> acDescriptors = getACInterfaceDescriptors(UsbACInterface.ACI_INPUT_TERMINAL, UsbACInterface.AUDIO_AUDIOCONTROL); for (UsbDescriptor descriptor : acDescriptors) { if (descriptor instanceof UsbACTerminal) { UsbACTerminal inDescr = (UsbACTerminal) descriptor; - if (inDescr.getTerminalType() == UsbTerminalTypes.TERMINAL_IN_MIC - || inDescr.getTerminalType() == UsbTerminalTypes.TERMINAL_BIDIR_HEADSET - || inDescr.getTerminalType() == UsbTerminalTypes.TERMINAL_BIDIR_UNDEFINED - || inDescr.getTerminalType() == UsbTerminalTypes.TERMINAL_EXTERN_LINE) { - hasMic = true; - break; + if (inDescr.isInputTerminal()) { + return true; } } else { Log.w(TAG, "Undefined Audio Input terminal l: " + descriptor.getLength() + " t:0x" + Integer.toHexString(descriptor.getType())); } } - return hasMic; + return false; } /** @@ -913,18 +907,20 @@ public final class UsbDescriptorParser { float probability = 0.0f; - // Look for a microphone - boolean hasMic = hasMic(); - // Look for a "speaker" boolean hasSpeaker = hasSpeaker(); - if (hasMic && hasSpeaker) { - probability += 0.75f; - } - - if (hasMic && hasHIDInterface()) { - probability += 0.25f; + if (hasMic()) { + if (hasSpeaker) { + probability += 0.75f; + } + if (hasHIDInterface()) { + probability += 0.25f; + } + if (getMaximumInputChannelCount() > 1) { + // A headset is more likely to only support mono capture. + probability -= 0.25f; + } } return probability; @@ -935,9 +931,11 @@ public final class UsbDescriptorParser { * headset. The probability range is between 0.0f (definitely NOT a headset) and * 1.0f (definitely IS a headset). A probability of 0.75f seems sufficient * to count on the peripheral being a headset. + * To align with the output device type, only treat the device as input headset if it is + * an output headset. */ public boolean isInputHeadset() { - return getInputHeadsetProbability() >= IN_HEADSET_TRIGGER; + return getInputHeadsetProbability() >= IN_HEADSET_TRIGGER && isOutputHeadset(); } // TODO: Up/Downmix process descriptor is not yet parsed, which may affect the result here. @@ -952,6 +950,32 @@ public final class UsbDescriptorParser { return maxChannelCount; } + private int getMaximumInputChannelCount() { + int maxChannelCount = 0; + ArrayList<UsbDescriptor> acDescriptors = + getACInterfaceDescriptors(UsbACInterface.ACI_INPUT_TERMINAL, + UsbACInterface.AUDIO_AUDIOCONTROL); + for (UsbDescriptor descriptor : acDescriptors) { + if (!(descriptor instanceof UsbACTerminal)) { + continue; + } + UsbACTerminal inDescr = (UsbACTerminal) descriptor; + if (!inDescr.isInputTerminal()) { + continue; + } + // For an input terminal, it should at lease has 1 channel. + // Comparing the max channel count with 1 here in case the USB device doesn't report + // audio channel cluster. + maxChannelCount = Math.max(maxChannelCount, 1); + if (!(descriptor instanceof UsbAudioChannelCluster)) { + continue; + } + maxChannelCount = Math.max(maxChannelCount, + ((UsbAudioChannelCluster) descriptor).getChannelCount()); + } + return maxChannelCount; + } + /** * @hide */ diff --git a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt index 8c04f647fb2f..e0532633d40b 100644 --- a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt +++ b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt @@ -736,30 +736,6 @@ class KeyGestureControllerTests { intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) ), TestData( - "META + ALT + '-' -> Magnification Zoom Out", - intArrayOf( - KeyEvent.KEYCODE_META_LEFT, - KeyEvent.KEYCODE_ALT_LEFT, - KeyEvent.KEYCODE_MINUS - ), - KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFICATION_ZOOM_OUT, - intArrayOf(KeyEvent.KEYCODE_MINUS), - KeyEvent.META_META_ON or KeyEvent.META_ALT_ON, - intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) - ), - TestData( - "META + ALT + '=' -> Magnification Zoom In", - intArrayOf( - KeyEvent.KEYCODE_META_LEFT, - KeyEvent.KEYCODE_ALT_LEFT, - KeyEvent.KEYCODE_EQUALS - ), - KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFICATION_ZOOM_IN, - intArrayOf(KeyEvent.KEYCODE_EQUALS), - KeyEvent.META_META_ON or KeyEvent.META_ALT_ON, - intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) - ), - TestData( "META + ALT + M -> Toggle Magnification", intArrayOf( KeyEvent.KEYCODE_META_LEFT, @@ -784,54 +760,6 @@ class KeyGestureControllerTests { intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) ), TestData( - "META + ALT + 'Down' -> Magnification Pan Down", - intArrayOf( - KeyEvent.KEYCODE_META_LEFT, - KeyEvent.KEYCODE_ALT_LEFT, - KeyEvent.KEYCODE_DPAD_DOWN - ), - KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFICATION_PAN_DOWN, - intArrayOf(KeyEvent.KEYCODE_DPAD_DOWN), - KeyEvent.META_META_ON or KeyEvent.META_ALT_ON, - intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) - ), - TestData( - "META + ALT + 'Up' -> Magnification Pan Up", - intArrayOf( - KeyEvent.KEYCODE_META_LEFT, - KeyEvent.KEYCODE_ALT_LEFT, - KeyEvent.KEYCODE_DPAD_UP - ), - KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFICATION_PAN_UP, - intArrayOf(KeyEvent.KEYCODE_DPAD_UP), - KeyEvent.META_META_ON or KeyEvent.META_ALT_ON, - intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) - ), - TestData( - "META + ALT + 'Left' -> Magnification Pan Left", - intArrayOf( - KeyEvent.KEYCODE_META_LEFT, - KeyEvent.KEYCODE_ALT_LEFT, - KeyEvent.KEYCODE_DPAD_LEFT - ), - KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFICATION_PAN_LEFT, - intArrayOf(KeyEvent.KEYCODE_DPAD_LEFT), - KeyEvent.META_META_ON or KeyEvent.META_ALT_ON, - intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) - ), - TestData( - "META + ALT + 'Right' -> Magnification Pan Right", - intArrayOf( - KeyEvent.KEYCODE_META_LEFT, - KeyEvent.KEYCODE_ALT_LEFT, - KeyEvent.KEYCODE_DPAD_RIGHT - ), - KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFICATION_PAN_RIGHT, - intArrayOf(KeyEvent.KEYCODE_DPAD_RIGHT), - KeyEvent.META_META_ON or KeyEvent.META_ALT_ON, - intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) - ), - TestData( "META + ALT + 'V' -> Toggle Voice Access", intArrayOf( KeyEvent.KEYCODE_META_LEFT, diff --git a/tests/NetworkSecurityConfigTest/Android.bp b/tests/NetworkSecurityConfigTest/Android.bp index 4c48eaa4622e..6a68f2bb7ff9 100644 --- a/tests/NetworkSecurityConfigTest/Android.bp +++ b/tests/NetworkSecurityConfigTest/Android.bp @@ -11,11 +11,12 @@ android_test { name: "NetworkSecurityConfigTests", certificate: "platform", libs: [ - "android.test.runner.stubs.system", "android.test.base.stubs.system", + "android.test.runner.stubs.system", ], static_libs: ["junit"], // Include all test java files. srcs: ["src/**/*.java"], platform_apis: true, + test_suites: ["general-tests"], } diff --git a/tests/NetworkSecurityConfigTest/TEST_MAPPING b/tests/NetworkSecurityConfigTest/TEST_MAPPING new file mode 100644 index 000000000000..d1b9aa1e3b53 --- /dev/null +++ b/tests/NetworkSecurityConfigTest/TEST_MAPPING @@ -0,0 +1,7 @@ +{ + "postsubmit": [ + { + "name": "NetworkSecurityConfigTests" + } + ] +} diff --git a/tools/aapt2/dump/DumpManifest.cpp b/tools/aapt2/dump/DumpManifest.cpp index a5962292b5b0..82ad9fa05145 100644 --- a/tools/aapt2/dump/DumpManifest.cpp +++ b/tools/aapt2/dump/DumpManifest.cpp @@ -2778,7 +2778,7 @@ bool ManifestExtractor::Extract(android::IDiagnostics* diag) { auto file_path = it->Next()->GetSource().path.c_str(); const char* last_slash = - android::util::ValidLibraryPathLastSlash(file_path, has_renderscript_bitcode, false); + android::util::ValidLibraryPathLastSlash(file_path, has_renderscript_bitcode); if (last_slash) { architectures_from_apk.insert(std::string(file_path + APK_LIB_LEN, last_slash)); } |