Merge "Allow starting activity from visible home if app switch stopped" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index f249884..302168d8 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -102,6 +102,7 @@
"com.android.media.flags.projection-aconfig-java",
"com.android.net.http.flags-aconfig-exported-java",
"com.android.net.thread.platform.flags-aconfig-java",
+ "com.android.permission.flags-aconfig-java-export",
"com.android.ranging.flags.ranging-aconfig-java-export",
"com.android.server.contextualsearch.flags-java",
"com.android.server.flags.services-aconfig-java",
@@ -115,6 +116,7 @@
"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 +165,14 @@
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 +1419,7 @@
// 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"],
@@ -1845,6 +1856,41 @@
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
+// SettingsTheme Lib
+aconfig_declarations {
+ name: "aconfig_settings_theme_flags",
+ package: "com.android.settingslib.widget.theme.flags",
+ container: "system",
+ exportable: true,
+ srcs: [
+ "packages/SettingsLib/SettingsTheme/aconfig/settingstheme.aconfig",
+ ],
+}
+
+java_aconfig_library {
+ name: "aconfig_settingstheme_exported_flags_java_lib",
+ aconfig_declarations: "aconfig_settings_theme_flags",
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+ mode: "exported",
+ min_sdk_version: "21",
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.adservices",
+ "com.android.cellbroadcast",
+ "com.android.devicelock",
+ "com.android.extservices",
+ "com.android.healthfitness",
+ "com.android.mediaprovider",
+ "com.android.permission",
+ ],
+}
+
+java_aconfig_library {
+ name: "aconfig_settingstheme_flags_java_lib",
+ aconfig_declarations: "aconfig_settings_theme_flags",
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
// Quick Access Wallet
aconfig_declarations {
name: "android.service.quickaccesswallet.flags-aconfig",
diff --git a/OWNERS b/OWNERS
index 058ea36..aa93a27 100644
--- a/OWNERS
+++ b/OWNERS
@@ -16,6 +16,7 @@
roosa@google.com #{LAST_RESORT_SUGGESTION}
smoreland@google.com #{LAST_RESORT_SUGGESTION}
yamasani@google.com #{LAST_RESORT_SUGGESTION}
+timmurray@google.com #{LAST_RESORT_SUGGESTION}
# API changes are already covered by API-Review+1 (http://mdb/android-api-council)
# via https://android.git.corp.google.com/All-Projects/+/refs/meta/config/rules.pl.
diff --git a/api/StubLibraries.bp b/api/StubLibraries.bp
index a949ff5..787fdee 100644
--- a/api/StubLibraries.bp
+++ b/api/StubLibraries.bp
@@ -1023,7 +1023,6 @@
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 54c2d39..76ec214 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 6138388..5734c84 100644
--- a/cmds/uinput/README.md
+++ b/cmds/uinput/README.md
@@ -59,7 +59,7 @@
| `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 @@
`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 @@
}
```
+[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/current.txt b/core/api/current.txt
index 19f68eb..26c64bd 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -10249,23 +10249,23 @@
public final class VirtualDevice implements android.os.Parcelable {
method public int describeContents();
method public int getDeviceId();
- method @FlaggedApi("android.companion.virtual.flags.vdm_public_apis") @NonNull public int[] getDisplayIds();
- method @FlaggedApi("android.companion.virtual.flags.vdm_public_apis") @Nullable public CharSequence getDisplayName();
+ method @NonNull public int[] getDisplayIds();
+ method @Nullable public CharSequence getDisplayName();
method @Nullable public String getName();
- method @FlaggedApi("android.companion.virtual.flags.vdm_public_apis") @Nullable public String getPersistentDeviceId();
- method @FlaggedApi("android.companion.virtual.flags.vdm_public_apis") public boolean hasCustomSensorSupport();
+ method @Nullable public String getPersistentDeviceId();
+ method public boolean hasCustomSensorSupport();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.companion.virtual.VirtualDevice> CREATOR;
}
public final class VirtualDeviceManager {
- method @FlaggedApi("android.companion.virtual.flags.vdm_public_apis") @Nullable public android.companion.virtual.VirtualDevice getVirtualDevice(int);
+ method @Nullable public android.companion.virtual.VirtualDevice getVirtualDevice(int);
method @NonNull public java.util.List<android.companion.virtual.VirtualDevice> getVirtualDevices();
- method @FlaggedApi("android.companion.virtual.flags.vdm_public_apis") public void registerVirtualDeviceListener(@NonNull java.util.concurrent.Executor, @NonNull android.companion.virtual.VirtualDeviceManager.VirtualDeviceListener);
- method @FlaggedApi("android.companion.virtual.flags.vdm_public_apis") public void unregisterVirtualDeviceListener(@NonNull android.companion.virtual.VirtualDeviceManager.VirtualDeviceListener);
+ method public void registerVirtualDeviceListener(@NonNull java.util.concurrent.Executor, @NonNull android.companion.virtual.VirtualDeviceManager.VirtualDeviceListener);
+ method public void unregisterVirtualDeviceListener(@NonNull android.companion.virtual.VirtualDeviceManager.VirtualDeviceListener);
}
- @FlaggedApi("android.companion.virtual.flags.vdm_public_apis") public static interface VirtualDeviceManager.VirtualDeviceListener {
+ public static interface VirtualDeviceManager.VirtualDeviceListener {
method public default void onVirtualDeviceClosed(int);
method public default void onVirtualDeviceCreated(int);
}
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index ae5542b..15ae79e 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -1290,9 +1290,7 @@
public class WallpaperManager {
method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public void clearWallpaper(int, int);
- method @FlaggedApi("android.app.customization_packs_apis") public static int getOrientation(@NonNull android.graphics.Point);
method @FloatRange(from=0.0f, to=1.0f) @RequiresPermission(android.Manifest.permission.SET_WALLPAPER_DIM_AMOUNT) public float getWallpaperDimAmount();
- method @FlaggedApi("android.app.customization_packs_apis") @Nullable public android.os.ParcelFileDescriptor getWallpaperFile(int, boolean);
method @FlaggedApi("android.app.live_wallpaper_content_handling") @Nullable @RequiresPermission(android.Manifest.permission.READ_WALLPAPER_INTERNAL) public android.app.wallpaper.WallpaperInstance getWallpaperInstance(int);
method public void setDisplayOffset(android.os.IBinder, int, int);
method @FlaggedApi("com.android.window.flags.multi_crop") @RequiresPermission(android.Manifest.permission.SET_WALLPAPER) public int setStreamWithCrops(@NonNull java.io.InputStream, @NonNull android.util.SparseArray<android.graphics.Rect>, boolean, int) throws java.io.IOException;
@@ -1301,10 +1299,6 @@
method @FlaggedApi("android.app.live_wallpaper_content_handling") @RequiresPermission(allOf={android.Manifest.permission.SET_WALLPAPER_COMPONENT, android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}, conditional=true) public boolean setWallpaperComponentWithDescription(@NonNull android.app.wallpaper.WallpaperDescription, int);
method @RequiresPermission(android.Manifest.permission.SET_WALLPAPER_COMPONENT) public boolean setWallpaperComponentWithFlags(@NonNull android.content.ComponentName, int);
method @RequiresPermission(android.Manifest.permission.SET_WALLPAPER_DIM_AMOUNT) public void setWallpaperDimAmount(@FloatRange(from=0.0f, to=1.0f) float);
- field @FlaggedApi("android.app.customization_packs_apis") public static final int ORIENTATION_LANDSCAPE = 1; // 0x1
- field @FlaggedApi("android.app.customization_packs_apis") public static final int ORIENTATION_PORTRAIT = 0; // 0x0
- field @FlaggedApi("android.app.customization_packs_apis") public static final int ORIENTATION_SQUARE_LANDSCAPE = 3; // 0x3
- field @FlaggedApi("android.app.customization_packs_apis") public static final int ORIENTATION_SQUARE_PORTRAIT = 2; // 0x2
}
}
@@ -3171,6 +3165,11 @@
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 {
@@ -3399,8 +3398,8 @@
}
public final class VirtualDevice implements android.os.Parcelable {
- method @FlaggedApi("android.companion.virtual.flags.vdm_public_apis") public boolean hasCustomAudioInputSupport();
- method @FlaggedApi("android.companion.virtual.flags.vdm_public_apis") public boolean hasCustomCameraSupport();
+ method public boolean hasCustomAudioInputSupport();
+ method public boolean hasCustomCameraSupport();
}
public final class VirtualDeviceManager {
@@ -3452,7 +3451,7 @@
method @NonNull public android.hardware.input.VirtualTouchscreen createVirtualTouchscreen(@NonNull android.hardware.input.VirtualTouchscreenConfig);
method @Deprecated @NonNull public android.hardware.input.VirtualTouchscreen createVirtualTouchscreen(@NonNull android.hardware.display.VirtualDisplay, @NonNull String, int, int);
method public int getDeviceId();
- method @FlaggedApi("android.companion.virtual.flags.vdm_public_apis") @Nullable public String getPersistentDeviceId();
+ method @Nullable public String getPersistentDeviceId();
method @NonNull public java.util.List<android.companion.virtual.sensor.VirtualSensor> getVirtualSensorList();
method @FlaggedApi("android.companion.virtualdevice.flags.device_aware_display_power") public void goToSleep();
method public void launchPendingIntent(int, @NonNull android.app.PendingIntent, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.IntConsumer);
@@ -3463,7 +3462,7 @@
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();
@@ -3482,7 +3481,7 @@
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();
@@ -3521,7 +3520,7 @@
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);
@@ -5325,7 +5324,7 @@
method @RequiresPermission(android.Manifest.permission.CONFIGURE_DISPLAY_BRIGHTNESS) public void setBrightnessConfiguration(android.hardware.display.BrightnessConfiguration);
method @RequiresPermission(android.Manifest.permission.CONFIGURE_DISPLAY_BRIGHTNESS) public void setBrightnessConfigurationForDisplay(@NonNull android.hardware.display.BrightnessConfiguration, @NonNull String);
method @Deprecated @RequiresPermission(android.Manifest.permission.CONTROL_DISPLAY_SATURATION) public void setSaturationLevel(float);
- field @FlaggedApi("android.companion.virtual.flags.vdm_public_apis") public static final int VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT = 128; // 0x80
+ field public static final int VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT = 128; // 0x80
field public static final int VIRTUAL_DISPLAY_FLAG_STEAL_TOP_FOCUS_DISABLED = 65536; // 0x10000
field public static final int VIRTUAL_DISPLAY_FLAG_TRUSTED = 1024; // 0x400
}
@@ -19044,7 +19043,7 @@
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 b118c7b..0126db7 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -508,7 +508,6 @@
method @Nullable public android.graphics.Bitmap getBitmap();
method @Nullable public android.graphics.Bitmap getBitmapAsUser(int, boolean, int);
method @FlaggedApi("com.android.window.flags.multi_crop") @NonNull @RequiresPermission(android.Manifest.permission.READ_WALLPAPER_INTERNAL) public java.util.List<android.graphics.Rect> getBitmapCrops(@NonNull java.util.List<android.graphics.Point>, int, boolean);
- method @FlaggedApi("android.app.customization_packs_apis") @NonNull @RequiresPermission(android.Manifest.permission.READ_WALLPAPER_INTERNAL) public android.util.SparseArray<android.graphics.Rect> getBitmapCrops(int);
method @FlaggedApi("com.android.window.flags.multi_crop") @NonNull public java.util.List<android.graphics.Rect> getBitmapCrops(@NonNull android.graphics.Point, @NonNull java.util.List<android.graphics.Point>, @Nullable java.util.Map<android.graphics.Point,android.graphics.Rect>);
method public boolean isLockscreenLiveWallpaperEnabled();
method @Nullable public android.graphics.Rect peekBitmapDimensions();
@@ -882,15 +881,6 @@
}
-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/Activity.java b/core/java/android/app/Activity.java
index 4782205..220fbb5 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -26,8 +26,6 @@
import static android.app.WindowConfiguration.inMultiWindowMode;
import static android.os.Process.myUid;
-import static com.android.sdksandbox.flags.Flags.sandboxActivitySdkBasedContext;
-
import static java.lang.Character.MIN_VALUE;
import android.Manifest;
@@ -8999,12 +8997,11 @@
Configuration config, String referrer, IVoiceInteractor voiceInteractor,
Window window, ActivityConfigCallback activityConfigCallback, IBinder assistToken,
IBinder shareableActivityToken, IBinder initialCallerInfoAccessToken) {
- if (sandboxActivitySdkBasedContext()) {
- // Sandbox activities extract a token from the intent's extra to identify the related
- // SDK as part of overriding attachBaseContext, then it wraps the passed context in an
- // SDK ContextWrapper, so mIntent has to be set before calling attachBaseContext.
- mIntent = intent;
- }
+
+ // mIntent field hast to be set before calling attachBaseContext, as SDK Runtime activities
+ // extract a token from the intent's extra to identify the related SDK as part of overriding
+ // attachBaseContext.
+ mIntent = intent;
attachBaseContext(context);
mFragments.attachHost(null /*parent*/);
@@ -9030,8 +9027,6 @@
mShareableActivityToken = shareableActivityToken;
mIdent = ident;
mApplication = application;
- //TODO(b/300059435): do not set the mIntent again as part of the flag clean up.
- mIntent = intent;
mReferrer = referrer;
mComponent = intent.getComponent();
mTitle = title;
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 0afef8e..2cfba4b 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -41,7 +41,6 @@
import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
import static com.android.internal.os.SafeZipPathValidatorCallback.VALIDATE_ZIP_PATH_FOR_PATH_TRAVERSAL;
-import static com.android.sdksandbox.flags.Flags.sandboxActivitySdkBasedContext;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -4072,9 +4071,7 @@
r.activityInfo.targetActivity);
}
- boolean isSandboxActivityContext =
- sandboxActivitySdkBasedContext()
- && SdkSandboxActivityAuthority.isSdkSandboxActivityIntent(
+ boolean isSandboxActivityContext = SdkSandboxActivityAuthority.isSdkSandboxActivityIntent(
mSystemContext, r.intent);
boolean isSandboxedSdkContextUsed = false;
ContextImpl activityBaseContext;
diff --git a/core/java/android/app/DisabledWallpaperManager.java b/core/java/android/app/DisabledWallpaperManager.java
index 233dc75..087bcd8 100644
--- a/core/java/android/app/DisabledWallpaperManager.java
+++ b/core/java/android/app/DisabledWallpaperManager.java
@@ -177,13 +177,6 @@
}
@Override
- @NonNull
- public SparseArray<Rect> getBitmapCrops(int which) {
- unsupported();
- return new SparseArray<>();
- }
-
- @Override
public List<Rect> getBitmapCrops(@NonNull Point bitmapSize, @NonNull List<Point> displaySizes,
@Nullable Map<Point, Rect> cropHints) {
return unsupported();
diff --git a/core/java/android/app/DreamManager.java b/core/java/android/app/DreamManager.java
index 4ac40a1..c597a9d 100644
--- a/core/java/android/app/DreamManager.java
+++ b/core/java/android/app/DreamManager.java
@@ -234,4 +234,19 @@
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 1738a92..8fa2362 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -271,6 +271,9 @@
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);
+
+ // TODO: b/389918945 - Remove once nm_binder_perf flags are going to Nextfood.
+ void incrementCounter(String metricId);
}
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 3c37b44..fbe5b94 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -6627,7 +6627,20 @@
*/
@Deprecated
public RemoteViews createContentView() {
- return createContentView(false /* increasedheight */ );
+ if (useExistingRemoteView(mN.contentView)) {
+ return fullyCustomViewRequiresDecoration(false /* fromStyle */)
+ ? minimallyDecoratedContentView(mN.contentView) : mN.contentView;
+ } else if (mStyle != null) {
+ final RemoteViews styleView = mStyle.makeContentView();
+ if (styleView != null) {
+ return fullyCustomViewRequiresDecoration(true /* fromStyle */)
+ ? minimallyDecoratedContentView(styleView) : styleView;
+ }
+ }
+ StandardTemplateParams p = mParams.reset()
+ .viewType(StandardTemplateParams.VIEW_TYPE_NORMAL)
+ .fillTextsFrom(this);
+ return applyStandardTemplate(getCollapsedBaseLayoutResource(), p, null /* result */);
}
// This code is executed on behalf of other apps' notifications, sometimes even by 3p apps,
@@ -6688,33 +6701,6 @@
return standard;
}
- /**
- * Construct a RemoteViews for the smaller content view.
- *
- * @param increasedHeight true if this layout be created with an increased height. Some
- * styles may support showing more then just that basic 1U size
- * and the system may decide to render important notifications
- * slightly bigger even when collapsed.
- *
- * @hide
- */
- public RemoteViews createContentView(boolean increasedHeight) {
- if (useExistingRemoteView(mN.contentView)) {
- return fullyCustomViewRequiresDecoration(false /* fromStyle */)
- ? minimallyDecoratedContentView(mN.contentView) : mN.contentView;
- } else if (mStyle != null) {
- final RemoteViews styleView = mStyle.makeContentView(increasedHeight);
- if (styleView != null) {
- return fullyCustomViewRequiresDecoration(true /* fromStyle */)
- ? minimallyDecoratedContentView(styleView) : styleView;
- }
- }
- StandardTemplateParams p = mParams.reset()
- .viewType(StandardTemplateParams.VIEW_TYPE_NORMAL)
- .fillTextsFrom(this);
- return applyStandardTemplate(getCollapsedBaseLayoutResource(), p, null /* result */);
- }
-
private boolean useExistingRemoteView(RemoteViews customContent) {
if (customContent == null) {
return false;
@@ -6850,48 +6836,13 @@
}
/**
- * Construct a RemoteViews for the final heads-up notification layout.
- *
- * @param increasedHeight true if this layout be created with an increased height. Some
- * styles may support showing more then just that basic 1U size
- * and the system may decide to render important notifications
- * slightly bigger even when collapsed.
- *
- * @hide
- */
- public RemoteViews createHeadsUpContentView(boolean increasedHeight) {
- if (useExistingRemoteView(mN.headsUpContentView)) {
- return fullyCustomViewRequiresDecoration(false /* fromStyle */)
- ? minimallyDecoratedHeadsUpContentView(mN.headsUpContentView)
- : mN.headsUpContentView;
- } else if (mStyle != null) {
- final RemoteViews styleView = mStyle.makeHeadsUpContentView(increasedHeight);
- if (styleView != null) {
- return fullyCustomViewRequiresDecoration(true /* fromStyle */)
- ? minimallyDecoratedHeadsUpContentView(styleView) : styleView;
- }
- } else if (mActions.size() == 0) {
- return null;
- }
-
- // We only want at most a single remote input history to be shown here, otherwise
- // the content would become squished.
- StandardTemplateParams p = mParams.reset()
- .viewType(StandardTemplateParams.VIEW_TYPE_HEADS_UP)
- .fillTextsFrom(this)
- .setMaxRemoteInputHistory(1);
- return applyStandardTemplateWithActions(getHeadsUpBaseLayoutResource(), p,
- null /* result */);
- }
-
- /**
* Construct a RemoteViews for the final compact heads-up notification layout.
* @hide
*/
public RemoteViews createCompactHeadsUpContentView() {
// Don't show compact heads up for FSI notifications.
if (mN.fullScreenIntent != null) {
- return createHeadsUpContentView(/* increasedHeight= */ false);
+ return createHeadsUpContentView();
}
if (mStyle != null) {
@@ -6929,7 +6880,28 @@
*/
@Deprecated
public RemoteViews createHeadsUpContentView() {
- return createHeadsUpContentView(false /* useIncreasedHeight */);
+ if (useExistingRemoteView(mN.headsUpContentView)) {
+ return fullyCustomViewRequiresDecoration(false /* fromStyle */)
+ ? minimallyDecoratedHeadsUpContentView(mN.headsUpContentView)
+ : mN.headsUpContentView;
+ } else if (mStyle != null) {
+ final RemoteViews styleView = mStyle.makeHeadsUpContentView();
+ if (styleView != null) {
+ return fullyCustomViewRequiresDecoration(true /* fromStyle */)
+ ? minimallyDecoratedHeadsUpContentView(styleView) : styleView;
+ }
+ } else if (mActions.size() == 0) {
+ return null;
+ }
+
+ // We only want at most a single remote input history to be shown here, otherwise
+ // the content would become squished.
+ StandardTemplateParams p = mParams.reset()
+ .viewType(StandardTemplateParams.VIEW_TYPE_HEADS_UP)
+ .fillTextsFrom(this)
+ .setMaxRemoteInputHistory(1);
+ return applyStandardTemplateWithActions(getHeadsUpBaseLayoutResource(), p,
+ null /* result */);
}
/**
@@ -8236,10 +8208,9 @@
* Construct a Style-specific RemoteViews for the collapsed notification layout.
* The default implementation has nothing additional to add.
*
- * @param increasedHeight true if this layout be created with an increased height.
* @hide
*/
- public RemoteViews makeContentView(boolean increasedHeight) {
+ public RemoteViews makeContentView() {
return null;
}
@@ -8254,10 +8225,9 @@
/**
* Construct a Style-specific RemoteViews for the final HUN layout.
*
- * @param increasedHeight true if this layout be created with an increased height.
* @hide
*/
- public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
+ public RemoteViews makeHeadsUpContentView() {
return null;
}
@@ -8543,9 +8513,9 @@
* @hide
*/
@Override
- public RemoteViews makeContentView(boolean increasedHeight) {
+ public RemoteViews makeContentView() {
if (mPictureIcon == null || !mShowBigPictureWhenCollapsed) {
- return super.makeContentView(increasedHeight);
+ return super.makeContentView();
}
StandardTemplateParams p = mBuilder.mParams.reset()
@@ -8559,9 +8529,9 @@
* @hide
*/
@Override
- public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
+ public RemoteViews makeHeadsUpContentView() {
if (mPictureIcon == null || !mShowBigPictureWhenCollapsed) {
- return super.makeHeadsUpContentView(increasedHeight);
+ return super.makeHeadsUpContentView();
}
StandardTemplateParams p = mBuilder.mParams.reset()
@@ -8792,35 +8762,6 @@
}
/**
- * @param increasedHeight true if this layout be created with an increased height.
- *
- * @hide
- */
- @Override
- public RemoteViews makeContentView(boolean increasedHeight) {
- if (increasedHeight) {
- ArrayList<Action> originalActions = mBuilder.mActions;
- mBuilder.mActions = new ArrayList<>();
- RemoteViews remoteViews = makeExpandedContentView();
- mBuilder.mActions = originalActions;
- return remoteViews;
- }
- return super.makeContentView(increasedHeight);
- }
-
- /**
- * @hide
- */
- @Override
- public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
- if (increasedHeight && mBuilder.mActions.size() > 0) {
- // TODO(b/163626038): pass VIEW_TYPE_HEADS_UP?
- return makeExpandedContentView();
- }
- return super.makeHeadsUpContentView(increasedHeight);
- }
-
- /**
* @hide
*/
public RemoteViews makeExpandedContentView() {
@@ -9404,7 +9345,7 @@
* @hide
*/
@Override
- public RemoteViews makeContentView(boolean increasedHeight) {
+ public RemoteViews makeContentView() {
// All messaging templates contain the actions
ArrayList<Action> originalActions = mBuilder.mActions;
try {
@@ -9649,7 +9590,7 @@
* @hide
*/
@Override
- public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
+ public RemoteViews makeHeadsUpContentView() {
return makeMessagingView(StandardTemplateParams.VIEW_TYPE_HEADS_UP);
}
@@ -10484,7 +10425,7 @@
* @hide
*/
@Override
- public RemoteViews makeContentView(boolean increasedHeight) {
+ public RemoteViews makeContentView() {
return makeMediaContentView(null /* customContent */);
}
@@ -10500,7 +10441,7 @@
* @hide
*/
@Override
- public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
+ public RemoteViews makeHeadsUpContentView() {
return makeMediaContentView(null /* customContent */);
}
@@ -10913,7 +10854,7 @@
* @hide
*/
@Override
- public RemoteViews makeContentView(boolean increasedHeight) {
+ public RemoteViews makeContentView() {
return makeCallLayout(StandardTemplateParams.VIEW_TYPE_NORMAL);
}
@@ -10921,7 +10862,7 @@
* @hide
*/
@Override
- public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
+ public RemoteViews makeHeadsUpContentView() {
return makeCallLayout(StandardTemplateParams.VIEW_TYPE_HEADS_UP);
}
@@ -10932,7 +10873,7 @@
@Override
public RemoteViews makeCompactHeadsUpContentView() {
// Use existing heads up for call style.
- return makeHeadsUpContentView(false);
+ return makeHeadsUpContentView();
}
/**
@@ -11701,7 +11642,7 @@
* @hide
*/
@Override
- public RemoteViews makeContentView(boolean increasedHeight) {
+ public RemoteViews makeContentView() {
final StandardTemplateParams p = mBuilder.mParams.reset()
.viewType(StandardTemplateParams.VIEW_TYPE_NORMAL)
.hideProgress(true)
@@ -11713,7 +11654,7 @@
* @hide
*/
@Override
- public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
+ public RemoteViews makeHeadsUpContentView() {
final StandardTemplateParams p = mBuilder.mParams.reset()
.viewType(StandardTemplateParams.VIEW_TYPE_HEADS_UP)
.hideProgress(true)
@@ -11913,7 +11854,7 @@
// If segment limit is exceeded. All segments will be replaced
// with a single segment
boolean allSameColor = true;
- int firstSegmentColor = segments.get(0).getColor();
+ int firstSegmentColor = segments.getFirst().getColor();
for (int i = 1; i < segments.size(); i++) {
if (segments.get(i).getColor() != firstSegmentColor) {
@@ -11946,8 +11887,31 @@
}
}
+ // If the segments and points can't all fit inside the progress drawable, the
+ // view will replace all segments with a single segment.
+ final int segmentsFallbackColor;
+ if (segments.size() <= 1) {
+ segmentsFallbackColor = NotificationProgressModel.INVALID_COLOR;
+ } else {
+
+ boolean allSameColor = true;
+ int firstSegmentColor = segments.getFirst().getColor();
+ for (int i = 1; i < segments.size(); i++) {
+ if (segments.get(i).getColor() != firstSegmentColor) {
+ allSameColor = false;
+ break;
+ }
+ }
+ // If the segments are of the same color, the view can just use that color.
+ // In that case there is no need to send the fallback color.
+ segmentsFallbackColor = allSameColor ? NotificationProgressModel.INVALID_COLOR
+ : sanitizeProgressColor(Notification.COLOR_DEFAULT, backgroundColor,
+ defaultProgressColor);
+ }
+
model = new NotificationProgressModel(segments, points,
- Math.clamp(mProgress, 0, totalLength), mIsStyledByProgress);
+ Math.clamp(mProgress, 0, totalLength), mIsStyledByProgress,
+ segmentsFallbackColor);
}
return model;
}
@@ -12192,7 +12156,7 @@
* @hide
*/
@Override
- public RemoteViews makeContentView(boolean increasedHeight) {
+ public RemoteViews makeContentView() {
return makeStandardTemplateWithCustomContent(mBuilder.mN.contentView);
}
@@ -12208,7 +12172,7 @@
* @hide
*/
@Override
- public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
+ public RemoteViews makeHeadsUpContentView() {
return makeDecoratedHeadsUpContentView();
}
@@ -12328,7 +12292,7 @@
* @hide
*/
@Override
- public RemoteViews makeContentView(boolean increasedHeight) {
+ public RemoteViews makeContentView() {
return makeMediaContentView(mBuilder.mN.contentView);
}
@@ -12347,7 +12311,7 @@
* @hide
*/
@Override
- public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
+ public RemoteViews makeHeadsUpContentView() {
RemoteViews customContent = mBuilder.mN.headsUpContentView != null
? mBuilder.mN.headsUpContentView
: mBuilder.mN.contentView;
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index 24f2495..1e1ec60 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -665,8 +665,10 @@
private final InstantSource mClock;
private final RateLimiter mUpdateRateLimiter = new RateLimiter("notify (update)",
+ "notifications.value_client_throttled_notify_update",
MAX_NOTIFICATION_UPDATE_RATE);
private final RateLimiter mUnnecessaryCancelRateLimiter = new RateLimiter("cancel (dupe)",
+ "notifications.value_client_throttled_cancel_duplicate",
MAX_NOTIFICATION_UNNECESSARY_CANCEL_RATE);
// Value is KNOWN_STATUS_ENQUEUED/_CANCELLED
private final LruCache<NotificationKey, Integer> mKnownNotifications = new LruCache<>(100);
@@ -848,19 +850,21 @@
/** Helper class to rate-limit Binder calls. */
private class RateLimiter {
- private static final Duration RATE_LIMITER_LOG_INTERVAL = Duration.ofSeconds(5);
+ private static final Duration RATE_LIMITER_LOG_INTERVAL = Duration.ofSeconds(1);
private final RateEstimator mInputRateEstimator;
private final RateEstimator mOutputRateEstimator;
private final String mName;
+ private final String mCounterName;
private final float mLimitRate;
private Instant mLogSilencedUntil;
- private RateLimiter(String name, float limitRate) {
+ private RateLimiter(String name, String counterName, float limitRate) {
mInputRateEstimator = new RateEstimator();
mOutputRateEstimator = new RateEstimator();
mName = name;
+ mCounterName = counterName;
mLimitRate = limitRate;
}
@@ -880,6 +884,14 @@
return;
}
+ if (Flags.nmBinderPerfLogNmThrottling()) {
+ try {
+ service().incrementCounter(mCounterName);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Ignoring error while trying to log " + mCounterName, e);
+ }
+ }
+
long nowMillis = now.toEpochMilli();
Slog.w(TAG, TextUtils.formatSimple(
"Shedding %s of %s, rate limit (%s) exceeded: input %s, output would be %s",
@@ -1644,7 +1656,8 @@
if (Flags.modesApi() && Flags.modesUi()) {
PackageManager pm = mContext.getPackageManager();
return !pm.hasSystemFeature(PackageManager.FEATURE_WATCH)
- && !pm.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
+ && !pm.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)
+ && !pm.hasSystemFeature(PackageManager.FEATURE_LEANBACK);
} else {
return false;
}
@@ -2163,12 +2176,10 @@
* @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/TaskInfo.java b/core/java/android/app/TaskInfo.java
index 76705dc..6936ddc 100644
--- a/core/java/android/app/TaskInfo.java
+++ b/core/java/android/app/TaskInfo.java
@@ -376,6 +376,14 @@
}
/**
+ * Returns the task id.
+ * @hide
+ */
+ public int getTaskId() {
+ return taskId;
+ }
+
+ /**
* Whether this task is visible.
*/
public boolean isVisible() {
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index 73ecc71..076f856 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -19,7 +19,6 @@
import static android.Manifest.permission.MANAGE_EXTERNAL_STORAGE;
import static android.Manifest.permission.READ_WALLPAPER_INTERNAL;
import static android.Manifest.permission.SET_WALLPAPER_DIM_AMOUNT;
-import static android.app.Flags.FLAG_CUSTOMIZATION_PACKS_APIS;
import static android.app.Flags.FLAG_LIVE_WALLPAPER_CONTENT_HANDLING;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.os.ParcelFileDescriptor.MODE_READ_ONLY;
@@ -343,32 +342,24 @@
* Portrait orientation of most screens
* @hide
*/
- @FlaggedApi(FLAG_CUSTOMIZATION_PACKS_APIS)
- @SystemApi
public static final int ORIENTATION_PORTRAIT = 0;
/**
* Landscape orientation of most screens
* @hide
*/
- @FlaggedApi(FLAG_CUSTOMIZATION_PACKS_APIS)
- @SystemApi
public static final int ORIENTATION_LANDSCAPE = 1;
/**
* Portrait orientation with similar width and height (e.g. the inner screen of a foldable)
* @hide
*/
- @FlaggedApi(FLAG_CUSTOMIZATION_PACKS_APIS)
- @SystemApi
public static final int ORIENTATION_SQUARE_PORTRAIT = 2;
/**
* Landscape orientation with similar width and height (e.g. the inner screen of a foldable)
* @hide
*/
- @FlaggedApi(FLAG_CUSTOMIZATION_PACKS_APIS)
- @SystemApi
public static final int ORIENTATION_SQUARE_LANDSCAPE = 3;
/**
@@ -377,8 +368,6 @@
* @return the corresponding {@link ScreenOrientation}.
* @hide
*/
- @FlaggedApi(FLAG_CUSTOMIZATION_PACKS_APIS)
- @SystemApi
public static @ScreenOrientation int getOrientation(@NonNull Point screenSize) {
float ratio = ((float) screenSize.x) / screenSize.y;
// ratios between 3/4 and 4/3 are considered square
@@ -1665,52 +1654,6 @@
}
/**
- * For the current user, if the wallpaper of the specified destination is an ImageWallpaper,
- * return the custom crops of the wallpaper, that have been provided for example via
- * {@link #setStreamWithCrops}. These crops are relative to the original bitmap.
- * <p>
- * This method helps apps that change wallpapers provide an undo option. Calling
- * {@link #setStreamWithCrops(InputStream, SparseArray, boolean, int)} with this SparseArray and
- * the current original bitmap file, that can be obtained with {@link #getWallpaperFile(int,
- * boolean)} with {@code getCropped=false}, will exactly lead to the current wallpaper state.
- *
- * @param which wallpaper type. Must be either {@link #FLAG_SYSTEM} or {@link #FLAG_LOCK}.
- * @return A map from {{@link #ORIENTATION_PORTRAIT}, {@link #ORIENTATION_LANDSCAPE},
- * {@link #ORIENTATION_SQUARE_PORTRAIT}, {{@link #ORIENTATION_SQUARE_LANDSCAPE}}} to
- * Rect, representing the custom cropHints. The map can be empty and will only contains
- * entries for screen orientations for which a custom crop was provided. If no custom
- * crop is provided for an orientation, the system will infer the crop based on the
- * custom crops of the other orientations; or center-align the full image if no custom
- * crops are provided at all.
- * <p>
- * Return an empty map if the wallpaper is not an ImageWallpaper. Also return
- * an empty map when called with which={@link #FLAG_LOCK} if there is a shared
- * home + lock wallpaper.
- *
- * @hide
- */
- @FlaggedApi(FLAG_CUSTOMIZATION_PACKS_APIS)
- @TestApi
- @RequiresPermission(READ_WALLPAPER_INTERNAL)
- @NonNull
- public SparseArray<Rect> getBitmapCrops(@SetWallpaperFlags int which) {
- checkExactlyOneWallpaperFlagSet(which);
- try {
- Bundle bundle = sGlobals.mService.getCurrentBitmapCrops(which, mContext.getUserId());
- SparseArray<Rect> result = new SparseArray<>();
- if (bundle == null) return result;
- for (String key : bundle.keySet()) {
- int intKey = Integer.parseInt(key);
- Rect rect = bundle.getParcelable(key, Rect.class);
- result.put(intKey, rect);
- }
- return result;
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
-
- /**
* For preview purposes.
* Return how a bitmap of a given size would be cropped for a given list of display sizes, if
* it was set as wallpaper via {@link #setBitmapWithCrops(Bitmap, Map, boolean, int)} or
@@ -1955,8 +1898,6 @@
* which={@link #FLAG_LOCK} if there is a shared home + lock wallpaper.
* @hide
*/
- @FlaggedApi(FLAG_CUSTOMIZATION_PACKS_APIS)
- @SystemApi
@Nullable
public ParcelFileDescriptor getWallpaperFile(@SetWallpaperFlags int which, boolean getCropped) {
return getWallpaperFile(which, mContext.getUserId(), getCropped);
diff --git a/core/java/android/app/notification.aconfig b/core/java/android/app/notification.aconfig
index edd17e8..914ca73 100644
--- a/core/java/android/app/notification.aconfig
+++ b/core/java/android/app/notification.aconfig
@@ -295,6 +295,16 @@
}
flag {
+ name: "nm_binder_perf_log_nm_throttling"
+ namespace: "systemui"
+ description: "Log throttled operations (notify, cancel) to statsd. This flag will NOT be pushed past Trunkfood."
+ bug: "389918945"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "no_sbnholder"
namespace: "systemui"
description: "removes sbnholder from NLS"
diff --git a/core/java/android/app/supervision/ISupervisionAppService.aidl b/core/java/android/app/supervision/ISupervisionAppService.aidl
index 033998f..207fab9 100644
--- a/core/java/android/app/supervision/ISupervisionAppService.aidl
+++ b/core/java/android/app/supervision/ISupervisionAppService.aidl
@@ -20,4 +20,6 @@
* @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 4468c78..4530be5 100644
--- a/core/java/android/app/supervision/SupervisionAppService.java
+++ b/core/java/android/app/supervision/SupervisionAppService.java
@@ -28,10 +28,29 @@
*/
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 a5b58f9..92241f3 100644
--- a/core/java/android/app/supervision/SupervisionManager.java
+++ b/core/java/android/app/supervision/SupervisionManager.java
@@ -34,6 +34,35 @@
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/supervision/flags.aconfig b/core/java/android/app/supervision/flags.aconfig
index 4ee3a03..18182b8 100644
--- a/core/java/android/app/supervision/flags.aconfig
+++ b/core/java/android/app/supervision/flags.aconfig
@@ -40,3 +40,11 @@
description: "Flag to enable the SupervisionAppService"
bug: "389123070"
}
+
+flag {
+ name: "enable_supervision_settings_screen"
+ is_exported: true
+ namespace: "supervision"
+ description: "Flag that enables the Supervision settings screen with top-level Android settings entry point"
+ bug: "383404606"
+}
diff --git a/core/java/android/app/wallpaper.aconfig b/core/java/android/app/wallpaper.aconfig
index be9e286..7aba172 100644
--- a/core/java/android/app/wallpaper.aconfig
+++ b/core/java/android/app/wallpaper.aconfig
@@ -24,14 +24,6 @@
}
flag {
- name: "customization_packs_apis"
- is_exported: true
- namespace: "systemui"
- description: "Move APIs related to bitmap and crops to @SystemApi."
- bug: "372344184"
-}
-
-flag {
name: "accurate_wallpaper_downsampling"
namespace: "systemui"
description: "Accurate downsampling of wallpaper bitmap for high resolution images"
diff --git a/core/java/android/app/wallpaper/WallpaperDescription.java b/core/java/android/app/wallpaper/WallpaperDescription.java
index 999a5da..a13af7f 100644
--- a/core/java/android/app/wallpaper/WallpaperDescription.java
+++ b/core/java/android/app/wallpaper/WallpaperDescription.java
@@ -19,9 +19,7 @@
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 @@
* @hide
*/
@NonNull
- @TestApi
- @SuppressLint("MissingGetterMatchingBuilder")
+ @SystemApi
public Builder setCropHints(@NonNull Map<Point, Rect> cropHints) {
mCropHints = new SparseArray<>();
cropHints.forEach(
@@ -531,8 +528,7 @@
* @hide
*/
@NonNull
- @TestApi
- @SuppressLint("MissingGetterMatchingBuilder")
+ @SystemApi
public Builder setCropHints(@NonNull SparseArray<Rect> cropHints) {
mCropHints = cropHints;
return this;
diff --git a/core/java/android/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java
index 67ad459..b54e17b 100644
--- a/core/java/android/appwidget/AppWidgetManager.java
+++ b/core/java/android/appwidget/AppWidgetManager.java
@@ -1684,10 +1684,14 @@
private IBinder mIBinder;
ConnectionTask(@NonNull FilterComparison filter) {
- mContext.bindService(filter.getIntent(),
- Context.BindServiceFlags.of(Context.BIND_AUTO_CREATE),
- mHandler::post,
- this);
+ try {
+ mContext.bindService(filter.getIntent(),
+ Context.BindServiceFlags.of(Context.BIND_AUTO_CREATE),
+ mHandler::post,
+ this);
+ } catch (Exception e) {
+ Log.e(TAG, "Error connecting to service in connection cache", e);
+ }
}
@Override
@@ -1737,7 +1741,11 @@
handleNext();
return;
}
- mContext.unbindService(this);
+ try {
+ mContext.unbindService(this);
+ } catch (Exception e) {
+ Log.e(TAG, "Error unbinding the cached connection", e);
+ }
mActiveConnections.values().remove(this);
}
}
diff --git a/core/java/android/companion/AssociationInfo.java b/core/java/android/companion/AssociationInfo.java
index 2f16115..ceafce2 100644
--- a/core/java/android/companion/AssociationInfo.java
+++ b/core/java/android/companion/AssociationInfo.java
@@ -287,8 +287,8 @@
/**
* Get the device icon of the associated device. The device icon represents the device type.
*
- * @return the device icon, or {@code null} if no device icon has been set for the
- * associated device.
+ * @return the device icon with size 24dp x 24dp.
+ * If the associated device has no icon set, it returns {@code null}.
*
* @see AssociationRequest.Builder#setDeviceIcon(Icon)
*/
@@ -377,6 +377,7 @@
if (this == o) return true;
if (!(o instanceof AssociationInfo)) return false;
final AssociationInfo that = (AssociationInfo) o;
+
return mId == that.mId
&& mUserId == that.mUserId
&& mSelfManaged == that.mSelfManaged
@@ -391,11 +392,17 @@
&& Objects.equals(mDeviceProfile, that.mDeviceProfile)
&& Objects.equals(mAssociatedDevice, that.mAssociatedDevice)
&& mSystemDataSyncFlags == that.mSystemDataSyncFlags
- && (mDeviceIcon == null ? that.mDeviceIcon == null
- : mDeviceIcon.sameAs(that.mDeviceIcon))
+ && isSameIcon(mDeviceIcon, that.mDeviceIcon)
&& Objects.equals(mDeviceId, that.mDeviceId);
}
+ private boolean isSameIcon(Icon iconA, Icon iconB) {
+ // Because we've already rescaled and converted both icons to bitmaps,
+ // we can now directly compare them by bitmap.
+ return (iconA == null && iconB == null)
+ || (iconA != null && iconB != null && iconA.getBitmap().sameAs(iconB.getBitmap()));
+ }
+
@Override
public int hashCode() {
return Objects.hash(mId, mUserId, mPackageName, mDeviceMacAddress, mDisplayName,
@@ -425,7 +432,7 @@
dest.writeLong(mTimeApprovedMs);
dest.writeLong(mLastTimeConnectedMs);
dest.writeInt(mSystemDataSyncFlags);
- if (mDeviceIcon != null) {
+ if (Flags.associationDeviceIcon() && mDeviceIcon != null) {
dest.writeInt(1);
mDeviceIcon.writeToParcel(dest, flags);
} else {
@@ -455,7 +462,8 @@
mTimeApprovedMs = in.readLong();
mLastTimeConnectedMs = in.readLong();
mSystemDataSyncFlags = in.readInt();
- if (in.readInt() == 1) {
+ int deviceIcon = in.readInt();
+ if (Flags.associationDeviceIcon() && deviceIcon == 1) {
mDeviceIcon = Icon.CREATOR.createFromParcel(in);
} else {
mDeviceIcon = null;
diff --git a/core/java/android/companion/AssociationRequest.java b/core/java/android/companion/AssociationRequest.java
index 32cbf32..a098a60 100644
--- a/core/java/android/companion/AssociationRequest.java
+++ b/core/java/android/companion/AssociationRequest.java
@@ -385,6 +385,10 @@
public void setAssociatedDevice(AssociatedDevice associatedDevice) {
mAssociatedDevice = associatedDevice;
}
+ /** @hide */
+ public void setDeviceIcon(Icon deviceIcon) {
+ mDeviceIcon = deviceIcon;
+ }
/** @hide */
@NonNull
@@ -492,9 +496,10 @@
/**
* Set the device icon for the self-managed device and to display the icon in the
* self-managed association dialog.
+ * <p>The given device icon will be resized to 24dp x 24dp.
*
- * @throws IllegalArgumentException if the icon is not exactly 24dp by 24dp
- * or if it is {@link Icon#TYPE_URI} or {@link Icon#TYPE_URI_ADAPTIVE_BITMAP}.
+ * @throws IllegalArgumentException if the icon is
+ * {@link Icon#TYPE_URI} or {@link Icon#TYPE_URI_ADAPTIVE_BITMAP}.
* @see #setSelfManaged(boolean)
*/
@NonNull
diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java
index a96ba11..566e78a 100644
--- a/core/java/android/companion/CompanionDeviceManager.java
+++ b/core/java/android/companion/CompanionDeviceManager.java
@@ -23,7 +23,6 @@
import static android.graphics.drawable.Icon.TYPE_URI;
import static android.graphics.drawable.Icon.TYPE_URI_ADAPTIVE_BITMAP;
-
import android.annotation.CallbackExecutor;
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
@@ -52,10 +51,10 @@
import android.content.IntentSender;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
+import android.graphics.Canvas;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
-import android.graphics.drawable.VectorDrawable;
import android.net.MacAddress;
import android.os.Binder;
import android.os.Handler;
@@ -110,6 +109,7 @@
@RequiresFeature(PackageManager.FEATURE_COMPANION_DEVICE_SETUP)
public final class CompanionDeviceManager {
private static final String TAG = "CDM_CompanionDeviceManager";
+ private static final int ICON_TARGET_SIZE = 24;
/** @hide */
@IntDef(prefix = {"RESULT_"}, value = {
@@ -474,10 +474,8 @@
if (Flags.associationDeviceIcon()) {
final Icon deviceIcon = request.getDeviceIcon();
-
- if (deviceIcon != null && !isValidIcon(deviceIcon, mContext)) {
- throw new IllegalArgumentException("The size of the device icon must be "
- + "24dp x 24dp to ensure proper display");
+ if (deviceIcon != null) {
+ request.setDeviceIcon(scaleIcon(deviceIcon, mContext));
}
}
@@ -547,10 +545,8 @@
if (Flags.associationDeviceIcon()) {
final Icon deviceIcon = request.getDeviceIcon();
-
- if (deviceIcon != null && !isValidIcon(deviceIcon, mContext)) {
- throw new IllegalArgumentException("The size of the device icon must be "
- + "24dp x 24dp to ensure proper display");
+ if (deviceIcon != null) {
+ request.setDeviceIcon(scaleIcon(deviceIcon, mContext));
}
}
@@ -2024,33 +2020,26 @@
}
}
- private boolean isValidIcon(Icon icon, Context context) {
+ private Icon scaleIcon(Icon icon, Context context) {
+ if (icon == null) return null;
if (icon.getType() == TYPE_URI_ADAPTIVE_BITMAP || icon.getType() == TYPE_URI) {
throw new IllegalArgumentException("The URI based Icon is not supported.");
}
+
+ Bitmap bitmap;
Drawable drawable = icon.loadDrawable(context);
- float density = context.getResources().getDisplayMetrics().density;
-
if (drawable instanceof BitmapDrawable) {
- Bitmap bitmap = ((BitmapDrawable) drawable).getBitmap();
-
- float widthDp = bitmap.getWidth() / density;
- float heightDp = bitmap.getHeight() / density;
-
- if (widthDp != 24 || heightDp != 24) {
- return false;
- }
- } else if (drawable instanceof VectorDrawable) {
- VectorDrawable vectorDrawable = (VectorDrawable) drawable;
- float widthDp = vectorDrawable.getIntrinsicWidth() / density;
- float heightDp = vectorDrawable.getIntrinsicHeight() / density;
-
- if (widthDp != 24 || heightDp != 24) {
- return false;
- }
+ bitmap = Bitmap.createScaledBitmap(
+ ((BitmapDrawable) drawable).getBitmap(), ICON_TARGET_SIZE, ICON_TARGET_SIZE,
+ false);
} else {
- throw new IllegalArgumentException("The format of the device icon is unsupported.");
+ bitmap = Bitmap.createBitmap(context.getResources().getDisplayMetrics(),
+ ICON_TARGET_SIZE, ICON_TARGET_SIZE, Bitmap.Config.ARGB_8888);
+ Canvas canvas = new Canvas(bitmap);
+ drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
+ drawable.draw(canvas);
}
- return true;
+
+ return Icon.createWithBitmap(bitmap);
}
}
diff --git a/core/java/android/companion/virtual/VirtualDevice.java b/core/java/android/companion/virtual/VirtualDevice.java
index b9e9afe..8ef4224 100644
--- a/core/java/android/companion/virtual/VirtualDevice.java
+++ b/core/java/android/companion/virtual/VirtualDevice.java
@@ -20,11 +20,9 @@
import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_CAMERA;
import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_SENSORS;
-import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
-import android.companion.virtual.flags.Flags;
import android.content.Context;
import android.os.Parcel;
import android.os.Parcelable;
@@ -34,8 +32,9 @@
* Details of a particular virtual device.
*
* <p>Read-only device representation exposing the properties of an existing virtual device.
+ *
+ * @see VirtualDeviceManager#registerVirtualDeviceListener
*/
-// TODO(b/310912420): Link to VirtualDeviceManager#registerVirtualDeviceListener from the docs
public final class VirtualDevice implements Parcelable {
private final @NonNull IVirtualDevice mVirtualDevice;
@@ -93,8 +92,8 @@
* per device.
*
* @see Context#createDeviceContext
+ * @see #getPersistentDeviceId()
*/
- // TODO(b/310912420): Link to #getPersistentDeviceId from the docs
public int getDeviceId() {
return mId;
}
@@ -111,7 +110,6 @@
* <p class="note">This identifier may not be unique across virtual devices, in case there are
* more than one virtual devices corresponding to the same physical device.
*/
- @FlaggedApi(Flags.FLAG_VDM_PUBLIC_APIS)
public @Nullable String getPersistentDeviceId() {
return mPersistentId;
}
@@ -127,7 +125,6 @@
* Returns the human-readable name of the virtual device, if defined, which is suitable to be
* shown in UI.
*/
- @FlaggedApi(Flags.FLAG_VDM_PUBLIC_APIS)
public @Nullable CharSequence getDisplayName() {
return mDisplayName;
}
@@ -138,7 +135,6 @@
* <p>The actual {@link android.view.Display} objects can be obtained by passing the returned
* IDs to {@link android.hardware.display.DisplayManager#getDisplay(int)}.</p>
*/
- @FlaggedApi(Flags.FLAG_VDM_PUBLIC_APIS)
public @NonNull int[] getDisplayIds() {
try {
return mVirtualDevice.getDisplayIds();
@@ -157,7 +153,6 @@
* @see Context#getDeviceId()
* @see Context#createDeviceContext(int)
*/
- @FlaggedApi(Flags.FLAG_VDM_PUBLIC_APIS)
public boolean hasCustomSensorSupport() {
try {
return mVirtualDevice.getDevicePolicy(POLICY_TYPE_SENSORS) == DEVICE_POLICY_CUSTOM;
@@ -172,7 +167,6 @@
* @hide
*/
@SystemApi
- @FlaggedApi(Flags.FLAG_VDM_PUBLIC_APIS)
public boolean hasCustomAudioInputSupport() {
try {
return mVirtualDevice.hasCustomAudioInputSupport();
@@ -194,7 +188,6 @@
* @hide
*/
@SystemApi
- @FlaggedApi(Flags.FLAG_VDM_PUBLIC_APIS)
public boolean hasCustomCameraSupport() {
try {
return mVirtualDevice.getDevicePolicy(POLICY_TYPE_CAMERA) == DEVICE_POLICY_CUSTOM;
diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java
index 91ea673..252db82 100644
--- a/core/java/android/companion/virtual/VirtualDeviceManager.java
+++ b/core/java/android/companion/virtual/VirtualDeviceManager.java
@@ -223,10 +223,9 @@
* existing virtual devices.</p>
*
* <p>Note that if a virtual device is closed and becomes invalid, the returned objects will
- * not be updated and may contain stale values.</p>
+ * not be updated and may contain stale values. Use a {@link VirtualDeviceListener} for real
+ * time updates of the availability of virtual devices.</p>
*/
- // TODO(b/310912420): Add "Use a VirtualDeviceListener for real time updates of the
- // availability of virtual devices." in the note paragraph above with a link annotation.
@NonNull
public List<android.companion.virtual.VirtualDevice> getVirtualDevices() {
if (mService == null) {
@@ -253,7 +252,6 @@
* @return the virtual device with the requested ID, or {@code null} if no such device exists or
* it has already been closed.
*/
- @FlaggedApi(Flags.FLAG_VDM_PUBLIC_APIS)
@Nullable
public android.companion.virtual.VirtualDevice getVirtualDevice(int deviceId) {
if (mService == null) {
@@ -278,7 +276,6 @@
* @param listener The listener to add.
* @see #unregisterVirtualDeviceListener
*/
- @FlaggedApi(Flags.FLAG_VDM_PUBLIC_APIS)
public void registerVirtualDeviceListener(
@NonNull @CallbackExecutor Executor executor,
@NonNull VirtualDeviceListener listener) {
@@ -306,7 +303,6 @@
* @param listener The listener to unregister.
* @see #registerVirtualDeviceListener
*/
- @FlaggedApi(Flags.FLAG_VDM_PUBLIC_APIS)
public void unregisterVirtualDeviceListener(@NonNull VirtualDeviceListener listener) {
if (mService == null) {
Log.w(TAG, "Failed to unregister listener; no virtual device manager service.");
@@ -389,9 +385,9 @@
* @return the display name associated with the given persistent device ID, or {@code null} if
* the persistent ID is invalid or does not correspond to a virtual device.
*
+ * @see VirtualDevice#getPersistentDeviceId()
* @hide
*/
- // TODO(b/315481938): Link @see VirtualDevice#getPersistentDeviceId()
@SystemApi
@Nullable
public CharSequence getDisplayNameForPersistentDeviceId(@NonNull String persistentDeviceId) {
@@ -411,9 +407,9 @@
* Returns all current persistent device IDs, including the ones for which no virtual device
* exists, as long as one may have existed or can be created.
*
+ * @see VirtualDevice#getPersistentDeviceId()
* @hide
*/
- // TODO(b/315481938): Link @see VirtualDevice#getPersistentDeviceId()
@SystemApi
@NonNull
public Set<String> getAllPersistentDeviceIds() {
@@ -588,7 +584,6 @@
/**
* Returns the persistent ID of this virtual device.
*/
- @FlaggedApi(Flags.FLAG_VDM_PUBLIC_APIS)
public @Nullable String getPersistentDeviceId() {
return mVirtualDeviceInternal.getPersistentDeviceId();
}
@@ -1121,11 +1116,8 @@
* @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);
}
/**
@@ -1339,7 +1331,6 @@
*
* @see #registerVirtualDeviceListener
*/
- @FlaggedApi(Flags.FLAG_VDM_PUBLIC_APIS)
public interface VirtualDeviceListener {
/**
* Called whenever a new virtual device has been added to the system.
diff --git a/core/java/android/companion/virtual/VirtualDeviceParams.java b/core/java/android/companion/virtual/VirtualDeviceParams.java
index 761e75b..95dee9b 100644
--- a/core/java/android/companion/virtual/VirtualDeviceParams.java
+++ b/core/java/android/companion/virtual/VirtualDeviceParams.java
@@ -440,7 +440,6 @@
*
* @see Builder#setInputMethodComponent
*/
- @FlaggedApi(Flags.FLAG_VDM_CUSTOM_IME)
@Nullable
public ComponentName getInputMethodComponent() {
return mInputMethodComponent;
@@ -945,7 +944,6 @@
* @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/Intent.java b/core/java/android/content/Intent.java
index 885a2db..4696882 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -16,7 +16,6 @@
package android.content;
-import static android.app.sdksandbox.SdkSandboxManager.ACTION_START_SANDBOXED_ACTIVITY;
import static android.content.ContentProvider.maybeAddUserId;
import static android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE;
import static android.security.Flags.FLAG_FRP_ENFORCEMENT;
@@ -13459,28 +13458,4 @@
public boolean isDocument() {
return (mFlags & FLAG_ACTIVITY_NEW_DOCUMENT) == FLAG_ACTIVITY_NEW_DOCUMENT;
}
-
- /**
- * @deprecated Use {@link SdkSandboxActivityAuthority#isSdkSandboxActivityIntent} instead.
- * Once the other API is finalized this method will be removed.
- *
- * TODO(b/300059435): remove as part of the cleanup.
- *
- * @hide
- */
- @Deprecated
- @android.ravenwood.annotation.RavenwoodThrow
- public boolean isSandboxActivity(@NonNull Context context) {
- if (mAction != null && mAction.equals(ACTION_START_SANDBOXED_ACTIVITY)) {
- return true;
- }
- final String sandboxPackageName = context.getPackageManager().getSdkSandboxPackageName();
- if (mPackage != null && mPackage.equals(sandboxPackageName)) {
- return true;
- }
- if (mComponent != null && mComponent.getPackageName().equals(sandboxPackageName)) {
- return true;
- }
- return false;
- }
}
diff --git a/core/java/android/content/pm/RegisteredServicesCache.java b/core/java/android/content/pm/RegisteredServicesCache.java
index 9d11710..8266384 100644
--- a/core/java/android/content/pm/RegisteredServicesCache.java
+++ b/core/java/android/content/pm/RegisteredServicesCache.java
@@ -85,6 +85,8 @@
private static final boolean DEBUG = false;
protected static final String REGISTERED_SERVICES_DIR = "registered_services";
+ static final long SERVICE_INFO_CACHES_TIMEOUT_MILLIS = 30000; // 30 seconds
+
public final Context mContext;
private final String mInterfaceName;
private final String mMetaDataName;
@@ -96,8 +98,18 @@
@GuardedBy("mServicesLock")
private final SparseArray<UserServices<V>> mUserServices = new SparseArray<UserServices<V>>(2);
- @GuardedBy("mServicesLock")
- private final ArrayMap<String, ServiceInfo<V>> mServiceInfoCaches = new ArrayMap<>();
+ @GuardedBy("mServiceInfoCaches")
+ private final ArrayMap<ComponentName, ServiceInfo<V>> mServiceInfoCaches = new ArrayMap<>();
+
+ private final Handler mBackgroundHandler;
+
+ private final Runnable mClearServiceInfoCachesRunnable = new Runnable() {
+ public void run() {
+ synchronized (mServiceInfoCaches) {
+ mServiceInfoCaches.clear();
+ }
+ }
+ };
private static class UserServices<V> {
@GuardedBy("mServicesLock")
@@ -172,9 +184,9 @@
if (isCore) {
intentFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
}
- Handler handler = BackgroundThread.getHandler();
+ mBackgroundHandler = BackgroundThread.getHandler();
mContext.registerReceiverAsUser(
- mPackageReceiver, UserHandle.ALL, intentFilter, null, handler);
+ mPackageReceiver, UserHandle.ALL, intentFilter, null, mBackgroundHandler);
// Register for events related to sdcard installation.
IntentFilter sdFilter = new IntentFilter();
@@ -183,7 +195,7 @@
if (isCore) {
sdFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
}
- mContext.registerReceiver(mExternalReceiver, sdFilter, null, handler);
+ mContext.registerReceiver(mExternalReceiver, sdFilter, null, mBackgroundHandler);
// Register for user-related events
IntentFilter userFilter = new IntentFilter();
@@ -191,7 +203,7 @@
if (isCore) {
userFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
}
- mContext.registerReceiver(mUserRemovedReceiver, userFilter, null, handler);
+ mContext.registerReceiver(mUserRemovedReceiver, userFilter, null, mBackgroundHandler);
}
private void handlePackageEvent(Intent intent, int userId) {
@@ -328,6 +340,10 @@
public final ComponentName componentName;
@UnsupportedAppUsage
public final int uid;
+ /**
+ * The last update time of the package that contains the service.
+ * It's from {@link PackageInfo#lastUpdateTime}.
+ */
public final long lastUpdateTime;
/** @hide */
@@ -342,7 +358,8 @@
@Override
public String toString() {
- return "ServiceInfo: " + type + ", " + componentName + ", uid " + uid;
+ return "ServiceInfo: " + type + ", " + componentName + ", uid " + uid
+ + ", lastUpdateTime " + lastUpdateTime;
}
}
@@ -496,19 +513,56 @@
final ArrayList<ServiceInfo<V>> serviceInfos = new ArrayList<>();
final List<ResolveInfo> resolveInfos = queryIntentServices(userId);
+ final PackageManager pm = mContext.getPackageManager();
for (ResolveInfo resolveInfo : resolveInfos) {
+ // Check if the service has been in the service cache.
+ long lastUpdateTime = -1;
+ final android.content.pm.ServiceInfo si = resolveInfo.serviceInfo;
+ final ComponentName componentName = si.getComponentName();
+ if (Flags.optimizeParsingInRegisteredServicesCache()) {
+ try {
+ PackageInfo packageInfo = pm.getPackageInfoAsUser(si.packageName,
+ PackageManager.MATCH_DIRECT_BOOT_AWARE
+ | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userId);
+ lastUpdateTime = packageInfo.lastUpdateTime;
+ } catch (NameNotFoundException | SecurityException e) {
+ Slog.d(TAG, "Fail to get the PackageInfo in generateServicesMap: " + e);
+ continue;
+ }
+ ServiceInfo<V> serviceInfo = getServiceInfoFromServiceCache(componentName,
+ lastUpdateTime);
+ if (serviceInfo != null) {
+ serviceInfos.add(serviceInfo);
+ continue;
+ }
+ }
try {
- ServiceInfo<V> info = parseServiceInfo(resolveInfo, userId);
+ ServiceInfo<V> info = parseServiceInfo(resolveInfo, lastUpdateTime);
if (info == null) {
Log.w(TAG, "Unable to load service info " + resolveInfo.toString());
continue;
}
serviceInfos.add(info);
+ if (Flags.optimizeParsingInRegisteredServicesCache()) {
+ synchronized (mServiceInfoCaches) {
+ mServiceInfoCaches.put(componentName, info);
+ }
+ }
} catch (XmlPullParserException | IOException e) {
Log.w(TAG, "Unable to load service info " + resolveInfo.toString(), e);
}
}
+ if (Flags.optimizeParsingInRegisteredServicesCache()) {
+ synchronized (mServiceInfoCaches) {
+ if (!mServiceInfoCaches.isEmpty()) {
+ mBackgroundHandler.removeCallbacks(mClearServiceInfoCachesRunnable);
+ mBackgroundHandler.postDelayed(mClearServiceInfoCachesRunnable,
+ SERVICE_INFO_CACHES_TIMEOUT_MILLIS);
+ }
+ }
+ }
+
synchronized (mServicesLock) {
final UserServices<V> user = findOrCreateUserLocked(userId);
final boolean firstScan = user.services == null;
@@ -645,32 +699,18 @@
return false;
}
+ /**
+ * If the service has already existed in the caches, this method will not be called to parse
+ * the service.
+ */
@VisibleForTesting
- protected ServiceInfo<V> parseServiceInfo(ResolveInfo service, int userId)
+ protected ServiceInfo<V> parseServiceInfo(ResolveInfo service, long lastUpdateTime)
throws XmlPullParserException, IOException {
android.content.pm.ServiceInfo si = service.serviceInfo;
ComponentName componentName = new ComponentName(si.packageName, si.name);
PackageManager pm = mContext.getPackageManager();
- // Check if the service has been in the service cache.
- long lastUpdateTime = -1;
- if (Flags.optimizeParsingInRegisteredServicesCache()) {
- try {
- PackageInfo packageInfo = pm.getPackageInfoAsUser(si.packageName,
- PackageManager.MATCH_DIRECT_BOOT_AWARE
- | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userId);
- lastUpdateTime = packageInfo.lastUpdateTime;
-
- ServiceInfo<V> serviceInfo = getServiceInfoFromServiceCache(si, lastUpdateTime);
- if (serviceInfo != null) {
- return serviceInfo;
- }
- } catch (NameNotFoundException | SecurityException e) {
- Slog.d(TAG, "Fail to get the PackageInfo in parseServiceInfo: " + e);
- }
- }
-
XmlResourceParser parser = null;
try {
parser = si.loadXmlMetaData(pm, mMetaDataName);
@@ -696,13 +736,7 @@
if (v == null) {
return null;
}
- ServiceInfo<V> serviceInfo = new ServiceInfo<V>(v, si, componentName, lastUpdateTime);
- if (Flags.optimizeParsingInRegisteredServicesCache()) {
- synchronized (mServicesLock) {
- mServiceInfoCaches.put(getServiceCacheKey(si), serviceInfo);
- }
- }
- return serviceInfo;
+ return new ServiceInfo<V>(v, si, componentName, lastUpdateTime);
} catch (NameNotFoundException e) {
throw new XmlPullParserException(
"Unable to load resources for pacakge " + si.packageName);
@@ -873,26 +907,13 @@
mContext.unregisterReceiver(mUserRemovedReceiver);
}
- private static String getServiceCacheKey(@NonNull android.content.pm.ServiceInfo serviceInfo) {
- StringBuilder sb = new StringBuilder(serviceInfo.packageName);
- sb.append('-');
- sb.append(serviceInfo.name);
- return sb.toString();
- }
-
- private ServiceInfo<V> getServiceInfoFromServiceCache(
- @NonNull android.content.pm.ServiceInfo serviceInfo, long lastUpdateTime) {
- String serviceCacheKey = getServiceCacheKey(serviceInfo);
- synchronized (mServicesLock) {
- ServiceInfo<V> serviceCache = mServiceInfoCaches.get(serviceCacheKey);
- if (serviceCache == null) {
- return null;
- }
- if (serviceCache.lastUpdateTime == lastUpdateTime) {
+ private ServiceInfo<V> getServiceInfoFromServiceCache(@NonNull ComponentName componentName,
+ long lastUpdateTime) {
+ synchronized (mServiceInfoCaches) {
+ ServiceInfo<V> serviceCache = mServiceInfoCaches.get(componentName);
+ if (serviceCache != null && serviceCache.lastUpdateTime == lastUpdateTime) {
return serviceCache;
}
- // The service is not latest, remove it from the cache.
- mServiceInfoCaches.remove(serviceCacheKey);
return null;
}
}
diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java
index bbfae81..7cd2d31 100644
--- a/core/java/android/content/res/AssetManager.java
+++ b/core/java/android/content/res/AssetManager.java
@@ -62,6 +62,7 @@
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 @@
// 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 @@
mObject = nativeCreate();
if (DEBUG_REFS) {
- mNumRefs = 0;
+ mNumRefs.set(0);
incRefsLocked(hashCode());
}
@@ -260,7 +261,7 @@
private AssetManager(boolean sentinel) {
mObject = nativeCreate();
if (DEBUG_REFS) {
- mNumRefs = 0;
+ mNumRefs.set(0);
incRefsLocked(hashCode());
}
}
@@ -324,7 +325,7 @@
}
mOpen = false;
- decRefsLocked(hashCode());
+ decRefs(hashCode());
}
}
@@ -1235,9 +1236,7 @@
}
void xmlBlockGone(int id) {
- synchronized (this) {
- decRefsLocked(id);
- }
+ decRefs(id);
}
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@@ -1308,9 +1307,7 @@
}
void releaseTheme(long themePtr) {
- synchronized (this) {
- decRefsLocked(themePtr);
- }
+ decRefs(themePtr);
}
static long getThemeFreeFunction() {
@@ -1332,7 +1329,7 @@
if (this != newAssetManager) {
synchronized (this) {
ensureValidLocked();
- decRefsLocked(themePtr);
+ decRefs(themePtr);
}
synchronized (newAssetManager) {
newAssetManager.ensureValidLocked();
@@ -1364,8 +1361,8 @@
@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 @@
nativeAssetDestroy(mAssetNativePtr);
mAssetNativePtr = 0;
- synchronized (AssetManager.this) {
- decRefsLocked(hashCode());
- }
+ decRefs(hashCode());
}
}
@@ -1680,19 +1675,25 @@
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);
+ private void decRefs(long id) {
+ if (DEBUG_REFS) {
+ synchronized (this) {
+ if (mRefStacks != null) {
+ mRefStacks.remove(id);
+ }
+ }
}
- mNumRefs--;
- if (mNumRefs == 0 && mObject != 0) {
- nativeDestroy(mObject);
- mObject = 0;
- mApkAssets = sEmptyApkAssets;
+ 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 2161e10..430ed2b 100644
--- a/core/java/android/credentials/flags.aconfig
+++ b/core/java/android/credentials/flags.aconfig
@@ -144,3 +144,13 @@
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 88d69b6..030c883 100644
--- a/core/java/android/database/sqlite/SQLiteOpenHelper.java
+++ b/core/java/android/database/sqlite/SQLiteOpenHelper.java
@@ -182,17 +182,10 @@
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/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index 6716598..0590a06 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -397,7 +397,6 @@
* @see #createVirtualDisplay
* @hide
*/
- @FlaggedApi(android.companion.virtual.flags.Flags.FLAG_VDM_PUBLIC_APIS)
@SystemApi
public static final int VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT = 1 << 7;
diff --git a/core/java/android/hardware/input/InputSettings.java b/core/java/android/hardware/input/InputSettings.java
index b380e25..cd48047 100644
--- a/core/java/android/hardware/input/InputSettings.java
+++ b/core/java/android/hardware/input/InputSettings.java
@@ -385,6 +385,42 @@
}
/**
+ * Whether touchpad acceleration is enabled or not.
+ *
+ * @param context The application context.
+ *
+ * @hide
+ */
+ public static boolean isTouchpadAccelerationEnabled(@NonNull Context context) {
+ if (!isPointerAccelerationFeatureFlagEnabled()) {
+ return false;
+ }
+
+ return Settings.System.getIntForUser(context.getContentResolver(),
+ Settings.System.TOUCHPAD_ACCELERATION_ENABLED, 1, UserHandle.USER_CURRENT)
+ == 1;
+ }
+
+ /**
+ * Enables or disables touchpad acceleration.
+ *
+ * @param context The application context.
+ * @param enabled Will enable touchpad acceleration if true, disable it if
+ * false.
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.WRITE_SETTINGS)
+ public static void setTouchpadAccelerationEnabled(@NonNull Context context,
+ boolean enabled) {
+ if (!isPointerAccelerationFeatureFlagEnabled()) {
+ return;
+ }
+ Settings.System.putIntForUser(context.getContentResolver(),
+ Settings.System.TOUCHPAD_ACCELERATION_ENABLED, enabled ? 1 : 0,
+ UserHandle.USER_CURRENT);
+ }
+
+ /**
* Returns true if the feature flag for disabling system gestures on touchpads is enabled.
*
* @hide
@@ -835,7 +871,6 @@
UserHandle.USER_CURRENT);
}
-
/**
* Whether Accessibility bounce keys feature is enabled.
*
diff --git a/core/java/android/hardware/input/KeyGestureEvent.java b/core/java/android/hardware/input/KeyGestureEvent.java
index 4025242..1a712d2 100644
--- a/core/java/android/hardware/input/KeyGestureEvent.java
+++ b/core/java/android/hardware/input/KeyGestureEvent.java
@@ -122,17 +122,11 @@
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 @@
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 @@
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 @@
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/hardware/location/ContextHubManager.java b/core/java/android/hardware/location/ContextHubManager.java
index 9181bd0..953ee08 100644
--- a/core/java/android/hardware/location/ContextHubManager.java
+++ b/core/java/android/hardware/location/ContextHubManager.java
@@ -771,6 +771,7 @@
*/
@FlaggedApi(Flags.FLAG_OFFLOAD_API)
private IContextHubEndpointDiscoveryCallback createDiscoveryCallback(
+ IContextHubService service,
Executor executor,
HubEndpointDiscoveryCallback callback,
@Nullable String serviceDescriptor) {
@@ -779,6 +780,7 @@
public void onEndpointsStarted(HubEndpointInfo[] hubEndpointInfoList) {
if (hubEndpointInfoList.length == 0) {
Log.w(TAG, "onEndpointsStarted: received empty discovery list");
+ invokeCallbackFinished(service);
return;
}
executor.execute(
@@ -791,6 +793,7 @@
} else {
callback.onEndpointsStarted(discoveryList);
}
+ invokeCallbackFinished(service);
});
}
@@ -798,6 +801,7 @@
public void onEndpointsStopped(HubEndpointInfo[] hubEndpointInfoList, int reason) {
if (hubEndpointInfoList.length == 0) {
Log.w(TAG, "onEndpointsStopped: received empty discovery list");
+ invokeCallbackFinished(service);
return;
}
executor.execute(
@@ -810,8 +814,17 @@
} else {
callback.onEndpointsStopped(discoveryList, reason);
}
+ invokeCallbackFinished(service);
});
}
+
+ private void invokeCallbackFinished(IContextHubService service) {
+ try {
+ service.onDiscoveryCallbackFinished();
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
};
}
@@ -873,7 +886,7 @@
Objects.requireNonNull(executor, "executor cannot be null");
Objects.requireNonNull(callback, "callback cannot be null");
IContextHubEndpointDiscoveryCallback iCallback =
- createDiscoveryCallback(executor, callback, null);
+ createDiscoveryCallback(mService, executor, callback, null);
try {
mService.registerEndpointDiscoveryCallbackId(endpointId, iCallback);
} catch (RemoteException e) {
@@ -919,7 +932,7 @@
}
IContextHubEndpointDiscoveryCallback iCallback =
- createDiscoveryCallback(executor, callback, serviceDescriptor);
+ createDiscoveryCallback(mService, executor, callback, serviceDescriptor);
try {
mService.registerEndpointDiscoveryCallbackDescriptor(serviceDescriptor, iCallback);
} catch (RemoteException e) {
diff --git a/core/java/android/hardware/location/IContextHubService.aidl b/core/java/android/hardware/location/IContextHubService.aidl
index d5b3fa2..bb5491d 100644
--- a/core/java/android/hardware/location/IContextHubService.aidl
+++ b/core/java/android/hardware/location/IContextHubService.aidl
@@ -150,4 +150,8 @@
// Unregister an endpoint with the context hub
@EnforcePermission("ACCESS_CONTEXT_HUB")
void unregisterEndpointDiscoveryCallback(in IContextHubEndpointDiscoveryCallback callback);
+
+ // Called when a discovery callback is finished executing
+ @EnforcePermission("ACCESS_CONTEXT_HUB")
+ void onDiscoveryCallbackFinished();
}
diff --git a/core/java/android/os/AppZygote.java b/core/java/android/os/AppZygote.java
index 0541a96..69b6597 100644
--- a/core/java/android/os/AppZygote.java
+++ b/core/java/android/os/AppZygote.java
@@ -16,15 +16,22 @@
package android.os;
+import static android.os.Process.ZYGOTE_POLICY_FLAG_EMPTY;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.pm.ApplicationInfo;
import android.content.pm.ProcessInfo;
import android.util.Log;
+import android.util.Pair;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.os.Zygote;
import dalvik.system.VMRuntime;
+import java.util.Map;
+
/**
* AppZygote is responsible for interfacing with an application-specific zygote.
*
@@ -94,12 +101,90 @@
return mAppInfo;
}
+ /**
+ * Start a new process.
+ *
+ * <p>Wrap ZygoteProcess.start with retry logic.
+ *
+ * @param processClass The class to use as the process's main entry
+ * point.
+ * @param niceName A more readable name to use for the process.
+ * @param uid The user-id under which the process will run.
+ * @param gids Additional group-ids associated with the process.
+ * @param runtimeFlags Additional flags.
+ * @param targetSdkVersion The target SDK version for the app.
+ * @param seInfo null-ok SELinux information for the new process.
+ * @param abi non-null the ABI this app should be started with.
+ * @param instructionSet null-ok the instruction set to use.
+ * @param appDataDir null-ok the data directory of the app.
+ * @param packageName null-ok the name of the package this process belongs to.
+ * @param isTopApp Whether the process starts for high priority application.
+ * @param disabledCompatChanges null-ok list of disabled compat changes for the process being
+ * started.
+ * @param pkgDataInfoMap Map from related package names to private data directory
+ * volume UUID and inode number.
+ * @param allowlistedDataInfoList Map from allowlisted package names to private data directory
+ * volume UUID and inode number.
+ * @param zygoteArgs Additional arguments to supply to the Zygote process.
+ * @return An object that describes the result of the attempt to start the process.
+ * @throws RuntimeException on fatal start failure
+ */
+ public final Process.ProcessStartResult startProcess(@NonNull final String processClass,
+ final String niceName,
+ int uid, @Nullable int[] gids,
+ int runtimeFlags, int mountExternal,
+ int targetSdkVersion,
+ @Nullable String seInfo,
+ @NonNull String abi,
+ @Nullable String instructionSet,
+ @Nullable String appDataDir,
+ @Nullable String packageName,
+ boolean isTopApp,
+ @Nullable long[] disabledCompatChanges,
+ @Nullable Map<String, Pair<String, Long>>
+ pkgDataInfoMap,
+ @Nullable Map<String, Pair<String, Long>>
+ allowlistedDataInfoList,
+ @Nullable String[] zygoteArgs) {
+ try {
+ return getProcess().start(processClass,
+ niceName, uid, uid, gids, runtimeFlags, mountExternal,
+ targetSdkVersion, seInfo, abi, instructionSet,
+ appDataDir, null, packageName,
+ /*zygotePolicyFlags=*/ ZYGOTE_POLICY_FLAG_EMPTY, isTopApp,
+ disabledCompatChanges, pkgDataInfoMap, allowlistedDataInfoList,
+ false, false, false,
+ zygoteArgs);
+ } catch (RuntimeException e) {
+ if (!Flags.appZygoteRetryStart()) {
+ throw e;
+ }
+ final boolean zygote_dead = getProcess().isDead();
+ if (!zygote_dead) {
+ throw e; // Zygote process is alive. Do nothing.
+ }
+ }
+ // Retry here if the previous start fails.
+ Log.w(LOG_TAG, "retry starting process " + niceName);
+ stopZygote();
+ return getProcess().start(processClass,
+ niceName, uid, uid, gids, runtimeFlags, mountExternal,
+ targetSdkVersion, seInfo, abi, instructionSet,
+ appDataDir, null, packageName,
+ /*zygotePolicyFlags=*/ ZYGOTE_POLICY_FLAG_EMPTY, isTopApp,
+ disabledCompatChanges, pkgDataInfoMap, allowlistedDataInfoList,
+ false, false, false,
+ zygoteArgs);
+ }
+
@GuardedBy("mLock")
private void stopZygoteLocked() {
if (mZygote != null) {
mZygote.close();
// use killProcessGroup() here, so we kill all untracked children as well.
- Process.killProcessGroup(mZygoteUid, mZygote.getPid());
+ if (!mZygote.isDead()) {
+ Process.killProcessGroup(mZygoteUid, mZygote.getPid());
+ }
mZygote = null;
}
}
diff --git a/core/java/android/os/ChildZygoteProcess.java b/core/java/android/os/ChildZygoteProcess.java
index 337a3e2..d8f825a 100644
--- a/core/java/android/os/ChildZygoteProcess.java
+++ b/core/java/android/os/ChildZygoteProcess.java
@@ -17,6 +17,10 @@
package android.os;
import android.net.LocalSocketAddress;
+import android.system.ErrnoException;
+import android.system.Os;
+
+import java.util.concurrent.atomic.AtomicBoolean;
/**
* Represents a connection to a child-zygote process. A child-zygote is spawend from another
@@ -30,9 +34,23 @@
*/
private final int mPid;
- ChildZygoteProcess(LocalSocketAddress socketAddress, int pid) {
+ /**
+ * The UID of the child zygote process.
+ */
+ private final int mUid;
+
+
+ /**
+ * If this zygote process was dead;
+ */
+ private AtomicBoolean mDead;
+
+
+ ChildZygoteProcess(LocalSocketAddress socketAddress, int pid, int uid) {
super(socketAddress, null);
mPid = pid;
+ mUid = uid;
+ mDead = new AtomicBoolean(false);
}
/**
@@ -41,4 +59,22 @@
public int getPid() {
return mPid;
}
+
+ /**
+ * Check if child-zygote process is dead
+ */
+ public boolean isDead() {
+ if (mDead.get()) {
+ return true;
+ }
+ try {
+ if (Os.stat("/proc/" + mPid).st_uid == mUid) {
+ return false;
+ }
+ } catch (ErrnoException e) {
+ // Do nothing, it's dead.
+ }
+ mDead.set(true);
+ return true;
+ }
}
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index 4aa7462..c6a63a7 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -50,6 +50,7 @@
import dalvik.annotation.optimization.CriticalNative;
import dalvik.annotation.optimization.FastNative;
+import dalvik.annotation.optimization.NeverInline;
import libcore.util.SneakyThrow;
@@ -628,6 +629,19 @@
}
}
+ @NeverInline
+ private void errorUsedWhileRecycling() {
+ String error = "Parcel used while recycled. "
+ + Log.getStackTraceString(new Throwable())
+ + " Original recycle call (if DEBUG_RECYCLE): ", mStack;
+ Log.wtf(TAG, error);
+ // TODO(b/381155347): harder error
+ }
+
+ private void assertNotRecycled() {
+ if (mRecycled) errorUsedWhileRecycling();
+ }
+
/**
* Set a {@link ReadWriteHelper}, which can be used to avoid having duplicate strings, for
* example.
@@ -1180,6 +1194,7 @@
* growing dataCapacity() if needed.
*/
public final void writeInt(int val) {
+ assertNotRecycled();
int err = nativeWriteInt(mNativePtr, val);
if (err != OK) {
nativeSignalExceptionForError(err);
@@ -3282,6 +3297,7 @@
* Read an integer value from the parcel at the current dataPosition().
*/
public final int readInt() {
+ assertNotRecycled();
return nativeReadInt(mNativePtr);
}
diff --git a/core/java/android/os/PerfettoTrace.java b/core/java/android/os/PerfettoTrace.java
index 164561a..e3f251e 100644
--- a/core/java/android/os/PerfettoTrace.java
+++ b/core/java/android/os/PerfettoTrace.java
@@ -22,7 +22,6 @@
import libcore.util.NativeAllocationRegistry;
import java.util.concurrent.atomic.AtomicInteger;
-import java.util.function.Consumer;
/**
* Writes trace events to the perfetto trace buffer. These trace events can be
@@ -72,7 +71,7 @@
* @param name The category name.
*/
public Category(String name) {
- this(name, null, null);
+ this(name, "", "");
}
/**
@@ -82,7 +81,7 @@
* @param tag An atrace tag name that this category maps to.
*/
public Category(String name, String tag) {
- this(name, tag, null);
+ this(name, tag, "");
}
/**
@@ -155,9 +154,6 @@
}
}
- @FastNative
- private static native void native_event(int type, long tag, String name, long ptr);
-
@CriticalNative
private static native long native_get_process_track_uuid();
@@ -170,176 +166,98 @@
/**
* Writes a trace message to indicate a given section of code was invoked.
*
- * @param category The perfetto category pointer.
- * @param eventName The event name to appear in the trace.
- * @param extra The extra arguments.
- */
- public static void instant(Category category, String eventName, PerfettoTrackEventExtra extra) {
- if (!category.isEnabled()) {
- return;
- }
-
- native_event(PERFETTO_TE_TYPE_INSTANT, category.getPtr(), eventName, extra.getPtr());
- extra.reset();
- }
-
- /**
- * Writes a trace message to indicate a given section of code was invoked.
- *
* @param category The perfetto category.
* @param eventName The event name to appear in the trace.
- * @param extraConfig Consumer for the extra arguments.
*/
- public static void instant(Category category, String eventName,
- Consumer<PerfettoTrackEventExtra.Builder> extraConfig) {
- PerfettoTrackEventExtra.Builder extra = PerfettoTrackEventExtra.builder();
- extraConfig.accept(extra);
- instant(category, eventName, extra.build());
+ public static PerfettoTrackEventExtra.Builder instant(Category category, String eventName) {
+ if (!category.isEnabled()) {
+ return PerfettoTrackEventExtra.noOpBuilder();
+ }
+
+ return PerfettoTrackEventExtra.builder().init(PERFETTO_TE_TYPE_INSTANT, category)
+ .setEventName(eventName);
}
/**
- * Writes a trace message to indicate a given section of code was invoked.
+ * Writes a trace message to indicate the start of a given section of code.
*
* @param category The perfetto category.
* @param eventName The event name to appear in the trace.
*/
- public static void instant(Category category, String eventName) {
- instant(category, eventName, PerfettoTrackEventExtra.builder().build());
- }
-
- /**
- * Writes a trace message to indicate the start of a given section of code.
- *
- * @param category The perfetto category pointer.
- * @param eventName The event name to appear in the trace.
- * @param extra The extra arguments.
- */
- public static void begin(Category category, String eventName, PerfettoTrackEventExtra extra) {
+ public static PerfettoTrackEventExtra.Builder begin(Category category, String eventName) {
if (!category.isEnabled()) {
- return;
+ return PerfettoTrackEventExtra.noOpBuilder();
}
- native_event(PERFETTO_TE_TYPE_SLICE_BEGIN, category.getPtr(), eventName, extra.getPtr());
- extra.reset();
- }
-
- /**
- * Writes a trace message to indicate the start of a given section of code.
- *
- * @param category The perfetto category pointer.
- * @param eventName The event name to appear in the trace.
- * @param extraConfig Consumer for the extra arguments.
- */
- public static void begin(Category category, String eventName,
- Consumer<PerfettoTrackEventExtra.Builder> extraConfig) {
- PerfettoTrackEventExtra.Builder extra = PerfettoTrackEventExtra.builder();
- extraConfig.accept(extra);
- begin(category, eventName, extra.build());
- }
-
- /**
- * Writes a trace message to indicate the start of a given section of code.
- *
- * @param category The perfetto category pointer.
- * @param eventName The event name to appear in the trace.
- */
- public static void begin(Category category, String eventName) {
- begin(category, eventName, PerfettoTrackEventExtra.builder().build());
+ return PerfettoTrackEventExtra.builder().init(PERFETTO_TE_TYPE_SLICE_BEGIN, category)
+ .setEventName(eventName);
}
/**
* Writes a trace message to indicate the end of a given section of code.
*
- * @param category The perfetto category pointer.
- * @param extra The extra arguments.
+ * @param category The perfetto category.
*/
- public static void end(Category category, PerfettoTrackEventExtra extra) {
+ public static PerfettoTrackEventExtra.Builder end(Category category) {
if (!category.isEnabled()) {
- return;
+ return PerfettoTrackEventExtra.noOpBuilder();
}
- native_event(PERFETTO_TE_TYPE_SLICE_END, category.getPtr(), "", extra.getPtr());
- extra.reset();
- }
-
- /**
- * Writes a trace message to indicate the end of a given section of code.
- *
- * @param category The perfetto category pointer.
- * @param extraConfig Consumer for the extra arguments.
- */
- public static void end(Category category,
- Consumer<PerfettoTrackEventExtra.Builder> extraConfig) {
- PerfettoTrackEventExtra.Builder extra = PerfettoTrackEventExtra.builder();
- extraConfig.accept(extra);
- end(category, extra.build());
- }
-
- /**
- * Writes a trace message to indicate the end of a given section of code.
- *
- * @param category The perfetto category pointer.
- */
- public static void end(Category category) {
- end(category, PerfettoTrackEventExtra.builder().build());
+ return PerfettoTrackEventExtra.builder().init(PERFETTO_TE_TYPE_SLICE_END, category);
}
/**
* Writes a trace message to indicate the value of a given section of code.
*
- * @param category The perfetto category pointer.
- * @param extra The extra arguments.
- */
- public static void counter(Category category, PerfettoTrackEventExtra extra) {
- if (!category.isEnabled()) {
- return;
- }
-
- native_event(PERFETTO_TE_TYPE_COUNTER, category.getPtr(), "", extra.getPtr());
- extra.reset();
- }
-
- /**
- * Writes a trace message to indicate the value of a given section of code.
- *
- * @param category The perfetto category pointer.
- * @param extraConfig Consumer for the extra arguments.
- */
- public static void counter(Category category,
- Consumer<PerfettoTrackEventExtra.Builder> extraConfig) {
- PerfettoTrackEventExtra.Builder extra = PerfettoTrackEventExtra.builder();
- extraConfig.accept(extra);
- counter(category, extra.build());
- }
-
- /**
- * Writes a trace message to indicate the value of a given section of code.
- *
- * @param category The perfetto category pointer.
- * @param trackName The trackName for the event.
+ * @param category The perfetto category.
* @param value The value of the counter.
*/
- public static void counter(Category category, String trackName, long value) {
- PerfettoTrackEventExtra extra = PerfettoTrackEventExtra.builder()
- .usingCounterTrack(trackName, PerfettoTrace.getProcessTrackUuid())
- .setCounter(value)
- .build();
- counter(category, extra);
+ public static PerfettoTrackEventExtra.Builder counter(Category category, long value) {
+ if (!category.isEnabled()) {
+ return PerfettoTrackEventExtra.noOpBuilder();
+ }
+
+ return PerfettoTrackEventExtra.builder().init(PERFETTO_TE_TYPE_COUNTER, category)
+ .setCounter(value);
}
/**
* Writes a trace message to indicate the value of a given section of code.
*
- * @param category The perfetto category pointer.
+ * @param category The perfetto category.
+ * @param value The value of the counter.
* @param trackName The trackName for the event.
+ */
+ public static PerfettoTrackEventExtra.Builder counter(
+ Category category, long value, String trackName) {
+ return counter(category, value).usingProcessCounterTrack(trackName);
+ }
+
+ /**
+ * Writes a trace message to indicate the value of a given section of code.
+ *
+ * @param category The perfetto category.
* @param value The value of the counter.
*/
- public static void counter(Category category, String trackName, double value) {
- PerfettoTrackEventExtra extra = PerfettoTrackEventExtra.builder()
- .usingCounterTrack(trackName, PerfettoTrace.getProcessTrackUuid())
- .setCounter(value)
- .build();
- counter(category, extra);
+ public static PerfettoTrackEventExtra.Builder counter(Category category, double value) {
+ if (!category.isEnabled()) {
+ return PerfettoTrackEventExtra.noOpBuilder();
+ }
+
+ return PerfettoTrackEventExtra.builder().init(PERFETTO_TE_TYPE_COUNTER, category)
+ .setCounter(value);
+ }
+
+ /**
+ * Writes a trace message to indicate the value of a given section of code.
+ *
+ * @param category The perfetto category.
+ * @param value The value of the counter.
+ * @param trackName The trackName for the event.
+ */
+ public static PerfettoTrackEventExtra.Builder counter(
+ Category category, double value, String trackName) {
+ return counter(category, value).usingProcessCounterTrack(trackName);
}
/**
@@ -360,7 +278,7 @@
* Returns the process track uuid that can be used as a parent track uuid.
*/
public static long getProcessTrackUuid() {
- if (IS_FLAG_ENABLED) {
+ if (!IS_FLAG_ENABLED) {
return 0;
}
return native_get_process_track_uuid();
@@ -370,7 +288,7 @@
* Given a thread tid, returns the thread track uuid that can be used as a parent track uuid.
*/
public static long getThreadTrackUuid(long tid) {
- if (IS_FLAG_ENABLED) {
+ if (!IS_FLAG_ENABLED) {
return 0;
}
return native_get_thread_track_uuid(tid);
@@ -380,7 +298,7 @@
* Activates a trigger by name {@code triggerName} with expiry in {@code ttlMs}.
*/
public static void activateTrigger(String triggerName, int ttlMs) {
- if (IS_FLAG_ENABLED) {
+ if (!IS_FLAG_ENABLED) {
return;
}
native_activate_trigger(triggerName, ttlMs);
diff --git a/core/java/android/os/PerfettoTrackEventExtra.java b/core/java/android/os/PerfettoTrackEventExtra.java
index a219b3b..e034fb3 100644
--- a/core/java/android/os/PerfettoTrackEventExtra.java
+++ b/core/java/android/os/PerfettoTrackEventExtra.java
@@ -31,6 +31,7 @@
*/
public final class PerfettoTrackEventExtra {
private static final int DEFAULT_EXTRA_CACHE_SIZE = 5;
+ private static final Builder NO_OP_BUILDER = new NoOpBuilder();
private static final ThreadLocal<PerfettoTrackEventExtra> sTrackEventExtra =
new ThreadLocal<PerfettoTrackEventExtra>() {
@Override
@@ -40,7 +41,6 @@
};
private static final AtomicLong sNamedTrackId = new AtomicLong();
- private boolean mIsInUse;
private CounterInt64 mCounterInt64;
private CounterDouble mCounterDouble;
private Proto mProto;
@@ -123,15 +123,299 @@
}
}
+ public interface Builder {
+ /**
+ * Emits the track event.
+ */
+ void emit();
+
+ /**
+ * Initialize the builder for a new trace event.
+ */
+ Builder init(int traceType, PerfettoTrace.Category category);
+
+ /**
+ * Sets the event name for the track event.
+ *
+ * @param eventName can contain a format string specifier, in which case, the
+ * {@code args} are the format arguments. If no {@code args} are provided,
+ * the {@code eventName} should be the string itself.
+ * @param args format arguments if {@code eventName} is specified.
+ */
+ Builder setEventName(String eventName, Object... args);
+
+ /**
+ * Adds a debug arg with key {@code name} and value {@code val}.
+ */
+ Builder addArg(String name, long val);
+
+ /**
+ * Adds a debug arg with key {@code name} and value {@code val}.
+ */
+ Builder addArg(String name, boolean val);
+
+ /**
+ * Adds a debug arg with key {@code name} and value {@code val}.
+ */
+ Builder addArg(String name, double val);
+
+ /**
+ * Adds a debug arg with key {@code name} and value {@code val}.
+ *
+ * @param val can contain a format string specifier, in which case, the
+ * {@code args} are the format arguments. If no {@code args} are provided,
+ * the {@code val} should be the string itself.
+ * @param args format arguments if {@code val} is specified.
+ */
+ Builder addArg(String name, String val, Object... args);
+
+ /**
+ * Adds a flow with {@code id}.
+ */
+ Builder addFlow(int id);
+
+ /**
+ * Adds a terminating flow with {@code id}.
+ */
+ Builder addTerminatingFlow(int id);
+
+ /**
+ * Adds the events to a named track instead of the thread track where the
+ * event occurred.
+ *
+ * @param name can contain a format string specifier, in which case, the
+ * {@code args} are the format arguments. If no {@code args} are provided,
+ * the {@code name} should be the string itself.
+ * @param args format arguments if {@code name} is specified.
+ */
+ Builder usingNamedTrack(long parentUuid, String name, Object... args);
+
+ /**
+ * Adds the events to a process scoped named track instead of the thread track where the
+ * event occurred.
+ *
+ * @param name can contain a format string specifier, in which case, the
+ * {@code args} are the format arguments. If no {@code args} are provided,
+ * the {@code name} should be the string itself.
+ * @param args format arguments if {@code name} is specified.
+ */
+ Builder usingProcessNamedTrack(String name, Object... args);
+
+ /**
+ * Adds the events to a thread scoped named track instead of the thread track where the
+ * event occurred.
+ *
+ * @param name can contain a format string specifier, in which case, the
+ * {@code args} are the format arguments. If no {@code args} are provided,
+ * the {@code name} should be the string itself.
+ * @param args format arguments if {@code name} is specified.
+ */
+ Builder usingThreadNamedTrack(long tid, String name, Object... args);
+
+ /**
+ * Adds the events to a counter track instead. This is required for
+ * setting counter values.
+ *
+ * @param name can contain a format string specifier, in which case, the
+ * {@code args} are the format arguments. If no {@code args} are provided,
+ * the {@code name} should be the string itself.
+ * @param args format arguments if {@code name} is specified.
+ */
+ Builder usingCounterTrack(long parentUuid, String name, Object... args);
+
+ /**
+ * Adds the events to a process scoped counter track instead. This is required for
+ * setting counter values.
+ *
+ * @param name can contain a format string specifier, in which case, the
+ * {@code args} are the format arguments. If no {@code args} are provided,
+ * the {@code name} should be the string itself.
+ * @param args format arguments if {@code eventName} is specified.
+ */
+ Builder usingProcessCounterTrack(String name, Object... args);
+
+ /**
+ * Adds the events to a thread scoped counter track instead. This is required for
+ * setting counter values.
+ *
+ * @param name can contain a format string specifier, in which case, the
+ * {@code args} are the format arguments. If no {@code args} are provided,
+ * the {@code name} should be the string itself.
+ * @param args format arguments if {@code name} is specified.
+ */
+ Builder usingThreadCounterTrack(long tid, String name, Object... args);
+
+ /**
+ * Sets a long counter value on the event.
+ *
+ */
+ Builder setCounter(long val);
+
+ /**
+ * Sets a double counter value on the event.
+ *
+ */
+ Builder setCounter(double val);
+
+ /**
+ * Adds a proto field with field id {@code id} and value {@code val}.
+ */
+ Builder addField(long id, long val);
+
+ /**
+ * Adds a proto field with field id {@code id} and value {@code val}.
+ */
+ Builder addField(long id, double val);
+
+ /**
+ * Adds a proto field with field id {@code id} and value {@code val}.
+ *
+ * @param val can contain a format string specifier, in which case, the
+ * {@code args} are the format arguments. If no {@code args} are provided,
+ * the {@code val} should be the string itself.
+ * @param args format arguments if {@code val} is specified.
+ */
+ Builder addField(long id, String val, Object... args);
+
+ /**
+ * Begins a proto field with field
+ * Fields can be added from this point and there must be a corresponding
+ * {@link endProto}.
+ *
+ * The proto field is a singleton and all proto fields get added inside the
+ * one {@link beginProto} and {@link endProto} within the {@link Builder}.
+ */
+ Builder beginProto();
+
+ /**
+ * Ends a proto field.
+ */
+ Builder endProto();
+
+ /**
+ * Begins a nested proto field with field id {@code id}.
+ * Fields can be added from this point and there must be a corresponding
+ * {@link endNested}.
+ */
+ Builder beginNested(long id);
+
+ /**
+ * Ends a nested proto field.
+ */
+ Builder endNested();
+ }
+
+ public static final class NoOpBuilder implements Builder {
+ @Override
+ public void emit() {}
+ @Override
+ public Builder init(int traceType, PerfettoTrace.Category category) {
+ return this;
+ }
+ @Override
+ public Builder setEventName(String eventName, Object... args) {
+ return this;
+ }
+ @Override
+ public Builder addArg(String name, long val) {
+ return this;
+ }
+ @Override
+ public Builder addArg(String name, boolean val) {
+ return this;
+ }
+ @Override
+ public Builder addArg(String name, double val) {
+ return this;
+ }
+ @Override
+ public Builder addArg(String name, String val, Object... args) {
+ return this;
+ }
+ @Override
+ public Builder addFlow(int id) {
+ return this;
+ }
+ @Override
+ public Builder addTerminatingFlow(int id) {
+ return this;
+ }
+ @Override
+ public Builder usingNamedTrack(long parentUuid, String name, Object... args) {
+ return this;
+ }
+ @Override
+ public Builder usingProcessNamedTrack(String name, Object... args) {
+ return this;
+ }
+ @Override
+ public Builder usingThreadNamedTrack(long tid, String name, Object... args) {
+ return this;
+ }
+ @Override
+ public Builder usingCounterTrack(long parentUuid, String name, Object... args) {
+ return this;
+ }
+ @Override
+ public Builder usingProcessCounterTrack(String name, Object... args) {
+ return this;
+ }
+ @Override
+ public Builder usingThreadCounterTrack(long tid, String name, Object... args) {
+ return this;
+ }
+ @Override
+ public Builder setCounter(long val) {
+ return this;
+ }
+ @Override
+ public Builder setCounter(double val) {
+ return this;
+ }
+ @Override
+ public Builder addField(long id, long val) {
+ return this;
+ }
+ @Override
+ public Builder addField(long id, double val) {
+ return this;
+ }
+ @Override
+ public Builder addField(long id, String val, Object... args) {
+ return this;
+ }
+ @Override
+ public Builder beginProto() {
+ return this;
+ }
+ @Override
+ public Builder endProto() {
+ return this;
+ }
+ @Override
+ public Builder beginNested(long id) {
+ return this;
+ }
+ @Override
+ public Builder endNested() {
+ return this;
+ }
+ }
+
/**
* Builder for Perfetto track event extras.
*/
- public static final class Builder {
+ public static final class BuilderImpl implements Builder {
// For performance reasons, we hold a reference to mExtra as a holder for
// perfetto pointers being added. This way, we avoid an additional list to hold
// the pointers in Java and we can pass them down directly to native code.
private final PerfettoTrackEventExtra mExtra;
+
+ private int mTraceType;
+ private PerfettoTrace.Category mCategory;
+ private String mEventName;
private boolean mIsBuilt;
+
private Builder mParent;
private FieldContainer mCurrentContainer;
@@ -151,16 +435,10 @@
private final Pool<FieldString> mFieldStringCache;
private final Pool<FieldNested> mFieldNestedCache;
private final Pool<Flow> mFlowCache;
- private final Pool<Builder> mBuilderCache;
+ private final Pool<BuilderImpl> mBuilderCache;
- private Builder() {
- this(sTrackEventExtra.get(), null, null);
- }
-
- private Builder(PerfettoTrackEventExtra extra, Builder parent, FieldContainer container) {
- mExtra = extra;
- mParent = parent;
- mCurrentContainer = container;
+ private BuilderImpl() {
+ mExtra = sTrackEventExtra.get();
mNamedTrackCache = mExtra.mNamedTrackCache;
mCounterTrackCache = mExtra.mCounterTrackCache;
@@ -180,25 +458,39 @@
mProto = mExtra.getProto();
}
- /**
- * Builds the track event extra.
- */
- public PerfettoTrackEventExtra build() {
+ @Override
+ public void emit() {
checkParent();
mIsBuilt = true;
+ native_emit(mTraceType, mCategory.getPtr(), mEventName, mExtra.getPtr());
+ // Reset after emitting to free any the extras used to trace the event.
+ mExtra.reset();
+ }
+
+ @Override
+ public Builder init(int traceType, PerfettoTrace.Category category) {
+ mTraceType = traceType;
+ mCategory = category;
+ mEventName = "";
mFieldInt64Cache.reset();
mFieldDoubleCache.reset();
mFieldStringCache.reset();
mFieldNestedCache.reset();
mBuilderCache.reset();
- return mExtra;
+ mExtra.reset();
+ // Reset after on init in case the thread created builders without calling emit
+ return initInternal(this, null);
}
- /**
- * Adds a debug arg with key {@code name} and value {@code val}.
- */
+ @Override
+ public Builder setEventName(String eventName, Object... args) {
+ mEventName = toString(eventName, args);
+ return this;
+ }
+
+ @Override
public Builder addArg(String name, long val) {
checkParent();
ArgInt64 arg = mArgInt64Cache.get(name.hashCode());
@@ -211,9 +503,7 @@
return this;
}
- /**
- * Adds a debug arg with key {@code name} and value {@code val}.
- */
+ @Override
public Builder addArg(String name, boolean val) {
checkParent();
ArgBool arg = mArgBoolCache.get(name.hashCode());
@@ -226,9 +516,7 @@
return this;
}
- /**
- * Adds a debug arg with key {@code name} and value {@code val}.
- */
+ @Override
public Builder addArg(String name, double val) {
checkParent();
ArgDouble arg = mArgDoubleCache.get(name.hashCode());
@@ -241,24 +529,20 @@
return this;
}
- /**
- * Adds a debug arg with key {@code name} and value {@code val}.
- */
- public Builder addArg(String name, String val) {
+ @Override
+ public Builder addArg(String name, String val, Object... args) {
checkParent();
ArgString arg = mArgStringCache.get(name.hashCode());
if (arg == null || !arg.getName().equals(name)) {
arg = new ArgString(name);
mArgStringCache.put(name.hashCode(), arg);
}
- arg.setValue(val);
+ arg.setValue(toString(val, args));
mExtra.addPerfettoPointer(arg);
return this;
}
- /**
- * Adds a flow with {@code id}.
- */
+ @Override
public Builder addFlow(int id) {
checkParent();
Flow flow = mFlowCache.get(Flow::new);
@@ -267,9 +551,7 @@
return this;
}
- /**
- * Adds a terminating flow with {@code id}.
- */
+ @Override
public Builder addTerminatingFlow(int id) {
checkParent();
Flow flow = mFlowCache.get(Flow::new);
@@ -278,12 +560,11 @@
return this;
}
- /**
- * Adds the events to a named track instead of the thread track where the
- * event occurred.
- */
- public Builder usingNamedTrack(String name, long parentUuid) {
+ @Override
+ public Builder usingNamedTrack(long parentUuid, String name, Object... args) {
checkParent();
+ name = toString(name, args);
+
NamedTrack track = mNamedTrackCache.get(name.hashCode());
if (track == null || !track.getName().equals(name)) {
track = new NamedTrack(name, parentUuid);
@@ -293,13 +574,21 @@
return this;
}
- /**
- * Adds the events to a counter track instead. This is required for
- * setting counter values.
- *
- */
- public Builder usingCounterTrack(String name, long parentUuid) {
+ @Override
+ public Builder usingProcessNamedTrack(String name, Object... args) {
+ return usingNamedTrack(PerfettoTrace.getProcessTrackUuid(), name, args);
+ }
+
+ @Override
+ public Builder usingThreadNamedTrack(long tid, String name, Object... args) {
+ return usingNamedTrack(PerfettoTrace.getThreadTrackUuid(tid), name, args);
+ }
+
+ @Override
+ public Builder usingCounterTrack(long parentUuid, String name, Object... args) {
checkParent();
+ name = toString(name, args);
+
CounterTrack track = mCounterTrackCache.get(name.hashCode());
if (track == null || !track.getName().equals(name)) {
track = new CounterTrack(name, parentUuid);
@@ -309,10 +598,17 @@
return this;
}
- /**
- * Sets a long counter value on the event.
- *
- */
+ @Override
+ public Builder usingProcessCounterTrack(String name, Object... args) {
+ return usingCounterTrack(PerfettoTrace.getProcessTrackUuid(), name, args);
+ }
+
+ @Override
+ public Builder usingThreadCounterTrack(long tid, String name, Object... args) {
+ return usingCounterTrack(PerfettoTrace.getThreadTrackUuid(tid), name, args);
+ }
+
+ @Override
public Builder setCounter(long val) {
checkParent();
mCounterInt64.setValue(val);
@@ -320,10 +616,7 @@
return this;
}
- /**
- * Sets a double counter value on the event.
- *
- */
+ @Override
public Builder setCounter(double val) {
checkParent();
mCounterDouble.setValue(val);
@@ -331,9 +624,7 @@
return this;
}
- /**
- * Adds a proto field with field id {@code id} and value {@code val}.
- */
+ @Override
public Builder addField(long id, long val) {
checkContainer();
FieldInt64 field = mFieldInt64Cache.get(FieldInt64::new);
@@ -342,9 +633,7 @@
return this;
}
- /**
- * Adds a proto field with field id {@code id} and value {@code val}.
- */
+ @Override
public Builder addField(long id, double val) {
checkContainer();
FieldDouble field = mFieldDoubleCache.get(FieldDouble::new);
@@ -353,35 +642,24 @@
return this;
}
- /**
- * Adds a proto field with field id {@code id} and value {@code val}.
- */
- public Builder addField(long id, String val) {
+ @Override
+ public Builder addField(long id, String val, Object... args) {
checkContainer();
FieldString field = mFieldStringCache.get(FieldString::new);
- field.setValue(id, val);
+ field.setValue(id, toString(val, args));
mCurrentContainer.addField(field);
return this;
}
- /**
- * Begins a proto field with field
- * Fields can be added from this point and there must be a corresponding
- * {@link endProto}.
- *
- * The proto field is a singleton and all proto fields get added inside the
- * one {@link beginProto} and {@link endProto} within the {@link Builder}.
- */
+ @Override
public Builder beginProto() {
checkParent();
mProto.clearFields();
mExtra.addPerfettoPointer(mProto);
- return mBuilderCache.get(Builder::new).init(this, mProto);
+ return mBuilderCache.get(BuilderImpl::new).initInternal(this, mProto);
}
- /**
- * Ends a proto field.
- */
+ @Override
public Builder endProto() {
if (mParent == null || mCurrentContainer == null) {
throw new IllegalStateException("No proto to end");
@@ -389,22 +667,16 @@
return mParent;
}
- /**
- * Begins a nested proto field with field id {@code id}.
- * Fields can be added from this point and there must be a corresponding
- * {@link endNested}.
- */
+ @Override
public Builder beginNested(long id) {
checkContainer();
FieldNested field = mFieldNestedCache.get(FieldNested::new);
field.setId(id);
mCurrentContainer.addField(field);
- return mBuilderCache.get(Builder::new).init(this, field);
+ return mBuilderCache.get(BuilderImpl::new).initInternal(this, field);
}
- /**
- * Ends a nested proto field.
- */
+ @Override
public Builder endNested() {
if (mParent == null || mCurrentContainer == null) {
throw new IllegalStateException("No nested field to end");
@@ -412,21 +684,15 @@
return mParent;
}
- /**
- * Initializes a {@link Builder}.
- */
- public Builder init(Builder parent, FieldContainer container) {
+ private static String toString(String val, Object... args) {
+ return args == null || args.length == 0 ? val : String.format(val, args);
+ }
+
+ private Builder initInternal(Builder parent, FieldContainer container) {
mParent = parent;
mCurrentContainer = container;
mIsBuilt = false;
- if (mParent == null) {
- if (mExtra.mIsInUse) {
- throw new IllegalStateException("Cannot create a new builder when another"
- + " extra is in use");
- }
- mExtra.mIsInUse = true;
- }
return this;
}
@@ -439,9 +705,8 @@
private void checkParent() {
checkState();
- if (mParent != null) {
- throw new IllegalStateException(
- "This builder has already been used. Create a new builder for another event.");
+ if (!this.equals(mParent)) {
+ throw new IllegalStateException("Operation not supported for proto");
}
}
@@ -458,7 +723,14 @@
* Start a {@link Builder} to build a {@link PerfettoTrackEventExtra}.
*/
public static Builder builder() {
- return sTrackEventExtra.get().mBuilderCache.get(Builder::new).init(null, null);
+ return sTrackEventExtra.get().mBuilderCache.get(BuilderImpl::new).initInternal(null, null);
+ }
+
+ /**
+ * Returns a no-op {@link Builder}. Useful if a category is disabled.
+ */
+ public static Builder noOpBuilder() {
+ return NO_OP_BUILDER;
}
private final RingBuffer<NamedTrack> mNamedTrackCache =
@@ -476,7 +748,7 @@
private final Pool<FieldString> mFieldStringCache = new Pool(DEFAULT_EXTRA_CACHE_SIZE);
private final Pool<FieldNested> mFieldNestedCache = new Pool(DEFAULT_EXTRA_CACHE_SIZE);
private final Pool<Flow> mFlowCache = new Pool(DEFAULT_EXTRA_CACHE_SIZE);
- private final Pool<Builder> mBuilderCache = new Pool(DEFAULT_EXTRA_CACHE_SIZE);
+ private final Pool<BuilderImpl> mBuilderCache = new Pool(DEFAULT_EXTRA_CACHE_SIZE);
private static final NativeAllocationRegistry sRegistry =
NativeAllocationRegistry.createMalloced(
@@ -509,7 +781,6 @@
*/
public void reset() {
native_clear_args(mPtr);
- mIsInUse = false;
}
private CounterInt64 getCounterInt64() {
@@ -1078,4 +1349,6 @@
private static native void native_add_arg(long ptr, long extraPtr);
@CriticalNative
private static native void native_clear_args(long ptr);
+ @FastNative
+ private static native void native_emit(int type, long tag, String name, long ptr);
}
diff --git a/core/java/android/os/PowerManagerInternal.java b/core/java/android/os/PowerManagerInternal.java
index 9435f4d..77b6d70 100644
--- a/core/java/android/os/PowerManagerInternal.java
+++ b/core/java/android/os/PowerManagerInternal.java
@@ -351,4 +351,10 @@
* 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/os/ZygoteProcess.java b/core/java/android/os/ZygoteProcess.java
index 73a74b2..2d487b1 100644
--- a/core/java/android/os/ZygoteProcess.java
+++ b/core/java/android/os/ZygoteProcess.java
@@ -1319,6 +1319,6 @@
throw new RuntimeException("Starting child-zygote through Zygote failed", ex);
}
- return new ChildZygoteProcess(serverAddress, result.pid);
+ return new ChildZygoteProcess(serverAddress, result.pid, uid);
}
}
diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig
index 8b83698..b12433a7 100644
--- a/core/java/android/os/flags.aconfig
+++ b/core/java/android/os/flags.aconfig
@@ -150,6 +150,13 @@
}
flag {
+ name: "app_zygote_retry_start"
+ namespace: "arc_next"
+ description: "Guard the new added retry logic in app zygote."
+ bug: "361799815"
+}
+
+flag {
name: "battery_part_status_api"
is_exported: true
namespace: "phoenix"
diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java
index 343d752..5188204 100644
--- a/core/java/android/permission/PermissionManager.java
+++ b/core/java/android/permission/PermissionManager.java
@@ -2045,7 +2045,7 @@
if (deviceId == Context.DEVICE_ID_DEFAULT) {
persistentDeviceId = VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT;
- } else if (android.companion.virtual.flags.Flags.vdmPublicApis()) {
+ } else {
VirtualDeviceManager virtualDeviceManager = mContext.getSystemService(
VirtualDeviceManager.class);
if (virtualDeviceManager != null) {
@@ -2059,9 +2059,6 @@
Slog.e(LOG_TAG, "Cannot find persistent device Id for " + deviceId);
}
}
- } else {
- Slog.e(LOG_TAG, "vdmPublicApis flag is not enabled when device Id " + deviceId
- + "is not default.");
}
return persistentDeviceId;
}
diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig
index 70d8891..a480a3b 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -493,3 +493,9 @@
bug: "341941666"
}
+flag {
+ name: "sqlite_discrete_op_event_logging_enabled"
+ namespace: "permissions"
+ description: "Collect sqlite performance metrics for discrete ops."
+ bug: "377584611"
+}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 0cfec2c..2b18b01 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -353,6 +353,18 @@
*/
public static final String ACTION_ONE_HANDED_SETTINGS =
"android.settings.action.ONE_HANDED_SETTINGS";
+
+ /**
+ * Activity Action: Show Double tap power gesture Settings page.
+ * <p>
+ * Input: Nothing
+ * <p>
+ * Output: Nothing
+ * @hide
+ */
+ public static final String ACTION_DOUBLE_TAP_POWER_SETTINGS =
+ "android.settings.action.DOUBLE_TAP_POWER_SETTINGS";
+
/**
* The return values for {@link Settings.Config#set}
* @hide
@@ -6318,6 +6330,17 @@
public static final String TOUCHPAD_SYSTEM_GESTURES = "touchpad_system_gestures";
/**
+ * Whether touchpad acceleration is enabled.
+ *
+ * When enabled, the speed of the pointer will increase as the user moves their
+ * finger faster on the touchpad.
+ *
+ * @hide
+ */
+ public static final String TOUCHPAD_ACCELERATION_ENABLED =
+ "touchpad_acceleration_enabled";
+
+ /**
* Whether to enable reversed vertical scrolling for connected mice.
*
* When enabled, scrolling down on the mouse wheel will move the screen up and vice versa.
@@ -6612,6 +6635,7 @@
PRIVATE_SETTINGS.add(TOUCHPAD_TAP_DRAGGING);
PRIVATE_SETTINGS.add(TOUCHPAD_RIGHT_CLICK_ZONE);
PRIVATE_SETTINGS.add(TOUCHPAD_SYSTEM_GESTURES);
+ PRIVATE_SETTINGS.add(TOUCHPAD_ACCELERATION_ENABLED);
PRIVATE_SETTINGS.add(CAMERA_FLASH_NOTIFICATION);
PRIVATE_SETTINGS.add(SCREEN_FLASH_NOTIFICATION);
PRIVATE_SETTINGS.add(SCREEN_FLASH_NOTIFICATION_COLOR);
@@ -9314,6 +9338,16 @@
"accessibility_autoclick_cursor_area_size";
/**
+ * Setting that specifies whether minor cursor movement will be ignored when
+ * {@link #ACCESSIBILITY_AUTOCLICK_ENABLED} is set.
+ *
+ * @see #ACCESSIBILITY_AUTOCLICK_ENABLED
+ * @hide
+ */
+ public static final String ACCESSIBILITY_AUTOCLICK_IGNORE_MINOR_CURSOR_MOVEMENT =
+ "accessibility_autoclick_ignore_minor_cursor_movement";
+
+ /**
* Whether or not larger size icons are used for the pointer of mouse/trackpad for
* accessibility.
* (0 = false, 1 = true)
@@ -12346,12 +12380,6 @@
public static final String CAMERA_EXTENSIONS_FALLBACK = "camera_extensions_fallback";
/**
- * Controls whether contextual suggestions can be shown in the media controls.
- * @hide
- */
- public static final String MEDIA_CONTROLS_RECOMMENDATION = "qs_media_recommend";
-
- /**
* Controls magnification mode when magnification is enabled via a system-wide triple tap
* gesture or the accessibility shortcut.
*
@@ -12869,6 +12897,19 @@
*/
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 4a9e945..792e6ff 100644
--- a/core/java/android/security/flags.aconfig
+++ b/core/java/android/security/flags.aconfig
@@ -43,7 +43,7 @@
flag {
name: "secure_array_zeroization"
- namespace: "platform_security"
+ namespace: "security"
description: "Enable secure array zeroization"
bug: "320392352"
metadata {
@@ -155,4 +155,11 @@
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 1c0a2c0..3ca9d93 100644
--- a/core/java/android/service/dreams/IDreamManager.aidl
+++ b/core/java/android/service/dreams/IDreamManager.aidl
@@ -53,6 +53,8 @@
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/quickaccesswallet/QuickAccessWalletClient.java b/core/java/android/service/quickaccesswallet/QuickAccessWalletClient.java
index b5251db..051885e 100644
--- a/core/java/android/service/quickaccesswallet/QuickAccessWalletClient.java
+++ b/core/java/android/service/quickaccesswallet/QuickAccessWalletClient.java
@@ -25,6 +25,7 @@
import android.content.Context;
import android.content.Intent;
import android.graphics.drawable.Drawable;
+import android.os.UserHandle;
import java.io.Closeable;
import java.util.concurrent.Executor;
@@ -232,6 +233,15 @@
Drawable getTileIcon();
/**
+ * Returns the user that should receive the wallet intents
+ *
+ * @return UserHandle
+ * @hide
+ */
+ @Nullable
+ UserHandle getUser();
+
+ /**
* Returns the service label specified by {@code android:label} in the service manifest entry.
*
* @hide
diff --git a/core/java/android/service/quickaccesswallet/QuickAccessWalletClientImpl.java b/core/java/android/service/quickaccesswallet/QuickAccessWalletClientImpl.java
index 97a4bef..1771642 100644
--- a/core/java/android/service/quickaccesswallet/QuickAccessWalletClientImpl.java
+++ b/core/java/android/service/quickaccesswallet/QuickAccessWalletClientImpl.java
@@ -243,8 +243,9 @@
return null;
}
String packageName = mServiceInfo.getComponentName().getPackageName();
+ int userId = mServiceInfo.getUserId();
String walletActivity = mServiceInfo.getWalletActivity();
- return createIntent(walletActivity, packageName, ACTION_VIEW_WALLET);
+ return createIntent(walletActivity, packageName, userId, ACTION_VIEW_WALLET);
}
@Override
@@ -302,12 +303,15 @@
}
String packageName = mServiceInfo.getComponentName().getPackageName();
String settingsActivity = mServiceInfo.getSettingsActivity();
- return createIntent(settingsActivity, packageName, ACTION_VIEW_WALLET_SETTINGS);
+ return createIntent(settingsActivity, packageName, UserHandle.myUserId(),
+ ACTION_VIEW_WALLET_SETTINGS);
}
@Nullable
- private Intent createIntent(@Nullable String activityName, String packageName, String action) {
- PackageManager pm = mContext.getPackageManager();
+ private Intent createIntent(@Nullable String activityName, String packageName,
+ int userId, String action) {
+ Context userContext = mContext.createContextAsUser(UserHandle.of(userId), 0);
+ PackageManager pm = userContext.getPackageManager();
if (TextUtils.isEmpty(activityName)) {
activityName = queryActivityForAction(pm, packageName, action);
}
@@ -361,6 +365,12 @@
return mServiceInfo == null ? null : mServiceInfo.getTileIcon();
}
+ @Nullable
+ @Override
+ public UserHandle getUser() {
+ return mServiceInfo == null ? null : UserHandle.of(mServiceInfo.getUserId());
+ }
+
@Override
@Nullable
public CharSequence getServiceLabel() {
diff --git a/core/java/android/service/quickaccesswallet/QuickAccessWalletServiceInfo.java b/core/java/android/service/quickaccesswallet/QuickAccessWalletServiceInfo.java
index 8a3f6ce..e1dc6f6 100644
--- a/core/java/android/service/quickaccesswallet/QuickAccessWalletServiceInfo.java
+++ b/core/java/android/service/quickaccesswallet/QuickAccessWalletServiceInfo.java
@@ -16,6 +16,10 @@
package android.service.quickaccesswallet;
+import static android.permission.flags.Flags.walletRoleCrossUserEnabled;
+
+import static com.android.permission.flags.Flags.crossUserRoleEnabled;
+
import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -32,10 +36,12 @@
import android.content.res.XmlResourceParser;
import android.graphics.drawable.Drawable;
import android.os.Binder;
+import android.os.UserHandle;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
+import android.util.Pair;
import android.util.Xml;
import com.android.internal.R;
@@ -59,22 +65,29 @@
private final ServiceInfo mServiceInfo;
private final ServiceMetadata mServiceMetadata;
private final TileServiceMetadata mTileServiceMetadata;
+ private final int mUserId;
private QuickAccessWalletServiceInfo(
@NonNull ServiceInfo serviceInfo,
@NonNull ServiceMetadata metadata,
- @NonNull TileServiceMetadata tileServiceMetadata) {
+ @NonNull TileServiceMetadata tileServiceMetadata,
+ int userId) {
mServiceInfo = serviceInfo;
mServiceMetadata = metadata;
mTileServiceMetadata = tileServiceMetadata;
+ mUserId = userId;
}
@Nullable
static QuickAccessWalletServiceInfo tryCreate(@NonNull Context context) {
String defaultAppPackageName = null;
+ int defaultAppUser = UserHandle.myUserId();
+
if (isWalletRoleAvailable(context)) {
- defaultAppPackageName = getDefaultWalletApp(context);
+ Pair<String, Integer> roleAndUser = getDefaultWalletApp(context);
+ defaultAppPackageName = roleAndUser.first;
+ defaultAppUser = roleAndUser.second;
} else {
ComponentName defaultPaymentApp = getDefaultPaymentApp(context);
if (defaultPaymentApp == null) {
@@ -83,7 +96,8 @@
defaultAppPackageName = defaultPaymentApp.getPackageName();
}
- ServiceInfo serviceInfo = getWalletServiceInfo(context, defaultAppPackageName);
+ ServiceInfo serviceInfo = getWalletServiceInfo(context, defaultAppPackageName,
+ defaultAppUser);
if (serviceInfo == null) {
return null;
}
@@ -98,15 +112,32 @@
ServiceMetadata metadata = parseServiceMetadata(context, serviceInfo);
TileServiceMetadata tileServiceMetadata =
new TileServiceMetadata(parseTileServiceMetadata(context, serviceInfo));
- return new QuickAccessWalletServiceInfo(serviceInfo, metadata, tileServiceMetadata);
+ return new QuickAccessWalletServiceInfo(serviceInfo, metadata, tileServiceMetadata,
+ defaultAppUser);
}
- private static String getDefaultWalletApp(Context context) {
+ @NonNull
+ private static Pair<String, Integer> getDefaultWalletApp(Context context) {
+ UserHandle user = UserHandle.of(UserHandle.myUserId());
+
final long token = Binder.clearCallingIdentity();
try {
RoleManager roleManager = context.getSystemService(RoleManager.class);
- List<String> roleHolders = roleManager.getRoleHolders(RoleManager.ROLE_WALLET);
- return roleHolders.isEmpty() ? null : roleHolders.get(0);
+
+ if (walletRoleCrossUserEnabled()
+ && crossUserRoleEnabled()
+ && context.checkCallingOrSelfPermission(
+ Manifest.permission.INTERACT_ACROSS_USERS_FULL)
+ == PackageManager.PERMISSION_GRANTED) {
+ user = roleManager.getActiveUserForRole(RoleManager.ROLE_WALLET);
+ if (user == null) {
+ return new Pair<>(null, UserHandle.myUserId());
+ }
+ }
+ List<String> roleHolders = roleManager.getRoleHoldersAsUser(RoleManager.ROLE_WALLET,
+ user);
+ return new Pair<>(roleHolders.isEmpty() ? null : roleHolders.get(0),
+ user.getIdentifier());
} finally {
Binder.restoreCallingIdentity(token);
}
@@ -128,15 +159,16 @@
return comp == null ? null : ComponentName.unflattenFromString(comp);
}
- private static ServiceInfo getWalletServiceInfo(Context context, String packageName) {
+ private static ServiceInfo getWalletServiceInfo(Context context, String packageName,
+ int userId) {
Intent intent = new Intent(QuickAccessWalletService.SERVICE_INTERFACE);
intent.setPackage(packageName);
List<ResolveInfo> resolveInfos =
- context.getPackageManager().queryIntentServices(intent,
+ context.getPackageManager().queryIntentServicesAsUser(intent,
PackageManager.MATCH_DIRECT_BOOT_AWARE
| PackageManager.MATCH_DIRECT_BOOT_UNAWARE
| PackageManager.MATCH_DEFAULT_ONLY
- | PackageManager.GET_META_DATA);
+ | PackageManager.GET_META_DATA, userId);
return resolveInfos.isEmpty() ? null : resolveInfos.get(0).serviceInfo;
}
@@ -247,6 +279,9 @@
return mServiceInfo.getComponentName();
}
+ int getUserId() {
+ return mUserId;
+ }
/**
* @return the fully qualified name of the activity that hosts the full wallet. If available,
* this intent should be started with the action
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 990b099..4647568 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -325,6 +325,7 @@
IWindowSession mSession;
final Object mLock = new Object();
+ private final Object mSurfaceReleaseLock = new Object();
boolean mOffsetMessageEnqueued;
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
@@ -1075,9 +1076,11 @@
animator.setDuration(DIMMING_ANIMATION_DURATION_MS);
animator.addUpdateListener((ValueAnimator va) -> {
final float dimValue = (float) va.getAnimatedValue();
- if (mBbqSurfaceControl != null) {
- surfaceControlTransaction
- .setAlpha(mBbqSurfaceControl, 1 - dimValue).apply();
+ synchronized (mSurfaceReleaseLock) {
+ if (mBbqSurfaceControl != null && mBbqSurfaceControl.isValid()) {
+ surfaceControlTransaction
+ .setAlpha(mBbqSurfaceControl, 1 - dimValue).apply();
+ }
}
});
animator.addListener(new AnimatorListenerAdapter() {
@@ -2356,35 +2359,39 @@
if (DEBUG) Log.v(TAG, "onDestroy(): " + this);
onDestroy();
- if (mCreated) {
- try {
- if (DEBUG) Log.v(TAG, "Removing window and destroying surface "
- + mSurfaceHolder.getSurface() + " of: " + this);
+ synchronized (mSurfaceReleaseLock) {
+ if (mCreated) {
+ try {
+ if (DEBUG) {
+ Log.v(TAG, "Removing window and destroying surface "
+ + mSurfaceHolder.getSurface() + " of: " + this);
+ }
- if (mInputEventReceiver != null) {
- mInputEventReceiver.dispose();
- mInputEventReceiver = null;
+ if (mInputEventReceiver != null) {
+ mInputEventReceiver.dispose();
+ mInputEventReceiver = null;
+ }
+
+ mSession.remove(mWindow.asBinder());
+ } catch (RemoteException e) {
}
+ mSurfaceHolder.mSurface.release();
+ if (mBlastBufferQueue != null) {
+ mBlastBufferQueue.destroy();
+ mBlastBufferQueue = null;
+ }
+ if (mBbqSurfaceControl != null) {
+ new SurfaceControl.Transaction().remove(mBbqSurfaceControl).apply();
+ mBbqSurfaceControl = null;
+ }
+ mCreated = false;
+ }
- mSession.remove(mWindow.asBinder());
- } catch (RemoteException e) {
+ if (mSurfaceControl != null) {
+ mSurfaceControl.release();
+ mSurfaceControl = null;
+ mRelayoutResult = null;
}
- mSurfaceHolder.mSurface.release();
- if (mBlastBufferQueue != null) {
- mBlastBufferQueue.destroy();
- mBlastBufferQueue = null;
- }
- if (mBbqSurfaceControl != null) {
- new SurfaceControl.Transaction().remove(mBbqSurfaceControl).apply();
- mBbqSurfaceControl = null;
- }
- mCreated = false;
- }
-
- if (mSurfaceControl != null) {
- mSurfaceControl.release();
- mSurfaceControl = null;
- mRelayoutResult = null;
}
}
@@ -2417,9 +2424,10 @@
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/MobileCountries.java b/core/java/android/timezone/MobileCountries.java
new file mode 100644
index 0000000..19ae608
--- /dev/null
+++ b/core/java/android/timezone/MobileCountries.java
@@ -0,0 +1,68 @@
+/*
+ * 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 android.timezone;
+
+import android.annotation.NonNull;
+
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * Information about a telephony network.
+ *
+ * @hide
+ */
+public final class MobileCountries {
+
+ @NonNull
+ private final com.android.i18n.timezone.MobileCountries mDelegate;
+
+ MobileCountries(@NonNull com.android.i18n.timezone.MobileCountries delegate) {
+ mDelegate = Objects.requireNonNull(delegate);
+ }
+
+ /**
+ * Returns the Mobile Country Code of the network.
+ */
+ @NonNull
+ public String getMcc() {
+ return mDelegate.getMcc();
+ }
+
+ /**
+ * Returns the Mobile Country Code of the network.
+ */
+ @NonNull
+ public Set<String> getCountryIsoCodes() {
+ return mDelegate.getCountryIsoCodes();
+ }
+
+ /**
+ * Returns the country in which the network operates as an ISO 3166 alpha-2 (lower case).
+ */
+ @NonNull
+ public String getDefaultCountryIsoCode() {
+ return mDelegate.getDefaultCountryIsoCode();
+ }
+
+ @Override
+ public String toString() {
+ return "MobileCountries{"
+ + "mDelegate=" + mDelegate
+ + '}';
+ }
+}
diff --git a/core/java/android/timezone/TelephonyNetworkFinder.java b/core/java/android/timezone/TelephonyNetworkFinder.java
index c69ddf8..eb50fc2 100644
--- a/core/java/android/timezone/TelephonyNetworkFinder.java
+++ b/core/java/android/timezone/TelephonyNetworkFinder.java
@@ -19,6 +19,8 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import com.android.icu.Flags;
+
import java.util.Objects;
/**
@@ -50,4 +52,19 @@
return telephonyNetworkDelegate != null
? new TelephonyNetwork(telephonyNetworkDelegate) : null;
}
+
+ /**
+ * Returns the countries where a given MCC is in use.
+ */
+ @Nullable
+ public MobileCountries findCountriesByMcc(@NonNull String mcc) {
+ if (!Flags.telephonyLookupMccExtension()) {
+ return null;
+ }
+ Objects.requireNonNull(mcc);
+
+ com.android.i18n.timezone.MobileCountries countriesByMcc =
+ mDelegate.findCountriesByMcc(mcc);
+ return countriesByMcc != null ? new MobileCountries(countriesByMcc) : null;
+ }
}
diff --git a/core/java/android/util/proto/ProtoFieldFilter.java b/core/java/android/util/proto/ProtoFieldFilter.java
new file mode 100644
index 0000000..c3ae106
--- /dev/null
+++ b/core/java/android/util/proto/ProtoFieldFilter.java
@@ -0,0 +1,335 @@
+/*
+ * 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 android.util.proto;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.function.Predicate;
+
+/**
+ * A utility class that reads raw protobuf data from an InputStream
+ * and copies only those fields for which a given predicate returns true.
+ *
+ * <p>
+ * This is a low-level approach that does not fully decode fields
+ * (unless necessary to determine lengths). It simply:
+ * <ul>
+ * <li>Parses each field's tag (varint for field number & wire type)</li>
+ * <li>If {@code includeFn(fieldNumber) == true}, copies
+ * the tag bytes and the field bytes directly to the output</li>
+ * <li>Otherwise, skips that field in the input</li>
+ * </ul>
+ * </p>
+ *
+ * <p>
+ * Because we do not re-encode, unknown or unrecognized fields are copied
+ * <i>verbatim</i> and remain exactly as in the input (useful for partial
+ * parsing or partial transformations).
+ * </p>
+ *
+ * <p>
+ * Note: This class only filters based on top-level field numbers. For length-delimited
+ * fields (including nested messages), the entire contents are either copied or skipped
+ * as a single unit. The class is not capable of nested filtering.
+ * </p>
+ *
+ * @hide
+ */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
+public class ProtoFieldFilter {
+
+ private static final int BUFFER_SIZE_BYTES = 4096;
+
+ private final Predicate<Integer> mFieldPredicate;
+ // General-purpose buffer for reading proto fields and their data
+ private final byte[] mBuffer;
+ // Buffer specifically designated to hold varint values (max 10 bytes in protobuf encoding)
+ private final byte[] mVarIntBuffer = new byte[10];
+
+ /**
+ * Constructs a ProtoFieldFilter with a predicate that considers depth.
+ *
+ * @param fieldPredicate A predicate returning true if the given fieldNumber should be
+ * included in the output.
+ * @param bufferSize The size of the internal buffer used for processing proto fields.
+ * Larger buffers may improve performance when processing large
+ * length-delimited fields.
+ */
+ public ProtoFieldFilter(Predicate<Integer> fieldPredicate, int bufferSize) {
+ this.mFieldPredicate = fieldPredicate;
+ this.mBuffer = new byte[bufferSize];
+ }
+
+ /**
+ * Constructs a ProtoFieldFilter with a predicate that considers depth and
+ * uses a default buffer size.
+ *
+ * @param fieldPredicate A predicate returning true if the given fieldNumber should be
+ * included in the output.
+ */
+ public ProtoFieldFilter(Predicate<Integer> fieldPredicate) {
+ this(fieldPredicate, BUFFER_SIZE_BYTES);
+ }
+
+ /**
+ * Reads raw protobuf data from {@code in} and writes only those fields
+ * passing {@code includeFn} to {@code out}. The predicate is given
+ * (fieldNumber, wireType) for each encountered field.
+ *
+ * @param in The input stream of protobuf data
+ * @param out The output stream to which we write the filtered protobuf
+ * @throws IOException If reading or writing fails, or if the protobuf data is corrupted
+ */
+ public void filter(InputStream in, OutputStream out) throws IOException {
+ int tagBytesLength;
+ while ((tagBytesLength = readRawVarint(in)) > 0) {
+ // Parse the varint loaded in mVarIntBuffer, through readRawVarint
+ long tagVal = parseVarint(mVarIntBuffer, tagBytesLength);
+ int fieldNumber = (int) (tagVal >>> ProtoStream.FIELD_ID_SHIFT);
+ int wireType = (int) (tagVal & ProtoStream.WIRE_TYPE_MASK);
+
+ if (fieldNumber == 0) {
+ break;
+ }
+ if (mFieldPredicate.test(fieldNumber)) {
+ out.write(mVarIntBuffer, 0, tagBytesLength);
+ copyFieldData(in, out, wireType);
+ } else {
+ skipFieldData(in, wireType);
+ }
+ }
+ }
+
+ /**
+ * Reads a varint (up to 10 bytes) from the stream as raw bytes
+ * and returns it in a byte array. If the stream is at EOF, returns null.
+ *
+ * @param in The input stream
+ * @return the size of the varint bytes moved to mVarIntBuffer
+ * @throws IOException If an error occurs, or if we detect a malformed varint
+ */
+ private int readRawVarint(InputStream in) throws IOException {
+ // We attempt to read 1 byte. If none available => null
+ int b = in.read();
+ if (b < 0) {
+ return 0;
+ }
+ int count = 0;
+ mVarIntBuffer[count++] = (byte) b;
+ // If the continuation bit is set, we continue
+ while ((b & 0x80) != 0) {
+ // read next byte
+ b = in.read();
+ // EOF
+ if (b < 0) {
+ throw new IOException("Malformed varint: reached EOF mid-varint");
+ }
+ // max 10 bytes for varint 64
+ if (count >= 10) {
+ throw new IOException("Malformed varint: too many bytes (max 10)");
+ }
+ mVarIntBuffer[count++] = (byte) b;
+ }
+ return count;
+ }
+
+ /**
+ * Parses a varint from the given raw bytes and returns it as a long.
+ *
+ * @param rawVarint The bytes representing the varint
+ * @param byteLength The number of bytes to read from rawVarint
+ * @return The decoded long value
+ */
+ private static long parseVarint(byte[] rawVarint, int byteLength) throws IOException {
+ long result = 0;
+ int shift = 0;
+ for (int i = 0; i < byteLength; i++) {
+ result |= ((rawVarint[i] & 0x7F) << shift);
+ shift += 7;
+ if (shift > 63) {
+ throw new IOException("Malformed varint: exceeds 64 bits");
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Copies the wire data for a single field from {@code in} to {@code out},
+ * assuming we have already read the field's tag.
+ *
+ * @param in The input stream (protobuf data)
+ * @param out The output stream
+ * @param wireType The wire type (0=varint, 1=fixed64, 2=length-delim, 5=fixed32)
+ * @throws IOException if reading/writing fails or data is malformed
+ */
+ private void copyFieldData(InputStream in, OutputStream out, int wireType)
+ throws IOException {
+ switch (wireType) {
+ case ProtoStream.WIRE_TYPE_VARINT:
+ copyVarint(in, out);
+ break;
+ case ProtoStream.WIRE_TYPE_FIXED64:
+ copyFixed(in, out, 8);
+ break;
+ case ProtoStream.WIRE_TYPE_LENGTH_DELIMITED:
+ copyLengthDelimited(in, out);
+ break;
+ case ProtoStream.WIRE_TYPE_FIXED32:
+ copyFixed(in, out, 4);
+ break;
+ // case WIRE_TYPE_START_GROUP:
+ // Not Supported
+ // case WIRE_TYPE_END_GROUP:
+ // Not Supported
+ default:
+ // Error or unrecognized wire type
+ throw new IOException("Unknown or unsupported wire type: " + wireType);
+ }
+ }
+
+ /**
+ * Skips the wire data for a single field from {@code in},
+ * assuming the field's tag was already read.
+ */
+ private void skipFieldData(InputStream in, int wireType) throws IOException {
+ switch (wireType) {
+ case ProtoStream.WIRE_TYPE_VARINT:
+ skipVarint(in);
+ break;
+ case ProtoStream.WIRE_TYPE_FIXED64:
+ skipBytes(in, 8);
+ break;
+ case ProtoStream.WIRE_TYPE_LENGTH_DELIMITED:
+ skipLengthDelimited(in);
+ break;
+ case ProtoStream.WIRE_TYPE_FIXED32:
+ skipBytes(in, 4);
+ break;
+ // case WIRE_TYPE_START_GROUP:
+ // Not Supported
+ // case WIRE_TYPE_END_GROUP:
+ // Not Supported
+ default:
+ throw new IOException("Unknown or unsupported wire type: " + wireType);
+ }
+ }
+
+ /** Copies a varint (the field's value) from in to out. */
+ private static void copyVarint(InputStream in, OutputStream out) throws IOException {
+ while (true) {
+ int b = in.read();
+ if (b < 0) {
+ throw new IOException("EOF while copying varint");
+ }
+ out.write(b);
+ if ((b & 0x80) == 0) {
+ break;
+ }
+ }
+ }
+
+ /**
+ * Copies exactly {@code length} bytes from {@code in} to {@code out}.
+ */
+ private void copyFixed(InputStream in, OutputStream out,
+ int length) throws IOException {
+ int toRead = length;
+ while (toRead > 0) {
+ int chunk = Math.min(toRead, mBuffer.length);
+ int readCount = in.read(mBuffer, 0, chunk);
+ if (readCount < 0) {
+ throw new IOException("EOF while copying fixed" + (length * 8) + " field");
+ }
+ out.write(mBuffer, 0, readCount);
+ toRead -= readCount;
+ }
+ }
+
+ /** Copies a length-delimited field */
+ private void copyLengthDelimited(InputStream in,
+ OutputStream out) throws IOException {
+ // 1) read length varint (and copy)
+ int lengthVarintLength = readRawVarint(in);
+ if (lengthVarintLength <= 0) {
+ throw new IOException("EOF reading length for length-delimited field");
+ }
+ out.write(mVarIntBuffer, 0, lengthVarintLength);
+
+ long lengthVal = parseVarint(mVarIntBuffer, lengthVarintLength);
+ if (lengthVal < 0 || lengthVal > Integer.MAX_VALUE) {
+ throw new IOException("Invalid length for length-delimited field: " + lengthVal);
+ }
+
+ // 2) copy that many bytes
+ copyFixed(in, out, (int) lengthVal);
+ }
+
+ /** Skips a varint in the input (does not write anything). */
+ private static void skipVarint(InputStream in) throws IOException {
+ int bytesSkipped = 0;
+ while (true) {
+ int b = in.read();
+ if (b < 0) {
+ throw new IOException("EOF while skipping varint");
+ }
+ if ((b & 0x80) == 0) {
+ break;
+ }
+ bytesSkipped++;
+ if (bytesSkipped > 10) {
+ throw new IOException("Malformed varint: exceeds maximum length of 10 bytes");
+ }
+ }
+ }
+
+ /** Skips exactly n bytes. */
+ private void skipBytes(InputStream in, long n) throws IOException {
+ long skipped = in.skip(n);
+ // If skip fails, fallback to reading the remaining bytes
+ if (skipped < n) {
+ long bytesRemaining = n - skipped;
+
+ while (bytesRemaining > 0) {
+ int bytesToRead = (int) Math.min(bytesRemaining, mBuffer.length);
+ int bytesRead = in.read(mBuffer, 0, bytesToRead);
+ if (bytesRemaining < 0) {
+ throw new IOException("EOF while skipping bytes");
+ }
+ bytesRemaining -= bytesRead;
+ }
+ }
+ }
+
+ /**
+ * Skips a length-delimited field.
+ * 1) read the length as varint,
+ * 2) skip that many bytes
+ */
+ private void skipLengthDelimited(InputStream in) throws IOException {
+ int lengthVarintLength = readRawVarint(in);
+ if (lengthVarintLength <= 0) {
+ throw new IOException("EOF reading length for length-delimited field");
+ }
+ long lengthVal = parseVarint(mVarIntBuffer, lengthVarintLength);
+ if (lengthVal < 0 || lengthVal > Integer.MAX_VALUE) {
+ throw new IOException("Invalid length to skip: " + lengthVal);
+ }
+ skipBytes(in, lengthVal);
+ }
+
+}
diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java
index 992790e..053ccdd 100644
--- a/core/java/android/view/Choreographer.java
+++ b/core/java/android/view/Choreographer.java
@@ -250,11 +250,14 @@
/**
* Set flag to indicate that client is blocked waiting for buffer release and
- * buffer stuffing recovery should soon begin.
+ * buffer stuffing recovery should soon begin. This is provided with the
+ * duration of time in nanoseconds that the client was blocked for.
* @hide
*/
- public void onWaitForBufferRelease() {
- mBufferStuffingState.isStuffed.set(true);
+ public void onWaitForBufferRelease(long durationNanos) {
+ if (durationNanos > mLastFrameIntervalNanos / 2) {
+ mBufferStuffingState.isStuffed.set(true);
+ }
}
/**
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 0280433..06eb042 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -227,9 +227,6 @@
@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 d7cf3e8..311fbee 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -3026,6 +3026,7 @@
// 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 @@
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 121c01b..0b528bf 100644
--- a/core/java/android/view/SurfaceControlRegistry.java
+++ b/core/java/android/view/SurfaceControlRegistry.java
@@ -334,13 +334,17 @@
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/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 780e761..dd32947 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -2391,4 +2391,9 @@
}
}
}
+
+ @Override
+ public CharSequence getAccessibilityClassName() {
+ return SurfaceView.class.getName();
+ }
}
diff --git a/core/java/android/view/TextureView.java b/core/java/android/view/TextureView.java
index ebc86ee..0c6eaae 100644
--- a/core/java/android/view/TextureView.java
+++ b/core/java/android/view/TextureView.java
@@ -918,6 +918,11 @@
mLastFrameTimeMillis = now;
}
+ @Override
+ public CharSequence getAccessibilityClassName() {
+ return TextureView.class.getName();
+ }
+
@UnsupportedAppUsage
private final SurfaceTexture.OnFrameAvailableListener mUpdateListener =
surfaceTexture -> {
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index d88b6d6..7206906 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -28365,10 +28365,8 @@
if (android.os.Flags.adpfMeasureDuringInputEventBoost()) {
final boolean notifyRenderer = hasExpensiveMeasuresDuringInputEvent();
if (notifyRenderer) {
- Trace.traceBegin(Trace.TRACE_TAG_VIEW,
- "CPU_LOAD_UP: " + "hasExpensiveMeasuresDuringInputEvent");
- getViewRootImpl().notifyRendererOfExpensiveFrame();
- Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+ getViewRootImpl().notifyRendererOfExpensiveFrame(
+ "ADPF_SendHint: hasExpensiveMeasuresDuringInputEvent");
}
}
// measure ourselves, this should set the measured dimension flag back
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index a657acd..cd8a85a 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -2759,14 +2759,15 @@
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();
@@ -2967,6 +2968,20 @@
}
}
+ /**
+ * Same as notifyRendererOfExpensiveFrame(), but adding {@code reason} for tracing.
+ *
+ * @hide
+ */
+ public void notifyRendererOfExpensiveFrame(String reason) {
+ Trace.traceBegin(Trace.TRACE_TAG_VIEW, reason);
+ try {
+ notifyRendererOfExpensiveFrame();
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+ }
+ }
+
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
void scheduleTraversals() {
if (!mTraversalScheduled) {
@@ -5551,6 +5566,9 @@
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/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java
index 25bd713..a5da0c3 100644
--- a/core/java/android/view/WindowManagerGlobal.java
+++ b/core/java/android/view/WindowManagerGlobal.java
@@ -50,6 +50,7 @@
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.policy.PhoneWindow;
import com.android.internal.util.FastPrintWriter;
import java.io.FileDescriptor;
@@ -375,7 +376,8 @@
if (context != null && wparams.type > LAST_APPLICATION_WINDOW) {
final TypedArray styles = context.obtainStyledAttributes(R.styleable.Window);
- if (styles.getBoolean(R.styleable.Window_windowOptOutEdgeToEdgeEnforcement, false)) {
+ if (PhoneWindow.isOptingOutEdgeToEdgeEnforcement(
+ context.getApplicationInfo(), true /* local */, styles)) {
wparams.privateFlags |= PRIVATE_FLAG_OPT_OUT_EDGE_TO_EDGE;
}
styles.recycle();
diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java
index 64277b1..d267c94 100644
--- a/core/java/android/view/accessibility/AccessibilityManager.java
+++ b/core/java/android/view/accessibility/AccessibilityManager.java
@@ -160,6 +160,9 @@
/** @hide */
public static final int AUTOCLICK_CURSOR_AREA_INCREMENT_SIZE = 20;
+ /** @hide */
+ public static final boolean AUTOCLICK_IGNORE_MINOR_CURSOR_MOVEMENT_DEFAULT = false;
+
/**
* Activity action: Launch UI to manage which accessibility service or feature is assigned
* to the navigation bar Accessibility button.
diff --git a/core/java/android/view/contentcapture/ChildContentCaptureSession.java b/core/java/android/view/contentcapture/ChildContentCaptureSession.java
index 8baa55f..6e2e100 100644
--- a/core/java/android/view/contentcapture/ChildContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/ChildContentCaptureSession.java
@@ -74,8 +74,8 @@
}
@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 724e8fa..b7a77d7 100644
--- a/core/java/android/view/contentcapture/ContentCaptureManager.java
+++ b/core/java/android/view/contentcapture/ContentCaptureManager.java
@@ -52,7 +52,6 @@
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 @@
mContext,
this,
prepareUiHandler(),
- prepareContentCaptureHandler(),
mService
);
if (sVerbose) Log.v(TAG, "getMainContentCaptureSession(): created " + mMainSession);
@@ -616,15 +614,6 @@
@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 @@
@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 9aeec20..791a6f4 100644
--- a/core/java/android/view/contentcapture/ContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/ContentCaptureSession.java
@@ -286,6 +286,9 @@
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 @@
/**
* 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 2fb78c0..29cae85 100644
--- a/core/java/android/view/contentcapture/MainContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/MainContentCaptureSession.java
@@ -57,10 +57,12 @@
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 @@
@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 @@
@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 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 @@
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 @@
} 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 @@
}
/**
- * 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
}
mDirectServiceInterface = null;
mContentProtectionEventProcessor = null;
- mContentCaptureHandler.removeMessages(MSG_FLUSH);
+ if (mContentCaptureHandler != null) {
+ mContentCaptureHandler.removeMessages(MSG_FLUSH);
+ }
}
@Override
@@ -917,6 +997,10 @@
* 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 @@
* 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 @@
* </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 @@
}
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 f709ed7..9df8350 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 @@
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 0a83bdc..8fef2d7 100644
--- a/core/java/android/view/inputmethod/InputMethodInfo.java
+++ b/core/java/android/view/inputmethod/InputMethodInfo.java
@@ -766,7 +766,6 @@
* 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/CompoundButton.java b/core/java/android/widget/CompoundButton.java
index ed6ec32..3cc0042 100644
--- a/core/java/android/widget/CompoundButton.java
+++ b/core/java/android/widget/CompoundButton.java
@@ -16,6 +16,8 @@
package android.widget;
+import static android.view.accessibility.Flags.triStateChecked;
+
import android.annotation.DrawableRes;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -209,6 +211,10 @@
mCheckedFromResource = false;
mChecked = checked;
refreshDrawableState();
+ if (triStateChecked()) {
+ notifyViewAccessibilityStateChangedIfNeeded(
+ AccessibilityEvent.CONTENT_CHANGE_TYPE_CHECKED);
+ }
// Avoid infinite recursions if setChecked() is called from a listener
if (mBroadcasting) {
@@ -490,7 +496,12 @@
public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfoInternal(info);
info.setCheckable(true);
- info.setChecked(mChecked);
+ if (triStateChecked()) {
+ info.setChecked(mChecked ? AccessibilityNodeInfo.CHECKED_STATE_TRUE :
+ AccessibilityNodeInfo.CHECKED_STATE_FALSE);
+ } else {
+ info.setChecked(mChecked);
+ }
}
@Override
diff --git a/core/java/android/widget/EditText.java b/core/java/android/widget/EditText.java
index 3e0161a..2056e22 100644
--- a/core/java/android/widget/EditText.java
+++ b/core/java/android/widget/EditText.java
@@ -57,8 +57,8 @@
* Choosing the input type configures the keyboard type that is shown, acceptable characters,
* and appearance of the edit text.
* For example, if you want to accept a secret number, like a unique pin or serial number,
- * you can set inputType to "numericPassword".
- * An inputType of "numericPassword" results in an edit text that accepts numbers only,
+ * you can set inputType to {@link android.R.styleable#TextView_inputType numberPassword}.
+ * An input type of {@code numberPassword} results in an edit text that accepts numbers only,
* shows a numeric keyboard when focused, and masks the text that is entered for privacy.
* <p>
* See the <a href="{@docRoot}guide/topics/ui/controls/text.html">Text Fields</a>
diff --git a/core/java/android/widget/Magnifier.java b/core/java/android/widget/Magnifier.java
index 33890b8..f70bf97 100644
--- a/core/java/android/widget/Magnifier.java
+++ b/core/java/android/widget/Magnifier.java
@@ -1021,8 +1021,9 @@
.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/ITaskOrganizerController.aidl b/core/java/android/window/ITaskOrganizerController.aidl
index 1748b9d..8934cf6 100644
--- a/core/java/android/window/ITaskOrganizerController.aidl
+++ b/core/java/android/window/ITaskOrganizerController.aidl
@@ -39,7 +39,12 @@
*/
void unregisterTaskOrganizer(ITaskOrganizer organizer);
- /** Creates a persistent root task in WM for a particular windowing-mode. */
+ /**
+ * Creates a persistent root task in WM for a particular windowing-mode.
+ *
+ * It may be removed using {@link #deleteRootTask} or through
+ * {@link WindowContainerTransaction#removeRootTask}.
+ */
void createRootTask(int displayId, int windowingMode, IBinder launchCookie,
boolean removeWithTaskOrganizer);
diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java
index 2c21417..ddbf9e4 100644
--- a/core/java/android/window/TransitionInfo.java
+++ b/core/java/android/window/TransitionInfo.java
@@ -211,7 +211,7 @@
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/android/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java
index 02f8e2f..ce0ccd5 100644
--- a/core/java/android/window/WindowContainerTransaction.java
+++ b/core/java/android/window/WindowContainerTransaction.java
@@ -614,6 +614,10 @@
/**
* Finds and removes a task and its children using its container token. The task is removed
* from recents.
+ *
+ * If the task is a root task, its leaves are removed but the root task is not. Use
+ * {@link #removeRootTask(WindowContainerToken)} to remove the root task.
+ *
* @param containerToken ContainerToken of Task to be removed
*/
@NonNull
@@ -623,6 +627,19 @@
}
/**
+ * Finds and removes a root task created by an organizer and its leaves using its container
+ * token.
+ *
+ * @param containerToken ContainerToken of the root task to be removed
+ * @hide
+ */
+ @NonNull
+ public WindowContainerTransaction removeRootTask(@NonNull WindowContainerToken containerToken) {
+ mHierarchyOps.add(HierarchyOp.createForRemoveRootTask(containerToken.asBinder()));
+ return this;
+ }
+
+ /**
* Sets whether a container is being drag-resized.
* When {@code true}, the client will reuse a single (larger) surface size to avoid
* continuous allocations on every size change.
@@ -1573,6 +1590,7 @@
public static final int HIERARCHY_OP_TYPE_SET_EXCLUDE_INSETS_TYPES = 21;
public static final int HIERARCHY_OP_TYPE_SET_KEYGUARD_STATE = 22;
public static final int HIERARCHY_OP_TYPE_SET_DISABLE_LAUNCH_ADJACENT = 23;
+ public static final int HIERARCHY_OP_TYPE_REMOVE_ROOT_TASK = 24;
@IntDef(prefix = {"HIERARCHY_OP_TYPE_"}, value = {
HIERARCHY_OP_TYPE_REPARENT,
@@ -1598,7 +1616,8 @@
HIERARCHY_OP_TYPE_RESTORE_BACK_NAVIGATION,
HIERARCHY_OP_TYPE_SET_EXCLUDE_INSETS_TYPES,
HIERARCHY_OP_TYPE_SET_KEYGUARD_STATE,
- HIERARCHY_OP_TYPE_SET_DISABLE_LAUNCH_ADJACENT
+ HIERARCHY_OP_TYPE_SET_DISABLE_LAUNCH_ADJACENT,
+ HIERARCHY_OP_TYPE_REMOVE_ROOT_TASK,
})
@Retention(RetentionPolicy.SOURCE)
public @interface HierarchyOpType {
@@ -1795,6 +1814,18 @@
.build();
}
+ /**
+ * Creates a hierarchy op for deleting a root task
+ *
+ * @hide
+ **/
+ @NonNull
+ public static HierarchyOp createForRemoveRootTask(@NonNull IBinder container) {
+ return new HierarchyOp.Builder(HIERARCHY_OP_TYPE_REMOVE_ROOT_TASK)
+ .setContainer(container)
+ .build();
+ }
+
/** Creates a hierarchy op for clearing adjacent root tasks. */
@NonNull
public static HierarchyOp createForClearAdjacentRoots(@NonNull IBinder root) {
@@ -2012,6 +2043,7 @@
return "removeInsetsFrameProvider";
case HIERARCHY_OP_TYPE_SET_ALWAYS_ON_TOP: return "setAlwaysOnTop";
case HIERARCHY_OP_TYPE_REMOVE_TASK: return "removeTask";
+ case HIERARCHY_OP_TYPE_REMOVE_ROOT_TASK: return "removeRootTask";
case HIERARCHY_OP_TYPE_FINISH_ACTIVITY: return "finishActivity";
case HIERARCHY_OP_TYPE_CLEAR_ADJACENT_ROOTS: return "clearAdjacentRoots";
case HIERARCHY_OP_TYPE_SET_REPARENT_LEAF_TASK_IF_RELAUNCH:
@@ -2096,6 +2128,9 @@
case HIERARCHY_OP_TYPE_REMOVE_TASK:
sb.append("task=").append(mContainer);
break;
+ case HIERARCHY_OP_TYPE_REMOVE_ROOT_TASK:
+ sb.append("rootTask=").append(mContainer);
+ break;
case HIERARCHY_OP_TYPE_FINISH_ACTIVITY:
sb.append("activity=").append(mContainer);
break;
diff --git a/core/java/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig
index f178b0e..1b946af 100644
--- a/core/java/android/window/flags/windowing_sdk.aconfig
+++ b/core/java/android/window/flags/windowing_sdk.aconfig
@@ -178,3 +178,10 @@
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ namespace: "windowing_sdk"
+ name: "safe_region_letterboxing"
+ description: "Enables letterboxing for a safe region"
+ bug: "380132497"
+}
diff --git a/core/java/com/android/internal/app/IntentForwarderActivity.java b/core/java/com/android/internal/app/IntentForwarderActivity.java
index 644d699..9d7bedc 100644
--- a/core/java/com/android/internal/app/IntentForwarderActivity.java
+++ b/core/java/com/android/internal/app/IntentForwarderActivity.java
@@ -689,14 +689,15 @@
}
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 a194535..db65d31 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -929,8 +929,11 @@
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 6ad7fef..e170d66 100644
--- a/core/java/com/android/internal/content/NativeLibraryHelper.java
+++ b/core/java/com/android/internal/content/NativeLibraryHelper.java
@@ -172,8 +172,7 @@
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 @@
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 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 @@
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/jank/Cuj.java b/core/java/com/android/internal/jank/Cuj.java
index 158b526..928fa8c 100644
--- a/core/java/com/android/internal/jank/Cuj.java
+++ b/core/java/com/android/internal/jank/Cuj.java
@@ -265,8 +265,17 @@
*/
public static final int CUJ_LAUNCHER_OVERVIEW_TASK_DISMISS = 121;
+ /**
+ * Track closing task in Desktop Windowing.
+ *
+ * <p> Tracking begins when the CloseDesktopTaskTransitionHandler in Launcher starts
+ * animating the task closure. This is triggered when the close button in the app header is
+ * clicked on a desktop window. </p>
+ */
+ public static final int CUJ_DESKTOP_MODE_CLOSE_TASK = 122;
+
// When adding a CUJ, update this and make sure to also update CUJ_TO_STATSD_INTERACTION_TYPE.
- @VisibleForTesting static final int LAST_CUJ = CUJ_LAUNCHER_OVERVIEW_TASK_DISMISS;
+ @VisibleForTesting static final int LAST_CUJ = CUJ_DESKTOP_MODE_CLOSE_TASK;
/** @hide */
@IntDef({
@@ -379,7 +388,8 @@
CUJ_DESKTOP_MODE_SNAP_RESIZE,
CUJ_DESKTOP_MODE_UNMAXIMIZE_WINDOW,
CUJ_DESKTOP_MODE_ENTER_FROM_OVERVIEW_MENU,
- CUJ_LAUNCHER_OVERVIEW_TASK_DISMISS
+ CUJ_LAUNCHER_OVERVIEW_TASK_DISMISS,
+ CUJ_DESKTOP_MODE_CLOSE_TASK
})
@Retention(RetentionPolicy.SOURCE)
public @interface CujType {}
@@ -503,6 +513,7 @@
CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DESKTOP_MODE_UNMAXIMIZE_WINDOW] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DESKTOP_MODE_UNMAXIMIZE_WINDOW;
CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DESKTOP_MODE_ENTER_FROM_OVERVIEW_MENU] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DESKTOP_MODE_ENTER_FROM_OVERVIEW_MENU;
CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_OVERVIEW_TASK_DISMISS] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_OVERVIEW_TASK_DISMISS;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DESKTOP_MODE_CLOSE_TASK] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DESKTOP_MODE_CLOSE_TASK;
}
private Cuj() {
@@ -741,6 +752,8 @@
return "DESKTOP_MODE_ENTER_FROM_OVERVIEW_MENU";
case CUJ_LAUNCHER_OVERVIEW_TASK_DISMISS:
return "LAUNCHER_OVERVIEW_TASK_DISMISS";
+ case CUJ_DESKTOP_MODE_CLOSE_TASK:
+ return "DESKTOP_MODE_CLOSE_TASK";
}
return "UNKNOWN";
}
diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java
index e0529b3..6c00921 100644
--- a/core/java/com/android/internal/policy/PhoneWindow.java
+++ b/core/java/com/android/internal/policy/PhoneWindow.java
@@ -184,6 +184,14 @@
private static final long ENFORCE_EDGE_TO_EDGE = 309578419;
/**
+ * Disable opting out the edge-to-edge enforcement.
+ * {@link Build.VERSION_CODES#BAKLAVA} or above.
+ */
+ @ChangeId
+ @EnabledSince(targetSdkVersion = Build.VERSION_CODES.BAKLAVA)
+ private static final long DISABLE_OPT_OUT_EDGE_TO_EDGE = 377864165;
+
+ /**
* Override the layout in display cutout mode behavior. This will only apply if the edge to edge
* is not enforced.
*/
@@ -450,7 +458,7 @@
*/
public static boolean isEdgeToEdgeEnforced(ApplicationInfo info, boolean local,
TypedArray windowStyle) {
- return !windowStyle.getBoolean(R.styleable.Window_windowOptOutEdgeToEdgeEnforcement, false)
+ return !isOptingOutEdgeToEdgeEnforcement(info, local, windowStyle)
&& (info.targetSdkVersion >= ENFORCE_EDGE_TO_EDGE_SDK_VERSION
|| (Flags.enforceEdgeToEdge() && (local
// Calling this doesn't require a permission.
@@ -459,6 +467,29 @@
: info.isChangeEnabled(ENFORCE_EDGE_TO_EDGE))));
}
+ /**
+ * Returns whether the given application is opting out edge-to-edge enforcement.
+ *
+ * @param info The application to query.
+ * @param local Whether this is called from the process of the given application.
+ * @param windowStyle The style of the window.
+ * @return {@code true} if the edge-to-edge enforcement is opting out. Otherwise, {@code false}.
+ */
+ public static boolean isOptingOutEdgeToEdgeEnforcement(ApplicationInfo info, boolean local,
+ TypedArray windowStyle) {
+ final boolean disabled = Flags.disableOptOutEdgeToEdge()
+ // TODO (b/377864165): Don't exclude system apps after they are ready.
+ && !info.isSystemApp()
+ && (local
+ // Calling this doesn't require a permission.
+ ? CompatChanges.isChangeEnabled(DISABLE_OPT_OUT_EDGE_TO_EDGE)
+ // Calling this requires permissions.
+ : info.isChangeEnabled(DISABLE_OPT_OUT_EDGE_TO_EDGE));
+ return !disabled && windowStyle.getBoolean(
+ R.styleable.Window_windowOptOutEdgeToEdgeEnforcement, false /* default */);
+
+ }
+
@Override
public final void setContainer(Window container) {
super.setContainer(container);
@@ -2486,6 +2517,7 @@
TypedArray a = getWindowStyle();
WindowManager.LayoutParams params = getAttributes();
+ ApplicationInfo appInfo = getContext().getApplicationInfo();
if (false) {
System.out.println("From style:");
@@ -2497,8 +2529,7 @@
System.out.println(s);
}
- mEdgeToEdgeEnforced = isEdgeToEdgeEnforced(
- getContext().getApplicationInfo(), true /* local */, a);
+ mEdgeToEdgeEnforced = isEdgeToEdgeEnforced(appInfo, true /* local */, a);
if (mEdgeToEdgeEnforced) {
getAttributes().privateFlags |= PRIVATE_FLAG_EDGE_TO_EDGE_ENFORCED;
mDecorFitsSystemWindows = false;
@@ -2507,8 +2538,7 @@
// mNavigationBarColor is not reset here because it might be used to draw the scrim.
}
if (CompatChanges.isChangeEnabled(OVERRIDE_LAYOUT_IN_DISPLAY_CUTOUT_MODE)
- && !a.getBoolean(R.styleable.Window_windowOptOutEdgeToEdgeEnforcement,
- false /* defValue */)) {
+ && !isOptingOutEdgeToEdgeEnforcement(appInfo, true /* local */, a)) {
getAttributes().privateFlags |= PRIVATE_FLAG_OVERRIDE_LAYOUT_IN_DISPLAY_CUTOUT_MODE;
}
diff --git a/core/java/com/android/internal/widget/NotificationProgressBar.java b/core/java/com/android/internal/widget/NotificationProgressBar.java
index 1b77020..973fd7e 100644
--- a/core/java/com/android/internal/widget/NotificationProgressBar.java
+++ b/core/java/com/android/internal/widget/NotificationProgressBar.java
@@ -47,6 +47,7 @@
import com.android.internal.widget.NotificationProgressDrawable.DrawableSegment;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -161,7 +162,7 @@
final int progress = mProgressModel.getProgress();
final int progressMax = mProgressModel.getProgressMax();
- mParts = processAndConvertToViewParts(mProgressModel.getSegments(),
+ mParts = processModelAndConvertToViewParts(mProgressModel.getSegments(),
mProgressModel.getPoints(),
progress,
progressMax);
@@ -439,23 +440,107 @@
return;
}
- mProgressDrawableParts = processAndConvertToDrawableParts(
+ final float segSegGap = mNotificationProgressDrawable.getSegSegGap();
+ final float segPointGap = mNotificationProgressDrawable.getSegPointGap();
+ final float pointRadius = mNotificationProgressDrawable.getPointRadius();
+ mProgressDrawableParts = processPartsAndConvertToDrawableParts(
mParts,
width,
- mNotificationProgressDrawable.getSegSegGap(),
- mNotificationProgressDrawable.getSegPointGap(),
- mNotificationProgressDrawable.getPointRadius(),
+ segSegGap,
+ segPointGap,
+ pointRadius,
mHasTrackerIcon
);
- Pair<List<DrawablePart>, Float> p = maybeStretchAndRescaleSegments(
- mParts,
- mProgressDrawableParts,
- mNotificationProgressDrawable.getSegmentMinWidth(),
- mNotificationProgressDrawable.getPointRadius(),
- getProgressFraction(),
- width,
- mProgressModel.isStyledByProgress(),
- mHasTrackerIcon ? 0F : mNotificationProgressDrawable.getSegSegGap());
+
+ final float segmentMinWidth = mNotificationProgressDrawable.getSegmentMinWidth();
+ final float progressFraction = getProgressFraction();
+ final boolean isStyledByProgress = mProgressModel.isStyledByProgress();
+ final float progressGap =
+ mHasTrackerIcon ? 0F : mNotificationProgressDrawable.getSegSegGap();
+ Pair<List<DrawablePart>, Float> p = null;
+ try {
+ p = maybeStretchAndRescaleSegments(
+ mParts,
+ mProgressDrawableParts,
+ segmentMinWidth,
+ pointRadius,
+ progressFraction,
+ width,
+ isStyledByProgress,
+ progressGap
+ );
+ } catch (NotEnoughWidthToFitAllPartsException ex) {
+ Log.w(TAG, "Failed to stretch and rescale segments", ex);
+ }
+
+ List<ProgressStyle.Segment> fallbackSegments = null;
+ if (p == null && mProgressModel.getSegments().size() > 1) {
+ Log.w(TAG, "Falling back to single segment");
+ try {
+ fallbackSegments = List.of(new ProgressStyle.Segment(getMax()).setColor(
+ mProgressModel.getSegmentsFallbackColor()
+ == NotificationProgressModel.INVALID_COLOR
+ ? mProgressModel.getSegments().getFirst().getColor()
+ : mProgressModel.getSegmentsFallbackColor()));
+ p = processModelAndConvertToFinalDrawableParts(
+ fallbackSegments,
+ mProgressModel.getPoints(),
+ mProgressModel.getProgress(),
+ getMax(),
+ width,
+ segSegGap,
+ segPointGap,
+ pointRadius,
+ mHasTrackerIcon,
+ segmentMinWidth,
+ isStyledByProgress
+ );
+ } catch (NotEnoughWidthToFitAllPartsException ex) {
+ Log.w(TAG, "Failed to stretch and rescale segments with single segment fallback",
+ ex);
+ }
+ }
+
+ if (p == null && !mProgressModel.getPoints().isEmpty()) {
+ Log.w(TAG, "Falling back to single segment and no points");
+ if (fallbackSegments == null) {
+ fallbackSegments = List.of(new ProgressStyle.Segment(getMax()).setColor(
+ mProgressModel.getSegmentsFallbackColor()
+ == NotificationProgressModel.INVALID_COLOR
+ ? mProgressModel.getSegments().getFirst().getColor()
+ : mProgressModel.getSegmentsFallbackColor()));
+ }
+ try {
+ p = processModelAndConvertToFinalDrawableParts(
+ fallbackSegments,
+ Collections.emptyList(),
+ mProgressModel.getProgress(),
+ getMax(),
+ width,
+ segSegGap,
+ segPointGap,
+ pointRadius,
+ mHasTrackerIcon,
+ segmentMinWidth,
+ isStyledByProgress
+ );
+ } catch (NotEnoughWidthToFitAllPartsException ex) {
+ Log.w(TAG,
+ "Failed to stretch and rescale segments with single segments and no points",
+ ex);
+ }
+ }
+
+ if (p == null) {
+ Log.w(TAG, "Falling back to no stretching and rescaling");
+ p = maybeSplitDrawableSegmentsByProgress(
+ mParts,
+ mProgressDrawableParts,
+ progressFraction,
+ width,
+ isStyledByProgress,
+ progressGap);
+ }
if (DEBUG) {
Log.d(TAG, "Updating NotificationProgressDrawable parts");
@@ -502,7 +587,11 @@
int min = getMin();
int max = getMax();
int range = max - min;
- return range > 0 ? (getProgress() - min) / (float) range : 0;
+ return getProgressFraction(range, (getProgress() - min));
+ }
+
+ private static float getProgressFraction(int progressMax, int progress) {
+ return progressMax > 0 ? progress / (float) progressMax : 0;
}
/**
@@ -636,7 +725,7 @@
* Processes the ProgressStyle data and convert to a list of {@code Part}.
*/
@VisibleForTesting
- public static List<Part> processAndConvertToViewParts(
+ public static List<Part> processModelAndConvertToViewParts(
List<ProgressStyle.Segment> segments,
List<ProgressStyle.Point> points,
int progress,
@@ -796,7 +885,7 @@
* Processes the list of {@code Part} and convert to a list of {@code DrawablePart}.
*/
@VisibleForTesting
- public static List<DrawablePart> processAndConvertToDrawableParts(
+ public static List<DrawablePart> processPartsAndConvertToDrawableParts(
List<Part> parts,
float totalWidth,
float segSegGap,
@@ -817,12 +906,13 @@
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, hasTrackerIcon);
final float end = x + segWidth - endOffset;
drawableParts.add(new DrawableSegment(start, end, segment.mColor, segment.mFaded));
@@ -840,10 +930,10 @@
// 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 +946,14 @@
}
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,
boolean hasTrackerIcon) {
if (nextPart == null) return 0F;
if (nextPart instanceof Segment nextSeg) {
@@ -874,7 +964,7 @@
return segSegGap;
}
- final float pointOffset = (endX == totalWidth) ? pointRadius : 0;
+ final float pointOffset = isSecondToLastPart ? pointRadius : 0;
return segPointGap + pointRadius + pointOffset;
}
@@ -893,7 +983,7 @@
float totalWidth,
boolean isStyledByProgress,
float progressGap
- ) {
+ ) throws NotEnoughWidthToFitAllPartsException {
final List<DrawableSegment> drawableSegments = drawableParts
.stream()
.filter(DrawableSegment.class::isInstance)
@@ -919,16 +1009,8 @@
}
if (totalExcessWidth < 0) {
- // TODO: b/372908709 - throw an error so that the caller can catch and go to fallback
- // option. (instead of return.)
- Log.w(TAG, "Not enough width to satisfy the minimum width for segments.");
- return maybeSplitDrawableSegmentsByProgress(
- parts,
- drawableParts,
- progressFraction,
- totalWidth,
- isStyledByProgress,
- progressGap);
+ throw new NotEnoughWidthToFitAllPartsException(
+ "Not enough width to satisfy the minimum width for segments.");
}
final int nParts = drawableParts.size();
@@ -1002,8 +1084,7 @@
final int nParts = parts.size();
for (int iPart = 0; iPart < nParts; iPart++) {
final Part part = parts.get(iPart);
- if (!(part instanceof Segment)) continue;
- final Segment segment = (Segment) part;
+ if (!(part instanceof Segment segment)) continue;
if (startFraction == progressFraction) {
iPartFirstSegmentToStyle = iPart;
rescaledProgressX = segment.mStart;
@@ -1065,11 +1146,37 @@
}
/**
+ * Processes the ProgressStyle data and convert to a pair of:
+ * - list of processed {@code DrawablePart}.
+ * - location of progress on the stretched and rescaled progress bar.
+ */
+ @VisibleForTesting
+ public static Pair<List<DrawablePart>, Float> processModelAndConvertToFinalDrawableParts(
+ List<ProgressStyle.Segment> segments,
+ List<ProgressStyle.Point> points,
+ int progress,
+ int progressMax,
+ float totalWidth,
+ float segSegGap,
+ float segPointGap,
+ float pointRadius,
+ boolean hasTrackerIcon,
+ float segmentMinWidth,
+ boolean isStyledByProgress
+ ) throws NotEnoughWidthToFitAllPartsException {
+ List<Part> parts = processModelAndConvertToViewParts(segments, points, progress,
+ progressMax);
+ List<DrawablePart> drawableParts = processPartsAndConvertToDrawableParts(parts, totalWidth,
+ segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+ return maybeStretchAndRescaleSegments(parts, drawableParts, segmentMinWidth, pointRadius,
+ getProgressFraction(progressMax, progress), totalWidth, isStyledByProgress,
+ hasTrackerIcon ? 0F : segSegGap);
+ }
+
+ /**
* A part of the progress bar, which is either a {@link Segment} with non-zero length, or a
* {@link Point} with zero length.
*/
- // TODO: b/372908709 - maybe this should be made private? Only test the final
- // NotificationDrawable.Parts.
public interface Part {
}
@@ -1175,4 +1282,10 @@
return Objects.hash(mColor);
}
}
+
+ public static class NotEnoughWidthToFitAllPartsException extends Exception {
+ public NotEnoughWidthToFitAllPartsException(String message) {
+ super(message);
+ }
+ }
}
diff --git a/core/java/com/android/internal/widget/NotificationProgressModel.java b/core/java/com/android/internal/widget/NotificationProgressModel.java
index e8cb37e..7eaf861 100644
--- a/core/java/com/android/internal/widget/NotificationProgressModel.java
+++ b/core/java/com/android/internal/widget/NotificationProgressModel.java
@@ -16,7 +16,6 @@
package com.android.internal.widget;
-
import android.annotation.ColorInt;
import android.annotation.FlaggedApi;
import android.annotation.NonNull;
@@ -45,24 +44,29 @@
*/
@FlaggedApi(Flags.FLAG_API_RICH_ONGOING)
public final class NotificationProgressModel {
- private static final int INVALID_INDETERMINATE_COLOR = Color.TRANSPARENT;
+ public static final int INVALID_COLOR = Color.TRANSPARENT;
private static final String KEY_SEGMENTS = "segments";
private static final String KEY_POINTS = "points";
private static final String KEY_PROGRESS = "progress";
private static final String KEY_IS_STYLED_BY_PROGRESS = "isStyledByProgress";
+ private static final String KEY_SEGMENTS_FALLBACK_COLOR = "segmentsFallColor";
private static final String KEY_INDETERMINATE_COLOR = "indeterminateColor";
private final List<Segment> mSegments;
private final List<Point> mPoints;
private final int mProgress;
private final boolean mIsStyledByProgress;
@ColorInt
+ private final int mSegmentsFallbackColor;
+
+ @ColorInt
private final int mIndeterminateColor;
public NotificationProgressModel(
@NonNull List<Segment> segments,
@NonNull List<Point> points,
int progress,
- boolean isStyledByProgress
+ boolean isStyledByProgress,
+ @ColorInt int segmentsFallbackColor
) {
Preconditions.checkArgument(progress >= 0);
Preconditions.checkArgument(!segments.isEmpty());
@@ -70,17 +74,19 @@
mPoints = points;
mProgress = progress;
mIsStyledByProgress = isStyledByProgress;
- mIndeterminateColor = INVALID_INDETERMINATE_COLOR;
+ mSegmentsFallbackColor = segmentsFallbackColor;
+ mIndeterminateColor = INVALID_COLOR;
}
public NotificationProgressModel(
@ColorInt int indeterminateColor
) {
- Preconditions.checkArgument(indeterminateColor != INVALID_INDETERMINATE_COLOR);
+ Preconditions.checkArgument(indeterminateColor != INVALID_COLOR);
mSegments = Collections.emptyList();
mPoints = Collections.emptyList();
mProgress = 0;
mIsStyledByProgress = false;
+ mSegmentsFallbackColor = INVALID_COLOR;
mIndeterminateColor = indeterminateColor;
}
@@ -105,12 +111,17 @@
}
@ColorInt
+ public int getSegmentsFallbackColor() {
+ return mSegmentsFallbackColor;
+ }
+
+ @ColorInt
public int getIndeterminateColor() {
return mIndeterminateColor;
}
public boolean isIndeterminate() {
- return mIndeterminateColor != INVALID_INDETERMINATE_COLOR;
+ return mIndeterminateColor != INVALID_COLOR;
}
/**
@@ -119,7 +130,7 @@
@NonNull
public Bundle toBundle() {
final Bundle bundle = new Bundle();
- if (mIndeterminateColor != INVALID_INDETERMINATE_COLOR) {
+ if (mIndeterminateColor != INVALID_COLOR) {
bundle.putInt(KEY_INDETERMINATE_COLOR, mIndeterminateColor);
} else {
bundle.putParcelableList(KEY_SEGMENTS,
@@ -128,6 +139,9 @@
Notification.ProgressStyle.getProgressPointsAsBundleList(mPoints));
bundle.putInt(KEY_PROGRESS, mProgress);
bundle.putBoolean(KEY_IS_STYLED_BY_PROGRESS, mIsStyledByProgress);
+ if (mSegmentsFallbackColor != INVALID_COLOR) {
+ bundle.putInt(KEY_SEGMENTS_FALLBACK_COLOR, mSegmentsFallbackColor);
+ }
}
return bundle;
}
@@ -138,8 +152,8 @@
@NonNull
public static NotificationProgressModel fromBundle(@NonNull Bundle bundle) {
final int indeterminateColor = bundle.getInt(KEY_INDETERMINATE_COLOR,
- INVALID_INDETERMINATE_COLOR);
- if (indeterminateColor != INVALID_INDETERMINATE_COLOR) {
+ INVALID_COLOR);
+ if (indeterminateColor != INVALID_COLOR) {
return new NotificationProgressModel(indeterminateColor);
} else {
final List<Segment> segments =
@@ -150,7 +164,10 @@
bundle.getParcelableArrayList(KEY_POINTS, Bundle.class));
final int progress = bundle.getInt(KEY_PROGRESS);
final boolean isStyledByProgress = bundle.getBoolean(KEY_IS_STYLED_BY_PROGRESS);
- return new NotificationProgressModel(segments, points, progress, isStyledByProgress);
+ final int segmentsFallbackColor = bundle.getInt(KEY_SEGMENTS_FALLBACK_COLOR,
+ INVALID_COLOR);
+ return new NotificationProgressModel(segments, points, progress, isStyledByProgress,
+ segmentsFallbackColor);
}
}
@@ -161,6 +178,7 @@
+ ", mPoints=" + mPoints
+ ", mProgress=" + mProgress
+ ", mIsStyledByProgress=" + mIsStyledByProgress
+ + ", mSegmentsFallbackColor=" + mSegmentsFallbackColor
+ ", mIndeterminateColor=" + mIndeterminateColor + "}";
}
@@ -171,6 +189,7 @@
final NotificationProgressModel that = (NotificationProgressModel) o;
return mProgress == that.mProgress
&& mIsStyledByProgress == that.mIsStyledByProgress
+ && mSegmentsFallbackColor == that.mSegmentsFallbackColor
&& mIndeterminateColor == that.mIndeterminateColor
&& Objects.equals(mSegments, that.mSegments)
&& Objects.equals(mPoints, that.mPoints);
@@ -182,6 +201,7 @@
mPoints,
mProgress,
mIsStyledByProgress,
+ mSegmentsFallbackColor,
mIndeterminateColor);
}
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java b/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java
index 26b0d11..f5f4e43 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java
@@ -62,7 +62,7 @@
// We also keep a more fine-grained BUILD number, exposed as
// ID_API_LEVEL = DOCUMENT_API_LEVEL + BUILD
- static final float BUILD = 0.2f;
+ static final float BUILD = 0.3f;
@NonNull ArrayList<Operation> mOperations = new ArrayList<>();
diff --git a/core/java/com/android/internal/widget/remotecompose/core/Operations.java b/core/java/com/android/internal/widget/remotecompose/core/Operations.java
index 9a37a22..0b6a3c4 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/Operations.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/Operations.java
@@ -88,6 +88,8 @@
import com.android.internal.widget.remotecompose.core.operations.layout.animation.AnimationSpec;
import com.android.internal.widget.remotecompose.core.operations.layout.managers.BoxLayout;
import com.android.internal.widget.remotecompose.core.operations.layout.managers.CanvasLayout;
+import com.android.internal.widget.remotecompose.core.operations.layout.managers.CollapsibleColumnLayout;
+import com.android.internal.widget.remotecompose.core.operations.layout.managers.CollapsibleRowLayout;
import com.android.internal.widget.remotecompose.core.operations.layout.managers.ColumnLayout;
import com.android.internal.widget.remotecompose.core.operations.layout.managers.RowLayout;
import com.android.internal.widget.remotecompose.core.operations.layout.managers.StateLayout;
@@ -97,6 +99,7 @@
import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ClipRectModifierOperation;
import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ComponentVisibilityOperation;
import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.GraphicsLayerModifierOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.HeightInModifierOperation;
import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.HeightModifierOperation;
import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.HostActionOperation;
import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.HostNamedActionOperation;
@@ -111,6 +114,7 @@
import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ValueIntegerChangeActionOperation;
import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ValueIntegerExpressionChangeActionOperation;
import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ValueStringChangeActionOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.WidthInModifierOperation;
import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.WidthModifierOperation;
import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ZIndexModifierOperation;
import com.android.internal.widget.remotecompose.core.operations.utilities.IntMap;
@@ -208,7 +212,9 @@
public static final int LAYOUT_CONTENT = 201;
public static final int LAYOUT_BOX = 202;
public static final int LAYOUT_ROW = 203;
+ public static final int LAYOUT_COLLAPSIBLE_ROW = 230;
public static final int LAYOUT_COLUMN = 204;
+ public static final int LAYOUT_COLLAPSIBLE_COLUMN = 233;
public static final int LAYOUT_CANVAS = 205;
public static final int LAYOUT_CANVAS_CONTENT = 207;
public static final int LAYOUT_TEXT = 208;
@@ -218,6 +224,8 @@
public static final int MODIFIER_WIDTH = 16;
public static final int MODIFIER_HEIGHT = 67;
+ public static final int MODIFIER_WIDTH_IN = 231;
+ public static final int MODIFIER_HEIGHT_IN = 232;
public static final int MODIFIER_BACKGROUND = 55;
public static final int MODIFIER_BORDER = 107;
public static final int MODIFIER_PADDING = 58;
@@ -324,6 +332,8 @@
map.put(MODIFIER_WIDTH, WidthModifierOperation::read);
map.put(MODIFIER_HEIGHT, HeightModifierOperation::read);
+ map.put(MODIFIER_WIDTH_IN, WidthInModifierOperation::read);
+ map.put(MODIFIER_HEIGHT_IN, HeightInModifierOperation::read);
map.put(MODIFIER_PADDING, PaddingModifierOperation::read);
map.put(MODIFIER_BACKGROUND, BackgroundModifierOperation::read);
map.put(MODIFIER_BORDER, BorderModifierOperation::read);
@@ -359,7 +369,9 @@
map.put(LAYOUT_CONTENT, LayoutComponentContent::read);
map.put(LAYOUT_BOX, BoxLayout::read);
map.put(LAYOUT_COLUMN, ColumnLayout::read);
+ map.put(LAYOUT_COLLAPSIBLE_COLUMN, CollapsibleColumnLayout::read);
map.put(LAYOUT_ROW, RowLayout::read);
+ map.put(LAYOUT_COLLAPSIBLE_ROW, CollapsibleRowLayout::read);
map.put(LAYOUT_CANVAS, CanvasLayout::read);
map.put(LAYOUT_CANVAS_CONTENT, CanvasContent::read);
map.put(LAYOUT_TEXT, TextLayout::read);
diff --git a/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java
index 39cc997..1cb8fef 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java
@@ -86,6 +86,8 @@
import com.android.internal.widget.remotecompose.core.operations.layout.RootLayoutComponent;
import com.android.internal.widget.remotecompose.core.operations.layout.managers.BoxLayout;
import com.android.internal.widget.remotecompose.core.operations.layout.managers.CanvasLayout;
+import com.android.internal.widget.remotecompose.core.operations.layout.managers.CollapsibleColumnLayout;
+import com.android.internal.widget.remotecompose.core.operations.layout.managers.CollapsibleRowLayout;
import com.android.internal.widget.remotecompose.core.operations.layout.managers.ColumnLayout;
import com.android.internal.widget.remotecompose.core.operations.layout.managers.RowLayout;
import com.android.internal.widget.remotecompose.core.operations.layout.managers.StateLayout;
@@ -691,6 +693,12 @@
return out;
}
+ /**
+ * Append a path to an existing path
+ *
+ * @param id id of the path to append to
+ * @param path the path to append
+ */
public void pathAppend(int id, float... path) {
PathAppend.apply(mBuffer, id, path);
}
@@ -772,8 +780,8 @@
* @param text The text to be drawn
* @param start The index of the first character in text to draw
* @param end (end - 1) is the index of the last character in text to draw
- * @param contextStart
- * @param contextEnd
+ * @param contextStart the context start
+ * @param contextEnd the context end
* @param x The x-coordinate of the origin of the text being drawn
* @param y The y-coordinate of the baseline of the text being drawn
* @param rtl Draw RTTL
@@ -798,8 +806,8 @@
* @param textId The text to be drawn
* @param start The index of the first character in text to draw
* @param end (end - 1) is the index of the last character in text to draw
- * @param contextStart
- * @param contextEnd
+ * @param contextStart the context start
+ * @param contextEnd the context end
* @param x The x-coordinate of the origin of the text being drawn
* @param y The y-coordinate of the baseline of the text being drawn
* @param rtl Draw RTTL
@@ -986,6 +994,11 @@
///////////////////////////////////////////////////////////////////////////////////////////////
+ /**
+ * inflate the buffer into a list of operations
+ *
+ * @param operations the operations list to add to
+ */
public void inflateFromBuffer(@NonNull ArrayList<Operation> operations) {
mBuffer.setIndex(0);
while (mBuffer.available()) {
@@ -1001,6 +1014,12 @@
}
}
+ /**
+ * Read the next operation from the buffer
+ *
+ * @param buffer The buff to read
+ * @param operations the operations list to add to
+ */
public static void readNextOperation(
@NonNull WireBuffer buffer, @NonNull ArrayList<Operation> operations) {
int opId = buffer.readByte();
@@ -1014,6 +1033,11 @@
operation.read(buffer, operations);
}
+ /**
+ * copy the current buffer to a new one
+ *
+ * @return A new RemoteComposeBuffer
+ */
@NonNull
RemoteComposeBuffer copy() {
ArrayList<Operation> operations = new ArrayList<>();
@@ -1022,6 +1046,11 @@
return copyFromOperations(operations, buffer);
}
+ /**
+ * add a set theme
+ *
+ * @param theme The theme to set
+ */
public void setTheme(int theme) {
Theme.apply(mBuffer, theme);
}
@@ -1040,6 +1069,14 @@
return buffer;
}
+ /**
+ * Create a RemoteComposeBuffer from a file
+ *
+ * @param file A file
+ * @param remoteComposeState The RemoteComposeState
+ * @return A RemoteComposeBuffer
+ * @throws IOException if the file cannot be read
+ */
@NonNull
public RemoteComposeBuffer fromFile(
@NonNull File file, @NonNull RemoteComposeState remoteComposeState) throws IOException {
@@ -1048,6 +1085,13 @@
return buffer;
}
+ /**
+ * Create a RemoteComposeBuffer from an InputStream
+ *
+ * @param inputStream An InputStream
+ * @param remoteComposeState The RemoteComposeState
+ * @return A RemoteComposeBuffer
+ */
@NonNull
public static RemoteComposeBuffer fromInputStream(
@NonNull InputStream inputStream, @NonNull RemoteComposeState remoteComposeState) {
@@ -1056,6 +1100,13 @@
return buffer;
}
+ /**
+ * Create a RemoteComposeBuffer from an array of operations
+ *
+ * @param operations An array of operations
+ * @param buffer A RemoteComposeBuffer
+ * @return A RemoteComposeBuffer
+ */
@NonNull
RemoteComposeBuffer copyFromOperations(
@NonNull ArrayList<Operation> operations, @NonNull RemoteComposeBuffer buffer) {
@@ -1834,12 +1885,12 @@
/**
* Add a marquee modifier
*
- * @param iterations
- * @param animationMode
- * @param repeatDelayMillis
- * @param initialDelayMillis
- * @param spacing
- * @param velocity
+ * @param iterations number of iterations
+ * @param animationMode animation mode
+ * @param repeatDelayMillis repeat delay
+ * @param initialDelayMillis initial delay
+ * @param spacing spacing between items
+ * @param velocity velocity of the marquee
*/
public void addModifierMarquee(
int iterations,
@@ -1861,14 +1912,21 @@
/**
* Add a graphics layer
*
- * @param scaleX
- * @param scaleY
- * @param rotationX
- * @param rotationY
- * @param rotationZ
- * @param shadowElevation
- * @param transformOriginX
- * @param transformOriginY
+ * @param scaleX scale x
+ * @param scaleY scale y
+ * @param rotationX rotation in X
+ * @param rotationY rotation in Y
+ * @param rotationZ rotation in Z
+ * @param shadowElevation shadow elevation
+ * @param transformOriginX transform origin x
+ * @param transformOriginY transform origin y
+ * @param alpha alpha value
+ * @param cameraDistance camera distance
+ * @param blendMode blend mode
+ * @param spotShadowColorId spot shadow color
+ * @param ambientShadowColorId ambient shadow color
+ * @param colorFilterId id of color filter
+ * @param renderEffectId id of render effect
*/
public void addModifierGraphicsLayer(
float scaleX,
@@ -1923,14 +1981,32 @@
ClipRectModifierOperation.apply(mBuffer);
}
+ /**
+ * add start of loop
+ *
+ * @param indexId id of the variable
+ * @param from start value
+ * @param step step value
+ * @param until stop value
+ */
public void addLoopStart(int indexId, float from, float step, float until) {
LoopOperation.apply(mBuffer, indexId, from, step, until);
}
+ /** Add a loop end */
public void addLoopEnd() {
ContainerEnd.apply(mBuffer);
}
+ /**
+ * add a state layout
+ *
+ * @param componentId id of the state
+ * @param animationId animation id
+ * @param horizontal horizontal alignment
+ * @param vertical vertical alignment
+ * @param indexId index of the state
+ */
public void addStateLayout(
int componentId, int animationId, int horizontal, int vertical, int indexId) {
mLastComponentId = getComponentId(componentId);
@@ -1966,6 +2042,22 @@
}
/**
+ * Add a row start tag
+ *
+ * @param componentId component id
+ * @param animationId animation id
+ * @param horizontal horizontal alignment
+ * @param vertical vertical alignment
+ * @param spacedBy spacing between items
+ */
+ public void addCollapsibleRowStart(
+ int componentId, int animationId, int horizontal, int vertical, float spacedBy) {
+ mLastComponentId = getComponentId(componentId);
+ CollapsibleRowLayout.apply(
+ mBuffer, mLastComponentId, animationId, horizontal, vertical, spacedBy);
+ }
+
+ /**
* Add a column start tag
*
* @param componentId component id
@@ -1981,6 +2073,22 @@
}
/**
+ * Add a column start tag
+ *
+ * @param componentId component id
+ * @param animationId animation id
+ * @param horizontal horizontal alignment
+ * @param vertical vertical alignment
+ * @param spacedBy spacing between items
+ */
+ public void addCollapsibleColumnStart(
+ int componentId, int animationId, int horizontal, int vertical, float spacedBy) {
+ mLastComponentId = getComponentId(componentId);
+ CollapsibleColumnLayout.apply(
+ mBuffer, mLastComponentId, animationId, horizontal, vertical, spacedBy);
+ }
+
+ /**
* Add a canvas start tag
*
* @param componentId component id
diff --git a/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeState.java b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeState.java
index 43f8ea7d..363b82b 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeState.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeState.java
@@ -67,8 +67,8 @@
* Get Object based on id. The system will cache things like bitmaps Paths etc. They can be
* accessed with this command
*
- * @param id
- * @return
+ * @param id the id of the object
+ * @return the object
*/
@Nullable
public Object getFromId(int id) {
@@ -78,8 +78,8 @@
/**
* true if the cache contain this id
*
- * @param id
- * @return
+ * @param id the id of the object
+ * @return true if the cache contain this id
*/
public boolean containsId(int id) {
return mIntDataMap.get(id) != null;
@@ -138,8 +138,8 @@
/**
* Get the path asociated with the Data
*
- * @param id
- * @return
+ * @param id of path
+ * @return path object
*/
public Object getPath(int id) {
return mPathMap.get(id);
@@ -180,7 +180,7 @@
/**
* Adds a data Override.
*
- * @param id
+ * @param id the id of the data
* @param item the new value
*/
public void overrideData(int id, @NonNull Object item) {
@@ -222,8 +222,8 @@
/**
* Adds a float Override.
*
- * @param id
- * @param value the new value
+ * @param id The id of the float
+ * @param value the override value
*/
public void overrideFloat(int id, float value) {
float previous = mFloatMap.get(id);
@@ -235,7 +235,12 @@
}
}
- /** Insert an item in the cache */
+ /**
+ * Insert an item in the cache
+ *
+ * @param item integer item to cache
+ * @return the id of the integer
+ */
public int cacheInteger(int item) {
int id = nextId();
mIntegerMap.put(id, item);
@@ -243,7 +248,12 @@
return id;
}
- /** Insert an integer item in the cache */
+ /**
+ * Insert an integer item in the cache
+ *
+ * @param id the id of the integer
+ * @param value the value of the integer
+ */
public void updateInteger(int id, int value) {
if (!mIntegerOverride[id]) {
int previous = mIntegerMap.get(id);
@@ -292,10 +302,10 @@
}
/**
- * Get the float value
+ * Get the color from the cache
*
- * @param id
- * @return
+ * @param id The id of the color
+ * @return The color
*/
public int getColor(int id) {
return mColorMap.get(id);
@@ -377,6 +387,9 @@
/**
* Method to determine if a cached value has been written to the documents WireBuffer based on
* its id.
+ *
+ * @param id id to check
+ * @return true if the value has not been written to the WireBuffer
*/
public boolean wasNotWritten(int id) {
return !mIntWrittenMap.get(id);
@@ -406,7 +419,7 @@
* Get the next available id 0 is normal (float,int,String,color) 1 is VARIABLES 2 is
* collections
*
- * @return
+ * @return return a unique id in the set
*/
public int nextId(int type) {
if (0 == type) {
@@ -418,7 +431,7 @@
/**
* Set the next id
*
- * @param id
+ * @param id set the id to increment off of
*/
public void setNextId(int id) {
mNextId = id;
@@ -440,8 +453,8 @@
/**
* Commands that listen to variables add themselves.
*
- * @param id
- * @param variableSupport
+ * @param id id of variable to listen to
+ * @param variableSupport command that listens to variable
*/
public void listenToVar(int id, @NonNull VariableSupport variableSupport) {
add(id, variableSupport);
@@ -450,8 +463,8 @@
/**
* Is any command listening to this variable
*
- * @param id
- * @return
+ * @param id The Variable id
+ * @return true if any command is listening to this variable
*/
public boolean hasListener(int id) {
return mVarListeners.get(id) != null;
@@ -460,8 +473,8 @@
/**
* List of Commands that need to be updated
*
- * @param context
- * @return
+ * @param context The context
+ * @return The number of ops to update
*/
public int getOpsToUpdate(@NonNull RemoteContext context) {
if (mVarListeners.get(RemoteContext.ID_CONTINUOUS_SEC) != null) {
@@ -479,7 +492,7 @@
/**
* Set the width of the overall document on screen.
*
- * @param width
+ * @param width the width of the document in pixels
*/
public void setWindowWidth(float width) {
updateFloat(RemoteContext.ID_WINDOW_WIDTH, width);
@@ -488,12 +501,18 @@
/**
* Set the width of the overall document on screen.
*
- * @param height
+ * @param height the height of the document in pixels
*/
public void setWindowHeight(float height) {
updateFloat(RemoteContext.ID_WINDOW_HEIGHT, height);
}
+ /**
+ * Add an array access
+ *
+ * @param id The id of the array Access
+ * @param collection The array access
+ */
public void addCollection(int id, @NonNull ArrayAccess collection) {
mCollectionMap.put(id & 0xFFFFF, collection);
}
@@ -513,10 +532,22 @@
return mCollectionMap.get(id & 0xFFFFF).getId(index);
}
+ /**
+ * adds a DataMap to the cache
+ *
+ * @param id The id of the data map
+ * @param map The data map
+ */
public void putDataMap(int id, @NonNull DataMap map) {
mDataMapMap.put(id, map);
}
+ /**
+ * Get the DataMap asociated with the id
+ *
+ * @param id the id of the DataMap
+ * @return the DataMap
+ */
public @Nullable DataMap getDataMap(int id) {
return mDataMapMap.get(id);
}
@@ -526,15 +557,32 @@
return mCollectionMap.get(id & 0xFFFFF).getLength();
}
+ /**
+ * sets the RemoteContext
+ *
+ * @param context the context
+ */
public void setContext(@NonNull RemoteContext context) {
mRemoteContext = context;
mRemoteContext.clearLastOpCount();
}
+ /**
+ * Add an object to the cache. Uses the id for the item and adds it to the cache based
+ *
+ * @param id the id of the object
+ * @param value the object
+ */
public void updateObject(int id, @NonNull Object value) {
mObjectMap.put(id, value);
}
+ /**
+ * Get an object from the cache
+ *
+ * @param id The id of the object
+ * @return The object
+ */
public @Nullable Object getObject(int id) {
return mObjectMap.get(id);
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java b/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java
index 23c3628..36e4ec1 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java
@@ -47,7 +47,7 @@
new RemoteComposeState(); // todo, is this a valid use of RemoteComposeState -- bbade@
@Nullable protected PaintContext mPaintContext = null;
- protected float mDensity = 2.75f;
+ protected float mDensity = Float.NaN;
@NonNull ContextMode mMode = ContextMode.UNSET;
@@ -77,7 +77,7 @@
* @param density
*/
public void setDensity(float density) {
- if (density > 0) {
+ if (!Float.isNaN(density) && density > 0) {
mDensity = density;
}
}
@@ -234,23 +234,60 @@
*/
public abstract void addCollection(int id, @NonNull ArrayAccess collection);
+ /**
+ * put DataMap under an id
+ *
+ * @param id the id of the DataMap
+ * @param map the DataMap
+ */
public abstract void putDataMap(int id, @NonNull DataMap map);
+ /**
+ * Get a DataMap given an id
+ *
+ * @param id the id of the DataMap
+ * @return the DataMap
+ */
public abstract @Nullable DataMap getDataMap(int id);
+ /**
+ * Run an action
+ *
+ * @param id the id of the action
+ * @param metadata the metadata of the action
+ */
public abstract void runAction(int id, @NonNull String metadata);
// TODO: we might add an interface to group all valid parameter types
+
+ /**
+ * Run an action with a named parameter
+ *
+ * @param textId the text id of the action
+ * @param value the value of the parameter
+ */
public abstract void runNamedAction(int textId, Object value);
+ /**
+ * Put an object under an id
+ *
+ * @param mId the id of the object
+ * @param command the object
+ */
public abstract void putObject(int mId, @NonNull Object command);
+ /**
+ * Get an object given an id
+ *
+ * @param mId the id of the object
+ * @return the object
+ */
public abstract @Nullable Object getObject(int mId);
/**
* Add a touch listener to the context
*
- * @param touchExpression
+ * @param touchExpression the touch expression
*/
public void addTouchListener(TouchListener touchExpression) {}
@@ -668,11 +705,24 @@
///////////////////////////////////////////////////////////////////////////////////////////////
// Click handling
///////////////////////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Is this a time id float
+ *
+ * @param fl the floatId to test
+ * @return true if it is a time id
+ */
public static boolean isTime(float fl) {
int value = Utils.idFromNan(fl);
return value >= ID_CONTINUOUS_SEC && value <= ID_DAY_OF_MONTH;
}
+ /**
+ * get the time from a float id that indicates a type of time
+ *
+ * @param fl id of the type of time information requested
+ * @return various time information such as seconds or min
+ */
public static float getTime(float fl) {
LocalDateTime dateTime =
LocalDateTime.now(ZoneId.systemDefault()); // TODO, pass in a timezone explicitly?
@@ -716,6 +766,17 @@
return fl;
}
+ /**
+ * Add a click area to the doc
+ *
+ * @param id the id of the click area
+ * @param contentDescription the content description of the click area
+ * @param left the left bounds of the click area
+ * @param top the top bounds of the click area
+ * @param right the right bounds of the click area
+ * @param bottom the
+ * @param metadataId the id of the metadata string
+ */
public abstract void addClickArea(
int id,
int contentDescription,
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/ClipPath.java b/core/java/com/android/internal/widget/remotecompose/core/operations/ClipPath.java
index b55f25c..06ef997 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/ClipPath.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/ClipPath.java
@@ -102,6 +102,12 @@
return OP_CODE;
}
+ /**
+ * Apply this operation to the buffer
+ *
+ * @param buffer the buffer to apply the operation to
+ * @param id the id of the path
+ */
public static void apply(@NonNull WireBuffer buffer, int id) {
buffer.start(OP_CODE);
buffer.writeInt(id);
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DataListFloat.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DataListFloat.java
index ac6271c..7a72b10 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DataListFloat.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DataListFloat.java
@@ -70,6 +70,13 @@
return "DataListFloat[" + Utils.idString(mId) + "] " + Arrays.toString(mValues);
}
+ /**
+ * Write this operation to the buffer
+ *
+ * @param buffer the buffer to apply the operation to
+ * @param id the id of the array
+ * @param values the values of the array
+ */
public static void apply(@NonNull WireBuffer buffer, int id, @NonNull float[] values) {
buffer.start(OP_CODE);
buffer.writeInt(id);
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DataListIds.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DataListIds.java
index 47cbff3..7e29620 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DataListIds.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DataListIds.java
@@ -62,6 +62,13 @@
return "map[" + Utils.idString(mId) + "] \"" + Arrays.toString(mIds) + "\"";
}
+ /**
+ * Write this operation to the buffer
+ *
+ * @param buffer the buffer to apply the operation to
+ * @param id the id of the array
+ * @param ids the values of the array
+ */
public static void apply(@NonNull WireBuffer buffer, int id, @NonNull int[] ids) {
buffer.start(OP_CODE);
buffer.writeInt(id);
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DataMapIds.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DataMapIds.java
index ff85721..33752e0 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DataMapIds.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DataMapIds.java
@@ -89,6 +89,15 @@
return builder.toString();
}
+ /**
+ * Write this operation to the buffer
+ *
+ * @param buffer the buffer to apply the operation to
+ * @param id the id
+ * @param names the names of the variables
+ * @param type the types of the variables
+ * @param ids the ids of the variables
+ */
public static void apply(
@NonNull WireBuffer buffer,
int id,
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase2.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase2.java
index c1e2e66..7f1ba6f 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase2.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase2.java
@@ -31,8 +31,8 @@
/** Base class for commands that take 3 float */
public abstract class DrawBase2 extends PaintOperation implements VariableSupport {
@NonNull protected String mName = "DrawRectBase";
- float mV1;
- float mV2;
+ protected float mV1;
+ protected float mV2;
float mValue1;
float mValue2;
@@ -76,6 +76,13 @@
return mName + " " + floatToString(mV1) + " " + floatToString(mV2);
}
+ /**
+ * Read this operation and add it to the list of operations
+ *
+ * @param maker the maker of the operation
+ * @param buffer the buffer to read
+ * @param operations the list of operations to add to
+ */
public static void read(
@NonNull Maker maker, @NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
float v1 = buffer.readFloat();
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase3.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase3.java
index 6fedea3..a6bfda8 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase3.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase3.java
@@ -92,6 +92,13 @@
+ floatToString(mV3);
}
+ /**
+ * Read this operation and add it to the list of operations
+ *
+ * @param maker the maker of the operation
+ * @param buffer the buffer to read
+ * @param operations the list of operations to add to
+ */
public static void read(
@NonNull Maker maker, @NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
float v1 = buffer.readFloat();
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase4.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase4.java
index aa9cc68..1e96bcd 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase4.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase4.java
@@ -102,6 +102,13 @@
+ floatToString(mY2Value, mY2);
}
+ /**
+ * Read this operation and add it to the list of operations
+ *
+ * @param maker the maker of the operation
+ * @param buffer the buffer to read
+ * @param operations the list of operations to add to
+ */
public static void read(
@NonNull Maker maker, @NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
float v1 = buffer.readFloat();
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase6.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase6.java
index 64c2730..bc59045 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase6.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase6.java
@@ -116,6 +116,13 @@
DrawBase6 create(float v1, float v2, float v3, float v4, float v5, float v6);
}
+ /**
+ * Read this operation and add it to the list of operations
+ *
+ * @param build interface to construct the component
+ * @param buffer the buffer to read from
+ * @param operations the list of operations to add to
+ */
public static void read(
@NonNull Maker build, @NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
float sv1 = buffer.readFloat();
@@ -132,13 +139,13 @@
/**
* writes out a the operation to the buffer.
*
- * @param v1
- * @param v2
- * @param v3
- * @param v4
- * @param v5
- * @param v6
- * @return
+ * @param v1 the first parameter
+ * @param v2 the second parameter
+ * @param v3 the third parameter
+ * @param v4 the fourth parameter
+ * @param v5 the fifth parameter
+ * @param v6 the sixth parameter
+ * @return the operation
*/
@Nullable
public Operation construct(float v1, float v2, float v3, float v4, float v5, float v6) {
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmap.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmap.java
index cdb527d..40d3bed 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmap.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmap.java
@@ -137,6 +137,17 @@
return OP_CODE;
}
+ /**
+ * Writes out the operation to the buffer
+ *
+ * @param buffer the buffer to write to
+ * @param id the id of the Bitmap
+ * @param left left most x coordinate
+ * @param top top most y coordinate
+ * @param right right most x coordinate
+ * @param bottom bottom most y coordinate
+ * @param descriptionId string id of the description
+ */
public static void apply(
@NonNull WireBuffer buffer,
int id,
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapInt.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapInt.java
index 638fe14..013dd1a 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapInt.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapInt.java
@@ -131,6 +131,21 @@
return OP_CODE;
}
+ /**
+ * Draw a bitmap using integer coordinates
+ *
+ * @param buffer the buffer to write to
+ * @param imageId the id of the bitmap
+ * @param srcLeft the left most pixel in the bitmap
+ * @param srcTop the top most pixel in the bitmap
+ * @param srcRight the right most pixel in the bitmap
+ * @param srcBottom the bottom most pixel in the bitmap
+ * @param dstLeft the left most pixel in the destination
+ * @param dstTop the top most pixel in the destination
+ * @param dstRight the right most pixel in the destination
+ * @param dstBottom the bottom most pixel in the destination
+ * @param cdId the content discription id
+ */
public static void apply(
@NonNull WireBuffer buffer,
int imageId,
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapScaled.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapScaled.java
index d6467c9..e1070f9 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapScaled.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapScaled.java
@@ -217,6 +217,23 @@
return OP_CODE;
}
+ /**
+ * Draw a bitmap using integer coordinates
+ *
+ * @param buffer the buffer to write to
+ * @param imageId the id of the image
+ * @param srcLeft the left most pixel in the image to draw
+ * @param srcTop the right most pixel in the image to draw
+ * @param srcRight the right most pixel in the image to draw
+ * @param srcBottom the bottom most pixel in the image to draw
+ * @param dstLeft the left most pixel in the destination
+ * @param dstTop the top most pixel in the destination
+ * @param dstRight the right most pixel in the destination
+ * @param dstBottom the bottom most pixel in the destination
+ * @param scaleType the type of scale operation
+ * @param scaleFactor the scalefactor to use with fixed scale
+ * @param cdId the content discription id
+ */
public static void apply(
@NonNull WireBuffer buffer,
int imageId,
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawPath.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawPath.java
index 398cf48..db9c4d3 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawPath.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawPath.java
@@ -81,6 +81,12 @@
return Operations.DRAW_PATH;
}
+ /**
+ * Draw a path
+ *
+ * @param buffer the buffer to write to
+ * @param id the id of the path
+ */
public static void apply(@NonNull WireBuffer buffer, int id) {
buffer.start(Operations.DRAW_PATH);
buffer.writeInt(id);
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextOnPath.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextOnPath.java
index 86f3c99..3ab4a87 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextOnPath.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextOnPath.java
@@ -117,6 +117,15 @@
return Operations.DRAW_TEXT_ON_PATH;
}
+ /**
+ * add a draw text on path operation to the buffer
+ *
+ * @param buffer the buffer to add to
+ * @param textId the id of the text string
+ * @param pathId the id of the path
+ * @param hOffset the horizontal offset to position the string
+ * @param vOffset the vertical offset to position the string
+ */
public static void apply(
@NonNull WireBuffer buffer, int textId, int pathId, float hOffset, float vOffset) {
buffer.start(OP_CODE);
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTweenPath.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTweenPath.java
index d4d4a5e..e288394 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTweenPath.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTweenPath.java
@@ -127,6 +127,16 @@
return Operations.DRAW_TWEEN_PATH;
}
+ /**
+ * add a draw tween path operation to the buffer
+ *
+ * @param buffer the buffer to add to
+ * @param path1Id the first path
+ * @param path2Id the second path
+ * @param tween the amount of the tween
+ * @param start the start sub range to draw
+ * @param stop the end of the sub range to draw
+ */
public static void apply(
@NonNull WireBuffer buffer,
int path1Id,
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixRestore.java b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixRestore.java
index 044430d..66daa13 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixRestore.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixRestore.java
@@ -74,6 +74,11 @@
return OP_CODE;
}
+ /**
+ * add a matrix restore operation to the buffer
+ *
+ * @param buffer the buffer to add to
+ */
public static void apply(@NonNull WireBuffer buffer) {
buffer.start(OP_CODE);
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixSave.java b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixSave.java
index aec316a..ec918e8 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixSave.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixSave.java
@@ -72,6 +72,11 @@
return OP_CODE;
}
+ /**
+ * add a matrix save operation to the buffer
+ *
+ * @param buffer the buffer to add to
+ */
public static void apply(@NonNull WireBuffer buffer) {
buffer.start(Operations.MATRIX_SAVE);
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/PaintData.java b/core/java/com/android/internal/widget/remotecompose/core/operations/PaintData.java
index daf2c55..f756b76 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/PaintData.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/PaintData.java
@@ -32,6 +32,7 @@
import java.util.List;
+/** Paint data operation */
public class PaintData extends PaintOperation implements VariableSupport {
private static final int OP_CODE = Operations.PAINT_VALUES;
private static final String CLASS_NAME = "PaintData";
@@ -80,6 +81,12 @@
return OP_CODE;
}
+ /**
+ * add a paint data to the buffer
+ *
+ * @param buffer the buffer to add to
+ * @param paintBundle the paint bundle
+ */
public static void apply(@NonNull WireBuffer buffer, @NonNull PaintBundle paintBundle) {
buffer.start(Operations.PAINT_VALUES);
paintBundle.writeBundle(buffer);
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/PathAppend.java b/core/java/com/android/internal/widget/remotecompose/core/operations/PathAppend.java
index 7ff879e..e7cce03 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/PathAppend.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/PathAppend.java
@@ -101,6 +101,7 @@
public static final int CUBIC = 14;
public static final int CLOSE = 15;
public static final int DONE = 16;
+ public static final int RESET = 17;
public static final float MOVE_NAN = Utils.asNan(MOVE);
public static final float LINE_NAN = Utils.asNan(LINE);
public static final float QUADRATIC_NAN = Utils.asNan(QUADRATIC);
@@ -108,6 +109,7 @@
public static final float CUBIC_NAN = Utils.asNan(CUBIC);
public static final float CLOSE_NAN = Utils.asNan(CLOSE);
public static final float DONE_NAN = Utils.asNan(DONE);
+ public static final float RESET_NAN = Utils.asNan(RESET);
/**
* The name of the class
@@ -128,6 +130,14 @@
return OP_CODE;
}
+ /**
+ * add a path append operation to the buffer. With PathCreate allows you create a path
+ * dynamically
+ *
+ * @param buffer add the data to this buffer
+ * @param id id of the path
+ * @param data the path data to append
+ */
public static void apply(@NonNull WireBuffer buffer, int id, @NonNull float[] data) {
buffer.start(OP_CODE);
buffer.writeInt(id);
@@ -175,6 +185,10 @@
public void apply(@NonNull RemoteContext context) {
float[] data = context.getPathData(mInstanceId);
float[] out = mOutputPath;
+ if (Float.floatToRawIntBits(out[0]) == Float.floatToRawIntBits(RESET_NAN)) {
+ context.loadPathData(mInstanceId, new float[0]);
+ return;
+ }
if (data != null) {
out = new float[data.length + mOutputPath.length];
@@ -190,6 +204,12 @@
context.loadPathData(mInstanceId, out);
}
+ /**
+ * Convert a path to a string
+ *
+ * @param path the path to convert
+ * @return text representation of path
+ */
@NonNull
public static String pathString(@Nullable float[] path) {
if (path == null) {
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/PathCreate.java b/core/java/com/android/internal/widget/remotecompose/core/operations/PathCreate.java
index 75562cd..1f76639 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/PathCreate.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/PathCreate.java
@@ -131,6 +131,14 @@
return OP_CODE;
}
+ /**
+ * add a create path operation
+ *
+ * @param buffer buffer to add to
+ * @param id the id of the path
+ * @param startX the start x of the path (moveTo x,y)
+ * @param startY the start y of the path (moveTo x,y)
+ */
public static void apply(@NonNull WireBuffer buffer, int id, float startX, float startY) {
buffer.start(OP_CODE);
buffer.writeInt(id);
@@ -165,6 +173,12 @@
.field(FLOAT, "startX", "initial start y");
}
+ /**
+ * convert a path to a string
+ *
+ * @param path path to convert (expressed as an array of floats)
+ * @return the text representing the path
+ */
@NonNull
public static String pathString(@Nullable float[] path) {
if (path == null) {
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/PathData.java b/core/java/com/android/internal/widget/remotecompose/core/operations/PathData.java
index 85a01fc..45d99a7 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/PathData.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/PathData.java
@@ -131,6 +131,13 @@
return OP_CODE;
}
+ /**
+ * add a create path operation
+ *
+ * @param buffer buffer to add to
+ * @param id the id of the path
+ * @param data the path
+ */
public static void apply(@NonNull WireBuffer buffer, int id, @NonNull float[] data) {
buffer.start(Operations.DATA_PATH);
buffer.writeInt(id);
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/TextData.java b/core/java/com/android/internal/widget/remotecompose/core/operations/TextData.java
index d48de37..5788d8f 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/TextData.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/TextData.java
@@ -73,6 +73,13 @@
return OP_CODE;
}
+ /**
+ * add a text data operation
+ *
+ * @param buffer buffer to add to
+ * @param textId the id for the text
+ * @param text the data to encode
+ */
public static void apply(@NonNull WireBuffer buffer, int textId, @NonNull String text) {
buffer.start(OP_CODE);
buffer.writeInt(textId);
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/TextLength.java b/core/java/com/android/internal/widget/remotecompose/core/operations/TextLength.java
index 37ea567..a6570a3 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/TextLength.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/TextLength.java
@@ -50,6 +50,11 @@
return CLASS_NAME + "[" + mLengthId + "] = " + mTextId;
}
+ /**
+ * The name of the class
+ *
+ * @return the name
+ */
public static @NonNull String name() {
return CLASS_NAME;
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/TextMeasure.java b/core/java/com/android/internal/widget/remotecompose/core/operations/TextMeasure.java
index d51b389..58cd68e 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/TextMeasure.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/TextMeasure.java
@@ -66,6 +66,11 @@
return "FloatConstant[" + mId + "] = " + mTextId + " " + mType;
}
+ /**
+ * The name of the class
+ *
+ * @return the name
+ */
public static @NonNull String name() {
return CLASS_NAME;
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LayoutComponent.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LayoutComponent.java
index e71cb9a..dcd3348 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LayoutComponent.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LayoutComponent.java
@@ -36,10 +36,12 @@
import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ComponentVisibilityOperation;
import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.DimensionModifierOperation;
import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.GraphicsLayerModifierOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.HeightInModifierOperation;
import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.HeightModifierOperation;
import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ModifierOperation;
import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.PaddingModifierOperation;
import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ScrollModifierOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.WidthInModifierOperation;
import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.WidthModifierOperation;
import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ZIndexModifierOperation;
@@ -204,6 +206,9 @@
mPaddingRight = 0f;
mPaddingBottom = 0f;
+ WidthInModifierOperation widthInConstraints = null;
+ HeightInModifierOperation heightInConstraints = null;
+
for (OperationInterface op : mComponentModifiers.getList()) {
if (op instanceof PaddingModifierOperation) {
// We are accumulating padding modifiers to compute the margin
@@ -221,6 +226,10 @@
mWidthModifier = (WidthModifierOperation) op;
} else if (op instanceof HeightModifierOperation && mHeightModifier == null) {
mHeightModifier = (HeightModifierOperation) op;
+ } else if (op instanceof WidthInModifierOperation) {
+ widthInConstraints = (WidthInModifierOperation) op;
+ } else if (op instanceof HeightInModifierOperation) {
+ heightInConstraints = (HeightInModifierOperation) op;
} else if (op instanceof ZIndexModifierOperation) {
mZIndexModifier = (ZIndexModifierOperation) op;
} else if (op instanceof GraphicsLayerModifierOperation) {
@@ -241,6 +250,12 @@
if (mHeightModifier == null) {
mHeightModifier = new HeightModifierOperation(DimensionModifierOperation.Type.WRAP);
}
+ if (widthInConstraints != null) {
+ mWidthModifier.setWidthIn(widthInConstraints);
+ }
+ if (heightInConstraints != null) {
+ mHeightModifier.setHeightIn(heightInConstraints);
+ }
setWidth(computeModifierDefinedWidth(null));
setHeight(computeModifierDefinedHeight(null));
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CollapsibleColumnLayout.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CollapsibleColumnLayout.java
new file mode 100644
index 0000000..afc41b1
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CollapsibleColumnLayout.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core.operations.layout.managers;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.PaintContext;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+import com.android.internal.widget.remotecompose.core.operations.layout.Component;
+import com.android.internal.widget.remotecompose.core.operations.layout.measure.ComponentMeasure;
+import com.android.internal.widget.remotecompose.core.operations.layout.measure.MeasurePass;
+import com.android.internal.widget.remotecompose.core.operations.layout.measure.Size;
+
+import java.util.List;
+
+public class CollapsibleColumnLayout extends ColumnLayout {
+
+ public CollapsibleColumnLayout(
+ @Nullable Component parent,
+ int componentId,
+ int animationId,
+ float x,
+ float y,
+ float width,
+ float height,
+ int horizontalPositioning,
+ int verticalPositioning,
+ float spacedBy) {
+ super(
+ parent,
+ componentId,
+ animationId,
+ x,
+ y,
+ width,
+ height,
+ horizontalPositioning,
+ verticalPositioning,
+ spacedBy);
+ }
+
+ public CollapsibleColumnLayout(
+ @Nullable Component parent,
+ int componentId,
+ int animationId,
+ int horizontalPositioning,
+ int verticalPositioning,
+ float spacedBy) {
+ super(
+ parent,
+ componentId,
+ animationId,
+ horizontalPositioning,
+ verticalPositioning,
+ spacedBy);
+ }
+
+ @NonNull
+ @Override
+ protected String getSerializedName() {
+ return "COLLAPSIBLE_COLUMN";
+ }
+
+ /**
+ * The OP_CODE for this command
+ *
+ * @return the opcode
+ */
+ public static int id() {
+ return Operations.LAYOUT_COLLAPSIBLE_COLUMN;
+ }
+
+ /**
+ * Write the operation to the buffer
+ *
+ * @param buffer wire buffer
+ * @param componentId component id
+ * @param animationId animation id (-1 if not set)
+ * @param horizontalPositioning horizontal positioning rules
+ * @param verticalPositioning vertical positioning rules
+ * @param spacedBy spaced by value
+ */
+ public static void apply(
+ @NonNull WireBuffer buffer,
+ int componentId,
+ int animationId,
+ int horizontalPositioning,
+ int verticalPositioning,
+ float spacedBy) {
+ buffer.start(id());
+ buffer.writeInt(componentId);
+ buffer.writeInt(animationId);
+ buffer.writeInt(horizontalPositioning);
+ buffer.writeInt(verticalPositioning);
+ buffer.writeFloat(spacedBy);
+ }
+
+ /**
+ * Read this operation and add it to the list of operations
+ *
+ * @param buffer the buffer to read
+ * @param operations the list of operations that will be added to
+ */
+ public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
+ int componentId = buffer.readInt();
+ int animationId = buffer.readInt();
+ int horizontalPositioning = buffer.readInt();
+ int verticalPositioning = buffer.readInt();
+ float spacedBy = buffer.readFloat();
+ operations.add(
+ new CollapsibleColumnLayout(
+ null,
+ componentId,
+ animationId,
+ horizontalPositioning,
+ verticalPositioning,
+ spacedBy));
+ }
+
+ @Override
+ protected boolean hasVerticalIntrinsicDimension() {
+ return true;
+ }
+
+ @Override
+ public void computeWrapSize(
+ @NonNull PaintContext context,
+ float maxWidth,
+ float maxHeight,
+ boolean horizontalWrap,
+ boolean verticalWrap,
+ @NonNull MeasurePass measure,
+ @NonNull Size size) {
+ super.computeWrapSize(
+ context, maxWidth, Float.MAX_VALUE, horizontalWrap, verticalWrap, measure, size);
+ }
+
+ @Override
+ public boolean applyVisibility(
+ float selfWidth, float selfHeight, @NonNull MeasurePass measure) {
+ float childrenWidth = 0f;
+ float childrenHeight = 0f;
+ boolean changedVisibility = false;
+ for (Component child : mChildrenComponents) {
+ ComponentMeasure childMeasure = measure.get(child);
+ if (childMeasure.getVisibility() == Visibility.GONE) {
+ continue;
+ }
+ if (childrenHeight + childMeasure.getH() > selfHeight) {
+ childMeasure.setVisibility(Visibility.GONE);
+ changedVisibility = true;
+ } else {
+ childrenHeight += childMeasure.getH();
+ childrenWidth = Math.max(childrenWidth, childMeasure.getW());
+ }
+ }
+ return changedVisibility;
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CollapsibleRowLayout.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CollapsibleRowLayout.java
new file mode 100644
index 0000000..0e7eb86
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CollapsibleRowLayout.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core.operations.layout.managers;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.PaintContext;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+import com.android.internal.widget.remotecompose.core.operations.layout.Component;
+import com.android.internal.widget.remotecompose.core.operations.layout.measure.ComponentMeasure;
+import com.android.internal.widget.remotecompose.core.operations.layout.measure.MeasurePass;
+import com.android.internal.widget.remotecompose.core.operations.layout.measure.Size;
+
+import java.util.List;
+
+public class CollapsibleRowLayout extends RowLayout {
+
+ public CollapsibleRowLayout(
+ @Nullable Component parent,
+ int componentId,
+ int animationId,
+ float x,
+ float y,
+ float width,
+ float height,
+ int horizontalPositioning,
+ int verticalPositioning,
+ float spacedBy) {
+ super(
+ parent,
+ componentId,
+ animationId,
+ x,
+ y,
+ width,
+ height,
+ horizontalPositioning,
+ verticalPositioning,
+ spacedBy);
+ }
+
+ public CollapsibleRowLayout(
+ @Nullable Component parent,
+ int componentId,
+ int animationId,
+ int horizontalPositioning,
+ int verticalPositioning,
+ float spacedBy) {
+ super(
+ parent,
+ componentId,
+ animationId,
+ horizontalPositioning,
+ verticalPositioning,
+ spacedBy);
+ }
+
+ @NonNull
+ @Override
+ protected String getSerializedName() {
+ return "COLLAPSIBLE_ROW";
+ }
+
+ /**
+ * The OP_CODE for this command
+ *
+ * @return the opcode
+ */
+ public static int id() {
+ return Operations.LAYOUT_COLLAPSIBLE_ROW;
+ }
+
+ /**
+ * Write the operation to the buffer
+ *
+ * @param buffer wire buffer
+ * @param componentId component id
+ * @param animationId animation id (-1 if not set)
+ * @param horizontalPositioning horizontal positioning rules
+ * @param verticalPositioning vertical positioning rules
+ * @param spacedBy spaced by value
+ */
+ public static void apply(
+ @NonNull WireBuffer buffer,
+ int componentId,
+ int animationId,
+ int horizontalPositioning,
+ int verticalPositioning,
+ float spacedBy) {
+ buffer.start(id());
+ buffer.writeInt(componentId);
+ buffer.writeInt(animationId);
+ buffer.writeInt(horizontalPositioning);
+ buffer.writeInt(verticalPositioning);
+ buffer.writeFloat(spacedBy);
+ }
+
+ /**
+ * Read this operation and add it to the list of operations
+ *
+ * @param buffer the buffer to read
+ * @param operations the list of operations that will be added to
+ */
+ public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
+ int componentId = buffer.readInt();
+ int animationId = buffer.readInt();
+ int horizontalPositioning = buffer.readInt();
+ int verticalPositioning = buffer.readInt();
+ float spacedBy = buffer.readFloat();
+ operations.add(
+ new CollapsibleRowLayout(
+ null,
+ componentId,
+ animationId,
+ horizontalPositioning,
+ verticalPositioning,
+ spacedBy));
+ }
+
+ @Override
+ protected boolean hasHorizontalIntrinsicDimension() {
+ return true;
+ }
+
+ @Override
+ public void computeWrapSize(
+ @NonNull PaintContext context,
+ float maxWidth,
+ float maxHeight,
+ boolean horizontalWrap,
+ boolean verticalWrap,
+ @NonNull MeasurePass measure,
+ @NonNull Size size) {
+ super.computeWrapSize(
+ context, Float.MAX_VALUE, maxHeight, horizontalWrap, verticalWrap, measure, size);
+ }
+
+ @Override
+ public boolean applyVisibility(
+ float selfWidth, float selfHeight, @NonNull MeasurePass measure) {
+ float childrenWidth = 0f;
+ float childrenHeight = 0f;
+ boolean changedVisibility = false;
+ for (Component child : mChildrenComponents) {
+ ComponentMeasure childMeasure = measure.get(child);
+ if (childMeasure.getVisibility() == Visibility.GONE) {
+ continue;
+ }
+ if (childrenWidth + childMeasure.getW() > selfWidth) {
+ childMeasure.setVisibility(Visibility.GONE);
+ changedVisibility = true;
+ } else {
+ childrenWidth += childMeasure.getW();
+ childrenHeight = Math.max(childrenHeight, childMeasure.getH());
+ }
+ }
+ return changedVisibility;
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/ColumnLayout.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/ColumnLayout.java
index f68d7b4..4d0cbef 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/ColumnLayout.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/ColumnLayout.java
@@ -32,6 +32,7 @@
import com.android.internal.widget.remotecompose.core.operations.layout.measure.ComponentMeasure;
import com.android.internal.widget.remotecompose.core.operations.layout.measure.MeasurePass;
import com.android.internal.widget.remotecompose.core.operations.layout.measure.Size;
+import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.HeightInModifierOperation;
import com.android.internal.widget.remotecompose.core.operations.layout.utils.DebugLog;
import java.util.List;
@@ -93,7 +94,8 @@
@NonNull
@Override
public String toString() {
- return "COLUMN ["
+ return getSerializedName()
+ + " ["
+ mComponentId
+ ":"
+ mAnimationId
@@ -213,41 +215,62 @@
selfHeight =
mComponentModifiers.getVerticalScrollDimension() - mPaddingTop - mPaddingBottom;
}
- boolean hasWeights = false;
- float totalWeights = 0f;
- for (Component child : mChildrenComponents) {
- ComponentMeasure childMeasure = measure.get(child);
- if (childMeasure.getVisibility() == Visibility.GONE) {
- continue;
- }
- if (child instanceof LayoutComponent
- && ((LayoutComponent) child).getHeightModifier().hasWeight()) {
- hasWeights = true;
- totalWeights += ((LayoutComponent) child).getHeightModifier().getValue();
- } else {
- childrenHeight += childMeasure.getH();
- }
- }
- if (hasWeights) {
- float availableSpace = selfHeight - childrenHeight;
+ boolean checkWeights = true;
+ while (checkWeights) {
+ checkWeights = false;
+ boolean hasWeights = false;
+ float totalWeights = 0f;
for (Component child : mChildrenComponents) {
+ ComponentMeasure childMeasure = measure.get(child);
+ if (childMeasure.getVisibility() == Visibility.GONE) {
+ continue;
+ }
if (child instanceof LayoutComponent
&& ((LayoutComponent) child).getHeightModifier().hasWeight()) {
- ComponentMeasure childMeasure = measure.get(child);
- if (childMeasure.getVisibility() == Visibility.GONE) {
- continue;
- }
- float weight = ((LayoutComponent) child).getHeightModifier().getValue();
- childMeasure.setH((weight * availableSpace) / totalWeights);
- child.measure(
- context,
- childMeasure.getW(),
- childMeasure.getW(),
- childMeasure.getH(),
- childMeasure.getH(),
- measure);
+ hasWeights = true;
+ totalWeights += ((LayoutComponent) child).getHeightModifier().getValue();
+ } else {
+ childrenHeight += childMeasure.getH();
}
}
+ if (hasWeights) {
+ float availableSpace = selfHeight - childrenHeight;
+ for (Component child : mChildrenComponents) {
+ if (child instanceof LayoutComponent
+ && ((LayoutComponent) child).getHeightModifier().hasWeight()) {
+ ComponentMeasure childMeasure = measure.get(child);
+ if (childMeasure.getVisibility() == Visibility.GONE) {
+ continue;
+ }
+ float weight = ((LayoutComponent) child).getHeightModifier().getValue();
+ float childHeight = (weight * availableSpace) / totalWeights;
+ HeightInModifierOperation heightInConstraints =
+ ((LayoutComponent) child).getHeightModifier().getHeightIn();
+ if (heightInConstraints != null) {
+ float min = heightInConstraints.getMin();
+ float max = heightInConstraints.getMax();
+ if (min != -1) {
+ childHeight = Math.max(min, childHeight);
+ }
+ if (max != -1) {
+ childHeight = Math.min(max, childHeight);
+ }
+ }
+ childMeasure.setH(childHeight);
+ child.measure(
+ context,
+ childMeasure.getW(),
+ childMeasure.getW(),
+ childMeasure.getH(),
+ childMeasure.getH(),
+ measure);
+ }
+ }
+ }
+
+ if (applyVisibility(selfWidth, selfHeight, measure) && hasWeights) {
+ checkWeights = true;
+ }
}
childrenHeight = 0f;
@@ -360,6 +383,16 @@
return Operations.LAYOUT_COLUMN;
}
+ /**
+ * Write the operation to the buffer
+ *
+ * @param buffer wire buffer
+ * @param componentId component id
+ * @param animationId animation id (-1 if not set)
+ * @param horizontalPositioning horizontal positioning rules
+ * @param verticalPositioning vertical positioning rules
+ * @param spacedBy spaced by value
+ */
public static void apply(
@NonNull WireBuffer buffer,
int componentId,
@@ -367,7 +400,7 @@
int horizontalPositioning,
int verticalPositioning,
float spacedBy) {
- buffer.start(Operations.LAYOUT_COLUMN);
+ buffer.start(id());
buffer.writeInt(componentId);
buffer.writeInt(animationId);
buffer.writeInt(horizontalPositioning);
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/LayoutManager.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/LayoutManager.java
index edfd69c..8b52bbe 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/LayoutManager.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/LayoutManager.java
@@ -43,6 +43,18 @@
super(parent, componentId, animationId, x, y, width, height);
}
+ /**
+ * Allows layout managers to override elements visibility
+ *
+ * @param selfWidth intrinsic width of the layout manager content
+ * @param selfHeight intrinsic height of the layout manager content
+ * @param measure measure pass
+ */
+ public boolean applyVisibility(
+ float selfWidth, float selfHeight, @NonNull MeasurePass measure) {
+ return false;
+ }
+
/** Implemented by subclasses to provide a layout/measure pass */
public void internalLayoutMeasure(@NonNull PaintContext context, @NonNull MeasurePass measure) {
// nothing here
@@ -197,7 +209,7 @@
}
if (!hasWrap) {
- if (hasHorizontalScroll()) {
+ if (hasHorizontalIntrinsicDimension()) {
mCachedWrapSize.setWidth(0f);
mCachedWrapSize.setHeight(0f);
computeWrapSize(
@@ -210,15 +222,19 @@
mCachedWrapSize);
float w = mCachedWrapSize.getWidth();
computeSize(context, 0f, w, 0, measuredHeight, measure);
- mComponentModifiers.setHorizontalScrollDimension(measuredWidth, w);
- } else if (hasVerticalScroll()) {
+ if (hasHorizontalScroll()) {
+ mComponentModifiers.setHorizontalScrollDimension(measuredWidth, w);
+ }
+ } else if (hasVerticalIntrinsicDimension()) {
mCachedWrapSize.setWidth(0f);
mCachedWrapSize.setHeight(0f);
computeWrapSize(
context, maxWidth, Float.MAX_VALUE, false, false, measure, mCachedWrapSize);
float h = mCachedWrapSize.getHeight();
computeSize(context, 0f, measuredWidth, 0, h, measure);
- mComponentModifiers.setVerticalScrollDimension(measuredHeight, h);
+ if (hasVerticalScroll()) {
+ mComponentModifiers.setVerticalScrollDimension(measuredHeight, h);
+ }
} else {
float maxChildWidth = measuredWidth - mPaddingLeft - mPaddingRight;
float maxChildHeight = measuredHeight - mPaddingTop - mPaddingBottom;
@@ -246,6 +262,14 @@
return mComponentModifiers.hasHorizontalScroll();
}
+ protected boolean hasHorizontalIntrinsicDimension() {
+ return hasHorizontalScroll();
+ }
+
+ protected boolean hasVerticalIntrinsicDimension() {
+ return hasVerticalScroll();
+ }
+
private boolean hasVerticalScroll() {
return mComponentModifiers.hasVerticalScroll();
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/RowLayout.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/RowLayout.java
index b688f6e..5b35c4c 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/RowLayout.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/RowLayout.java
@@ -32,6 +32,7 @@
import com.android.internal.widget.remotecompose.core.operations.layout.measure.ComponentMeasure;
import com.android.internal.widget.remotecompose.core.operations.layout.measure.MeasurePass;
import com.android.internal.widget.remotecompose.core.operations.layout.measure.Size;
+import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.WidthInModifierOperation;
import com.android.internal.widget.remotecompose.core.operations.layout.utils.DebugLog;
import java.util.List;
@@ -91,7 +92,8 @@
@NonNull
@Override
public String toString() {
- return "ROW ["
+ return getSerializedName()
+ + " ["
+ mComponentId
+ ":"
+ mAnimationId
@@ -212,44 +214,66 @@
mComponentModifiers.getVerticalScrollDimension() - mPaddingTop - mPaddingBottom;
}
- boolean hasWeights = false;
- float totalWeights = 0f;
- for (Component child : mChildrenComponents) {
- ComponentMeasure childMeasure = measure.get(child);
- if (childMeasure.getVisibility() == Visibility.GONE) {
- continue;
- }
- if (child instanceof LayoutComponent
- && ((LayoutComponent) child).getWidthModifier().hasWeight()) {
- hasWeights = true;
- totalWeights += ((LayoutComponent) child).getWidthModifier().getValue();
- } else {
- childrenWidth += childMeasure.getW();
- }
- }
+ boolean checkWeights = true;
- // TODO: need to move the weight measuring in the measure function,
- // currently we'll measure unnecessarily
- if (hasWeights) {
- float availableSpace = selfWidth - childrenWidth;
+ while (checkWeights) {
+ checkWeights = false;
+ boolean hasWeights = false;
+ float totalWeights = 0f;
for (Component child : mChildrenComponents) {
+ ComponentMeasure childMeasure = measure.get(child);
+ if (childMeasure.getVisibility() == Visibility.GONE) {
+ continue;
+ }
if (child instanceof LayoutComponent
&& ((LayoutComponent) child).getWidthModifier().hasWeight()) {
- ComponentMeasure childMeasure = measure.get(child);
- if (childMeasure.getVisibility() == Visibility.GONE) {
- continue;
- }
- float weight = ((LayoutComponent) child).getWidthModifier().getValue();
- childMeasure.setW((weight * availableSpace) / totalWeights);
- child.measure(
- context,
- childMeasure.getW(),
- childMeasure.getW(),
- childMeasure.getH(),
- childMeasure.getH(),
- measure);
+ hasWeights = true;
+ totalWeights += ((LayoutComponent) child).getWidthModifier().getValue();
+ } else {
+ childrenWidth += childMeasure.getW();
}
}
+
+ // TODO: need to move the weight measuring in the measure function,
+ // currently we'll measure unnecessarily
+ if (hasWeights) {
+ float availableSpace = selfWidth - childrenWidth;
+ for (Component child : mChildrenComponents) {
+ if (child instanceof LayoutComponent
+ && ((LayoutComponent) child).getWidthModifier().hasWeight()) {
+ ComponentMeasure childMeasure = measure.get(child);
+ if (childMeasure.getVisibility() == Visibility.GONE) {
+ continue;
+ }
+ float weight = ((LayoutComponent) child).getWidthModifier().getValue();
+ float childWidth = (weight * availableSpace) / totalWeights;
+ WidthInModifierOperation widthInConstraints =
+ ((LayoutComponent) child).getWidthModifier().getWidthIn();
+ if (widthInConstraints != null) {
+ float min = widthInConstraints.getMin();
+ float max = widthInConstraints.getMax();
+ if (min != -1) {
+ childWidth = Math.max(min, childWidth);
+ }
+ if (max != -1) {
+ childWidth = Math.min(max, childWidth);
+ }
+ }
+ childMeasure.setW(childWidth);
+ child.measure(
+ context,
+ childMeasure.getW(),
+ childMeasure.getW(),
+ childMeasure.getH(),
+ childMeasure.getH(),
+ measure);
+ }
+ }
+ }
+
+ if (applyVisibility(selfWidth, selfHeight, measure) && hasWeights) {
+ checkWeights = true;
+ }
}
childrenWidth = 0f;
@@ -363,6 +387,16 @@
return Operations.LAYOUT_ROW;
}
+ /**
+ * Write the operation to the buffer
+ *
+ * @param buffer wire buffer
+ * @param componentId component id
+ * @param animationId animation id (-1 if not set)
+ * @param horizontalPositioning horizontal positioning rules
+ * @param verticalPositioning vertical positioning rules
+ * @param spacedBy spaced by value
+ */
public static void apply(
@NonNull WireBuffer buffer,
int componentId,
@@ -370,7 +404,7 @@
int horizontalPositioning,
int verticalPositioning,
float spacedBy) {
- buffer.start(Operations.LAYOUT_ROW);
+ buffer.start(id());
buffer.writeInt(componentId);
buffer.writeInt(animationId);
buffer.writeInt(horizontalPositioning);
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HeightInModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HeightInModifierOperation.java
new file mode 100644
index 0000000..c19bd2f
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HeightInModifierOperation.java
@@ -0,0 +1,124 @@
+/*
+ * 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.internal.widget.remotecompose.core.operations.layout.modifiers;
+
+import android.annotation.NonNull;
+
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.PaintContext;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
+import com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation;
+import com.android.internal.widget.remotecompose.core.operations.DrawBase2;
+import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer;
+
+import java.util.List;
+
+/** Set the min / max height dimension on a component */
+public class HeightInModifierOperation extends DrawBase2 implements ModifierOperation {
+ private static final int OP_CODE = Operations.MODIFIER_HEIGHT_IN;
+ public static final String CLASS_NAME = "HeightInModifierOperation";
+
+ /**
+ * Read this operation and add it to the list of operations
+ *
+ * @param buffer the buffer to read
+ * @param operations the list of operations that will be added to
+ */
+ public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
+ Maker m = HeightInModifierOperation::new;
+ read(m, buffer, operations);
+ }
+
+ /**
+ * Returns the min value
+ *
+ * @return minimum value
+ */
+ public float getMin() {
+ return mV1;
+ }
+
+ /**
+ * Returns the max value
+ *
+ * @return maximum value
+ */
+ public float getMax() {
+ return mV2;
+ }
+
+ /**
+ * The OP_CODE for this command
+ *
+ * @return the opcode
+ */
+ public static int id() {
+ return OP_CODE;
+ }
+
+ /**
+ * The name of the class
+ *
+ * @return the name
+ */
+ @NonNull
+ public static String name() {
+ return CLASS_NAME;
+ }
+
+ @Override
+ protected void write(@NonNull WireBuffer buffer, float v1, float v2) {
+ apply(buffer, v1, v2);
+ }
+
+ /**
+ * Populate the documentation with a description of this operation
+ *
+ * @param doc to append the description to.
+ */
+ public static void documentation(@NonNull DocumentationBuilder doc) {
+ doc.operation("Layout Operations", OP_CODE, "HeightInModifierOperation")
+ .description("Add additional constraints to the height")
+ .field(DocumentedOperation.FLOAT, "min", "The minimum height, -1 if not applied")
+ .field(DocumentedOperation.FLOAT, "max", "The maximum height, -1 if not applied");
+ }
+
+ public HeightInModifierOperation(float min, float max) {
+ super(min, max);
+ mName = CLASS_NAME;
+ }
+
+ @Override
+ public void paint(@NonNull PaintContext context) {}
+
+ /**
+ * Writes out the HeightInModifier to the buffer
+ *
+ * @param buffer buffer to write to
+ * @param x1 start x of DrawOval
+ * @param y1 start y of the DrawOval
+ */
+ public static void apply(@NonNull WireBuffer buffer, float x1, float y1) {
+ write(buffer, OP_CODE, x1, y1);
+ }
+
+ @Override
+ public void serializeToString(int indent, @NonNull StringSerializer serializer) {
+ serializer.append(indent, "HEIGHT_IN = [" + getMin() + ", " + getMax() + "]");
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HeightModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HeightModifierOperation.java
index ec078a9..4b50a91 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HeightModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HeightModifierOperation.java
@@ -31,6 +31,7 @@
public class HeightModifierOperation extends DimensionModifierOperation {
private static final int OP_CODE = Operations.MODIFIER_HEIGHT;
public static final String CLASS_NAME = "HeightModifierOperation";
+ private HeightInModifierOperation mHeightIn = null;
/**
* The name of the class
@@ -110,4 +111,22 @@
.field(INT, "type", "")
.field(FLOAT, "value", "");
}
+
+ /**
+ * Set height in constraints
+ *
+ * @param heightInConstraints height constraints
+ */
+ public void setHeightIn(HeightInModifierOperation heightInConstraints) {
+ mHeightIn = heightInConstraints;
+ }
+
+ /**
+ * Returns height in constraints
+ *
+ * @return height in constraints
+ */
+ public HeightInModifierOperation getHeightIn() {
+ return mHeightIn;
+ }
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/WidthInModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/WidthInModifierOperation.java
new file mode 100644
index 0000000..c3624e5
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/WidthInModifierOperation.java
@@ -0,0 +1,124 @@
+/*
+ * 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.internal.widget.remotecompose.core.operations.layout.modifiers;
+
+import android.annotation.NonNull;
+
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.PaintContext;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
+import com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation;
+import com.android.internal.widget.remotecompose.core.operations.DrawBase2;
+import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer;
+
+import java.util.List;
+
+/** Set the min / max width dimension on a component */
+public class WidthInModifierOperation extends DrawBase2 implements ModifierOperation {
+ private static final int OP_CODE = Operations.MODIFIER_WIDTH_IN;
+ public static final String CLASS_NAME = "WidthInModifierOperation";
+
+ /**
+ * Read this operation and add it to the list of operations
+ *
+ * @param buffer the buffer to read
+ * @param operations the list of operations that will be added to
+ */
+ public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
+ Maker m = WidthInModifierOperation::new;
+ read(m, buffer, operations);
+ }
+
+ /**
+ * Returns the min value
+ *
+ * @return minimum value
+ */
+ public float getMin() {
+ return mV1;
+ }
+
+ /**
+ * Returns the max value
+ *
+ * @return maximum value
+ */
+ public float getMax() {
+ return mV2;
+ }
+
+ /**
+ * The OP_CODE for this command
+ *
+ * @return the opcode
+ */
+ public static int id() {
+ return OP_CODE;
+ }
+
+ /**
+ * The name of the class
+ *
+ * @return the name
+ */
+ @NonNull
+ public static String name() {
+ return CLASS_NAME;
+ }
+
+ @Override
+ protected void write(@NonNull WireBuffer buffer, float v1, float v2) {
+ apply(buffer, v1, v2);
+ }
+
+ /**
+ * Populate the documentation with a description of this operation
+ *
+ * @param doc to append the description to.
+ */
+ public static void documentation(@NonNull DocumentationBuilder doc) {
+ doc.operation("Layout Operations", OP_CODE, "WidthInModifierOperation")
+ .description("Add additional constraints to the width")
+ .field(DocumentedOperation.FLOAT, "min", "The minimum width, -1 if not applied")
+ .field(DocumentedOperation.FLOAT, "max", "The maximum width, -1 if not applied");
+ }
+
+ public WidthInModifierOperation(float min, float max) {
+ super(min, max);
+ mName = CLASS_NAME;
+ }
+
+ @Override
+ public void paint(@NonNull PaintContext context) {}
+
+ /**
+ * Writes out the WidthInModifier to the buffer
+ *
+ * @param buffer buffer to write to
+ * @param x1 start x of DrawOval
+ * @param y1 start y of the DrawOval
+ */
+ public static void apply(@NonNull WireBuffer buffer, float x1, float y1) {
+ write(buffer, OP_CODE, x1, y1);
+ }
+
+ @Override
+ public void serializeToString(int indent, @NonNull StringSerializer serializer) {
+ serializer.append(indent, "WIDTH_IN = [" + getMin() + ", " + getMax() + "]");
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/WidthModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/WidthModifierOperation.java
index 0530598..532027a 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/WidthModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/WidthModifierOperation.java
@@ -31,6 +31,7 @@
public class WidthModifierOperation extends DimensionModifierOperation {
private static final int OP_CODE = Operations.MODIFIER_WIDTH;
public static final String CLASS_NAME = "WidthModifierOperation";
+ private WidthInModifierOperation mWidthIn = null;
/**
* The name of the class
@@ -110,4 +111,22 @@
.field(INT, "type", "")
.field(FLOAT, "value", "");
}
+
+ /**
+ * Set width in constraints
+ *
+ * @param widthInConstraints width constraints
+ */
+ public void setWidthIn(WidthInModifierOperation widthInConstraints) {
+ mWidthIn = widthInConstraints;
+ }
+
+ /**
+ * Returns width in constraints
+ *
+ * @return width in constraints
+ */
+ public WidthInModifierOperation getWidthIn() {
+ return mWidthIn;
+ }
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/AnimatedFloatExpression.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/AnimatedFloatExpression.java
index b2ea0af..eb834a9 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/AnimatedFloatExpression.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/AnimatedFloatExpression.java
@@ -150,17 +150,47 @@
/** RAND_SEED operator */
public static final float RAND_SEED = asNan(OFFSET + 40);
+ /** NOISE_FROM operator calculate a random 0..1 number based on a seed */
+ public static final float NOISE_FROM = asNan(OFFSET + 41);
+
+ /** RANDOM_IN_RANGE random number in range */
+ public static final float RAND_IN_RANGE = asNan(OFFSET + 42);
+
+ /** SQUARE_SUM the sum of the square of two numbers */
+ public static final float SQUARE_SUM = asNan(OFFSET + 43);
+
+ /** STEP x > edge ? 1 : 0; */
+ public static final float STEP = asNan(OFFSET + 44);
+
+ /** SQUARE x*x; */
+ public static final float SQUARE = asNan(OFFSET + 45);
+
+ /** DUP x,x; */
+ public static final float DUP = asNan(OFFSET + 46);
+
+ /** HYPOT sqrt(x*x+y*y); */
+ public static final float HYPOT = asNan(OFFSET + 47);
+
+ /** SWAP y,x; */
+ public static final float SWAP = asNan(OFFSET + 48);
+
+ /** LERP (1-t)*x+t*y; */
+ public static final float LERP = asNan(OFFSET + 49);
+
+ /** SMOOTH_STEP (1-smoothstep(edge0,edge1,x)); */
+ public static final float SMOOTH_STEP = asNan(OFFSET + 50);
+
/** LAST valid operator */
- public static final int LAST_OP = OFFSET + 40;
+ public static final int LAST_OP = OFFSET + 50;
/** VAR1 operator */
- public static final float VAR1 = asNan(OFFSET + 41);
+ public static final float VAR1 = asNan(OFFSET + 51);
/** VAR2 operator */
- public static final float VAR2 = asNan(OFFSET + 42);
+ public static final float VAR2 = asNan(OFFSET + 52);
/** VAR2 operator */
- public static final float VAR3 = asNan(OFFSET + 43);
+ public static final float VAR3 = asNan(OFFSET + 53);
// TODO SQUARE, DUP, HYPOT, SWAP
// private static final float FP_PI = (float) Math.PI;
@@ -399,6 +429,17 @@
sNames.put(k++, "RAND");
sNames.put(k++, "RAND_SEED");
+ sNames.put(k++, "noise_from");
+ sNames.put(k++, "rand_in_range");
+ sNames.put(k++, "square_sum");
+ sNames.put(k++, "step");
+ sNames.put(k++, "square");
+ sNames.put(k++, "dup");
+ sNames.put(k++, "hypot");
+ sNames.put(k++, "swap");
+ sNames.put(k++, "lerp");
+ sNames.put(k++, "smooth_step");
+
sNames.put(k++, "a[0]");
sNames.put(k++, "a[1]");
sNames.put(k++, "a[2]");
@@ -615,9 +656,20 @@
private static final int OP_RAND = OFFSET + 39;
private static final int OP_RAND_SEED = OFFSET + 40;
- private static final int OP_FIRST_VAR = OFFSET + 41;
- private static final int OP_SECOND_VAR = OFFSET + 42;
- private static final int OP_THIRD_VAR = OFFSET + 43;
+ private static final int OP_NOISE_FROM = OFFSET + 41;
+ private static final int OP_RAND_IN_RANGE = OFFSET + 42;
+ private static final int OP_SQUARE_SUM = OFFSET + 43;
+ private static final int OP_STEP = OFFSET + 44;
+ private static final int OP_SQUARE = OFFSET + 45;
+ private static final int OP_DUP = OFFSET + 46;
+ private static final int OP_HYPOT = OFFSET + 47;
+ private static final int OP_SWAP = OFFSET + 48;
+ private static final int OP_LERP = OFFSET + 49;
+ private static final int OP_SMOOTH_STEP = OFFSET + 50;
+
+ private static final int OP_FIRST_VAR = OFFSET + 51;
+ private static final int OP_SECOND_VAR = OFFSET + 52;
+ private static final int OP_THIRD_VAR = OFFSET + 53;
int opEval(int sp, int id) {
float[] array;
@@ -824,6 +876,66 @@
}
}
return sp - 1;
+ case OP_NOISE_FROM:
+ int x = Float.floatToRawIntBits(mStack[sp]);
+ x = (x << 13) ^ x; // / Bitwise scrambling return
+ mStack[sp] =
+ (1.0f
+ - ((x * (x * x * 15731 + 789221) + 1376312589) & 0x7fffffff)
+ / 1073741824.0f);
+ return sp;
+
+ case OP_RAND_IN_RANGE:
+ if (sRandom == null) {
+ sRandom = new Random();
+ }
+ mStack[sp] = sRandom.nextFloat() * (mStack[sp] - mStack[sp - 1]) + mStack[sp - 1];
+ return sp;
+ case OP_SQUARE_SUM:
+ mStack[sp - 1] = mStack[sp - 1] * mStack[sp - 1] + mStack[sp] * mStack[sp];
+ return sp - 1;
+ case OP_STEP:
+ System.out.println(mStack[sp] + " > " + mStack[sp - 1]);
+ mStack[sp - 1] = (mStack[sp - 1] > mStack[sp]) ? 1f : 0f;
+ return sp - 1;
+ case OP_SQUARE:
+ mStack[sp] = mStack[sp] * mStack[sp];
+ return sp;
+ case OP_DUP:
+ mStack[sp + 1] = mStack[sp];
+ return sp + 1;
+ case OP_HYPOT:
+ mStack[sp - 1] = (float) Math.hypot(mStack[sp - 1], mStack[sp]);
+ return sp - 1;
+ case OP_SWAP:
+ float swap = mStack[sp - 1];
+ mStack[sp - 1] = mStack[sp];
+ mStack[sp] = swap;
+ return sp;
+ case OP_LERP:
+ float tmp1 = mStack[sp - 2];
+ float tmp2 = mStack[sp - 1];
+ float tmp3 = mStack[sp];
+ mStack[sp - 2] = tmp1 + (tmp2 - tmp1) * tmp3;
+ return sp - 2;
+ case OP_SMOOTH_STEP:
+ float val3 = mStack[sp - 2];
+ float max2 = mStack[sp - 1];
+ float min1 = mStack[sp];
+ System.out.println("val3 = " + val3 + " min1 = " + min1 + " max2 = " + max2);
+ if (val3 < min1) {
+ mStack[sp - 2] = 0f;
+ System.out.println("below min ");
+ } else if (val3 > max2) {
+ mStack[sp - 2] = 1f;
+ System.out.println("above max ");
+
+ } else {
+ float v = (val3 - min1) / (max2 - min1);
+ System.out.println("v = " + v);
+ mStack[sp - 2] = v * v * (3 - 2 * v);
+ }
+ return sp - 2;
case OP_FIRST_VAR:
mStack[sp] = mVar[0];
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/Easing.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/Easing.java
index d8bc83e..2b53682 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/Easing.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/Easing.java
@@ -19,24 +19,58 @@
public abstract class Easing {
int mType;
- /** get the value at point x */
+ /**
+ * get the value at point x
+ *
+ * @param x the position at which to get the slope
+ * @return the value at the point
+ */
public abstract float get(float x);
- /** get the slope of the easing function at at x */
+ /**
+ * get the slope of the easing function at at x
+ *
+ * @param x the position at which to get the slope
+ * @return the slope
+ */
public abstract float getDiff(float x);
+ /**
+ * get the type of easing function
+ *
+ * @return the type of easing function
+ */
public int getType() {
return mType;
}
+ /** cubic Easing function that accelerates and decelerates */
public static final int CUBIC_STANDARD = 1;
+
+ /** cubic Easing function that accelerates */
public static final int CUBIC_ACCELERATE = 2;
+
+ /** cubic Easing function that decelerates */
public static final int CUBIC_DECELERATE = 3;
+
+ /** cubic Easing function that just linearly interpolates */
public static final int CUBIC_LINEAR = 4;
+
+ /** cubic Easing function that goes bacwards and then accelerates */
public static final int CUBIC_ANTICIPATE = 5;
+
+ /** cubic Easing function that overshoots and then goes back */
public static final int CUBIC_OVERSHOOT = 6;
+
+ /** cubic Easing function that you customize */
public static final int CUBIC_CUSTOM = 11;
+
+ /** a monotonic spline Easing function that you customize */
public static final int SPLINE_CUSTOM = 12;
+
+ /** a bouncing Easing function */
public static final int EASE_OUT_BOUNCE = 13;
+
+ /** a elastic Easing function */
public static final int EASE_OUT_ELASTIC = 14;
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/FloatAnimation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/FloatAnimation.java
index 465c95d..65472c2 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/FloatAnimation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/FloatAnimation.java
@@ -52,16 +52,25 @@
return str;
}
- public FloatAnimation() {
- mType = CUBIC_STANDARD;
- mEasingCurve = new CubicEasing(mType);
- }
-
+ /**
+ * Create an animation based on a float encoding of the animation
+ *
+ * @param description the float encoding of the animation
+ */
public FloatAnimation(@NonNull float... description) {
mType = CUBIC_STANDARD;
setAnimationDescription(description);
}
+ /**
+ * Create an animation based on the parameters
+ *
+ * @param type The type of animation
+ * @param duration The duration of the animation
+ * @param description The float parameters describing the animation
+ * @param initialValue The initial value of the float (NaN if none)
+ * @param wrap The wrap value of the animation NaN if it does not wrap
+ */
public FloatAnimation(
int type,
float duration,
@@ -139,8 +148,8 @@
/**
* Useful to debug the packed form of an animation string
*
- * @param description
- * @return
+ * @param description the float encoding of the animation
+ * @return a string describing the animation
*/
public static String unpackAnimationToString(float[] description) {
float[] mSpec = description;
@@ -223,7 +232,7 @@
/**
* Create an animation based on a float encoding of the animation
*
- * @param description
+ * @param description the float encoding of the animation
*/
public void setAnimationDescription(@NonNull float[] description) {
mSpec = description;
@@ -288,7 +297,7 @@
/**
* Set the initial Value
*
- * @param value
+ * @param value the value to set
*/
public void setInitialValue(float value) {
@@ -321,7 +330,7 @@
/**
* Set the target value to interpolate to
*
- * @param value
+ * @param value the value to set
*/
public void setTargetValue(float value) {
mTargetValue = value;
@@ -342,6 +351,11 @@
setScaleOffset();
}
+ /**
+ * Get the target value
+ *
+ * @return the target value
+ */
public float getTargetValue() {
return mTargetValue;
}
@@ -369,6 +383,11 @@
return mEasingCurve.getDiff(t / mDuration) * (mTargetValue - mInitialValue);
}
+ /**
+ * Get the initial value
+ *
+ * @return the initial value
+ */
public float getInitialValue() {
return mInitialValue;
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/GeneralEasing.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/GeneralEasing.java
index 06969cc..960eff2 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/GeneralEasing.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/GeneralEasing.java
@@ -25,13 +25,18 @@
/**
* Set the curve based on the float encoding of it
*
- * @param data
+ * @param data the float encoding of the curve
*/
public void setCurveSpecification(@NonNull float[] data) {
mEasingData = data;
createEngine();
}
+ /**
+ * Get the float encoding of the curve
+ *
+ * @return the float encoding of the curve
+ */
public @NonNull float[] getCurveSpecification() {
return mEasingData;
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/MonotonicCurveFit.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/MonotonicCurveFit.java
index f4579a2..01d64df 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/MonotonicCurveFit.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/MonotonicCurveFit.java
@@ -76,10 +76,10 @@
}
/**
- * Get the position of all curves at time t
+ * Get the position of all curves at position t
*
- * @param t
- * @param v
+ * @param t the point on the spline
+ * @param v the array to fill (for multiple curves)
*/
public void getPos(double t, @NonNull double[] v) {
final int n = mT.length;
@@ -136,10 +136,10 @@
}
/**
- * Get the position of all curves at time t
+ * Get the position of all curves at position t
*
- * @param t
- * @param v
+ * @param t the point on the spline
+ * @param v the array to fill
*/
public void getPos(double t, @NonNull float[] v) {
final int n = mT.length;
@@ -196,11 +196,11 @@
}
/**
- * Get the position of the jth curve at time t
+ * Get the position of the jth curve at position t
*
- * @param t
- * @param j
- * @return
+ * @param t the position
+ * @param j the curve to get
+ * @return the position
*/
public double getPos(double t, int j) {
final int n = mT.length;
@@ -240,8 +240,8 @@
/**
* Get the slope of all the curves at position t
*
- * @param t
- * @param v
+ * @param t the position
+ * @param v the array to fill
*/
public void getSlope(double t, @NonNull double[] v) {
final int n = mT.length;
@@ -271,9 +271,9 @@
/**
* Get the slope of the j curve at position t
*
- * @param t
- * @param j
- * @return
+ * @param t the position
+ * @param j the curve to get the value at
+ * @return the slope
*/
public double getSlope(double t, int j) {
final int n = mT.length;
@@ -297,6 +297,11 @@
return 0; // should never reach here
}
+ /**
+ * Get the time point used to create the curve
+ *
+ * @return the time points used to create the curve
+ */
public @NonNull double[] getTimePoints() {
return mT;
}
@@ -332,7 +337,12 @@
+ h * t1;
}
- /** This builds a monotonic spline to be used as a wave function */
+ /**
+ * This builds a monotonic spline to be used as a wave function
+ *
+ * @param configString the configuration string
+ * @return the curve
+ */
@NonNull
public static MonotonicCurveFit buildWave(@NonNull String configString) {
// done this way for efficiency
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/MonotonicSpline.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/MonotonicSpline.java
index 23a6643..8bb7dae 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/MonotonicSpline.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/MonotonicSpline.java
@@ -76,6 +76,11 @@
mTangent = tangent;
}
+ /**
+ * Get the value point used in the interpolator.
+ *
+ * @return the value points
+ */
public float[] getArray() {
return mY;
}
@@ -83,7 +88,7 @@
/**
* Get the position of all curves at time t
*
- * @param t
+ * @param t the position along spline
* @return position at t
*/
public float getPos(float t) {
@@ -139,7 +144,7 @@
/**
* Get the slope of the curve at position t
*
- * @param t
+ * @param t the position along spline
* @return slope at t
*/
public float getSlope(float t) {
@@ -167,6 +172,11 @@
return v;
}
+ /**
+ * Get the time points used in the interpolator.
+ *
+ * @return the time points
+ */
public float[] getTimePoints() {
return mT;
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/SpringStopEngine.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/SpringStopEngine.java
index 03e4503..2f1379b 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/SpringStopEngine.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/SpringStopEngine.java
@@ -42,9 +42,9 @@
private float mStopThreshold;
private int mBoundaryMode = 0;
- public String debug(String desc, float time) {
- return null;
- }
+ // public String debug(String desc, float time) {
+ // return null;
+ // }
void log(String str) {
StackTraceElement s = new Throwable().getStackTrace()[1];
@@ -53,20 +53,41 @@
System.out.println(line + str);
}
+ /** */
public SpringStopEngine() {}
+ /**
+ * get the value the sping is pulling towards
+ *
+ * @return the value the sping is pulling towards
+ */
public float getTargetValue() {
return (float) mTargetPos;
}
+ /**
+ * get the value the sping is starting from
+ *
+ * @param v the value the sping is starting from
+ */
public void setInitialValue(float v) {
mPos = v;
}
+ /**
+ * set the value the sping is pulling towards
+ *
+ * @param v the value the sping is pulling towards
+ */
public void setTargetValue(float v) {
mTargetPos = v;
}
+ /**
+ * Create a sping engine with the parameters encoded as an array of floats
+ *
+ * @param parameters the parameters to use
+ */
public SpringStopEngine(float[] parameters) {
if (parameters[0] != 0) {
throw new RuntimeException(" parameter[0] should be 0");
@@ -83,9 +104,9 @@
/**
* Config the spring starting conditions
*
- * @param currentPos
- * @param target
- * @param currentVelocity
+ * @param currentPos the current position of the spring
+ * @param target the target position of the spring
+ * @param currentVelocity the current velocity of the spring
*/
public void springStart(float currentPos, float target, float currentVelocity) {
mTargetPos = target;
@@ -115,10 +136,22 @@
mLastTime = 0;
}
+ /**
+ * get the velocity of the spring at a time
+ *
+ * @param time the time to get the velocity at
+ * @return the velocity of the spring at a time
+ */
public float getVelocity(float time) {
return (float) mV;
}
+ /**
+ * get the position of the spring at a time
+ *
+ * @param time the time to get the position at
+ * @return the position of the spring at a time
+ */
public float get(float time) {
compute(time - mLastTime);
mLastTime = time;
@@ -128,6 +161,11 @@
return (float) mPos;
}
+ /**
+ * get the acceleration of the spring
+ *
+ * @return the acceleration of the spring
+ */
public float getAcceleration() {
double k = mStiffness;
double c = mDamping;
@@ -135,10 +173,20 @@
return (float) (-k * x - c * mV) / mMass;
}
+ /**
+ * get the velocity of the spring
+ *
+ * @return the velocity of the spring
+ */
public float getVelocity() {
return 0;
}
+ /**
+ * is the spring stopped
+ *
+ * @return true if the spring is stopped
+ */
public boolean isStopped() {
double x = (mPos - mTargetPos);
double k = mStiffness;
@@ -149,6 +197,11 @@
return max_def <= mStopThreshold;
}
+ /**
+ * increment the spring position over time dt
+ *
+ * @param dt the time to increment the spring position over
+ */
private void compute(double dt) {
if (dt <= 0) {
// Nothing to compute if there's no time difference
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/StepCurve.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/StepCurve.java
index b1eb804..376e1e9 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/StepCurve.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/StepCurve.java
@@ -26,6 +26,13 @@
// private static final boolean DEBUG = false;
@NonNull private final MonotonicCurveFit mCurveFit;
+ /**
+ * Create a step curve from a series of values
+ *
+ * @param params the series of values to ease over
+ * @param offset the offset into the array
+ * @param len the length of the array to use
+ */
public StepCurve(@NonNull float[] params, int offset, int len) {
mCurveFit = genSpline(params, offset, len);
}
diff --git a/core/java/com/android/internal/widget/remotecompose/player/RemoteComposePlayer.java b/core/java/com/android/internal/widget/remotecompose/player/RemoteComposePlayer.java
index 5de11a19..b17e3dc 100644
--- a/core/java/com/android/internal/widget/remotecompose/player/RemoteComposePlayer.java
+++ b/core/java/com/android/internal/widget/remotecompose/player/RemoteComposePlayer.java
@@ -66,6 +66,28 @@
}
/**
+ * @inheritDoc
+ */
+ public void requestLayout() {
+ super.requestLayout();
+
+ if (mInner != null) {
+ mInner.requestLayout();
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public void invalidate() {
+ super.invalidate();
+
+ if (mInner != null) {
+ mInner.invalidate();
+ }
+ }
+
+ /**
* Returns true if the document supports drag touch events
*
* @return true if draggable content, false otherwise
diff --git a/core/java/com/android/internal/widget/remotecompose/player/platform/RemoteComposeCanvas.java b/core/java/com/android/internal/widget/remotecompose/player/platform/RemoteComposeCanvas.java
index 970cc4a..334ba62 100644
--- a/core/java/com/android/internal/widget/remotecompose/player/platform/RemoteComposeCanvas.java
+++ b/core/java/com/android/internal/widget/remotecompose/player/platform/RemoteComposeCanvas.java
@@ -48,7 +48,7 @@
boolean mHasClickAreas = false;
Point mActionDownPoint = new Point(0, 0);
AndroidRemoteContext mARContext = new AndroidRemoteContext();
- float mDensity = 1f;
+ float mDensity = Float.NaN;
long mStart = System.nanoTime();
long mLastFrameDelay = 1;
@@ -68,24 +68,18 @@
public RemoteComposeCanvas(Context context) {
super(context);
- if (USE_VIEW_AREA_CLICK) {
- addOnAttachStateChangeListener(this);
- }
+ addOnAttachStateChangeListener(this);
}
public RemoteComposeCanvas(Context context, AttributeSet attrs) {
super(context, attrs);
- if (USE_VIEW_AREA_CLICK) {
- addOnAttachStateChangeListener(this);
- }
+ addOnAttachStateChangeListener(this);
}
public RemoteComposeCanvas(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setBackgroundColor(Color.WHITE);
- if (USE_VIEW_AREA_CLICK) {
- addOnAttachStateChangeListener(this);
- }
+ addOnAttachStateChangeListener(this);
}
public void setDebug(int value) {
@@ -124,6 +118,7 @@
mChoreographer.postFrameCallback(mFrameCallback);
}
mDensity = getContext().getResources().getDisplayMetrics().density;
+ mARContext.setDensity(mDensity);
if (mDocument == null) {
return;
}
diff --git a/core/jni/android_graphics_BLASTBufferQueue.cpp b/core/jni/android_graphics_BLASTBufferQueue.cpp
index 7b61a5d..10d6d33 100644
--- a/core/jni/android_graphics_BLASTBufferQueue.cpp
+++ b/core/jni/android_graphics_BLASTBufferQueue.cpp
@@ -107,10 +107,11 @@
}
}
- void onWaitForBufferRelease() {
+ void onWaitForBufferRelease(const nsecs_t durationNanos) {
JNIEnv* env = getenv(mVm);
getenv(mVm)->CallVoidMethod(mWaitForBufferReleaseObject,
- gWaitForBufferReleaseCallback.onWaitForBufferRelease);
+ gWaitForBufferReleaseCallback.onWaitForBufferRelease,
+ durationNanos);
DieIfException(env, "Uncaught exception in WaitForBufferReleaseCallback.");
}
@@ -255,7 +256,9 @@
} else {
sp<WaitForBufferReleaseCallbackWrapper> wrapper =
new WaitForBufferReleaseCallbackWrapper{env, waitForBufferReleaseCallback};
- queue->setWaitForBufferReleaseCallback([wrapper]() { wrapper->onWaitForBufferRelease(); });
+ queue->setWaitForBufferReleaseCallback([wrapper](const nsecs_t durationNanos) {
+ wrapper->onWaitForBufferRelease(durationNanos);
+ });
}
}
@@ -305,7 +308,7 @@
jclass waitForBufferReleaseClass =
FindClassOrDie(env, "android/graphics/BLASTBufferQueue$WaitForBufferReleaseCallback");
gWaitForBufferReleaseCallback.onWaitForBufferRelease =
- GetMethodIDOrDie(env, waitForBufferReleaseClass, "onWaitForBufferRelease", "()V");
+ GetMethodIDOrDie(env, waitForBufferReleaseClass, "onWaitForBufferRelease", "(J)V");
return 0;
}
diff --git a/core/jni/android_os_PerfettoTrace.cpp b/core/jni/android_os_PerfettoTrace.cpp
index 988aea7..962aefc 100644
--- a/core/jni/android_os_PerfettoTrace.cpp
+++ b/core/jni/android_os_PerfettoTrace.cpp
@@ -23,6 +23,7 @@
#include <nativehelper/scoped_local_ref.h>
#include <nativehelper/scoped_primitive_array.h>
#include <nativehelper/scoped_utf_chars.h>
+#include <nativehelper/utils.h>
#include <tracing_sdk.h>
namespace android {
@@ -36,30 +37,6 @@
return static_cast<jlong>(reinterpret_cast<uintptr_t>(ptr));
}
-static const char* fromJavaString(JNIEnv* env, jstring jstr) {
- if (!jstr) return "";
- ScopedUtfChars chars(env, jstr);
-
- if (!chars.c_str()) {
- ALOGE("Failed extracting string");
- return "";
- }
-
- return chars.c_str();
-}
-
-static void android_os_PerfettoTrace_event(JNIEnv* env, jclass, jint type, jlong cat_ptr,
- jstring name, jlong extra_ptr) {
- ScopedUtfChars name_utf(env, name);
- if (!name_utf.c_str()) {
- ALOGE("Failed extracting string");
- }
-
- tracing_perfetto::Category* category = toPointer<tracing_perfetto::Category>(cat_ptr);
- tracing_perfetto::trace_event(type, category->get(), name_utf.c_str(),
- toPointer<tracing_perfetto::Extra>(extra_ptr));
-}
-
static jlong android_os_PerfettoTrace_get_process_track_uuid() {
return tracing_perfetto::get_process_track_uuid();
}
@@ -70,20 +47,18 @@
static void android_os_PerfettoTrace_activate_trigger(JNIEnv* env, jclass, jstring name,
jint ttl_ms) {
- ScopedUtfChars name_utf(env, name);
- if (!name_utf.c_str()) {
- ALOGE("Failed extracting string");
- return;
- }
-
- tracing_perfetto::activate_trigger(name_utf.c_str(), static_cast<uint32_t>(ttl_ms));
+ ScopedUtfChars name_chars = GET_UTF_OR_RETURN_VOID(env, name);
+ tracing_perfetto::activate_trigger(name_chars.c_str(), static_cast<uint32_t>(ttl_ms));
}
static jlong android_os_PerfettoTraceCategory_init(JNIEnv* env, jclass, jstring name, jstring tag,
jstring severity) {
- return toJLong(new tracing_perfetto::Category(fromJavaString(env, name),
- fromJavaString(env, tag),
- fromJavaString(env, severity)));
+ ScopedUtfChars name_chars = GET_UTF_OR_RETURN(env, name);
+ ScopedUtfChars tag_chars = GET_UTF_OR_RETURN(env, tag);
+ ScopedUtfChars severity_chars = GET_UTF_OR_RETURN(env, severity);
+
+ return toJLong(new tracing_perfetto::Category(name_chars.c_str(), tag_chars.c_str(),
+ severity_chars.c_str()));
}
static jlong android_os_PerfettoTraceCategory_delete() {
@@ -121,8 +96,7 @@
};
static const JNINativeMethod gTraceMethods[] =
- {{"native_event", "(IJLjava/lang/String;J)V", (void*)android_os_PerfettoTrace_event},
- {"native_get_process_track_uuid", "()J",
+ {{"native_get_process_track_uuid", "()J",
(void*)android_os_PerfettoTrace_get_process_track_uuid},
{"native_get_thread_track_uuid", "(J)J",
(void*)android_os_PerfettoTrace_get_thread_track_uuid},
@@ -132,10 +106,11 @@
int register_android_os_PerfettoTrace(JNIEnv* env) {
int res = jniRegisterNativeMethods(env, "android/os/PerfettoTrace", gTraceMethods,
NELEM(gTraceMethods));
+ LOG_ALWAYS_FATAL_IF(res < 0, "Unable to register perfetto native methods.");
res = jniRegisterNativeMethods(env, "android/os/PerfettoTrace$Category", gCategoryMethods,
NELEM(gCategoryMethods));
- LOG_ALWAYS_FATAL_IF(res < 0, "Unable to register native methods.");
+ LOG_ALWAYS_FATAL_IF(res < 0, "Unable to register category native methods.");
return 0;
}
diff --git a/core/jni/android_os_PerfettoTrackEventExtra.cpp b/core/jni/android_os_PerfettoTrackEventExtra.cpp
index 9adad7b..b8bdc8c 100644
--- a/core/jni/android_os_PerfettoTrackEventExtra.cpp
+++ b/core/jni/android_os_PerfettoTrackEventExtra.cpp
@@ -20,6 +20,7 @@
#include <log/log.h>
#include <nativehelper/JNIHelp.h>
#include <nativehelper/scoped_utf_chars.h>
+#include <nativehelper/utils.h>
#include <tracing_sdk.h>
static constexpr ssize_t kMaxStrLen = 4096;
@@ -34,32 +35,24 @@
return static_cast<jlong>(reinterpret_cast<uintptr_t>(ptr));
}
-static const char* fromJavaString(JNIEnv* env, jstring jstr) {
- if (!jstr) return "";
- ScopedUtfChars chars(env, jstr);
-
- if (!chars.c_str()) {
- ALOGE("Failed extracting string");
- return "";
- }
-
- return chars.c_str();
-}
-
static jlong android_os_PerfettoTrackEventExtraArgInt64_init(JNIEnv* env, jclass, jstring name) {
- return toJLong(new tracing_perfetto::DebugArg<int64_t>(fromJavaString(env, name)));
+ ScopedUtfChars name_chars = GET_UTF_OR_RETURN(env, name);
+ return toJLong(new tracing_perfetto::DebugArg<int64_t>(name_chars.c_str()));
}
static jlong android_os_PerfettoTrackEventExtraArgBool_init(JNIEnv* env, jclass, jstring name) {
- return toJLong(new tracing_perfetto::DebugArg<bool>(fromJavaString(env, name)));
+ ScopedUtfChars name_chars = GET_UTF_OR_RETURN(env, name);
+ return toJLong(new tracing_perfetto::DebugArg<bool>(name_chars.c_str()));
}
static jlong android_os_PerfettoTrackEventExtraArgDouble_init(JNIEnv* env, jclass, jstring name) {
- return toJLong(new tracing_perfetto::DebugArg<double>(fromJavaString(env, name)));
+ ScopedUtfChars name_chars = GET_UTF_OR_RETURN(env, name);
+ return toJLong(new tracing_perfetto::DebugArg<double>(name_chars.c_str()));
}
static jlong android_os_PerfettoTrackEventExtraArgString_init(JNIEnv* env, jclass, jstring name) {
- return toJLong(new tracing_perfetto::DebugArg<const char*>(fromJavaString(env, name)));
+ ScopedUtfChars name_chars = GET_UTF_OR_RETURN(env, name);
+ return toJLong(new tracing_perfetto::DebugArg<const char*>(name_chars.c_str()));
}
static jlong android_os_PerfettoTrackEventExtraArgInt64_delete() {
@@ -116,9 +109,11 @@
static void android_os_PerfettoTrackEventExtraArgString_set_value(JNIEnv* env, jclass, jlong ptr,
jstring val) {
+ ScopedUtfChars val_chars = GET_UTF_OR_RETURN_VOID(env, val);
+
tracing_perfetto::DebugArg<const char*>* arg =
toPointer<tracing_perfetto::DebugArg<const char*>>(ptr);
- arg->set_value(strdup(fromJavaString(env, val)));
+ arg->set_value(strdup(val_chars.c_str()));
}
static jlong android_os_PerfettoTrackEventExtraFieldInt64_init() {
@@ -191,9 +186,11 @@
static void android_os_PerfettoTrackEventExtraFieldString_set_value(JNIEnv* env, jclass, jlong ptr,
jlong id, jstring val) {
+ ScopedUtfChars val_chars = GET_UTF_OR_RETURN_VOID(env, val);
+
tracing_perfetto::ProtoField<const char*>* field =
toPointer<tracing_perfetto::ProtoField<const char*>>(ptr);
- field->set_value(id, strdup(fromJavaString(env, val)));
+ field->set_value(id, strdup(val_chars.c_str()));
}
static void android_os_PerfettoTrackEventExtraFieldNested_add_field(jlong field_ptr,
@@ -234,7 +231,8 @@
static jlong android_os_PerfettoTrackEventExtraNamedTrack_init(JNIEnv* env, jclass, jlong id,
jstring name, jlong parent_uuid) {
- return toJLong(new tracing_perfetto::NamedTrack(id, parent_uuid, fromJavaString(env, name)));
+ ScopedUtfChars name_chars = GET_UTF_OR_RETURN(env, name);
+ return toJLong(new tracing_perfetto::NamedTrack(id, parent_uuid, name_chars.c_str()));
}
static jlong android_os_PerfettoTrackEventExtraNamedTrack_delete() {
@@ -248,8 +246,9 @@
static jlong android_os_PerfettoTrackEventExtraCounterTrack_init(JNIEnv* env, jclass, jstring name,
jlong parent_uuid) {
- return toJLong(
- new tracing_perfetto::RegisteredTrack(1, parent_uuid, fromJavaString(env, name), true));
+ ScopedUtfChars name_chars = GET_UTF_OR_RETURN(env, name);
+
+ return toJLong(new tracing_perfetto::RegisteredTrack(1, parent_uuid, name_chars.c_str(), true));
}
static jlong android_os_PerfettoTrackEventExtraCounterTrack_delete() {
@@ -317,6 +316,15 @@
extra->clear_extras();
}
+static void android_os_PerfettoTrackEventExtra_emit(JNIEnv* env, jclass, jint type, jlong cat_ptr,
+ jstring name, jlong extra_ptr) {
+ ScopedUtfChars name_chars = GET_UTF_OR_RETURN_VOID(env, name);
+
+ tracing_perfetto::Category* category = toPointer<tracing_perfetto::Category>(cat_ptr);
+ tracing_perfetto::trace_event(type, category->get(), name_chars.c_str(),
+ toPointer<tracing_perfetto::Extra>(extra_ptr));
+}
+
static jlong android_os_PerfettoTrackEventExtraProto_init() {
return toJLong(new tracing_perfetto::Proto());
}
@@ -344,7 +352,9 @@
{{"native_init", "()J", (void*)android_os_PerfettoTrackEventExtra_init},
{"native_delete", "()J", (void*)android_os_PerfettoTrackEventExtra_delete},
{"native_add_arg", "(JJ)V", (void*)android_os_PerfettoTrackEventExtra_add_arg},
- {"native_clear_args", "(J)V", (void*)android_os_PerfettoTrackEventExtra_clear_args}};
+ {"native_clear_args", "(J)V", (void*)android_os_PerfettoTrackEventExtra_clear_args},
+ {"native_emit", "(IJLjava/lang/String;J)V",
+ (void*)android_os_PerfettoTrackEventExtra_emit}};
static const JNINativeMethod gProtoMethods[] =
{{"native_init", "()J", (void*)android_os_PerfettoTrackEventExtraProto_init},
diff --git a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
index 3108f1f..e78c524 100644
--- a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
+++ b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
@@ -349,21 +349,19 @@
* 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 @@
}
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 @@
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 @@
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 @@
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 @@
{
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 @@
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 @@
{"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 0eb7c4a..5225ce8 100644
--- a/core/jni/fd_utils.cpp
+++ b/core/jni/fd_utils.cpp
@@ -51,7 +51,8 @@
"/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/proto/android/providers/settings/secure.proto b/core/proto/android/providers/settings/secure.proto
index 5d0b340..69c812c 100644
--- a/core/proto/android/providers/settings/secure.proto
+++ b/core/proto/android/providers/settings/secure.proto
@@ -109,6 +109,7 @@
optional SettingProto em_value = 61 [ (android.privacy).dest = DEST_AUTOMATIC ];
// Settings for accessibility autoclick
optional SettingProto autoclick_cursor_area_size = 62 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ optional SettingProto autoclick_ignore_minor_cursor_movement = 63 [ (android.privacy).dest = DEST_AUTOMATIC ];
}
optional Accessibility accessibility = 2;
diff --git a/core/proto/android/providers/settings/system.proto b/core/proto/android/providers/settings/system.proto
index 64c9f54..325790c 100644
--- a/core/proto/android/providers/settings/system.proto
+++ b/core/proto/android/providers/settings/system.proto
@@ -218,7 +218,8 @@
optional SettingProto tap_to_click = 4 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto tap_dragging = 5 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto three_finger_tap_customization = 6 [ (android.privacy).dest = DEST_AUTOMATIC ];
- optional SettingProto system_gestures = 7 [ (android.privacy).dest = DEST_AUTOMATIC ];;
+ optional SettingProto system_gestures = 7 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ optional SettingProto acceleration_enabled = 8 [ (android.privacy).dest = DEST_AUTOMATIC ];;
}
optional Touchpad touchpad = 36;
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 4450802..aad8f8a 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" />
@@ -8323,16 +8324,15 @@
<!-- Allows an application to perform actions on behalf of users inside of
applications.
- <p>This permission is currently only granted to preinstalled / system apps having the
- {@link android.app.role.ASSISTANT} role.
+ <p>This permission is currently only granted to privileged system apps.
<p>Apps contributing app functions can opt to disallow callers with this permission,
limiting to only callers with {@link android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED}
instead.
- <p>Protection level: internal|role
+ <p>Protection level: internal|privileged
@FlaggedApi(android.app.appfunctions.flags.Flags.FLAG_ENABLE_APP_FUNCTION_MANAGER) -->
<permission android:name="android.permission.EXECUTE_APP_FUNCTIONS"
android:featureFlag="android.app.appfunctions.flags.enable_app_function_manager"
- android:protectionLevel="internal|role" />
+ android:protectionLevel="internal|privileged" />
<!-- Allows an application to display its suggestions using the autofill framework.
<p>For now, this permission is only granted to the Browser application.
diff --git a/core/res/res/layout/preference_list_fragment.xml b/core/res/res/layout/preference_list_fragment.xml
index 44a5df9..c43975e 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 4df7602..db2fe7d 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/attrs.xml b/core/res/res/values/attrs.xml
index 8bf61bb..a1f85c3 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -2737,7 +2737,7 @@
The default is true. -->
<attr name="windowIsFrameRatePowerSavingsBalanced" format="boolean"/>
- <!-- Flag indicating whether this window would opt-out the edge-to-edge enforcement.
+ <!-- Flag indicating whether this window would opt out the edge-to-edge enforcement.
<p>If this is false, the edge-to-edge enforcement will be applied to the window if it
belongs to an app targeting
@@ -2757,8 +2757,9 @@
The Configuration will be stable regardless of the system insets change.
</ul>
- <p>If this is true, the edge-to-edge enforcement won't be applied. However, this
- attribute will be deprecated and disabled in a future SDK level.
+ <p>If this is true, the edge-to-edge enforcement won't be applied. But if the window
+ belongs to an app targeting {@link android.os.Build.VERSION_CODES#BAKLAVA BAKLAVA} or
+ above, this attribute is ignored and the enforcement is applied regardless.
<p>This is false by default. -->
<attr name="windowOptOutEdgeToEdgeEnforcement" format="boolean"/>
@@ -7584,7 +7585,7 @@
<!-- Used to config the segments of a NotificationProgressDrawable. -->
<!-- @hide internal use only -->
<declare-styleable name="NotificationProgressDrawableSegments">
- <!-- TODO: b/372908709 - maybe move this to NotificationProgressBar, because that's the only
+ <!-- TODO: b/390196782 - maybe move this to NotificationProgressBar, because that's the only
place this is used actually. Same for NotificationProgressDrawable.segSegGap/segPointGap
above. -->
<!-- Minimum required drawing width. The drawing width refers to the width after
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 416e0ae..ec1be83 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 666f1cf..965c69d 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 e82992b..7baaa6d 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 -->
@@ -159,15 +159,15 @@
<!-- @FlaggedApi(android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE_MODULE)
@hide @SystemApi -->
- <public name="config_defaultOnDeviceIntelligenceService"></public>
+ <public name="config_defaultOnDeviceIntelligenceService" />
<!-- @FlaggedApi(android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE_MODULE)
@hide @SystemApi -->
- <public name="config_defaultOnDeviceSandboxedInferenceService"></public>
+ <public name="config_defaultOnDeviceSandboxedInferenceService" />
<!-- @FlaggedApi(android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE_MODULE)
@hide @SystemApi -->
- <public name="config_defaultOnDeviceIntelligenceDeviceConfigNamespace"></public>
+ <public name="config_defaultOnDeviceIntelligenceDeviceConfigNamespace" />
</staging-public-group>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 84d51f0..77cc686 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 44b2d90..dfe7d03 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.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 @@
}
@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/app/NotificationManagerTest.java b/core/tests/coretests/src/android/app/NotificationManagerTest.java
index 18ba6a1..4143229 100644
--- a/core/tests/coretests/src/android/app/NotificationManagerTest.java
+++ b/core/tests/coretests/src/android/app/NotificationManagerTest.java
@@ -30,8 +30,10 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.annotation.Nullable;
import android.content.Context;
import android.content.ContextWrapper;
+import android.content.pm.PackageManager;
import android.content.pm.ParceledListSlice;
import android.os.UserHandle;
import android.platform.test.annotations.EnableFlags;
@@ -263,6 +265,38 @@
}
@Test
+ @EnableFlags({Flags.FLAG_NM_BINDER_PERF_THROTTLE_NOTIFY,
+ Flags.FLAG_NM_BINDER_PERF_LOG_NM_THROTTLING})
+ public void notify_rapidUpdate_logsOncePerSecond() throws Exception {
+ Notification n = exampleNotification();
+
+ for (int i = 0; i < 650; i++) {
+ mNotificationManager.notify(1, n);
+ mClock.advanceByMillis(10);
+ }
+
+ // Runs for a total of 6.5 seconds, so should log once (when RateEstimator catches up) + 6
+ // more times (after 1 second each).
+ verify(mNotificationManager.mBackendService, times(7)).incrementCounter(
+ eq("notifications.value_client_throttled_notify_update"));
+ }
+
+ @Test
+ @EnableFlags({Flags.FLAG_NM_BINDER_PERF_THROTTLE_NOTIFY,
+ Flags.FLAG_NM_BINDER_PERF_LOG_NM_THROTTLING})
+ public void cancel_unnecessaryAndRapid_logsOncePerSecond() throws Exception {
+ for (int i = 0; i < 650; i++) {
+ mNotificationManager.cancel(1);
+ mClock.advanceByMillis(10);
+ }
+
+ // Runs for a total of 6.5 seconds, so should log once (when RateEstimator catches up) + 6
+ // more times (after 1 second each).
+ verify(mNotificationManager.mBackendService, times(7)).incrementCounter(
+ eq("notifications.value_client_throttled_cancel_duplicate"));
+ }
+
+ @Test
@EnableFlags(Flags.FLAG_NM_BINDER_PERF_CACHE_CHANNELS)
public void getNotificationChannel_cachedUntilInvalidated() throws Exception {
// Invalidate the cache first because the cache won't do anything until then
@@ -409,6 +443,46 @@
.getOrCreateNotificationChannels(any(), any(), anyInt(), anyBoolean());
}
+ @Test
+ @EnableFlags({Flags.FLAG_MODES_API, Flags.FLAG_MODES_UI})
+ public void areAutomaticZenRulesUserManaged_handheld_isTrue() {
+ PackageManager pm = mock(PackageManager.class);
+ when(pm.hasSystemFeature(any())).thenReturn(false);
+ mContext.setPackageManager(pm);
+
+ assertThat(mNotificationManager.areAutomaticZenRulesUserManaged()).isTrue();
+ }
+
+ @Test
+ @EnableFlags({Flags.FLAG_MODES_API, Flags.FLAG_MODES_UI})
+ public void areAutomaticZenRulesUserManaged_auto_isFalse() {
+ PackageManager pm = mock(PackageManager.class);
+ when(pm.hasSystemFeature(eq(PackageManager.FEATURE_AUTOMOTIVE))).thenReturn(true);
+ mContext.setPackageManager(pm);
+
+ assertThat(mNotificationManager.areAutomaticZenRulesUserManaged()).isFalse();
+ }
+
+ @Test
+ @EnableFlags({Flags.FLAG_MODES_API, Flags.FLAG_MODES_UI})
+ public void areAutomaticZenRulesUserManaged_tv_isFalse() {
+ PackageManager pm = mock(PackageManager.class);
+ when(pm.hasSystemFeature(eq(PackageManager.FEATURE_LEANBACK))).thenReturn(true);
+ mContext.setPackageManager(pm);
+
+ assertThat(mNotificationManager.areAutomaticZenRulesUserManaged()).isFalse();
+ }
+
+ @Test
+ @EnableFlags({Flags.FLAG_MODES_API, Flags.FLAG_MODES_UI})
+ public void areAutomaticZenRulesUserManaged_watch_isFalse() {
+ PackageManager pm = mock(PackageManager.class);
+ when(pm.hasSystemFeature(eq(PackageManager.FEATURE_WATCH))).thenReturn(true);
+ mContext.setPackageManager(pm);
+
+ assertThat(mNotificationManager.areAutomaticZenRulesUserManaged()).isFalse();
+ }
+
private Notification exampleNotification() {
return new Notification.Builder(mContext, "channel")
.setSmallIcon(android.R.drawable.star_big_on)
@@ -438,6 +512,7 @@
// Helper context wrapper class where we can control just the return values of getPackageName,
// getOpPackageName, and getUserId (used in getNotificationChannels).
private static class PackageTestableContext extends ContextWrapper {
+ private PackageManager mPm;
private String mPackage;
private String mOpPackage;
private Integer mUserId;
@@ -446,6 +521,10 @@
super(base);
}
+ void setPackageManager(@Nullable PackageManager pm) {
+ mPm = pm;
+ }
+
void setParameters(String packageName, String opPackageName, int userId) {
mPackage = packageName;
mOpPackage = opPackageName;
@@ -453,6 +532,12 @@
}
@Override
+ public PackageManager getPackageManager() {
+ if (mPm != null) return mPm;
+ return super.getPackageManager();
+ }
+
+ @Override
public String getPackageName() {
if (mPackage != null) return mPackage;
return super.getPackageName();
diff --git a/core/tests/coretests/src/android/app/NotificationTest.java b/core/tests/coretests/src/android/app/NotificationTest.java
index 7be6950..ca6ad6f 100644
--- a/core/tests/coretests/src/android/app/NotificationTest.java
+++ b/core/tests/coretests/src/android/app/NotificationTest.java
@@ -2504,21 +2504,6 @@
@Test
@EnableFlags(Flags.FLAG_API_RICH_ONGOING)
- public void progressStyle_setProgressSegments() {
- final List<Notification.ProgressStyle.Segment> segments = List.of(
- new Notification.ProgressStyle.Segment(100).setColor(Color.WHITE),
- new Notification.ProgressStyle.Segment(50).setColor(Color.RED),
- new Notification.ProgressStyle.Segment(50).setColor(Color.BLUE)
- );
-
- final Notification.ProgressStyle progressStyle1 = new Notification.ProgressStyle();
- progressStyle1.setProgressSegments(segments);
-
- assertThat(progressStyle1.getProgressSegments()).isEqualTo(segments);
- }
-
- @Test
- @EnableFlags(Flags.FLAG_API_RICH_ONGOING)
public void progressStyle_addProgressPoint_dropsNegativePoints() {
// GIVEN
final Notification.ProgressStyle progressStyle = new Notification.ProgressStyle();
@@ -2547,21 +2532,6 @@
@Test
@EnableFlags(Flags.FLAG_API_RICH_ONGOING)
- public void progressStyle_setProgressPoints() {
- final List<Notification.ProgressStyle.Point> points = List.of(
- new Notification.ProgressStyle.Point(0).setColor(Color.WHITE),
- new Notification.ProgressStyle.Point(50).setColor(Color.RED),
- new Notification.ProgressStyle.Point(100).setColor(Color.BLUE)
- );
-
- final Notification.ProgressStyle progressStyle1 = new Notification.ProgressStyle();
- progressStyle1.setProgressPoints(points);
-
- assertThat(progressStyle1.getProgressPoints()).isEqualTo(points);
- }
-
- @Test
- @EnableFlags(Flags.FLAG_API_RICH_ONGOING)
public void progressStyle_createProgressModel_ignoresPointsExceedingMax() {
// GIVEN
final Notification.ProgressStyle progressStyle = new Notification.ProgressStyle();
@@ -2703,58 +2673,11 @@
@Test
@EnableFlags(Flags.FLAG_API_RICH_ONGOING)
- public void progressStyle_setProgressIndeterminate() {
- final Notification.ProgressStyle progressStyle1 = new Notification.ProgressStyle();
- progressStyle1.setProgressIndeterminate(true);
- assertThat(progressStyle1.isProgressIndeterminate()).isTrue();
- }
-
- @Test
- @EnableFlags(Flags.FLAG_API_RICH_ONGOING)
public void progressStyle_styledByProgress_defaultValueTrue() {
final Notification.ProgressStyle progressStyle1 = new Notification.ProgressStyle();
assertThat(progressStyle1.isStyledByProgress()).isTrue();
}
-
- @Test
- @EnableFlags(Flags.FLAG_API_RICH_ONGOING)
- public void progressStyle_setStyledByProgress() {
- final Notification.ProgressStyle progressStyle1 = new Notification.ProgressStyle();
- progressStyle1.setStyledByProgress(false);
- assertThat(progressStyle1.isStyledByProgress()).isFalse();
- }
-
- @Test
- @EnableFlags(Flags.FLAG_API_RICH_ONGOING)
- public void progressStyle_point() {
- final int id = 1;
- final int position = 10;
- final int color = Color.RED;
-
- final Notification.ProgressStyle.Point point =
- new Notification.ProgressStyle.Point(position).setId(id).setColor(color);
-
- assertEquals(id, point.getId());
- assertEquals(position, point.getPosition());
- assertEquals(color, point.getColor());
- }
-
- @Test
- @EnableFlags(Flags.FLAG_API_RICH_ONGOING)
- public void progressStyle_segment() {
- final int id = 1;
- final int length = 100;
- final int color = Color.RED;
-
- final Notification.ProgressStyle.Segment segment =
- new Notification.ProgressStyle.Segment(length).setId(id).setColor(color);
-
- assertEquals(id, segment.getId());
- assertEquals(length, segment.getLength());
- assertEquals(color, segment.getColor());
- }
-
private void assertValid(Notification.Colors c) {
// Assert that all colors are populated
assertThat(c.getBackgroundColor()).isNotEqualTo(Notification.COLOR_INVALID);
diff --git a/core/tests/coretests/src/android/content/pm/RegisteredServicesCacheTest.java b/core/tests/coretests/src/android/content/pm/RegisteredServicesCacheTest.java
index 939bf2e..ee4761b 100644
--- a/core/tests/coretests/src/android/content/pm/RegisteredServicesCacheTest.java
+++ b/core/tests/coretests/src/android/content/pm/RegisteredServicesCacheTest.java
@@ -76,6 +76,10 @@
mUsers = new ArrayList<>();
mUsers.add(new UserInfo(0, "Owner", UserInfo.FLAG_ADMIN));
mUsers.add(new UserInfo(1, "User1", 0));
+ addServiceInfoIntoResolveInfo(r1, "r1.package.name" /* packageName */,
+ "r1.service.name" /* serviceName */);
+ addServiceInfoIntoResolveInfo(r2, "r2.package.name" /* packageName */,
+ "r2.service.name" /* serviceName */);
}
public void testGetAllServicesHappyPath() {
@@ -218,6 +222,14 @@
assertTrue("File should be created at " + file, file.length() > 0);
}
+ private void addServiceInfoIntoResolveInfo(ResolveInfo resolveInfo, String packageName,
+ String serviceName) {
+ final ServiceInfo serviceInfo = new ServiceInfo();
+ serviceInfo.packageName = packageName;
+ serviceInfo.name = serviceName;
+ resolveInfo.serviceInfo = serviceInfo;
+ }
+
/**
* Mock implementation of {@link android.content.pm.RegisteredServicesCache} for testing
*/
@@ -301,8 +313,8 @@
}
@Override
- protected ServiceInfo<TestServiceType> parseServiceInfo(
- ResolveInfo resolveInfo, int userId) throws XmlPullParserException, IOException {
+ protected ServiceInfo<TestServiceType> parseServiceInfo(ResolveInfo resolveInfo,
+ long lastUpdateTime) throws XmlPullParserException, IOException {
int size = mServices.size();
for (int i = 0; i < size; i++) {
Map<ResolveInfo, ServiceInfo<TestServiceType>> map = mServices.valueAt(i);
diff --git a/core/tests/coretests/src/android/os/PerfettoTraceTest.java b/core/tests/coretests/src/android/os/PerfettoTraceTest.java
index 292f750..ad28383 100644
--- a/core/tests/coretests/src/android/os/PerfettoTraceTest.java
+++ b/core/tests/coretests/src/android/os/PerfettoTraceTest.java
@@ -112,15 +112,14 @@
long ptr = nativeStartTracing(traceConfig.toByteArray());
- PerfettoTrackEventExtra extra = PerfettoTrackEventExtra.builder()
+ PerfettoTrace.instant(FOO_CATEGORY, "event")
.addFlow(2)
.addTerminatingFlow(3)
.addArg("long_val", 10000000000L)
.addArg("bool_val", true)
.addArg("double_val", 3.14)
.addArg("string_val", FOO)
- .build();
- PerfettoTrace.instant(FOO_CATEGORY, "event", extra);
+ .emit();
byte[] traceBytes = nativeStopTracing(ptr);
@@ -163,12 +162,12 @@
@Test
@RequiresFlagsEnabled(android.os.Flags.FLAG_PERFETTO_SDK_TRACING_V2)
- public void testDebugAnnotationsWithLamda() throws Exception {
+ public void testDebugAnnotationsWithLambda() throws Exception {
TraceConfig traceConfig = getTraceConfig(FOO);
long ptr = nativeStartTracing(traceConfig.toByteArray());
- PerfettoTrace.instant(FOO_CATEGORY, "event", e -> e.addArg("long_val", 123L));
+ PerfettoTrace.instant(FOO_CATEGORY, "event").addArg("long_val", 123L).emit();
byte[] traceBytes = nativeStopTracing(ptr);
@@ -203,15 +202,14 @@
long ptr = nativeStartTracing(traceConfig.toByteArray());
- PerfettoTrackEventExtra beginExtra = PerfettoTrackEventExtra.builder()
- .usingNamedTrack(FOO, PerfettoTrace.getProcessTrackUuid())
- .build();
- PerfettoTrace.begin(FOO_CATEGORY, "event", beginExtra);
+ PerfettoTrace.begin(FOO_CATEGORY, "event")
+ .usingNamedTrack(PerfettoTrace.getProcessTrackUuid(), FOO)
+ .emit();
- PerfettoTrackEventExtra endExtra = PerfettoTrackEventExtra.builder()
- .usingNamedTrack("bar", PerfettoTrace.getThreadTrackUuid(Process.myTid()))
- .build();
- PerfettoTrace.end(FOO_CATEGORY, endExtra);
+
+ PerfettoTrace.end(FOO_CATEGORY)
+ .usingNamedTrack(PerfettoTrace.getThreadTrackUuid(Process.myTid()), "bar")
+ .emit();
Trace trace = Trace.parseFrom(nativeStopTracing(ptr));
@@ -242,26 +240,67 @@
assertThat(hasTrackUuid).isTrue();
assertThat(mCategoryNames).contains(FOO);
assertThat(mTrackNames).contains(FOO);
+ assertThat(mTrackNames).contains("bar");
}
@Test
@RequiresFlagsEnabled(android.os.Flags.FLAG_PERFETTO_SDK_TRACING_V2)
- public void testCounter() throws Exception {
+ public void testProcessThreadNamedTrack() throws Exception {
TraceConfig traceConfig = getTraceConfig(FOO);
long ptr = nativeStartTracing(traceConfig.toByteArray());
- PerfettoTrackEventExtra intExtra = PerfettoTrackEventExtra.builder()
- .usingCounterTrack(FOO, PerfettoTrace.getProcessTrackUuid())
- .setCounter(16)
- .build();
- PerfettoTrace.counter(FOO_CATEGORY, intExtra);
+ PerfettoTrace.begin(FOO_CATEGORY, "event")
+ .usingProcessNamedTrack(FOO)
+ .emit();
- PerfettoTrackEventExtra doubleExtra = PerfettoTrackEventExtra.builder()
- .usingCounterTrack("bar", PerfettoTrace.getProcessTrackUuid())
- .setCounter(3.14)
- .build();
- PerfettoTrace.counter(FOO_CATEGORY, doubleExtra);
+
+ PerfettoTrace.end(FOO_CATEGORY)
+ .usingThreadNamedTrack(Process.myTid(), "%s-%s", "bar", "stool")
+ .emit();
+
+ Trace trace = Trace.parseFrom(nativeStopTracing(ptr));
+
+ boolean hasTrackEvent = false;
+ boolean hasTrackUuid = false;
+ for (TracePacket packet: trace.getPacketList()) {
+ TrackEvent event;
+ if (packet.hasTrackEvent()) {
+ hasTrackEvent = true;
+ event = packet.getTrackEvent();
+
+ if (TrackEvent.Type.TYPE_SLICE_BEGIN.equals(event.getType())
+ && event.hasTrackUuid()) {
+ hasTrackUuid = true;
+ }
+
+ if (TrackEvent.Type.TYPE_SLICE_END.equals(event.getType())
+ && event.hasTrackUuid()) {
+ hasTrackUuid &= true;
+ }
+ }
+
+ collectInternedData(packet);
+ collectTrackNames(packet);
+ }
+
+ assertThat(hasTrackEvent).isTrue();
+ assertThat(hasTrackUuid).isTrue();
+ assertThat(mCategoryNames).contains(FOO);
+ assertThat(mTrackNames).contains(FOO);
+ assertThat(mTrackNames).contains("bar-stool");
+ }
+
+ @Test
+ @RequiresFlagsEnabled(android.os.Flags.FLAG_PERFETTO_SDK_TRACING_V2)
+ public void testCounterSimple() throws Exception {
+ TraceConfig traceConfig = getTraceConfig(FOO);
+
+ long ptr = nativeStartTracing(traceConfig.toByteArray());
+
+ PerfettoTrace.counter(FOO_CATEGORY, 16, FOO).emit();
+
+ PerfettoTrace.counter(FOO_CATEGORY, 3.14, "bar").emit();
Trace trace = Trace.parseFrom(nativeStopTracing(ptr));
@@ -297,12 +336,102 @@
@Test
@RequiresFlagsEnabled(android.os.Flags.FLAG_PERFETTO_SDK_TRACING_V2)
+ public void testCounter() throws Exception {
+ TraceConfig traceConfig = getTraceConfig(FOO);
+
+ long ptr = nativeStartTracing(traceConfig.toByteArray());
+
+ PerfettoTrace.counter(FOO_CATEGORY, 16)
+ .usingCounterTrack(PerfettoTrace.getProcessTrackUuid(), FOO).emit();
+
+ PerfettoTrace.counter(FOO_CATEGORY, 3.14)
+ .usingCounterTrack(PerfettoTrace.getThreadTrackUuid(Process.myTid()),
+ "%s-%s", "bar", "stool").emit();
+
+ Trace trace = Trace.parseFrom(nativeStopTracing(ptr));
+
+ boolean hasTrackEvent = false;
+ boolean hasCounterValue = false;
+ boolean hasDoubleCounterValue = false;
+ for (TracePacket packet: trace.getPacketList()) {
+ TrackEvent event;
+ if (packet.hasTrackEvent()) {
+ hasTrackEvent = true;
+ event = packet.getTrackEvent();
+
+ if (TrackEvent.Type.TYPE_COUNTER.equals(event.getType())
+ && event.getCounterValue() == 16) {
+ hasCounterValue = true;
+ }
+
+ if (TrackEvent.Type.TYPE_COUNTER.equals(event.getType())
+ && event.getDoubleCounterValue() == 3.14) {
+ hasDoubleCounterValue = true;
+ }
+ }
+
+ collectTrackNames(packet);
+ }
+
+ assertThat(hasTrackEvent).isTrue();
+ assertThat(hasCounterValue).isTrue();
+ assertThat(hasDoubleCounterValue).isTrue();
+ assertThat(mTrackNames).contains(FOO);
+ assertThat(mTrackNames).contains("bar-stool");
+ }
+
+ @Test
+ @RequiresFlagsEnabled(android.os.Flags.FLAG_PERFETTO_SDK_TRACING_V2)
+ public void testProcessThreadCounter() throws Exception {
+ TraceConfig traceConfig = getTraceConfig(FOO);
+
+ long ptr = nativeStartTracing(traceConfig.toByteArray());
+
+ PerfettoTrace.counter(FOO_CATEGORY, 16).usingProcessCounterTrack(FOO).emit();
+
+ PerfettoTrace.counter(FOO_CATEGORY, 3.14)
+ .usingThreadCounterTrack(Process.myTid(), "%s-%s", "bar", "stool").emit();
+
+ Trace trace = Trace.parseFrom(nativeStopTracing(ptr));
+
+ boolean hasTrackEvent = false;
+ boolean hasCounterValue = false;
+ boolean hasDoubleCounterValue = false;
+ for (TracePacket packet: trace.getPacketList()) {
+ TrackEvent event;
+ if (packet.hasTrackEvent()) {
+ hasTrackEvent = true;
+ event = packet.getTrackEvent();
+
+ if (TrackEvent.Type.TYPE_COUNTER.equals(event.getType())
+ && event.getCounterValue() == 16) {
+ hasCounterValue = true;
+ }
+
+ if (TrackEvent.Type.TYPE_COUNTER.equals(event.getType())
+ && event.getDoubleCounterValue() == 3.14) {
+ hasDoubleCounterValue = true;
+ }
+ }
+
+ collectTrackNames(packet);
+ }
+
+ assertThat(hasTrackEvent).isTrue();
+ assertThat(hasCounterValue).isTrue();
+ assertThat(hasDoubleCounterValue).isTrue();
+ assertThat(mTrackNames).contains(FOO);
+ assertThat(mTrackNames).contains("bar-stool");
+ }
+
+ @Test
+ @RequiresFlagsEnabled(android.os.Flags.FLAG_PERFETTO_SDK_TRACING_V2)
public void testProto() throws Exception {
TraceConfig traceConfig = getTraceConfig(FOO);
long ptr = nativeStartTracing(traceConfig.toByteArray());
- PerfettoTrackEventExtra extra5 = PerfettoTrackEventExtra.builder()
+ PerfettoTrace.instant(FOO_CATEGORY, "event_proto")
.beginProto()
.beginNested(33L)
.addField(4L, 2L)
@@ -310,8 +439,7 @@
.endNested()
.addField(2001, "AIDL::IActivityManager")
.endProto()
- .build();
- PerfettoTrace.instant(FOO_CATEGORY, "event_proto", extra5);
+ .emit();
byte[] traceBytes = nativeStopTracing(ptr);
@@ -351,7 +479,7 @@
long ptr = nativeStartTracing(traceConfig.toByteArray());
- PerfettoTrackEventExtra extra6 = PerfettoTrackEventExtra.builder()
+ PerfettoTrace.instant(FOO_CATEGORY, "event_proto_nested")
.beginProto()
.beginNested(29L)
.beginNested(4L)
@@ -364,8 +492,7 @@
.endNested()
.endNested()
.endProto()
- .build();
- PerfettoTrace.instant(FOO_CATEGORY, "event_proto_nested", extra6);
+ .emit();
byte[] traceBytes = nativeStopTracing(ptr);
@@ -413,8 +540,7 @@
long ptr = nativeStartTracing(traceConfig.toByteArray());
- PerfettoTrackEventExtra extra = PerfettoTrackEventExtra.builder().build();
- PerfettoTrace.instant(FOO_CATEGORY, "event_trigger", extra);
+ PerfettoTrace.instant(FOO_CATEGORY, "event_trigger").emit();
PerfettoTrace.activateTrigger(FOO, 1000);
@@ -439,49 +565,21 @@
@Test
@RequiresFlagsEnabled(android.os.Flags.FLAG_PERFETTO_SDK_TRACING_V2)
- public void testMultipleExtras() throws Exception {
- boolean hasException = false;
- try {
- PerfettoTrackEventExtra.builder();
-
- // Unclosed extra will throw an exception here
- PerfettoTrackEventExtra.builder();
- } catch (Exception e) {
- hasException = true;
- }
-
- try {
- PerfettoTrackEventExtra.builder().build();
-
- // Closed extra but unused (reset hasn't been called internally) will throw an exception
- // here.
- PerfettoTrackEventExtra.builder();
- } catch (Exception e) {
- hasException &= true;
- }
-
- assertThat(hasException).isTrue();
- }
-
- @Test
- @RequiresFlagsEnabled(android.os.Flags.FLAG_PERFETTO_SDK_TRACING_V2)
public void testRegister() throws Exception {
TraceConfig traceConfig = getTraceConfig(BAR);
Category barCategory = new Category(BAR);
long ptr = nativeStartTracing(traceConfig.toByteArray());
- PerfettoTrackEventExtra beforeExtra = PerfettoTrackEventExtra.builder()
+ PerfettoTrace.instant(barCategory, "event")
.addArg("before", 1)
- .build();
- PerfettoTrace.instant(barCategory, "event", beforeExtra);
+ .emit();
barCategory.register();
- PerfettoTrackEventExtra afterExtra = PerfettoTrackEventExtra.builder()
+ PerfettoTrace.instant(barCategory, "event")
.addArg("after", 1)
- .build();
- PerfettoTrace.instant(barCategory, "event", afterExtra);
+ .emit();
byte[] traceBytes = nativeStopTracing(ptr);
diff --git a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java
index f87b699..ee8d428 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 @@
}
@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 b42bcee..a1d7f87 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 @@
session.mEvents = new ArrayList<>(Arrays.asList(EVENT));
session.mDirectServiceInterface = mMockContentCaptureDirectManager;
- session.flush(REASON);
+ session.internalFlush(REASON);
mTestableLooper.processAllMessages();
verifyZeroInteractions(mMockContentProtectionEventProcessor);
@@ -280,7 +280,7 @@
session.mEvents = new ArrayList<>(Arrays.asList(EVENT));
session.mDirectServiceInterface = mMockContentCaptureDirectManager;
- session.flush(REASON);
+ session.internalFlush(REASON);
mTestableLooper.processAllMessages();
verifyZeroInteractions(mMockContentProtectionEventProcessor);
@@ -298,7 +298,7 @@
session.mEvents = new ArrayList<>(Arrays.asList(EVENT));
session.mDirectServiceInterface = mMockContentCaptureDirectManager;
- session.flush(REASON);
+ session.internalFlush(REASON);
mTestableLooper.processAllMessages();
verifyZeroInteractions(mMockContentProtectionEventProcessor);
@@ -316,7 +316,7 @@
session.mEvents = new ArrayList<>(Arrays.asList(EVENT));
session.mDirectServiceInterface = mMockContentCaptureDirectManager;
- session.flush(REASON);
+ session.internalFlush(REASON);
mTestableLooper.processAllMessages();
verifyZeroInteractions(mMockContentProtectionEventProcessor);
@@ -499,6 +499,57 @@
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 @@
sStrippedContext,
manager,
testHandler,
- testHandler,
mMockSystemServerInterface);
+ session.mContentCaptureHandler = testHandler;
session.mComponentName = COMPONENT_NAME;
return session;
}
diff --git a/core/tests/coretests/src/com/android/internal/widget/NotificationProgressBarTest.java b/core/tests/coretests/src/com/android/internal/widget/NotificationProgressBarTest.java
index 9818e19..ec19c0c 100644
--- a/core/tests/coretests/src/com/android/internal/widget/NotificationProgressBarTest.java
+++ b/core/tests/coretests/src/com/android/internal/widget/NotificationProgressBarTest.java
@@ -16,6 +16,8 @@
package com.android.internal.widget;
+import static com.android.internal.widget.NotificationProgressBar.NotEnoughWidthToFitAllPartsException;
+
import static com.google.common.truth.Truth.assertThat;
import android.app.Notification.ProgressStyle;
@@ -35,6 +37,7 @@
import org.junit.runner.RunWith;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
@RunWith(AndroidJUnit4.class)
@@ -47,7 +50,7 @@
int progress = 50;
int progressMax = 100;
- NotificationProgressBar.processAndConvertToViewParts(segments, points, progress,
+ NotificationProgressBar.processModelAndConvertToViewParts(segments, points, progress,
progressMax);
}
@@ -60,7 +63,7 @@
int progress = 50;
int progressMax = 100;
- NotificationProgressBar.processAndConvertToViewParts(segments, points, progress,
+ NotificationProgressBar.processModelAndConvertToViewParts(segments, points, progress,
progressMax);
}
@@ -73,7 +76,7 @@
int progress = 50;
int progressMax = 100;
- NotificationProgressBar.processAndConvertToViewParts(segments, points, progress,
+ NotificationProgressBar.processModelAndConvertToViewParts(segments, points, progress,
progressMax);
}
@@ -86,7 +89,7 @@
int progress = 50;
int progressMax = 100;
- NotificationProgressBar.processAndConvertToViewParts(segments, points, progress,
+ NotificationProgressBar.processModelAndConvertToViewParts(segments, points, progress,
progressMax);
}
@@ -98,20 +101,21 @@
int progress = -50;
int progressMax = 100;
- NotificationProgressBar.processAndConvertToViewParts(segments, points, progress,
+ NotificationProgressBar.processModelAndConvertToViewParts(segments, points, progress,
progressMax);
}
@Test
- public void processAndConvertToParts_progressIsZero() {
+ public void processAndConvertToParts_progressIsZero()
+ throws NotificationProgressBar.NotEnoughWidthToFitAllPartsException {
List<ProgressStyle.Segment> segments = new ArrayList<>();
segments.add(new ProgressStyle.Segment(100).setColor(Color.RED));
List<ProgressStyle.Point> points = new ArrayList<>();
int progress = 0;
int progressMax = 100;
- List<Part> parts = NotificationProgressBar.processAndConvertToViewParts(segments, points,
- progress, progressMax);
+ List<Part> parts = NotificationProgressBar.processModelAndConvertToViewParts(segments,
+ points, progress, progressMax);
List<Part> expectedParts = new ArrayList<>(List.of(new Segment(1f, Color.RED)));
@@ -123,8 +127,9 @@
float pointRadius = 6;
boolean hasTrackerIcon = true;
- List<DrawablePart> drawableParts = NotificationProgressBar.processAndConvertToDrawableParts(
- parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+ List<DrawablePart> drawableParts =
+ NotificationProgressBar.processPartsAndConvertToDrawableParts(
+ parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
List<DrawablePart> expectedDrawableParts = new ArrayList<>(
List.of(new DrawableSegment(0, 300, Color.RED)));
@@ -148,15 +153,16 @@
}
@Test
- public void processAndConvertToParts_progressAtMax() {
+ public void processAndConvertToParts_progressAtMax()
+ throws NotEnoughWidthToFitAllPartsException {
List<ProgressStyle.Segment> segments = new ArrayList<>();
segments.add(new ProgressStyle.Segment(100).setColor(Color.RED));
List<ProgressStyle.Point> points = new ArrayList<>();
int progress = 100;
int progressMax = 100;
- List<Part> parts = NotificationProgressBar.processAndConvertToViewParts(segments, points,
- progress, progressMax);
+ List<Part> parts = NotificationProgressBar.processModelAndConvertToViewParts(segments,
+ points, progress, progressMax);
List<Part> expectedParts = new ArrayList<>(List.of(new Segment(1f, Color.RED)));
@@ -168,8 +174,9 @@
float pointRadius = 6;
boolean hasTrackerIcon = true;
- List<DrawablePart> drawableParts = NotificationProgressBar.processAndConvertToDrawableParts(
- parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+ List<DrawablePart> drawableParts =
+ NotificationProgressBar.processPartsAndConvertToDrawableParts(
+ parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
List<DrawablePart> expectedDrawableParts = new ArrayList<>(
List.of(new DrawableSegment(0, 300, Color.RED)));
@@ -195,7 +202,7 @@
int progress = 150;
int progressMax = 100;
- NotificationProgressBar.processAndConvertToViewParts(segments, points, progress,
+ NotificationProgressBar.processModelAndConvertToViewParts(segments, points, progress,
progressMax);
}
@@ -208,7 +215,7 @@
int progress = 50;
int progressMax = 100;
- NotificationProgressBar.processAndConvertToViewParts(segments, points, progress,
+ NotificationProgressBar.processModelAndConvertToViewParts(segments, points, progress,
progressMax);
}
@@ -221,12 +228,62 @@
int progress = 50;
int progressMax = 100;
- NotificationProgressBar.processAndConvertToViewParts(segments, points, progress,
+ NotificationProgressBar.processModelAndConvertToViewParts(segments, points, progress,
progressMax);
}
@Test
- public void processAndConvertToParts_multipleSegmentsWithoutPoints() {
+ public void processAndConvertToParts_singleSegmentWithoutPoints()
+ throws NotEnoughWidthToFitAllPartsException {
+ List<ProgressStyle.Segment> segments = new ArrayList<>();
+ segments.add(new ProgressStyle.Segment(100).setColor(Color.BLUE));
+ List<ProgressStyle.Point> points = new ArrayList<>();
+ int progress = 60;
+ int progressMax = 100;
+
+ List<Part> parts = NotificationProgressBar.processModelAndConvertToViewParts(segments,
+ points, progress, progressMax);
+
+ List<Part> expectedParts = new ArrayList<>(
+ List.of(new Segment(1, Color.BLUE)));
+
+ assertThat(parts).isEqualTo(expectedParts);
+
+ float drawableWidth = 300;
+ float segSegGap = 4;
+ float segPointGap = 4;
+ float pointRadius = 6;
+ boolean hasTrackerIcon = true;
+
+ List<DrawablePart> drawableParts =
+ NotificationProgressBar.processPartsAndConvertToDrawableParts(
+ parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+
+ List<DrawablePart> expectedDrawableParts = new ArrayList<>(
+ List.of(new DrawableSegment(0, 300, Color.BLUE)));
+
+ assertThat(drawableParts).isEqualTo(expectedDrawableParts);
+
+ float segmentMinWidth = 16;
+ boolean isStyledByProgress = true;
+
+ Pair<List<DrawablePart>, Float> p = NotificationProgressBar.maybeStretchAndRescaleSegments(
+ parts, drawableParts, segmentMinWidth, pointRadius, (float) progress / progressMax,
+ 300, isStyledByProgress, hasTrackerIcon ? 0 : segSegGap);
+
+ // Colors with 40% opacity
+ int fadedBlue = 0x660000FF;
+ expectedDrawableParts = new ArrayList<>(
+ List.of(new DrawableSegment(0, 180, Color.BLUE),
+ new DrawableSegment(180, 300, fadedBlue, true)));
+
+ assertThat(p.second).isEqualTo(180);
+ assertThat(p.first).isEqualTo(expectedDrawableParts);
+ }
+
+ @Test
+ public void processAndConvertToParts_multipleSegmentsWithoutPoints()
+ throws NotEnoughWidthToFitAllPartsException {
List<ProgressStyle.Segment> segments = new ArrayList<>();
segments.add(new ProgressStyle.Segment(50).setColor(Color.RED));
segments.add(new ProgressStyle.Segment(50).setColor(Color.GREEN));
@@ -234,8 +291,8 @@
int progress = 60;
int progressMax = 100;
- List<Part> parts = NotificationProgressBar.processAndConvertToViewParts(segments, points,
- progress, progressMax);
+ List<Part> parts = NotificationProgressBar.processModelAndConvertToViewParts(segments,
+ points, progress, progressMax);
List<Part> expectedParts = new ArrayList<>(
List.of(new Segment(0.50f, Color.RED), new Segment(0.50f, Color.GREEN)));
@@ -248,8 +305,9 @@
float pointRadius = 6;
boolean hasTrackerIcon = true;
- List<DrawablePart> drawableParts = NotificationProgressBar.processAndConvertToDrawableParts(
- parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+ List<DrawablePart> drawableParts =
+ NotificationProgressBar.processPartsAndConvertToDrawableParts(
+ parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
List<DrawablePart> expectedDrawableParts = new ArrayList<>(
List.of(new DrawableSegment(0, 146, Color.RED),
@@ -274,15 +332,16 @@
}
@Test
- public void processAndConvertToParts_multipleSegmentsWithoutPoints_noTracker() {
+ public void processAndConvertToParts_multipleSegmentsWithoutPoints_noTracker()
+ throws NotEnoughWidthToFitAllPartsException {
List<ProgressStyle.Segment> segments = new ArrayList<>();
segments.add(new ProgressStyle.Segment(50).setColor(Color.RED));
segments.add(new ProgressStyle.Segment(50).setColor(Color.GREEN));
List<ProgressStyle.Point> points = new ArrayList<>();
int progress = 60;
int progressMax = 100;
- List<Part> parts = NotificationProgressBar.processAndConvertToViewParts(segments, points,
- progress, progressMax);
+ List<Part> parts = NotificationProgressBar.processModelAndConvertToViewParts(segments,
+ points, progress, progressMax);
List<Part> expectedParts = new ArrayList<>(
List.of(new Segment(0.50f, Color.RED), new Segment(0.50f, Color.GREEN)));
@@ -295,8 +354,9 @@
float pointRadius = 6;
boolean hasTrackerIcon = false;
- List<DrawablePart> drawableParts = NotificationProgressBar.processAndConvertToDrawableParts(
- parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+ List<DrawablePart> drawableParts =
+ NotificationProgressBar.processPartsAndConvertToDrawableParts(
+ parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
List<DrawablePart> expectedDrawableParts = new ArrayList<>(
List.of(new DrawableSegment(0, 146, Color.RED),
@@ -321,7 +381,8 @@
}
@Test
- public void processAndConvertToParts_singleSegmentWithPoints() {
+ public void processAndConvertToParts_singleSegmentWithPoints()
+ throws NotEnoughWidthToFitAllPartsException {
List<ProgressStyle.Segment> segments = new ArrayList<>();
segments.add(new ProgressStyle.Segment(100).setColor(Color.BLUE));
List<ProgressStyle.Point> points = new ArrayList<>();
@@ -332,8 +393,8 @@
int progress = 60;
int progressMax = 100;
- List<Part> parts = NotificationProgressBar.processAndConvertToViewParts(segments, points,
- progress, progressMax);
+ List<Part> parts = NotificationProgressBar.processModelAndConvertToViewParts(segments,
+ points, progress, progressMax);
List<Part> expectedParts = new ArrayList<>(
List.of(new Segment(0.15f, Color.BLUE),
@@ -354,8 +415,9 @@
float pointRadius = 6;
boolean hasTrackerIcon = true;
- List<DrawablePart> drawableParts = NotificationProgressBar.processAndConvertToDrawableParts(
- parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+ List<DrawablePart> drawableParts =
+ NotificationProgressBar.processPartsAndConvertToDrawableParts(
+ parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
List<DrawablePart> expectedDrawableParts = new ArrayList<>(
List.of(new DrawableSegment(0, 35, Color.BLUE),
@@ -396,7 +458,8 @@
}
@Test
- public void processAndConvertToParts_multipleSegmentsWithPoints() {
+ public void processAndConvertToParts_multipleSegmentsWithPoints()
+ throws NotEnoughWidthToFitAllPartsException {
List<ProgressStyle.Segment> segments = new ArrayList<>();
segments.add(new ProgressStyle.Segment(50).setColor(Color.RED));
segments.add(new ProgressStyle.Segment(50).setColor(Color.GREEN));
@@ -408,8 +471,8 @@
int progress = 60;
int progressMax = 100;
- List<Part> parts = NotificationProgressBar.processAndConvertToViewParts(segments, points,
- progress, progressMax);
+ List<Part> parts = NotificationProgressBar.processModelAndConvertToViewParts(segments,
+ points, progress, progressMax);
List<Part> expectedParts = new ArrayList<>(
List.of(new Segment(0.15f, Color.RED),
@@ -430,8 +493,9 @@
float segPointGap = 4;
float pointRadius = 6;
boolean hasTrackerIcon = true;
- List<DrawablePart> drawableParts = NotificationProgressBar.processAndConvertToDrawableParts(
- parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+ List<DrawablePart> drawableParts =
+ NotificationProgressBar.processPartsAndConvertToDrawableParts(
+ parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
List<DrawablePart> expectedDrawableParts = new ArrayList<>(
List.of(new DrawableSegment(0, 35, Color.RED), new DrawablePoint(39, 51, Color.RED),
@@ -473,7 +537,8 @@
}
@Test
- public void processAndConvertToParts_multipleSegmentsWithPointsAtStartAndEnd() {
+ public void processAndConvertToParts_multipleSegmentsWithPointsAtStartAndEnd()
+ throws NotEnoughWidthToFitAllPartsException {
List<ProgressStyle.Segment> segments = new ArrayList<>();
segments.add(new ProgressStyle.Segment(50).setColor(Color.RED));
segments.add(new ProgressStyle.Segment(50).setColor(Color.GREEN));
@@ -485,8 +550,8 @@
int progress = 60;
int progressMax = 100;
- List<Part> parts = NotificationProgressBar.processAndConvertToViewParts(segments, points,
- progress, progressMax);
+ List<Part> parts = NotificationProgressBar.processModelAndConvertToViewParts(segments,
+ points, progress, progressMax);
List<Part> expectedParts = new ArrayList<>(
List.of(new Point(Color.RED),
@@ -505,8 +570,10 @@
float segPointGap = 4;
float pointRadius = 6;
boolean hasTrackerIcon = true;
- List<DrawablePart> drawableParts = NotificationProgressBar.processAndConvertToDrawableParts(
- parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+
+ List<DrawablePart> drawableParts =
+ NotificationProgressBar.processPartsAndConvertToDrawableParts(
+ parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
List<DrawablePart> expectedDrawableParts = new ArrayList<>(
List.of(new DrawablePoint(0, 12, Color.RED),
@@ -547,7 +614,8 @@
// The points are so close to start/end that they would go out of bounds without the minimum
// segment width requirement.
@Test
- public void processAndConvertToParts_multipleSegmentsWithPointsNearStartAndEnd() {
+ public void processAndConvertToParts_multipleSegmentsWithPointsNearStartAndEnd()
+ throws NotEnoughWidthToFitAllPartsException {
List<ProgressStyle.Segment> segments = new ArrayList<>();
segments.add(new ProgressStyle.Segment(50).setColor(Color.RED));
segments.add(new ProgressStyle.Segment(50).setColor(Color.GREEN));
@@ -559,8 +627,8 @@
int progress = 60;
int progressMax = 100;
- List<Part> parts = NotificationProgressBar.processAndConvertToViewParts(segments, points,
- progress, progressMax);
+ List<Part> parts = NotificationProgressBar.processModelAndConvertToViewParts(segments,
+ points, progress, progressMax);
List<Part> expectedParts = new ArrayList<>(
List.of(new Segment(0.01f, Color.RED),
@@ -581,8 +649,10 @@
float segPointGap = 4;
float pointRadius = 6;
boolean hasTrackerIcon = true;
- List<DrawablePart> drawableParts = NotificationProgressBar.processAndConvertToDrawableParts(
- parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+
+ List<DrawablePart> drawableParts =
+ NotificationProgressBar.processPartsAndConvertToDrawableParts(
+ parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
List<DrawablePart> expectedDrawableParts = new ArrayList<>(
List.of(new DrawableSegment(0, -7, Color.RED),
@@ -625,7 +695,8 @@
}
@Test
- public void processAndConvertToParts_multipleSegmentsWithPoints_notStyledByProgress() {
+ public void processAndConvertToParts_multipleSegmentsWithPoints_notStyledByProgress()
+ throws NotEnoughWidthToFitAllPartsException {
List<ProgressStyle.Segment> segments = new ArrayList<>();
segments.add(new ProgressStyle.Segment(50).setColor(Color.RED));
segments.add(new ProgressStyle.Segment(50).setColor(Color.GREEN));
@@ -636,8 +707,8 @@
int progress = 60;
int progressMax = 100;
- List<Part> parts = NotificationProgressBar.processAndConvertToViewParts(segments, points,
- progress, progressMax);
+ List<Part> parts = NotificationProgressBar.processModelAndConvertToViewParts(segments,
+ points, progress, progressMax);
List<Part> expectedParts = new ArrayList<>(
List.of(new Segment(0.15f, Color.RED), new Point(Color.RED),
@@ -653,8 +724,9 @@
float pointRadius = 6;
boolean hasTrackerIcon = true;
- List<DrawablePart> drawableParts = NotificationProgressBar.processAndConvertToDrawableParts(
- parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+ List<DrawablePart> drawableParts =
+ NotificationProgressBar.processPartsAndConvertToDrawableParts(
+ parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
List<DrawablePart> expectedDrawableParts = new ArrayList<>(
List.of(new DrawableSegment(0, 35, Color.RED), new DrawablePoint(39, 51, Color.RED),
@@ -691,7 +763,8 @@
// The only difference from the `zeroWidthDrawableSegment` test below is the longer
// segmentMinWidth (= 16dp).
@Test
- public void maybeStretchAndRescaleSegments_negativeWidthDrawableSegment() {
+ public void maybeStretchAndRescaleSegments_negativeWidthDrawableSegment()
+ throws NotEnoughWidthToFitAllPartsException {
List<ProgressStyle.Segment> segments = new ArrayList<>();
segments.add(new ProgressStyle.Segment(100).setColor(Color.BLUE));
segments.add(new ProgressStyle.Segment(200).setColor(Color.BLUE));
@@ -702,8 +775,8 @@
int progress = 1000;
int progressMax = 1000;
- List<Part> parts = NotificationProgressBar.processAndConvertToViewParts(segments, points,
- progress, progressMax);
+ List<Part> parts = NotificationProgressBar.processModelAndConvertToViewParts(segments,
+ points, progress, progressMax);
List<Part> expectedParts = new ArrayList<>(
List.of(new Point(Color.BLUE), new Segment(0.1f, Color.BLUE),
@@ -717,8 +790,10 @@
float segPointGap = 4;
float pointRadius = 6;
boolean hasTrackerIcon = true;
- List<DrawablePart> drawableParts = NotificationProgressBar.processAndConvertToDrawableParts(
- parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+
+ List<DrawablePart> drawableParts =
+ NotificationProgressBar.processPartsAndConvertToDrawableParts(
+ parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
List<DrawablePart> expectedDrawableParts = new ArrayList<>(
List.of(new DrawablePoint(0, 12, Color.BLUE),
@@ -749,7 +824,8 @@
// The only difference from the `negativeWidthDrawableSegment` test above is the shorter
// segmentMinWidth (= 10dp).
@Test
- public void maybeStretchAndRescaleSegments_zeroWidthDrawableSegment() {
+ public void maybeStretchAndRescaleSegments_zeroWidthDrawableSegment()
+ throws NotEnoughWidthToFitAllPartsException {
List<ProgressStyle.Segment> segments = new ArrayList<>();
segments.add(new ProgressStyle.Segment(100).setColor(Color.BLUE));
segments.add(new ProgressStyle.Segment(200).setColor(Color.BLUE));
@@ -760,8 +836,8 @@
int progress = 1000;
int progressMax = 1000;
- List<Part> parts = NotificationProgressBar.processAndConvertToViewParts(segments, points,
- progress, progressMax);
+ List<Part> parts = NotificationProgressBar.processModelAndConvertToViewParts(segments,
+ points, progress, progressMax);
List<Part> expectedParts = new ArrayList<>(
List.of(new Point(Color.BLUE), new Segment(0.1f, Color.BLUE),
@@ -775,8 +851,10 @@
float segPointGap = 4;
float pointRadius = 6;
boolean hasTrackerIcon = true;
- List<DrawablePart> drawableParts = NotificationProgressBar.processAndConvertToDrawableParts(
- parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+
+ List<DrawablePart> drawableParts =
+ NotificationProgressBar.processPartsAndConvertToDrawableParts(
+ parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
List<DrawablePart> expectedDrawableParts = new ArrayList<>(
List.of(new DrawablePoint(0, 12, Color.BLUE),
@@ -805,7 +883,8 @@
}
@Test
- public void maybeStretchAndRescaleSegments_noStretchingNecessary() {
+ public void maybeStretchAndRescaleSegments_noStretchingNecessary()
+ throws NotEnoughWidthToFitAllPartsException {
List<ProgressStyle.Segment> segments = new ArrayList<>();
segments.add(new ProgressStyle.Segment(200).setColor(Color.BLUE));
segments.add(new ProgressStyle.Segment(100).setColor(Color.BLUE));
@@ -816,8 +895,8 @@
int progress = 1000;
int progressMax = 1000;
- List<Part> parts = NotificationProgressBar.processAndConvertToViewParts(segments, points,
- progress, progressMax);
+ List<Part> parts = NotificationProgressBar.processModelAndConvertToViewParts(segments,
+ points, progress, progressMax);
List<Part> expectedParts = new ArrayList<>(
List.of(new Point(Color.BLUE), new Segment(0.2f, Color.BLUE),
@@ -832,8 +911,9 @@
float pointRadius = 6;
boolean hasTrackerIcon = true;
- List<DrawablePart> drawableParts = NotificationProgressBar.processAndConvertToDrawableParts(
- parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+ List<DrawablePart> drawableParts =
+ NotificationProgressBar.processPartsAndConvertToDrawableParts(
+ parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
List<DrawablePart> expectedDrawableParts = new ArrayList<>(
List.of(new DrawablePoint(0, 12, Color.BLUE),
@@ -854,4 +934,168 @@
assertThat(p.second).isEqualTo(200);
assertThat(p.first).isEqualTo(expectedDrawableParts);
}
+
+ @Test(expected = NotEnoughWidthToFitAllPartsException.class)
+ public void maybeStretchAndRescaleSegments_notEnoughWidthToFitAllParts()
+ throws NotEnoughWidthToFitAllPartsException {
+ final int orange = 0xff7f50;
+ List<ProgressStyle.Segment> segments = new ArrayList<>();
+ segments.add(new ProgressStyle.Segment(10).setColor(orange));
+ segments.add(new ProgressStyle.Segment(10).setColor(Color.YELLOW));
+ segments.add(new ProgressStyle.Segment(10).setColor(Color.BLUE));
+ segments.add(new ProgressStyle.Segment(10).setColor(Color.GREEN));
+ segments.add(new ProgressStyle.Segment(10).setColor(Color.RED));
+ segments.add(new ProgressStyle.Segment(10).setColor(orange));
+ segments.add(new ProgressStyle.Segment(10).setColor(Color.YELLOW));
+ segments.add(new ProgressStyle.Segment(10).setColor(Color.BLUE));
+ segments.add(new ProgressStyle.Segment(10).setColor(Color.GREEN));
+ segments.add(new ProgressStyle.Segment(10).setColor(Color.RED));
+ List<ProgressStyle.Point> points = new ArrayList<>();
+ points.add(new ProgressStyle.Point(0).setColor(orange));
+ points.add(new ProgressStyle.Point(1).setColor(Color.BLUE));
+ points.add(new ProgressStyle.Point(55).setColor(Color.BLUE));
+ points.add(new ProgressStyle.Point(100).setColor(orange));
+ int progress = 50;
+ int progressMax = 100;
+
+ List<Part> parts = NotificationProgressBar.processModelAndConvertToViewParts(segments,
+ points, progress, progressMax);
+
+ List<Part> expectedParts = new ArrayList<>(
+ List.of(new Point(orange),
+ new Segment(0.01f, orange),
+ new Point(Color.BLUE),
+ new Segment(0.09f, orange),
+ new Segment(0.1f, Color.YELLOW),
+ new Segment(0.1f, Color.BLUE),
+ new Segment(0.1f, Color.GREEN),
+ new Segment(0.1f, Color.RED),
+ new Segment(0.05f, orange),
+ new Point(Color.BLUE),
+ new Segment(0.05f, orange),
+ new Segment(0.1f, Color.YELLOW),
+ new Segment(0.1f, Color.BLUE),
+ new Segment(0.1f, Color.GREEN),
+ new Segment(0.1f, Color.RED),
+ new Point(orange)));
+
+ assertThat(parts).isEqualTo(expectedParts);
+
+ // For the list of ProgressStyle.Part used in this test, 300 is the minimum width.
+ float drawableWidth = 299;
+ float segSegGap = 4;
+ float segPointGap = 4;
+ float pointRadius = 6;
+ boolean hasTrackerIcon = true;
+
+ List<DrawablePart> drawableParts =
+ NotificationProgressBar.processPartsAndConvertToDrawableParts(
+ parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+
+ // Skips the validation of the intermediate list of DrawableParts.
+
+ float segmentMinWidth = 16;
+ boolean isStyledByProgress = true;
+
+ NotificationProgressBar.maybeStretchAndRescaleSegments(
+ parts, drawableParts, segmentMinWidth, pointRadius, (float) progress / progressMax,
+ 300, isStyledByProgress, hasTrackerIcon ? 0 : segSegGap);
+ }
+
+ @Test
+ public void processModelAndConvertToFinalDrawableParts_singleSegmentWithPoints()
+ throws NotEnoughWidthToFitAllPartsException {
+ List<ProgressStyle.Segment> segments = new ArrayList<>();
+ segments.add(new ProgressStyle.Segment(100).setColor(Color.BLUE));
+ List<ProgressStyle.Point> points = new ArrayList<>();
+ points.add(new ProgressStyle.Point(15).setColor(Color.RED));
+ points.add(new ProgressStyle.Point(25).setColor(Color.BLUE));
+ points.add(new ProgressStyle.Point(60).setColor(Color.BLUE));
+ points.add(new ProgressStyle.Point(75).setColor(Color.YELLOW));
+ int progress = 60;
+ int progressMax = 100;
+
+ float drawableWidth = 300;
+ float segSegGap = 4;
+ float segPointGap = 4;
+ float pointRadius = 6;
+ boolean hasTrackerIcon = true;
+
+ float segmentMinWidth = 16;
+ boolean isStyledByProgress = true;
+
+ Pair<List<DrawablePart>, Float> p =
+ NotificationProgressBar.processModelAndConvertToFinalDrawableParts(
+ segments,
+ points,
+ progress,
+ progressMax,
+ drawableWidth,
+ segSegGap,
+ segPointGap,
+ pointRadius,
+ hasTrackerIcon,
+ segmentMinWidth,
+ isStyledByProgress
+ );
+
+ // Colors with 40% opacity
+ int fadedBlue = 0x660000FF;
+ int fadedYellow = 0x66FFFF00;
+ List<DrawablePart> expectedDrawableParts = new ArrayList<>(
+ List.of(new DrawableSegment(0, 34.219177F, Color.BLUE),
+ new DrawablePoint(38.219177F, 50.219177F, Color.RED),
+ new DrawableSegment(54.219177F, 70.21918F, Color.BLUE),
+ new DrawablePoint(74.21918F, 86.21918F, Color.BLUE),
+ new DrawableSegment(90.21918F, 172.38356F, Color.BLUE),
+ new DrawablePoint(176.38356F, 188.38356F, Color.BLUE),
+ new DrawableSegment(192.38356F, 217.0137F, fadedBlue, true),
+ new DrawablePoint(221.0137F, 233.0137F, fadedYellow),
+ new DrawableSegment(237.0137F, 300F, fadedBlue, true)));
+
+ assertThat(p.second).isEqualTo(182.38356F);
+ assertThat(p.first).isEqualTo(expectedDrawableParts);
+ }
+
+ @Test
+ public void processModelAndConvertToFinalDrawableParts_singleSegmentWithoutPoints()
+ throws NotEnoughWidthToFitAllPartsException {
+ List<ProgressStyle.Segment> segments = new ArrayList<>();
+ segments.add(new ProgressStyle.Segment(100).setColor(Color.BLUE));
+ int progress = 60;
+ int progressMax = 100;
+
+ float drawableWidth = 100;
+ float segSegGap = 4;
+ float segPointGap = 4;
+ float pointRadius = 6;
+ boolean hasTrackerIcon = true;
+
+ float segmentMinWidth = 16;
+ boolean isStyledByProgress = true;
+
+ Pair<List<DrawablePart>, Float> p =
+ NotificationProgressBar.processModelAndConvertToFinalDrawableParts(
+ segments,
+ Collections.emptyList(),
+ progress,
+ progressMax,
+ drawableWidth,
+ segSegGap,
+ segPointGap,
+ pointRadius,
+ hasTrackerIcon,
+ segmentMinWidth,
+ isStyledByProgress
+ );
+
+ // Colors with 40% opacity
+ int fadedBlue = 0x660000FF;
+ List<DrawablePart> expectedDrawableParts = new ArrayList<>(
+ List.of(new DrawableSegment(0, 60.000004F, Color.BLUE),
+ new DrawableSegment(60.000004F, 100, fadedBlue, true)));
+
+ assertThat(p.second).isWithin(1e-5f).of(60);
+ assertThat(p.first).isEqualTo(expectedDrawableParts);
+ }
}
diff --git a/core/tests/coretests/src/com/android/internal/widget/NotificationProgressModelTest.java b/core/tests/coretests/src/com/android/internal/widget/NotificationProgressModelTest.java
index 962399e..e1f5b1c 100644
--- a/core/tests/coretests/src/com/android/internal/widget/NotificationProgressModelTest.java
+++ b/core/tests/coretests/src/com/android/internal/widget/NotificationProgressModelTest.java
@@ -49,7 +49,8 @@
new NotificationProgressModel(List.of(),
List.of(),
10,
- false);
+ false,
+ Color.TRANSPARENT);
}
@Test(expected = IllegalArgumentException.class)
@@ -58,7 +59,8 @@
List.of(new Notification.ProgressStyle.Segment(50).setColor(Color.YELLOW)),
List.of(),
-1,
- false);
+ false,
+ Color.TRANSPARENT);
}
@Test
@@ -74,14 +76,15 @@
// THEN
assertThat(restoredModel.getIndeterminateColor()).isEqualTo(Color.RED);
assertThat(restoredModel.isIndeterminate()).isTrue();
- assertThat(restoredModel.getProgress()).isEqualTo(-1);
+ assertThat(restoredModel.getProgress()).isEqualTo(0);
assertThat(restoredModel.getSegments()).isEmpty();
assertThat(restoredModel.getPoints()).isEmpty();
assertThat(restoredModel.isStyledByProgress()).isFalse();
+ assertThat(restoredModel.getSegmentsFallbackColor()).isEqualTo(Color.TRANSPARENT);
}
@Test
- public void save_and_restore_non_indeterminate_progress_model() {
+ public void save_and_restore_determinate_progress_model() {
// GIVEN
final List<Notification.ProgressStyle.Segment> segments = List.of(
new Notification.ProgressStyle.Segment(50).setColor(Color.YELLOW),
@@ -92,7 +95,8 @@
final NotificationProgressModel savedModel = new NotificationProgressModel(segments,
points,
100,
- true);
+ true,
+ Color.RED);
final Bundle bundle = savedModel.toBundle();
@@ -106,6 +110,38 @@
assertThat(restoredModel.getPoints()).isEqualTo(points);
assertThat(restoredModel.getProgress()).isEqualTo(100);
assertThat(restoredModel.isStyledByProgress()).isTrue();
- assertThat(restoredModel.getIndeterminateColor()).isEqualTo(-1);
+ assertThat(restoredModel.getSegmentsFallbackColor()).isEqualTo(Color.RED);
+ assertThat(restoredModel.getIndeterminateColor()).isEqualTo(Color.TRANSPARENT);
+ }
+
+ @Test
+ public void save_and_restore_non_determinate_progress_model_segments_fallback_color_invalid() {
+ // GIVEN
+ final List<Notification.ProgressStyle.Segment> segments = List.of(
+ new Notification.ProgressStyle.Segment(50).setColor(Color.YELLOW),
+ new Notification.ProgressStyle.Segment(50).setColor(Color.YELLOW));
+ final List<Notification.ProgressStyle.Point> points = List.of(
+ new Notification.ProgressStyle.Point(0).setColor(Color.RED),
+ new Notification.ProgressStyle.Point(20).setColor(Color.BLUE));
+ final NotificationProgressModel savedModel = new NotificationProgressModel(segments,
+ points,
+ 100,
+ true,
+ Color.TRANSPARENT);
+
+ final Bundle bundle = savedModel.toBundle();
+
+ // WHEN
+ final NotificationProgressModel restoredModel =
+ NotificationProgressModel.fromBundle(bundle);
+
+ // THEN
+ assertThat(restoredModel.isIndeterminate()).isFalse();
+ assertThat(restoredModel.getSegments()).isEqualTo(segments);
+ assertThat(restoredModel.getPoints()).isEqualTo(points);
+ assertThat(restoredModel.getProgress()).isEqualTo(100);
+ assertThat(restoredModel.isStyledByProgress()).isTrue();
+ assertThat(restoredModel.getSegmentsFallbackColor()).isEqualTo(Color.TRANSPARENT);
+ assertThat(restoredModel.getIndeterminateColor()).isEqualTo(Color.TRANSPARENT);
}
}
diff --git a/core/tests/utiltests/src/android/util/proto/ProtoFieldFilterTest.java b/core/tests/utiltests/src/android/util/proto/ProtoFieldFilterTest.java
new file mode 100644
index 0000000..76d0aaa
--- /dev/null
+++ b/core/tests/utiltests/src/android/util/proto/ProtoFieldFilterTest.java
@@ -0,0 +1,230 @@
+/*
+ * 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 android.util.proto;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+
+/**
+ * Unit tests for {@link android.util.proto.ProtoFieldFilter}.
+ *
+ * Build/Install/Run:
+ * atest FrameworksCoreTests:ProtoFieldFilterTest
+ *
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class ProtoFieldFilterTest {
+
+ private static final class FieldTypes {
+ static final long INT64 = ProtoStream.FIELD_TYPE_INT64 | ProtoStream.FIELD_COUNT_SINGLE;
+ static final long FIXED64 = ProtoStream.FIELD_TYPE_FIXED64 | ProtoStream.FIELD_COUNT_SINGLE;
+ static final long BYTES = ProtoStream.FIELD_TYPE_BYTES | ProtoStream.FIELD_COUNT_SINGLE;
+ static final long FIXED32 = ProtoStream.FIELD_TYPE_FIXED32 | ProtoStream.FIELD_COUNT_SINGLE;
+ static final long MESSAGE = ProtoStream.FIELD_TYPE_MESSAGE | ProtoStream.FIELD_COUNT_SINGLE;
+ static final long INT32 = ProtoStream.FIELD_TYPE_INT32 | ProtoStream.FIELD_COUNT_SINGLE;
+ }
+
+ private static ProtoOutputStream createBasicTestProto() {
+ ProtoOutputStream out = new ProtoOutputStream();
+
+ out.writeInt64(ProtoStream.makeFieldId(1, FieldTypes.INT64), 12345L);
+ out.writeFixed64(ProtoStream.makeFieldId(2, FieldTypes.FIXED64), 0x1234567890ABCDEFL);
+ out.writeBytes(ProtoStream.makeFieldId(3, FieldTypes.BYTES), new byte[]{1, 2, 3, 4, 5});
+ out.writeFixed32(ProtoStream.makeFieldId(4, FieldTypes.FIXED32), 0xDEADBEEF);
+
+ return out;
+ }
+
+ private static byte[] filterProto(byte[] input, ProtoFieldFilter filter) throws IOException {
+ ByteArrayInputStream inputStream = new ByteArrayInputStream(input);
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ filter.filter(inputStream, outputStream);
+ return outputStream.toByteArray();
+ }
+
+ @Test
+ public void testNoFieldsFiltered() throws IOException {
+ byte[] input = createBasicTestProto().getBytes();
+ byte[] output = filterProto(input, new ProtoFieldFilter(fieldNumber -> true));
+ assertArrayEquals("No fields should be filtered out", input, output);
+ }
+
+ @Test
+ public void testAllFieldsFiltered() throws IOException {
+ byte[] input = createBasicTestProto().getBytes();
+ byte[] output = filterProto(input, new ProtoFieldFilter(fieldNumber -> false));
+
+ assertEquals("All fields should be filtered out", 0, output.length);
+ }
+
+ @Test
+ public void testSpecificFieldsFiltered() throws IOException {
+
+ ProtoOutputStream out = createBasicTestProto();
+ byte[] output = filterProto(out.getBytes(), new ProtoFieldFilter(n -> n != 2));
+
+ ProtoInputStream in = new ProtoInputStream(output);
+ boolean[] fieldsFound = new boolean[5];
+
+ int fieldNumber;
+ while ((fieldNumber = in.nextField()) != ProtoInputStream.NO_MORE_FIELDS) {
+ fieldsFound[fieldNumber] = true;
+ switch (fieldNumber) {
+ case 1:
+ assertEquals(12345L, in.readLong(ProtoStream.makeFieldId(1, FieldTypes.INT64)));
+ break;
+ case 2:
+ fail("Field 2 should be filtered out");
+ break;
+ case 3:
+ assertArrayEquals(new byte[]{1, 2, 3, 4, 5},
+ in.readBytes(ProtoStream.makeFieldId(3, FieldTypes.BYTES)));
+ break;
+ case 4:
+ assertEquals(0xDEADBEEF,
+ in.readInt(ProtoStream.makeFieldId(4, FieldTypes.FIXED32)));
+ break;
+ default:
+ fail("Unexpected field number: " + fieldNumber);
+ }
+ }
+
+ assertTrue("Field 1 should be present", fieldsFound[1]);
+ assertFalse("Field 2 should be filtered", fieldsFound[2]);
+ assertTrue("Field 3 should be present", fieldsFound[3]);
+ assertTrue("Field 4 should be present", fieldsFound[4]);
+ }
+
+ @Test
+ public void testDifferentWireTypes() throws IOException {
+ ProtoOutputStream out = new ProtoOutputStream();
+
+ out.writeInt64(ProtoStream.makeFieldId(1, FieldTypes.INT64), 12345L);
+ out.writeFixed64(ProtoStream.makeFieldId(2, FieldTypes.FIXED64), 0x1234567890ABCDEFL);
+ out.writeBytes(ProtoStream.makeFieldId(3, FieldTypes.BYTES), new byte[]{10, 20, 30});
+
+ long token = out.start(ProtoStream.makeFieldId(4, FieldTypes.MESSAGE));
+ out.writeInt32(ProtoStream.makeFieldId(1, FieldTypes.INT32), 42);
+ out.end(token);
+
+ out.writeFixed32(ProtoStream.makeFieldId(5, FieldTypes.FIXED32), 0xDEADBEEF);
+
+ byte[] output = filterProto(out.getBytes(), new ProtoFieldFilter(fieldNumber -> true));
+
+ ProtoInputStream in = new ProtoInputStream(output);
+ boolean[] fieldsFound = new boolean[6];
+
+ int fieldNumber;
+ while ((fieldNumber = in.nextField()) != ProtoInputStream.NO_MORE_FIELDS) {
+ fieldsFound[fieldNumber] = true;
+ switch (fieldNumber) {
+ case 1:
+ assertEquals(12345L, in.readLong(ProtoStream.makeFieldId(1, FieldTypes.INT64)));
+ break;
+ case 2:
+ assertEquals(0x1234567890ABCDEFL,
+ in.readLong(ProtoStream.makeFieldId(2, FieldTypes.FIXED64)));
+ break;
+ case 3:
+ assertArrayEquals(new byte[]{10, 20, 30},
+ in.readBytes(ProtoStream.makeFieldId(3, FieldTypes.BYTES)));
+ break;
+ case 4:
+ token = in.start(ProtoStream.makeFieldId(4, FieldTypes.MESSAGE));
+ assertTrue(in.nextField() == 1);
+ assertEquals(42, in.readInt(ProtoStream.makeFieldId(1, FieldTypes.INT32)));
+ assertTrue(in.nextField() == ProtoInputStream.NO_MORE_FIELDS);
+ in.end(token);
+ break;
+ case 5:
+ assertEquals(0xDEADBEEF,
+ in.readInt(ProtoStream.makeFieldId(5, FieldTypes.FIXED32)));
+ break;
+ default:
+ fail("Unexpected field number: " + fieldNumber);
+ }
+ }
+
+ assertTrue("All fields should be present",
+ fieldsFound[1] && fieldsFound[2] && fieldsFound[3]
+ && fieldsFound[4] && fieldsFound[5]);
+ }
+ @Test
+ public void testNestedMessagesUnfiltered() throws IOException {
+ ProtoOutputStream out = new ProtoOutputStream();
+
+ out.writeInt64(ProtoStream.makeFieldId(1, FieldTypes.INT64), 12345L);
+
+ long token = out.start(ProtoStream.makeFieldId(2, FieldTypes.MESSAGE));
+ out.writeInt32(ProtoStream.makeFieldId(1, FieldTypes.INT32), 6789);
+ out.writeFixed32(ProtoStream.makeFieldId(2, FieldTypes.FIXED32), 0xCAFEBABE);
+ out.end(token);
+
+ byte[] output = filterProto(out.getBytes(), new ProtoFieldFilter(n -> n != 2));
+
+ // Verify output
+ ProtoInputStream in = new ProtoInputStream(output);
+ boolean[] fieldsFound = new boolean[3];
+
+ int fieldNumber;
+ while ((fieldNumber = in.nextField()) != ProtoInputStream.NO_MORE_FIELDS) {
+ fieldsFound[fieldNumber] = true;
+ if (fieldNumber == 1) {
+ assertEquals(12345L, in.readLong(ProtoStream.makeFieldId(1, FieldTypes.INT64)));
+ } else {
+ fail("Unexpected field number: " + fieldNumber);
+ }
+ }
+
+ assertTrue("Field 1 should be present", fieldsFound[1]);
+ assertFalse("Field 2 should be filtered out", fieldsFound[2]);
+ }
+
+ @Test
+ public void testRepeatedFields() throws IOException {
+
+ ProtoOutputStream out = new ProtoOutputStream();
+ long fieldId = ProtoStream.makeFieldId(1,
+ ProtoStream.FIELD_TYPE_INT32 | ProtoStream.FIELD_COUNT_REPEATED);
+
+ out.writeRepeatedInt32(fieldId, 100);
+ out.writeRepeatedInt32(fieldId, 200);
+ out.writeRepeatedInt32(fieldId, 300);
+
+ byte[] input = out.getBytes();
+
+ byte[] output = filterProto(input, new ProtoFieldFilter(fieldNumber -> true));
+
+ assertArrayEquals("Repeated fields should be preserved", input, output);
+ }
+
+}
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index a30570a..b8059d0 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -613,6 +613,10 @@
<permission name="android.permission.SUBSCRIBE_TO_KEYGUARD_LOCKED_STATE" />
<!-- Permission required for CTS test - CtsContentProviderMultiUserTest -->
<permission name="android.permission.RESOLVE_COMPONENT_FOR_UID"/>
+ <!-- Permission required for CTS test - MediaQualityTest -->
+ <permission name="android.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE"/>
+ <permission name="android.permission.MANAGE_GLOBAL_SOUND_QUALITY_SERVICE"/>
+ <permission name="android.permission.READ_COLOR_ZONES"/>
</privapp-permissions>
<privapp-permissions package="com.android.statementservice">
diff --git a/graphics/java/android/graphics/BLASTBufferQueue.java b/graphics/java/android/graphics/BLASTBufferQueue.java
index 906c71d..9b9be244 100644
--- a/graphics/java/android/graphics/BLASTBufferQueue.java
+++ b/graphics/java/android/graphics/BLASTBufferQueue.java
@@ -61,17 +61,13 @@
/**
* Indicates that the client is waiting on buffer release
* due to no free buffers being available to render into.
+ * @param durationNanos The length of time in nanoseconds
+ * that the client was blocked on buffer release.
*/
- void onWaitForBufferRelease();
+ void onWaitForBufferRelease(long durationNanos);
}
/** 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 ef8d26c..1ea1976 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/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index 4c7e477..d0d1721 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -73,6 +73,7 @@
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
+import android.os.OperationCanceledException;
import android.os.RemoteException;
import android.os.SystemProperties;
import android.util.ArrayMap;
@@ -1106,7 +1107,12 @@
void onTaskFragmentError(@NonNull WindowContainerTransaction wct,
@Nullable IBinder errorCallbackToken, @Nullable TaskFragmentInfo taskFragmentInfo,
@TaskFragmentOperation.OperationType int opType, @NonNull Throwable exception) {
- Log.e(TAG, "onTaskFragmentError=" + exception.getMessage());
+ if (exception instanceof OperationCanceledException) {
+ // This is a non-fatal error and the operation just canceled.
+ Log.i(TAG, "operation canceled:" + exception.getMessage());
+ } else {
+ Log.e(TAG, "onTaskFragmentError=" + exception.getMessage(), exception);
+ }
switch (opType) {
case OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT:
case OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT: {
diff --git a/libs/WindowManager/Shell/aconfig/multitasking.aconfig b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
index b10b099..e421026 100644
--- a/libs/WindowManager/Shell/aconfig/multitasking.aconfig
+++ b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
@@ -134,6 +134,16 @@
}
flag {
+ name: "enable_recents_bookend_transition"
+ namespace: "multitasking"
+ description: "Use a finish-transition to clean up recents instead of the finish-WCT"
+ bug: "346588978"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "bubble_view_info_executors"
namespace: "multitasking"
description: "Use executors to inflate bubble views"
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 c62d2a0..dd387b3 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.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.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
@@ -51,6 +51,7 @@
import com.android.wm.shell.sysui.ShellCommandHandler
import com.android.wm.shell.sysui.ShellController
import com.android.wm.shell.sysui.ShellInit
+import com.android.wm.shell.taskview.TaskViewRepository
import com.android.wm.shell.taskview.TaskViewTransitions
import com.android.wm.shell.transition.Transitions
import com.google.common.truth.Truth.assertThat
@@ -282,6 +283,7 @@
mainExecutor,
mock<Handler>(),
bgExecutor,
+ mock<TaskViewRepository>(),
mock<TaskViewTransitions>(),
mock<Transitions>(),
SyncTransactionQueue(TransactionPool(), mainExecutor),
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 ab2e552..9d445f0 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,12 @@
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.BubbleStackView.SurfaceSynchronizer
+import com.android.wm.shell.bubbles.Bubbles.BubbleExpandListener
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
@@ -75,6 +77,7 @@
private lateinit var bubbleTaskViewFactory: BubbleTaskViewFactory
private lateinit var bubbleData: BubbleData
private lateinit var bubbleStackViewManager: FakeBubbleStackViewManager
+ private lateinit var surfaceSynchronizer: FakeSurfaceSynchronizer
private var sysuiProxy = mock<SysuiProxy>()
@Before
@@ -108,13 +111,14 @@
bubbleStackViewManager = FakeBubbleStackViewManager()
expandedViewManager = FakeBubbleExpandedViewManager()
bubbleTaskViewFactory = FakeBubbleTaskViewFactory(context, shellExecutor)
+ surfaceSynchronizer = FakeSurfaceSynchronizer()
bubbleStackView =
BubbleStackView(
context,
bubbleStackViewManager,
positioner,
bubbleData,
- null,
+ surfaceSynchronizer,
FloatingContentCoordinator(),
{ sysuiProxy },
shellExecutor
@@ -309,6 +313,7 @@
@Test
fun tapDifferentBubble_shouldReorder() {
+ surfaceSynchronizer.isActive = false
val bubble1 = createAndInflateChatBubble(key = "bubble1")
val bubble2 = createAndInflateChatBubble(key = "bubble2")
InstrumentationRegistry.getInstrumentation().runOnMainSync {
@@ -378,6 +383,147 @@
.inOrder()
}
+ @Test
+ fun tapDifferentBubble_imeVisible_shouldWaitForIme() {
+ val bubble1 = createAndInflateChatBubble(key = "bubble1")
+ val bubble2 = createAndInflateChatBubble(key = "bubble2")
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ bubbleStackView.addBubble(bubble1)
+ bubbleStackView.addBubble(bubble2)
+ }
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync()
+
+ assertThat(bubbleStackView.bubbleCount).isEqualTo(2)
+ assertThat(bubbleData.bubbles).hasSize(2)
+ assertThat(bubbleData.selectedBubble).isEqualTo(bubble2)
+ assertThat(bubble2.iconView).isNotNull()
+
+ val expandListener = FakeBubbleExpandListener()
+ bubbleStackView.setExpandListener(expandListener)
+
+ var lastUpdate: BubbleData.Update? = null
+ val semaphore = Semaphore(0)
+ val listener =
+ BubbleData.Listener { update ->
+ lastUpdate = update
+ semaphore.release()
+ }
+ bubbleData.setListener(listener)
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ bubble2.iconView!!.performClick()
+ assertThat(bubbleData.isExpanded).isTrue()
+
+ bubbleStackView.setSelectedBubble(bubble2)
+ bubbleStackView.isExpanded = true
+ shellExecutor.flushAll()
+ }
+
+ assertThat(semaphore.tryAcquire(5, TimeUnit.SECONDS)).isTrue()
+ assertThat(lastUpdate!!.expanded).isTrue()
+ assertThat(lastUpdate!!.bubbles.map { it.key })
+ .containsExactly("bubble2", "bubble1")
+ .inOrder()
+
+ // wait for idle to allow the animation to start
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync()
+ // wait for the expansion animation to complete before interacting with the bubbles
+ PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(
+ AnimatableScaleMatrix.SCALE_X, AnimatableScaleMatrix.SCALE_Y)
+
+ // make the IME visible and tap on bubble1 to select it
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ positioner.setImeVisible(true, 100)
+ bubble1.iconView!!.performClick()
+ // we have to set the selected bubble in the stack view manually because we don't have a
+ // listener wired up.
+ bubbleStackView.setSelectedBubble(bubble1)
+ shellExecutor.flushAll()
+ }
+
+ val onImeHidden = bubbleStackViewManager.onImeHidden
+ assertThat(onImeHidden).isNotNull()
+
+ assertThat(expandListener.bubblesExpandedState).isEqualTo(mapOf("bubble2" to true))
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ onImeHidden!!.run()
+ shellExecutor.flushAll()
+ }
+
+ assertThat(expandListener.bubblesExpandedState)
+ .isEqualTo(mapOf("bubble1" to true, "bubble2" to false))
+ assertThat(semaphore.tryAcquire(5, TimeUnit.SECONDS)).isTrue()
+ assertThat(bubbleData.selectedBubble).isEqualTo(bubble1)
+ }
+
+ @Test
+ fun tapDifferentBubble_imeHidden_updatesImmediately() {
+ val bubble1 = createAndInflateChatBubble(key = "bubble1")
+ val bubble2 = createAndInflateChatBubble(key = "bubble2")
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ bubbleStackView.addBubble(bubble1)
+ bubbleStackView.addBubble(bubble2)
+ }
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync()
+
+ assertThat(bubbleStackView.bubbleCount).isEqualTo(2)
+ assertThat(bubbleData.bubbles).hasSize(2)
+ assertThat(bubbleData.selectedBubble).isEqualTo(bubble2)
+ assertThat(bubble2.iconView).isNotNull()
+
+ val expandListener = FakeBubbleExpandListener()
+ bubbleStackView.setExpandListener(expandListener)
+
+ var lastUpdate: BubbleData.Update? = null
+ val semaphore = Semaphore(0)
+ val listener =
+ BubbleData.Listener { update ->
+ lastUpdate = update
+ semaphore.release()
+ }
+ bubbleData.setListener(listener)
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ bubble2.iconView!!.performClick()
+ assertThat(bubbleData.isExpanded).isTrue()
+
+ bubbleStackView.setSelectedBubble(bubble2)
+ bubbleStackView.isExpanded = true
+ shellExecutor.flushAll()
+ }
+
+ assertThat(semaphore.tryAcquire(5, TimeUnit.SECONDS)).isTrue()
+ assertThat(lastUpdate!!.expanded).isTrue()
+ assertThat(lastUpdate!!.bubbles.map { it.key })
+ .containsExactly("bubble2", "bubble1")
+ .inOrder()
+
+ // wait for idle to allow the animation to start
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync()
+ // wait for the expansion animation to complete before interacting with the bubbles
+ PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(
+ AnimatableScaleMatrix.SCALE_X, AnimatableScaleMatrix.SCALE_Y)
+
+ // make the IME hidden and tap on bubble1 to select it
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ positioner.setImeVisible(false, 0)
+ bubble1.iconView!!.performClick()
+ // we have to set the selected bubble in the stack view manually because we don't have a
+ // listener wired up.
+ bubbleStackView.setSelectedBubble(bubble1)
+ shellExecutor.flushAll()
+ }
+
+ val onImeHidden = bubbleStackViewManager.onImeHidden
+ assertThat(onImeHidden).isNull()
+
+ assertThat(expandListener.bubblesExpandedState)
+ .isEqualTo(mapOf("bubble1" to true, "bubble2" to false))
+ assertThat(semaphore.tryAcquire(5, TimeUnit.SECONDS)).isTrue()
+ assertThat(bubbleData.selectedBubble).isEqualTo(bubble1)
+ }
+
@EnableFlags(Flags.FLAG_ENABLE_OPTIONAL_BUBBLE_OVERFLOW)
@Test
fun testCreateStackView_noOverflowContents_noOverflow() {
@@ -563,4 +709,18 @@
this.onImeHidden = onImeHidden
}
}
+
+ private class FakeBubbleExpandListener : BubbleExpandListener {
+ val bubblesExpandedState = mutableMapOf<String, Boolean>()
+ override fun onBubbleExpandChanged(isExpanding: Boolean, key: String) {
+ bubblesExpandedState[key] = isExpanding
+ }
+ }
+
+ private class FakeSurfaceSynchronizer : SurfaceSynchronizer {
+ var isActive = true
+ override fun syncSurfaceAndRun(callback: Runnable) {
+ if (isActive) callback.run()
+ }
+ }
}
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 3043e2b..f1ba042 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.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,11 +43,13 @@
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
import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.taskview.TaskView
+import com.android.wm.shell.taskview.TaskViewRepository
import com.android.wm.shell.taskview.TaskViewTransitions
import com.android.wm.shell.transition.Transitions
import com.google.common.truth.Truth.assertThat
@@ -155,6 +156,7 @@
mainExecutor,
mock<Handler>(),
bgExecutor,
+ mock<TaskViewRepository>(),
mock<TaskViewTransitions>(),
mock<Transitions>(),
SyncTransactionQueue(TransactionPool(), mainExecutor),
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 bcaa63b..7501786 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.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/FakeBubbleTaskViewFactory.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/FakeBubbleTaskViewFactory.kt
index 42b66aa..896f2ee 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/FakeBubbleTaskViewFactory.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/FakeBubbleTaskViewFactory.kt
@@ -20,6 +20,7 @@
import android.content.Context
import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.taskview.TaskView
+import com.android.wm.shell.taskview.TaskViewController
import com.android.wm.shell.taskview.TaskViewTaskController
import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever
@@ -33,7 +34,7 @@
) : BubbleTaskViewFactory {
override fun create(): BubbleTaskView {
val taskViewTaskController = mock<TaskViewTaskController>()
- val taskView = TaskView(context, taskViewTaskController)
+ val taskView = TaskView(context, mock<TaskViewController>(), taskViewTaskController)
val taskInfo = mock<ActivityManager.RunningTaskInfo>()
whenever(taskViewTaskController.taskInfo).thenReturn(taskInfo)
return BubbleTaskView(taskView, mainExecutor)
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 117ede0..d3cfbd0 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.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,7 +45,9 @@
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.TaskViewController
import com.android.wm.shell.taskview.TaskViewTaskController
import com.google.common.truth.Truth.assertThat
import java.util.concurrent.Semaphore
@@ -58,6 +59,7 @@
import org.junit.runner.RunWith
import org.mockito.kotlin.any
import org.mockito.kotlin.clearInvocations
+import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
@@ -165,7 +167,9 @@
fun animateSwitch_bubbleToBubble_updateTaskBounds() {
val fromBubble = createBubble("from").initialize(container)
val toBubbleTaskController = mock<TaskViewTaskController>()
- val toBubble = createBubble("to", toBubbleTaskController).initialize(container)
+ val taskController = mock<TaskViewController>()
+ val toBubble = createBubble("to", taskController, toBubbleTaskController).initialize(
+ container)
activityScenario.onActivity {
animationHelper.animateSwitch(fromBubble, toBubble) {}
@@ -174,11 +178,11 @@
}
getInstrumentation().waitForIdleSync()
// Clear invocations to ensure that bounds update happens after animation ends
- clearInvocations(toBubbleTaskController)
+ clearInvocations(taskController)
getInstrumentation().runOnMainSync { animatorTestRule.advanceTimeBy(900) }
getInstrumentation().waitForIdleSync()
- verify(toBubbleTaskController).setWindowBounds(any())
+ verify(taskController).setTaskBounds(eq(toBubbleTaskController), any())
}
@Test
@@ -229,8 +233,9 @@
@Test
fun animateToRestPosition_updateTaskBounds() {
- val taskController = mock<TaskViewTaskController>()
- val bubble = createBubble("key", taskController).initialize(container)
+ val taskView = mock<TaskViewTaskController>()
+ val controller = mock<TaskViewController>()
+ val bubble = createBubble("key", controller, taskView).initialize(container)
val semaphore = Semaphore(0)
val after = Runnable { semaphore.release() }
@@ -247,11 +252,11 @@
animatorTestRule.advanceTimeBy(100)
}
// Clear invocations to ensure that bounds update happens after animation ends
- clearInvocations(taskController)
+ clearInvocations(controller)
getInstrumentation().runOnMainSync { animatorTestRule.advanceTimeBy(900) }
getInstrumentation().waitForIdleSync()
- verify(taskController).setWindowBounds(any())
+ verify(controller).setTaskBounds(eq(taskView), any())
}
@Test
@@ -329,9 +334,10 @@
private fun createBubble(
key: String,
+ taskViewController: TaskViewController = mock<TaskViewController>(),
taskViewTaskController: TaskViewTaskController = mock<TaskViewTaskController>(),
): Bubble {
- val taskView = TaskView(context, taskViewTaskController)
+ val taskView = TaskView(context, taskViewController, taskViewTaskController)
val taskInfo = mock<ActivityManager.RunningTaskInfo>()
whenever(taskViewTaskController.taskInfo).thenReturn(taskInfo)
val bubbleTaskView = BubbleTaskView(taskView, mainExecutor)
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 bfc798b..7f65e22 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 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,8 +43,10 @@
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.TaskViewController
import com.android.wm.shell.taskview.TaskViewTaskController
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
@@ -356,7 +357,7 @@
private inner class FakeBubbleTaskViewFactory : BubbleTaskViewFactory {
override fun create(): BubbleTaskView {
val taskViewTaskController = mock<TaskViewTaskController>()
- val taskView = TaskView(context, taskViewTaskController)
+ val taskView = TaskView(context, mock<TaskViewController>(), taskViewTaskController)
val taskInfo = mock<ActivityManager.RunningTaskInfo>()
whenever(taskViewTaskController.taskInfo).thenReturn(taskInfo)
return BubbleTaskView(taskView, mainExecutor)
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 9b1645e..a649247 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.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,12 +57,14 @@
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
import com.android.wm.shell.sysui.ShellCommandHandler
import com.android.wm.shell.sysui.ShellController
import com.android.wm.shell.sysui.ShellInit
+import com.android.wm.shell.taskview.TaskViewRepository
import com.android.wm.shell.taskview.TaskViewTransitions
import com.android.wm.shell.transition.Transitions
import com.google.common.truth.Truth.assertThat
@@ -194,6 +195,7 @@
mainExecutor,
mock<Handler>(),
bgExecutor,
+ mock<TaskViewRepository>(),
mock<TaskViewTransitions>(),
mock<Transitions>(),
SyncTransactionQueue(TransactionPool(), mainExecutor),
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
similarity index 94%
rename from libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/TestShellExecutor.kt
rename to libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/common/TestShellExecutor.kt
index ef8e71c..6b549b4 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 e5fe1b54..83a3959 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 c3ee0f7..0974930 100644
--- a/libs/WindowManager/Shell/shared/Android.bp
+++ b/libs/WindowManager/Shell/shared/Android.bp
@@ -56,6 +56,7 @@
static_libs: [
"androidx.core_core-animation",
"androidx.dynamicanimation_dynamicanimation",
+ "com_android_wm_shell_flags_lib",
"jsr330",
],
kotlincflags: ["-Xjvm-default=all"],
@@ -77,3 +78,17 @@
"com.android.window.flags.window-aconfig-java",
],
}
+
+// Things that can be shared with launcher3
+java_library {
+ name: "WindowManager-Shell-shared-AOSP",
+
+ sdk_version: "current",
+
+ srcs: [
+ "src/com/android/wm/shell/shared/bubbles/BubbleAnythingFlagHelper.java",
+ ],
+ static_libs: [
+ "com_android_wm_shell_flags_lib",
+ ],
+}
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 e033f67..840de2c 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.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 @@
/** 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 0000000..e1f1d0c
--- /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/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index 08e3692..30d6790 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -117,6 +117,8 @@
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.taskview.TaskView;
+import com.android.wm.shell.taskview.TaskViewController;
+import com.android.wm.shell.taskview.TaskViewRepository;
import com.android.wm.shell.taskview.TaskViewTaskController;
import com.android.wm.shell.taskview.TaskViewTransitions;
import com.android.wm.shell.transition.Transitions;
@@ -192,7 +194,7 @@
private final TaskStackListenerImpl mTaskStackListener;
private final ShellTaskOrganizer mTaskOrganizer;
private final DisplayController mDisplayController;
- private final TaskViewTransitions mTaskViewTransitions;
+ private final TaskViewController mTaskViewController;
private final Transitions mTransitions;
private final SyncTransactionQueue mSyncQueue;
private final ShellController mShellController;
@@ -309,6 +311,7 @@
@ShellMainThread ShellExecutor mainExecutor,
@ShellMainThread Handler mainHandler,
@ShellBackgroundThread ShellExecutor bgExecutor,
+ TaskViewRepository taskViewRepository,
TaskViewTransitions taskViewTransitions,
Transitions transitions,
SyncTransactionQueue syncQueue,
@@ -347,7 +350,12 @@
context.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.importance_ring_stroke_width));
mDisplayController = displayController;
- mTaskViewTransitions = taskViewTransitions;
+ if (TaskViewTransitions.useRepo()) {
+ mTaskViewController = new TaskViewTransitions(transitions, taskViewRepository,
+ organizer, syncQueue);
+ } else {
+ mTaskViewController = taskViewTransitions;
+ }
mTransitions = transitions;
mOneHandedOptional = oneHandedOptional;
mDragAndDropController = dragAndDropController;
@@ -359,8 +367,9 @@
@Override
public BubbleTaskView create() {
TaskViewTaskController taskViewTaskController = new TaskViewTaskController(
- context, organizer, taskViewTransitions, syncQueue);
- TaskView taskView = new TaskView(context, taskViewTaskController);
+ context, organizer, mTaskViewController, syncQueue);
+ TaskView taskView = new TaskView(context, mTaskViewController,
+ taskViewTaskController);
return new BubbleTaskView(taskView, mainExecutor);
}
};
@@ -843,14 +852,6 @@
return mTaskOrganizer;
}
- SyncTransactionQueue getSyncTransactionQueue() {
- return mSyncQueue;
- }
-
- TaskViewTransitions getTaskViewTransitions() {
- return mTaskViewTransitions;
- }
-
/** Contains information to help position things on the screen. */
@VisibleForTesting
public BubblePositioner getPositioner() {
@@ -1439,9 +1440,9 @@
*
* @param intent the intent for the bubble.
*/
- public void expandStackAndSelectBubble(Intent intent) {
+ public void expandStackAndSelectBubble(Intent intent, UserHandle user) {
if (!Flags.enableBubbleAnything()) return;
- Bubble b = mBubbleData.getOrCreateBubble(intent); // Removes from overflow
+ Bubble b = mBubbleData.getOrCreateBubble(intent, user); // Removes from overflow
ProtoLog.v(WM_SHELL_BUBBLES, "expandStackAndSelectBubble - intent=%s", intent);
if (b.isInflated()) {
mBubbleData.setSelectedBubbleAndExpandStack(b);
@@ -2648,8 +2649,8 @@
}
@Override
- public void showAppBubble(Intent intent) {
- mMainExecutor.execute(() -> mController.expandStackAndSelectBubble(intent));
+ public void showAppBubble(Intent intent, UserHandle user) {
+ mMainExecutor.execute(() -> mController.expandStackAndSelectBubble(intent, user));
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
index dc2025b..76d91ed 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
@@ -461,10 +461,8 @@
return bubbleToReturn;
}
- Bubble getOrCreateBubble(Intent intent) {
- UserHandle user = UserHandle.of(mCurrentUserId);
- String bubbleKey = Bubble.getAppBubbleKeyForApp(intent.getPackage(),
- user);
+ Bubble getOrCreateBubble(Intent intent, UserHandle user) {
+ String bubbleKey = Bubble.getAppBubbleKeyForApp(intent.getPackage(), user);
Bubble bubbleToReturn = findAndRemoveBubbleFromOverflow(bubbleKey);
if (bubbleToReturn == null) {
bubbleToReturn = Bubble.createAppBubble(intent, user, null, mMainExecutor, mBgExecutor);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index 979d958..1094c29 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -2183,34 +2183,39 @@
ProtoLog.d(WM_SHELL_BUBBLES, "showNewlySelectedBubble b=%s, previouslySelected=%s,"
+ " mIsExpanded=%b", newlySelectedKey, previouslySelectedKey, mIsExpanded);
if (mIsExpanded) {
- hideCurrentInputMethod();
-
- if (Flags.enableRetrievableBubbles()) {
- if (mBubbleData.getBubbles().size() == 1) {
- // First bubble, check if overflow visibility needs to change
- updateOverflowVisibility();
+ Runnable onImeHidden = () -> {
+ if (Flags.enableRetrievableBubbles()) {
+ if (mBubbleData.getBubbles().size() == 1) {
+ // First bubble, check if overflow visibility needs to change
+ updateOverflowVisibility();
+ }
}
+
+ // Make the container of the expanded view transparent before removing the expanded
+ // view from it. Otherwise a punch hole created by {@link android.view.SurfaceView}
+ // in the expanded view becomes visible on the screen. See b/126856255
+ mExpandedViewContainer.setAlpha(0.0f);
+ mSurfaceSynchronizer.syncSurfaceAndRun(() -> {
+ if (previouslySelected != null) {
+ previouslySelected.setTaskViewVisibility(false);
+ }
+
+ updateExpandedBubble();
+ requestUpdate();
+
+ logBubbleEvent(previouslySelected,
+ FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__COLLAPSED);
+ logBubbleEvent(bubbleToSelect,
+ FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__EXPANDED);
+ notifyExpansionChanged(previouslySelected, false /* expanded */);
+ notifyExpansionChanged(bubbleToSelect, true /* expanded */);
+ });
+ };
+ if (mPositioner.isImeVisible()) {
+ hideCurrentInputMethod(onImeHidden);
+ } else {
+ onImeHidden.run();
}
-
- // Make the container of the expanded view transparent before removing the expanded view
- // from it. Otherwise a punch hole created by {@link android.view.SurfaceView} in the
- // expanded view becomes visible on the screen. See b/126856255
- mExpandedViewContainer.setAlpha(0.0f);
- mSurfaceSynchronizer.syncSurfaceAndRun(() -> {
- if (previouslySelected != null) {
- previouslySelected.setTaskViewVisibility(false);
- }
-
- updateExpandedBubble();
- requestUpdate();
-
- logBubbleEvent(previouslySelected,
- FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__COLLAPSED);
- logBubbleEvent(bubbleToSelect,
- FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__EXPANDED);
- notifyExpansionChanged(previouslySelected, false /* expanded */);
- notifyExpansionChanged(bubbleToSelect, true /* expanded */);
- });
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
index 9c2d3543..0a4d79a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
@@ -17,8 +17,9 @@
package com.android.wm.shell.bubbles;
import android.content.Intent;
-import android.graphics.Rect;
import android.content.pm.ShortcutInfo;
+import android.graphics.Rect;
+import android.os.UserHandle;
import com.android.wm.shell.bubbles.IBubblesListener;
import com.android.wm.shell.shared.bubbles.BubbleBarLocation;
@@ -52,7 +53,7 @@
oneway void showShortcutBubble(in ShortcutInfo info) = 12;
- oneway void showAppBubble(in Intent intent) = 13;
+ oneway void showAppBubble(in Intent intent, in UserHandle user) = 13;
oneway void showExpandedView() = 14;
}
\ No newline at end of file
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 67592e6..0d0bc9b 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 @@
/** 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/DisplayController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java
index 72be066..e69d60d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java
@@ -20,6 +20,7 @@
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Rect;
+import android.graphics.RectF;
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayTopology;
import android.os.RemoteException;
@@ -41,7 +42,9 @@
import com.android.wm.shell.sysui.ShellInit;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import java.util.Set;
/**
@@ -62,6 +65,7 @@
private final SparseArray<DisplayRecord> mDisplays = new SparseArray<>();
private final ArrayList<OnDisplaysChangedListener> mDisplayChangedListeners = new ArrayList<>();
+ private final Map<Integer, RectF> mUnpopulatedDisplayBounds = new HashMap<>();
public DisplayController(Context context, IWindowManager wmService, ShellInit shellInit,
ShellExecutor mainExecutor, DisplayManager displayManager) {
@@ -193,7 +197,12 @@
? mContext
: mContext.createDisplayContext(display);
final DisplayRecord record = new DisplayRecord(displayId);
- record.setDisplayLayout(context, new DisplayLayout(context, display));
+ DisplayLayout displayLayout = new DisplayLayout(context, display);
+ if (Flags.enableConnectedDisplaysWindowDrag()
+ && mUnpopulatedDisplayBounds.containsKey(displayId)) {
+ displayLayout.setGlobalBoundsDp(mUnpopulatedDisplayBounds.get(displayId));
+ }
+ record.setDisplayLayout(context, displayLayout);
mDisplays.put(displayId, record);
for (int i = 0; i < mDisplayChangedListeners.size(); ++i) {
mDisplayChangedListeners.get(i).onDisplayAdded(displayId);
@@ -231,10 +240,27 @@
}
private void onDisplayTopologyChanged(DisplayTopology topology) {
- // TODO(b/381472611): Call DisplayTopology#getCoordinates and update values in
- // DisplayLayout when DM code is ready.
+ if (topology == null) {
+ return;
+ }
+ SparseArray<RectF> absoluteBounds = topology.getAbsoluteBounds();
+ mUnpopulatedDisplayBounds.clear();
+ for (int i = 0; i < absoluteBounds.size(); ++i) {
+ int displayId = absoluteBounds.keyAt(i);
+ DisplayLayout displayLayout = getDisplayLayout(displayId);
+ if (displayLayout == null) {
+ // onDisplayTopologyChanged can arrive before onDisplayAdded.
+ // Store the bounds to be applied later in onDisplayAdded.
+ Slog.d(TAG, "Storing bounds for onDisplayTopologyChanged on unknown"
+ + " display, displayId=" + displayId);
+ mUnpopulatedDisplayBounds.put(displayId, absoluteBounds.valueAt(i));
+ } else {
+ displayLayout.setGlobalBoundsDp(absoluteBounds.valueAt(i));
+ }
+ }
+
for (int i = 0; i < mDisplayChangedListeners.size(); ++i) {
- mDisplayChangedListeners.get(i).onTopologyChanged();
+ mDisplayChangedListeners.get(i).onTopologyChanged(topology);
}
}
@@ -429,6 +455,6 @@
/**
* Called when the display topology has changed.
*/
- default void onTopologyChanged() {}
+ default void onTopologyChanged(DisplayTopology topology) {}
}
}
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 0000000..a13ad20
--- /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/common/SyncTransactionQueue.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SyncTransactionQueue.java
index bcd40a9..c4696d5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SyncTransactionQueue.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SyncTransactionQueue.java
@@ -192,15 +192,22 @@
throw new IllegalStateException("Sync Transactions must be serialized. In Flight: "
+ mInFlight.mId + " - " + mInFlight.mWCT);
}
- mInFlight = this;
if (DEBUG) Slog.d(TAG, "Sending sync transaction: " + mWCT);
- if (mLegacyTransition != null) {
- mId = new WindowOrganizer().startLegacyTransition(mLegacyTransition.getType(),
- mLegacyTransition.getAdapter(), this, mWCT);
- } else {
- mId = new WindowOrganizer().applySyncTransaction(mWCT, this);
+ try {
+ if (mLegacyTransition != null) {
+ mId = new WindowOrganizer().startLegacyTransition(mLegacyTransition.getType(),
+ mLegacyTransition.getAdapter(), this, mWCT);
+ } else {
+ mId = new WindowOrganizer().applySyncTransaction(mWCT, this);
+ }
+ } catch (RuntimeException e) {
+ Slog.e(TAG, "Send failed", e);
+ // Finish current sync callback immediately.
+ onTransactionReady(mId, new SurfaceControl.Transaction());
+ return;
}
if (DEBUG) Slog.d(TAG, " Sent sync transaction. Got id=" + mId);
+ mInFlight = this;
mMainExecutor.executeDelayed(mOnReplyTimeout, REPLY_TIMEOUT);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/IPip.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/IPip.aidl
index e779879..3777907 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/IPip.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/IPip.aidl
@@ -16,6 +16,7 @@
package com.android.wm.shell.common.pip;
+import android.app.ActivityManager;
import android.app.PictureInPictureParams;
import android.view.SurfaceControl;
import android.content.ComponentName;
@@ -41,9 +42,8 @@
bounds
* @return destination bounds the PiP window should land into
*/
- Rect startSwipePipToHome(in ComponentName componentName, in ActivityInfo activityInfo,
- in PictureInPictureParams pictureInPictureParams,
- int launcherRotation, in Rect hotseatKeepClearArea) = 1;
+ Rect startSwipePipToHome(in ActivityManager.RunningTaskInfo taskInfo, int launcherRotation,
+ in Rect hotseatKeepClearArea) = 1;
/**
* Notifies the swiping Activity to PiP onto home transition is finished
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipMenuController.java
index 85353d3..bad4a93 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipMenuController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipMenuController.java
@@ -18,7 +18,6 @@
import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
import static android.view.WindowManager.LayoutParams.FLAG_SLIPPERY;
-import static android.view.WindowManager.LayoutParams.FLAG_SPLIT_TOUCH;
import static android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
@@ -111,7 +110,7 @@
int width, int height) {
final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(width, height,
TYPE_APPLICATION_OVERLAY,
- FLAG_WATCH_OUTSIDE_TOUCH | FLAG_SPLIT_TOUCH | FLAG_SLIPPERY | FLAG_NOT_TOUCHABLE,
+ FLAG_WATCH_OUTSIDE_TOUCH | FLAG_SLIPPERY | FLAG_NOT_TOUCHABLE,
PixelFormat.TRANSLUCENT);
lp.privateFlags |= PRIVATE_FLAG_TRUSTED_OVERLAY;
lp.setTitle(title);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java
index 89573cc..84b710d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java
@@ -19,7 +19,6 @@
import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
import static android.view.WindowManager.LayoutParams.FLAG_SLIPPERY;
-import static android.view.WindowManager.LayoutParams.FLAG_SPLIT_TOUCH;
import static android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
@@ -126,7 +125,7 @@
WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
dividerBounds.width(), dividerBounds.height(), TYPE_DOCK_DIVIDER,
FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCH_MODAL | FLAG_WATCH_OUTSIDE_TOUCH
- | FLAG_SPLIT_TOUCH | FLAG_SLIPPERY,
+ | FLAG_SLIPPERY,
PixelFormat.TRANSLUCENT);
lp.token = new Binder();
lp.setTitle(mWindowName);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index cbbe8a2..8404259 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -771,8 +771,9 @@
@WMSingleton
@Provides
static TaskViewTransitions provideTaskViewTransitions(Transitions transitions,
- TaskViewRepository repository) {
- return new TaskViewTransitions(transitions, repository);
+ TaskViewRepository repository, ShellTaskOrganizer organizer,
+ SyncTransactionQueue syncQueue) {
+ return new TaskViewTransitions(transitions, repository, organizer, syncQueue);
}
@WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index ac510f8..67e3453 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -133,6 +133,7 @@
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.taskview.TaskViewRepository;
import com.android.wm.shell.taskview.TaskViewTransitions;
import com.android.wm.shell.transition.DefaultMixedHandler;
import com.android.wm.shell.transition.FocusTransitionObserver;
@@ -247,6 +248,7 @@
@ShellMainThread ShellExecutor mainExecutor,
@ShellMainThread Handler mainHandler,
@ShellBackgroundThread ShellExecutor bgExecutor,
+ TaskViewRepository taskViewRepository,
TaskViewTransitions taskViewTransitions,
Transitions transitions,
SyncTransactionQueue syncQueue,
@@ -280,6 +282,7 @@
mainExecutor,
mainHandler,
bgExecutor,
+ taskViewRepository,
taskViewTransitions,
transitions,
syncQueue,
@@ -946,7 +949,8 @@
FocusTransitionObserver focusTransitionObserver,
DesktopModeEventLogger desktopModeEventLogger,
DesktopModeUiEventLogger desktopModeUiEventLogger,
- WindowDecorTaskResourceLoader taskResourceLoader
+ WindowDecorTaskResourceLoader taskResourceLoader,
+ RecentsTransitionHandler recentsTransitionHandler
) {
if (!DesktopModeStatus.canEnterDesktopModeOrShowAppHandle(context)) {
return Optional.empty();
@@ -962,7 +966,7 @@
desktopTasksLimiter, appHandleEducationController, appToWebEducationController,
windowDecorCaptionHandleRepository, activityOrientationChangeHandler,
focusTransitionObserver, desktopModeEventLogger, desktopModeUiEventLogger,
- taskResourceLoader));
+ taskResourceLoader, recentsTransitionHandler));
}
@WMSingleton
@@ -1027,8 +1031,9 @@
static CloseDesktopTaskTransitionHandler provideCloseDesktopTaskTransitionHandler(
Context context,
@ShellMainThread ShellExecutor mainExecutor,
- @ShellAnimationThread ShellExecutor animExecutor) {
- return new CloseDesktopTaskTransitionHandler(context, mainExecutor, animExecutor);
+ @ShellAnimationThread ShellExecutor animExecutor,
+ @ShellMainThread Handler handler) {
+ return new CloseDesktopTaskTransitionHandler(context, mainExecutor, animExecutor, handler);
}
@WMSingleton
@@ -1153,9 +1158,12 @@
Context context,
ShellInit shellInit,
Transitions transitions,
- DesktopModeEventLogger desktopModeEventLogger) {
+ DesktopModeEventLogger desktopModeEventLogger,
+ Optional<DesktopTasksLimiter> desktopTasksLimiter,
+ ShellTaskOrganizer shellTaskOrganizer) {
return new DesktopModeLoggerTransitionObserver(
- context, shellInit, transitions, desktopModeEventLogger);
+ context, shellInit, transitions, desktopModeEventLogger,
+ desktopTasksLimiter, shellTaskOrganizer);
}
@WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
index c8d0dab..793bdf0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
@@ -83,13 +83,14 @@
@NonNull PipTransitionState pipStackListenerController,
@NonNull PipDisplayLayoutState pipDisplayLayoutState,
@NonNull PipUiStateChangeController pipUiStateChangeController,
+ DisplayController displayController,
Optional<DesktopUserRepositories> desktopUserRepositoriesOptional,
Optional<DesktopWallpaperActivityTokenProvider>
desktopWallpaperActivityTokenProviderOptional) {
return new PipTransition(context, shellInit, shellTaskOrganizer, transitions,
pipBoundsState, null, pipBoundsAlgorithm, pipTaskListener,
pipScheduler, pipStackListenerController, pipDisplayLayoutState,
- pipUiStateChangeController, desktopUserRepositoriesOptional,
+ pipUiStateChangeController, displayController, desktopUserRepositoriesOptional,
desktopWallpaperActivityTokenProviderOptional);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/CloseDesktopTaskTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/CloseDesktopTaskTransitionHandler.kt
index 9b5a289..1ce093e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/CloseDesktopTaskTransitionHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/CloseDesktopTaskTransitionHandler.kt
@@ -23,8 +23,10 @@
import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
import android.content.Context
import android.graphics.Rect
+import android.os.Handler
import android.os.IBinder
import android.util.TypedValue
+import android.view.Choreographer
import android.view.SurfaceControl.Transaction
import android.view.WindowManager
import android.window.TransitionInfo
@@ -32,7 +34,10 @@
import android.window.WindowContainerTransaction
import androidx.core.animation.addListener
import com.android.app.animation.Interpolators
+import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_CLOSE_TASK
+import com.android.internal.jank.InteractionJankMonitor
import com.android.wm.shell.common.ShellExecutor
+import com.android.wm.shell.shared.annotations.ShellMainThread
import com.android.wm.shell.transition.Transitions
import java.util.function.Supplier
@@ -44,9 +49,11 @@
private val mainExecutor: ShellExecutor,
private val animExecutor: ShellExecutor,
private val transactionSupplier: Supplier<Transaction> = Supplier { Transaction() },
+ @ShellMainThread private val handler: Handler,
) : Transitions.TransitionHandler {
private val runningAnimations = mutableMapOf<IBinder, List<Animator>>()
+ private val interactionJankMonitor = InteractionJankMonitor.getInstance()
/** Returns null, as it only handles transitions started from Shell. */
override fun handleRequest(
@@ -71,18 +78,27 @@
// All animations completed, finish the transition
runningAnimations.remove(transition)
finishCallback.onTransitionFinished(/* wct= */ null)
+ interactionJankMonitor.end(CUJ_DESKTOP_MODE_CLOSE_TASK)
}
}
}
+ val closingChanges =
+ info.changes.filter {
+ it.mode == WindowManager.TRANSIT_CLOSE &&
+ it.taskInfo?.windowingMode == WINDOWING_MODE_FREEFORM
+ }
animations +=
- info.changes
- .filter {
- it.mode == WindowManager.TRANSIT_CLOSE &&
- it.taskInfo?.windowingMode == WINDOWING_MODE_FREEFORM
- }
- .map { createCloseAnimation(it, finishTransaction, onAnimFinish) }
+ closingChanges.map { createCloseAnimation(it, finishTransaction, onAnimFinish) }
if (animations.isEmpty()) return false
runningAnimations[transition] = animations
+ closingChanges.lastOrNull()?.leash?.let { lastChangeLeash ->
+ interactionJankMonitor.begin(
+ lastChangeLeash,
+ context,
+ handler,
+ CUJ_DESKTOP_MODE_CLOSE_TASK,
+ )
+ }
animExecutor.execute { animations.forEach(Animator::start) }
return true
}
@@ -127,6 +143,7 @@
.get()
.setPosition(change.leash, animBounds.left.toFloat(), animBounds.top.toFloat())
.setScale(change.leash, animScale, animScale)
+ .setFrameTimeline(Choreographer.getInstance().vsyncId)
.apply()
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt
index e8f9a78..68bdbd1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt
@@ -467,9 +467,13 @@
FrameworkStatsLog
.DESKTOP_MODE_SESSION_TASK_UPDATE__MINIMIZE_REASON__MINIMIZE_TASK_LIMIT
),
- MINIMIZE_BUTTON( // TODO(b/356843241): use this enum value
+ MINIMIZE_BUTTON(
FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__MINIMIZE_REASON__MINIMIZE_BUTTON
),
+ KEY_GESTURE(
+ FrameworkStatsLog
+ .DESKTOP_MODE_SESSION_TASK_UPDATE__MINIMIZE_REASON__MINIMIZE_KEY_GESTURE
+ ),
}
// Default value used when the task was not unminimized.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandler.kt
index 1ddb834..9334898 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandler.kt
@@ -30,6 +30,7 @@
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.ShellExecutor
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.MinimizeReason
import com.android.wm.shell.desktopmode.common.ToggleTaskSizeInteraction
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
import com.android.wm.shell.shared.annotations.ShellMainThread
@@ -125,7 +126,9 @@
KeyGestureEvent.KEY_GESTURE_TYPE_MINIMIZE_FREEFORM_WINDOW -> {
logV("Key gesture MINIMIZE_FREEFORM_WINDOW is handled")
getGloballyFocusedFreeformTask()?.let {
- mainExecutor.execute { desktopTasksController.get().minimizeTask(it) }
+ mainExecutor.execute {
+ desktopTasksController.get().minimizeTask(it, MinimizeReason.KEY_GESTURE)
+ }
}
return true
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt
index c09504e..2dd89c7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt
@@ -36,6 +36,7 @@
import androidx.core.util.plus
import androidx.core.util.putAll
import com.android.internal.protolog.ProtoLog
+import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.EnterReason
import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ExitReason
import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.MinimizeReason
@@ -52,6 +53,8 @@
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.transition.Transitions
+import java.util.Optional
+import kotlin.jvm.optionals.getOrNull
/**
* A [Transitions.TransitionObserver] that observes transitions and the proposed changes to log
@@ -63,6 +66,8 @@
shellInit: ShellInit,
private val transitions: Transitions,
private val desktopModeEventLogger: DesktopModeEventLogger,
+ private val desktopTasksLimiter: Optional<DesktopTasksLimiter>,
+ private val shellTaskOrganizer: ShellTaskOrganizer,
) : Transitions.TransitionObserver {
init {
@@ -141,6 +146,7 @@
// identify if we need to log any changes and update the state of visible freeform tasks
identifyLogEventAndUpdateState(
+ transition = transition,
transitionInfo = info,
preTransitionVisibleFreeformTasks = visibleFreeformTaskInfos,
postTransitionVisibleFreeformTasks = postTransitionVisibleFreeformTasks,
@@ -227,6 +233,7 @@
* state and update it
*/
private fun identifyLogEventAndUpdateState(
+ transition: IBinder,
transitionInfo: TransitionInfo,
preTransitionVisibleFreeformTasks: SparseArray<TaskInfo>,
postTransitionVisibleFreeformTasks: SparseArray<TaskInfo>,
@@ -238,6 +245,7 @@
) {
// Sessions is finishing, log task updates followed by an exit event
identifyAndLogTaskUpdates(
+ transition,
transitionInfo,
preTransitionVisibleFreeformTasks,
postTransitionVisibleFreeformTasks,
@@ -255,6 +263,7 @@
desktopModeEventLogger.logSessionEnter(getEnterReason(transitionInfo))
identifyAndLogTaskUpdates(
+ transition,
transitionInfo,
preTransitionVisibleFreeformTasks,
postTransitionVisibleFreeformTasks,
@@ -262,6 +271,7 @@
} else if (isSessionActive) {
// Session is neither starting, nor finishing, log task updates if there are any
identifyAndLogTaskUpdates(
+ transition,
transitionInfo,
preTransitionVisibleFreeformTasks,
postTransitionVisibleFreeformTasks,
@@ -275,6 +285,7 @@
/** Compare the old and new state of taskInfos and identify and log the changes */
private fun identifyAndLogTaskUpdates(
+ transition: IBinder,
transitionInfo: TransitionInfo,
preTransitionVisibleFreeformTasks: SparseArray<TaskInfo>,
postTransitionVisibleFreeformTasks: SparseArray<TaskInfo>,
@@ -310,12 +321,9 @@
// find old tasks that were removed
preTransitionVisibleFreeformTasks.forEach { taskId, taskInfo ->
if (!postTransitionVisibleFreeformTasks.containsKey(taskId)) {
- val minimizeReason =
- if (transitionInfo.type == Transitions.TRANSIT_MINIMIZE) {
- MinimizeReason.MINIMIZE_BUTTON
- } else {
- null
- }
+ // The task is no longer visible, it might have been minimized, get the minimize
+ // reason (if any)
+ val minimizeReason = getMinimizeReason(transition, transitionInfo, taskInfo)
val taskUpdate =
buildTaskUpdateForTask(
taskInfo,
@@ -336,6 +344,21 @@
}
}
+ private fun getMinimizeReason(
+ transition: IBinder,
+ transitionInfo: TransitionInfo,
+ taskInfo: TaskInfo,
+ ): MinimizeReason? {
+ if (transitionInfo.type == Transitions.TRANSIT_MINIMIZE) {
+ return MinimizeReason.MINIMIZE_BUTTON
+ }
+ val minimizingTask = desktopTasksLimiter.getOrNull()?.getMinimizingTask(transition)
+ if (minimizingTask?.taskId == taskInfo.taskId) {
+ return minimizingTask.minimizeReason
+ }
+ return null
+ }
+
private fun buildTaskUpdateForTask(
taskInfo: TaskInfo,
visibleTasks: Int,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt
index c975533..fa69668 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt
@@ -24,8 +24,8 @@
import android.view.Display.INVALID_DISPLAY
import android.window.DesktopModeFlags
import androidx.core.util.forEach
-import androidx.core.util.keyIterator
import androidx.core.util.valueIterator
+import com.android.internal.annotations.VisibleForTesting
import com.android.internal.protolog.ProtoLog
import com.android.window.flags.Flags
import com.android.wm.shell.desktopmode.persistence.DesktopPersistentRepository
@@ -43,26 +43,36 @@
@ShellMainThread private val mainCoroutineScope: CoroutineScope,
val userId: Int,
) {
+ /** A display that supports desktops. */
+ private data class DesktopDisplay(
+ val displayId: Int,
+ val orderedDesks: MutableSet<Desk> = mutableSetOf(),
+ // TODO: b/389960283 - update on desk activation / deactivation.
+ var activeDeskId: Int? = null,
+ )
+
/**
- * Task data tracked per desktop.
+ * Task data tracked per desk.
*
- * @property activeTasks task ids of active tasks currently or previously visible in Desktop
- * mode session. Tasks become inactive when task closes or when desktop mode session ends.
+ * @property activeTasks task ids of active tasks currently or previously visible in the desk.
+ * Tasks become inactive when task closes or when the desk becomes inactive.
* @property visibleTasks task ids for active freeform tasks that are currently visible. There
- * might be other active tasks in desktop mode that are not visible.
+ * might be other active tasks in a desk that are not visible.
* @property minimizedTasks task ids for active freeform tasks that are currently minimized.
* @property closingTasks task ids for tasks that are going to close, but are currently visible.
* @property freeformTasksInZOrder list of current freeform task ids ordered from top to bottom
- * @property fullImmersiveTaskId the task id of the desktop task that is in full-immersive mode.
+ * @property fullImmersiveTaskId the task id of the desk's task that is in full-immersive mode.
* @property topTransparentFullscreenTaskId the task id of any current top transparent
- * fullscreen task launched on top of Desktop Mode. Cleared when the transparent task is
- * closed or sent to back. (top is at index 0).
+ * fullscreen task launched on top of the desk. Cleared when the transparent task is closed or
+ * sent to back. (top is at index 0).
* @property pipTaskId the task id of PiP task entered while in Desktop Mode.
- * @property pipShouldKeepDesktopActive whether an active PiP window should keep the Desktop
- * Mode session active. Only false when we are explicitly exiting Desktop Mode (via user
- * action) while there is an active PiP window.
+ * @property pipShouldKeepDesktopActive whether an active PiP window should keep the desk
+ * active. Only false when we are explicitly exiting Desktop Mode (via user action) while
+ * there is an active PiP window.
*/
- private data class DesktopTaskData(
+ private data class Desk(
+ val deskId: Int,
+ val displayId: Int,
val activeTasks: ArraySet<Int> = ArraySet(),
val visibleTasks: ArraySet<Int> = ArraySet(),
val minimizedTasks: ArraySet<Int> = ArraySet(),
@@ -72,10 +82,13 @@
var fullImmersiveTaskId: Int? = null,
var topTransparentFullscreenTaskId: Int? = null,
var pipTaskId: Int? = null,
+ // TODO: b/389960283 - consolidate this with [DesktopDisplay#activeDeskId].
var pipShouldKeepDesktopActive: Boolean = true,
) {
- fun deepCopy(): DesktopTaskData =
- DesktopTaskData(
+ fun deepCopy(): Desk =
+ Desk(
+ deskId = deskId,
+ displayId = displayId,
activeTasks = ArraySet(activeTasks),
visibleTasks = ArraySet(visibleTasks),
minimizedTasks = ArraySet(minimizedTasks),
@@ -87,6 +100,8 @@
pipShouldKeepDesktopActive = pipShouldKeepDesktopActive,
)
+ // TODO: b/362720497 - remove when multi-desktops is enabled where instances aren't
+ // reusable.
fun clear() {
activeTasks.clear()
visibleTasks.clear()
@@ -121,11 +136,11 @@
private var desktopGestureExclusionListener: Consumer<Region>? = null
private var desktopGestureExclusionExecutor: Executor? = null
- private val desktopTaskDataByDisplayId =
- object : SparseArray<DesktopTaskData>() {
- /** Gets [DesktopTaskData] for existing [displayId] or creates a new one. */
- fun getOrCreate(displayId: Int): DesktopTaskData =
- this[displayId] ?: DesktopTaskData().also { this[displayId] = it }
+ private val desktopData: DesktopData =
+ if (Flags.enableMultipleDesktopsBackend()) {
+ MultiDesktopData()
+ } else {
+ SingleDesktopData()
}
/** Adds [activeTasksListener] to be notified of updates to active tasks. */
@@ -136,10 +151,16 @@
/** Adds [visibleTasksListener] to be notified of updates to visible tasks. */
fun addVisibleTasksListener(visibleTasksListener: VisibleTasksListener, executor: Executor) {
visibleTasksListeners[visibleTasksListener] = executor
- desktopTaskDataByDisplayId.keyIterator().forEach {
- val visibleTaskCount = getVisibleTaskCount(it)
- executor.execute { visibleTasksListener.onTasksVisibilityChanged(it, visibleTaskCount) }
- }
+ desktopData
+ .desksSequence()
+ .groupBy { it.displayId }
+ .keys
+ .forEach { displayId ->
+ val visibleTaskCount = getVisibleTaskCount(displayId)
+ executor.execute {
+ visibleTasksListener.onTasksVisibilityChanged(displayId, visibleTaskCount)
+ }
+ }
}
/** Updates tasks changes on all the active task listeners for given display id. */
@@ -147,9 +168,8 @@
activeTasksListeners.onEach { it.onActiveTasksChanged(displayId) }
}
- /** Returns a list of all [DesktopTaskData] in the repository. */
- private fun desktopTaskDataSequence(): Sequence<DesktopTaskData> =
- desktopTaskDataByDisplayId.valueIterator().asSequence()
+ /** Returns a list of all [Desk]s in the repository. */
+ private fun desksSequence(): Sequence<Desk> = desktopData.desksSequence()
/** Adds [regionListener] to inform about changes to exclusion regions for all Desktop tasks. */
fun setExclusionRegionListener(regionListener: Consumer<Region>, executor: Executor) {
@@ -179,99 +199,183 @@
visibleTasksListeners.remove(visibleTasksListener)
}
- /** Adds task with [taskId] to the list of freeform tasks on [displayId]. */
+ /** Adds the given desk under the given display. */
+ fun addDesk(displayId: Int, deskId: Int) {
+ desktopData.getOrCreateDesk(displayId, deskId)
+ }
+
+ /** Returns the default desk in the given display. */
+ fun getDefaultDesk(displayId: Int): Int? = desktopData.getDefaultDesk(displayId)?.deskId
+
+ /** Sets the given desk as the active one in the given display. */
+ fun setActiveDesk(displayId: Int, deskId: Int) {
+ desktopData.setActiveDesk(displayId = displayId, deskId = deskId)
+ }
+
+ /**
+ * Adds task with [taskId] to the list of freeform tasks on [displayId]'s active desk.
+ *
+ * TODO: b/389960283 - add explicit [deskId] argument.
+ */
fun addTask(displayId: Int, taskId: Int, isVisible: Boolean) {
addOrMoveFreeformTaskToTop(displayId, taskId)
addActiveTask(displayId, taskId)
updateTask(displayId, taskId, isVisible)
}
- /** Adds task with [taskId] to the list of active tasks on [displayId]. */
+ /**
+ * Adds task with [taskId] to the list of active tasks on [displayId]'s active desk.
+ *
+ * TODO: b/389960283 - add explicit [deskId] argument.
+ */
private fun addActiveTask(displayId: Int, taskId: Int) {
- // Removes task if it is active on another display excluding [displayId].
- removeActiveTask(taskId, excludedDisplayId = displayId)
+ val activeDeskId =
+ desktopData.getActiveDesk(displayId)?.deskId
+ ?: error("Expected active desk in display: $displayId")
- if (desktopTaskDataByDisplayId.getOrCreate(displayId).activeTasks.add(taskId)) {
- logD("Adds active task=%d displayId=%d", taskId, displayId)
+ // Removes task if it is active on another desk excluding [activeDesk].
+ removeActiveTask(taskId, excludedDeskId = activeDeskId)
+
+ if (desktopData.getOrCreateDesk(displayId, activeDeskId).activeTasks.add(taskId)) {
+ logD("Adds active task=%d displayId=%d deskId=%d", taskId, displayId, activeDeskId)
updateActiveTasksListeners(displayId)
}
}
- /** Removes task from active task list of displays excluding the [excludedDisplayId]. */
- fun removeActiveTask(taskId: Int, excludedDisplayId: Int? = null) {
- desktopTaskDataByDisplayId.forEach { displayId, desktopTaskData ->
- if ((displayId != excludedDisplayId) && desktopTaskData.activeTasks.remove(taskId)) {
- logD("Removed active task=%d displayId=%d", taskId, displayId)
- updateActiveTasksListeners(displayId)
+ /** Removes task from active task list of desks excluding the [excludedDeskId]. */
+ @VisibleForTesting
+ fun removeActiveTask(taskId: Int, excludedDeskId: Int? = null) {
+ val affectedDisplays = mutableSetOf<Int>()
+ desktopData.forAllDesks { displayId, desk ->
+ if (desk.deskId != excludedDeskId && desk.activeTasks.remove(taskId)) {
+ logD(
+ "Removed active task=%d displayId=%d deskId=%d",
+ taskId,
+ displayId,
+ desk.deskId,
+ )
+ affectedDisplays.add(displayId)
}
}
+ affectedDisplays.forEach { displayId -> updateActiveTasksListeners(displayId) }
}
- /** Adds given task to the closing task list for [displayId]. */
+ /**
+ * Adds given task to the closing task list for [displayId]'s active desk.
+ *
+ * TODO: b/389960283 - add explicit [deskId] argument.
+ */
fun addClosingTask(displayId: Int, taskId: Int) {
- if (desktopTaskDataByDisplayId.getOrCreate(displayId).closingTasks.add(taskId)) {
- logD("Added closing task=%d displayId=%d", taskId, displayId)
+ val activeDeskId =
+ desktopData.getActiveDesk(displayId)?.deskId
+ ?: error("Expected active desk in display: $displayId")
+ if (desktopData.getOrCreateDesk(displayId, activeDeskId).closingTasks.add(taskId)) {
+ logD("Added closing task=%d displayId=%d deskId=%d", taskId, displayId, activeDeskId)
} else {
// If the task hasn't been removed from closing list after it disappeared.
- logW("Task with taskId=%d displayId=%d is already closing", taskId, displayId)
+ logW(
+ "Task with taskId=%d displayId=%d deskId=%d is already closing",
+ taskId,
+ displayId,
+ activeDeskId,
+ )
}
}
- /** Removes task from the list of closing tasks for [displayId]. */
+ /** Removes task from the list of closing tasks for all desks. */
fun removeClosingTask(taskId: Int) {
- desktopTaskDataByDisplayId.forEach { displayId, taskInfo ->
- if (taskInfo.closingTasks.remove(taskId)) {
- logD("Removed closing task=%d displayId=%d", taskId, displayId)
+ desktopData.forAllDesks { desk ->
+ if (desk.closingTasks.remove(taskId)) {
+ logD("Removed closing task=%d deskId=%d", taskId, desk.deskId)
}
}
}
- fun isActiveTask(taskId: Int) = desktopTaskDataSequence().any { taskId in it.activeTasks }
+ fun isActiveTask(taskId: Int) = desksSequence().any { taskId in it.activeTasks }
- fun isClosingTask(taskId: Int) = desktopTaskDataSequence().any { taskId in it.closingTasks }
+ fun isClosingTask(taskId: Int) = desksSequence().any { taskId in it.closingTasks }
- fun isVisibleTask(taskId: Int) = desktopTaskDataSequence().any { taskId in it.visibleTasks }
+ fun isVisibleTask(taskId: Int) = desksSequence().any { taskId in it.visibleTasks }
- fun isMinimizedTask(taskId: Int) = desktopTaskDataSequence().any { taskId in it.minimizedTasks }
+ fun isMinimizedTask(taskId: Int) = desksSequence().any { taskId in it.minimizedTasks }
- /** Checks if a task is the only visible, non-closing, non-minimized task on its display. */
+ /**
+ * Checks if a task is the only visible, non-closing, non-minimized task on the active desk of
+ * the given display, or any display's active desk if [displayId] is [INVALID_DISPLAY].
+ *
+ * TODO: b/389960283 - add explicit [deskId] argument.
+ */
fun isOnlyVisibleNonClosingTask(taskId: Int, displayId: Int = INVALID_DISPLAY): Boolean {
- val seq =
+ val activeDesks =
if (displayId != INVALID_DISPLAY) {
- sequenceOf(desktopTaskDataByDisplayId[displayId]).filterNotNull()
+ setOfNotNull(desktopData.getActiveDesk(displayId))
} else {
- desktopTaskDataSequence()
+ desktopData.getAllActiveDesks()
}
- return seq.any {
- it.visibleTasks.subtract(it.closingTasks).subtract(it.minimizedTasks).singleOrNull() ==
- taskId
+ return activeDesks.any { desk ->
+ desk.visibleTasks
+ .subtract(desk.closingTasks)
+ .subtract(desk.minimizedTasks)
+ .singleOrNull() == taskId
}
}
+ /**
+ * Returns the active tasks in the given display's active desk.
+ *
+ * TODO: b/389960283 - add explicit [deskId] argument.
+ */
+ @VisibleForTesting
fun getActiveTasks(displayId: Int): ArraySet<Int> =
- ArraySet(desktopTaskDataByDisplayId[displayId]?.activeTasks)
+ ArraySet(desktopData.getActiveDesk(displayId)?.activeTasks)
+ /**
+ * Returns the minimized tasks in the given display's active desk.
+ *
+ * TODO: b/389960283 - add explicit [deskId] argument.
+ */
fun getMinimizedTasks(displayId: Int): ArraySet<Int> =
- ArraySet(desktopTaskDataByDisplayId[displayId]?.minimizedTasks)
+ ArraySet(desktopData.getActiveDesk(displayId)?.minimizedTasks)
- /** Returns all active non-minimized tasks for [displayId] ordered from top to bottom. */
+ /**
+ * Returns all active non-minimized tasks for [displayId] ordered from top to bottom.
+ *
+ * TODO: b/389960283 - add explicit [deskId] argument.
+ */
fun getExpandedTasksOrdered(displayId: Int): List<Int> =
getFreeformTasksInZOrder(displayId).filter { !isMinimizedTask(it) }
- /** Returns the count of active non-minimized tasks for [displayId]. */
+ /**
+ * Returns the count of active non-minimized tasks for [displayId].
+ *
+ * TODO: b/389960283 - add explicit [deskId] argument.
+ */
fun getExpandedTaskCount(displayId: Int): Int {
return getActiveTasks(displayId).count { !isMinimizedTask(it) }
}
- /** Returns a list of freeform tasks, ordered from top-bottom (top at index 0). */
+ /**
+ * Returns a list of freeform tasks, ordered from top-bottom (top at index 0).
+ *
+ * TODO: b/389960283 - add explicit [deskId] argument.
+ */
+ @VisibleForTesting
fun getFreeformTasksInZOrder(displayId: Int): ArrayList<Int> =
- ArrayList(desktopTaskDataByDisplayId[displayId]?.freeformTasksInZOrder ?: emptyList())
+ ArrayList(desktopData.getActiveDesk(displayId)?.freeformTasksInZOrder ?: emptyList())
+
+ /** Returns the tasks inside the given desk. */
+ fun getActiveTaskIdsInDesk(deskId: Int): Set<Int> =
+ desktopData.getDesk(deskId)?.activeTasks?.toSet()
+ ?: run {
+ logW("getTasksInDesk: could not find desk: deskId=%d", deskId)
+ emptySet()
+ }
/** Removes task from visible tasks of all displays except [excludedDisplayId]. */
private fun removeVisibleTask(taskId: Int, excludedDisplayId: Int? = null) {
- desktopTaskDataByDisplayId.forEach { displayId, data ->
- if ((displayId != excludedDisplayId) && data.visibleTasks.remove(taskId)) {
- notifyVisibleTaskListeners(displayId, data.visibleTasks.size)
+ desktopData.forAllDesks { displayId, desk ->
+ if (displayId != excludedDisplayId && desk.visibleTasks.remove(taskId)) {
+ notifyVisibleTaskListeners(displayId, desk.visibleTasks.size)
}
}
}
@@ -281,6 +385,8 @@
*
* If task was visible on a different display with a different [displayId], removes from the set
* of visible tasks on that display and notifies listeners.
+ *
+ * TODO: b/389960283 - add explicit [deskId] argument.
*/
fun updateTask(displayId: Int, taskId: Int, isVisible: Boolean) {
logD("updateTask taskId=%d, displayId=%d, isVisible=%b", taskId, displayId, isVisible)
@@ -295,10 +401,11 @@
}
val prevCount = getVisibleTaskCount(displayId)
if (isVisible) {
- desktopTaskDataByDisplayId.getOrCreate(displayId).visibleTasks.add(taskId)
+ desktopData.getActiveDesk(displayId)?.visibleTasks?.add(taskId)
+ ?: error("Expected non-null active desk in display $displayId")
unminimizeTask(displayId, taskId)
} else {
- desktopTaskDataByDisplayId[displayId]?.visibleTasks?.remove(taskId)
+ desktopData.getActiveDesk(displayId)?.visibleTasks?.remove(taskId)
}
val newCount = getVisibleTaskCount(displayId)
if (prevCount != newCount) {
@@ -316,57 +423,94 @@
}
}
- /** Set whether the given task is the Desktop-entered PiP task in this display. */
+ /**
+ * Set whether the given task is the Desktop-entered PiP task in this display's active desk.
+ *
+ * TODO: b/389960283 - add explicit [deskId] argument.
+ */
fun setTaskInPip(displayId: Int, taskId: Int, enterPip: Boolean) {
- val desktopData = desktopTaskDataByDisplayId.getOrCreate(displayId)
+ val activeDesk =
+ desktopData.getActiveDesk(displayId)
+ ?: error("Expected active desk in display: $displayId")
if (enterPip) {
- desktopData.pipTaskId = taskId
- desktopData.pipShouldKeepDesktopActive = true
+ activeDesk.pipTaskId = taskId
+ activeDesk.pipShouldKeepDesktopActive = true
} else {
- desktopData.pipTaskId =
- if (desktopData.pipTaskId == taskId) null
+ activeDesk.pipTaskId =
+ if (activeDesk.pipTaskId == taskId) null
else {
logW(
- "setTaskInPip: taskId=$taskId did not match saved taskId=${desktopData.pipTaskId}"
+ "setTaskInPip: taskId=%d did not match saved taskId=%d",
+ taskId,
+ activeDesk.pipTaskId,
)
- desktopData.pipTaskId
+ activeDesk.pipTaskId
}
}
notifyVisibleTaskListeners(displayId, getVisibleTaskCount(displayId))
}
- /** Returns whether there is a PiP that was entered/minimized from Desktop in this display. */
+ /**
+ * Returns whether there is a PiP that was entered/minimized from Desktop in this display's
+ * active desk.
+ *
+ * TODO: b/389960283 - add explicit [deskId] argument.
+ */
fun isMinimizedPipPresentInDisplay(displayId: Int): Boolean =
- desktopTaskDataByDisplayId.getOrCreate(displayId).pipTaskId != null
+ desktopData.getActiveDesk(displayId)?.pipTaskId != null
- /** Returns whether the given task is the Desktop-entered PiP task in this display. */
+ /**
+ * Returns whether the given task is the Desktop-entered PiP task in this display's active desk.
+ *
+ * TODO: b/389960283 - add explicit [deskId] argument.
+ */
fun isTaskMinimizedPipInDisplay(displayId: Int, taskId: Int): Boolean =
- desktopTaskDataByDisplayId.getOrCreate(displayId).pipTaskId == taskId
+ desktopData.getActiveDesk(displayId)?.pipTaskId == taskId
- /** Returns whether Desktop session should be active in this display due to active PiP. */
+ /**
+ * Returns whether a desk should be active in this display due to active PiP.
+ *
+ * TODO: b/389960283 - add explicit [deskId] argument.
+ */
fun shouldDesktopBeActiveForPip(displayId: Int): Boolean =
Flags.enableDesktopWindowingPip() &&
isMinimizedPipPresentInDisplay(displayId) &&
- desktopTaskDataByDisplayId.getOrCreate(displayId).pipShouldKeepDesktopActive
+ (desktopData.getActiveDesk(displayId)?.pipShouldKeepDesktopActive ?: false)
- /** Saves whether a PiP window should keep Desktop session active in this display. */
+ /**
+ * Saves whether a PiP window should keep Desktop session active in this display.
+ *
+ * TODO: b/389960283 - add explicit [deskId] argument.
+ */
fun setPipShouldKeepDesktopActive(displayId: Int, keepActive: Boolean) {
- desktopTaskDataByDisplayId.getOrCreate(displayId).pipShouldKeepDesktopActive = keepActive
+ desktopData.getActiveDesk(displayId)?.pipShouldKeepDesktopActive = keepActive
}
- /** Saves callback to handle a pending PiP transition being aborted. */
- fun setOnPipAbortedCallback(callbackIfPipAborted: ((Int, Int) -> Unit)?) {
+ /**
+ * Saves callback to handle a pending PiP transition being aborted.
+ *
+ * TODO: b/389960283 - add explicit [deskId] argument.
+ */
+ fun setOnPipAbortedCallback(callbackIfPipAborted: ((displayId: Int, pipTaskId: Int) -> Unit)?) {
onPipAbortedCallback = callbackIfPipAborted
}
- /** Invokes callback to handle a pending PiP transition with the given task id being aborted. */
+ /**
+ * Invokes callback to handle a pending PiP transition with the given task id being aborted.
+ *
+ * TODO: b/389960283 - add explicit [deskId] argument.
+ */
fun onPipAborted(displayId: Int, pipTaskId: Int) {
onPipAbortedCallback?.invoke(displayId, pipTaskId)
}
- /** Set whether the given task is the full-immersive task in this display. */
+ /**
+ * Set whether the given task is the full-immersive task in this display's active desk.
+ *
+ * TODO: b/389960283 - add explicit [deskId] argument.
+ */
fun setTaskInFullImmersiveState(displayId: Int, taskId: Int, immersive: Boolean) {
- val desktopData = desktopTaskDataByDisplayId.getOrCreate(displayId)
+ val desktopData = desktopData.getActiveDesk(displayId) ?: return
if (immersive) {
desktopData.fullImmersiveTaskId = taskId
} else {
@@ -378,25 +522,41 @@
/* Whether the task is in full-immersive state. */
fun isTaskInFullImmersiveState(taskId: Int): Boolean {
- return desktopTaskDataSequence().any { taskId == it.fullImmersiveTaskId }
+ return desksSequence().any { taskId == it.fullImmersiveTaskId }
}
- /** Returns the task that is currently in immersive mode in this display, or null. */
+ /**
+ * Returns the task that is currently in immersive mode in this display, or null.
+ *
+ * TODO: b/389960283 - add explicit [deskId] argument.
+ */
fun getTaskInFullImmersiveState(displayId: Int): Int? =
- desktopTaskDataByDisplayId.getOrCreate(displayId).fullImmersiveTaskId
+ desktopData.getActiveDesk(displayId)?.fullImmersiveTaskId
- /** Sets the top transparent fullscreen task id for a given display. */
+ /**
+ * Sets the top transparent fullscreen task id for a given display's active desk.
+ *
+ * TODO: b/389960283 - add explicit [deskId] argument.
+ */
fun setTopTransparentFullscreenTaskId(displayId: Int, taskId: Int) {
- desktopTaskDataByDisplayId.getOrCreate(displayId).topTransparentFullscreenTaskId = taskId
+ desktopData.getActiveDesk(displayId)?.topTransparentFullscreenTaskId = taskId
}
- /** Returns the top transparent fullscreen task id for a given display, or null. */
+ /**
+ * Returns the top transparent fullscreen task id for a given display's active desk, or null.
+ *
+ * TODO: b/389960283 - add explicit [deskId] argument.
+ */
fun getTopTransparentFullscreenTaskId(displayId: Int): Int? =
- desktopTaskDataByDisplayId.getOrCreate(displayId).topTransparentFullscreenTaskId
+ desktopData.getActiveDesk(displayId)?.topTransparentFullscreenTaskId
- /** Clears the top transparent fullscreen task id info for a given display. */
+ /**
+ * Clears the top transparent fullscreen task id info for a given display's active desk.
+ *
+ * TODO: b/389960283 - add explicit [deskId] argument.
+ */
fun clearTopTransparentFullscreenTaskId(displayId: Int) {
- desktopTaskDataByDisplayId.getOrCreate(displayId).topTransparentFullscreenTaskId = null
+ desktopData.getActiveDesk(displayId)?.topTransparentFullscreenTaskId = null
}
private fun notifyVisibleTaskListeners(displayId: Int, visibleTasksCount: Int) {
@@ -409,22 +569,35 @@
}
}
- /** Gets number of visible freeform tasks on given [displayId] */
+ /**
+ * Gets number of visible freeform tasks on given [displayId]'s active desk.
+ *
+ * TODO: b/389960283 - add explicit [deskId] argument.
+ */
fun getVisibleTaskCount(displayId: Int): Int =
- desktopTaskDataByDisplayId[displayId]?.visibleTasks?.size
- ?: 0.also { logD("getVisibleTaskCount=$it") }
+ (desktopData.getActiveDesk(displayId)?.visibleTasks?.size ?: 0).also {
+ logD("getVisibleTaskCount=$it")
+ }
/**
* Adds task (or moves if it already exists) to the top of the ordered list.
*
* Unminimizes the task if it is minimized.
+ *
+ * TODO: b/389960283 - add explicit [deskId] argument.
*/
private fun addOrMoveFreeformTaskToTop(displayId: Int, taskId: Int) {
- logD("Add or move task to top: display=%d taskId=%d", taskId, displayId)
- desktopTaskDataByDisplayId.forEach { _, value ->
- value.freeformTasksInZOrder.remove(taskId)
- }
- desktopTaskDataByDisplayId.getOrCreate(displayId).freeformTasksInZOrder.add(0, taskId)
+ val activeDesk =
+ desktopData.getActiveDesk(displayId)
+ ?: error("Expected a desk to be active in display: $displayId")
+ logD(
+ "Add or move task to top: display=%d taskId=%d deskId=%d",
+ taskId,
+ displayId,
+ activeDesk.deskId,
+ )
+ desktopData.forAllDesks { _, desk -> desk.freeformTasksInZOrder.remove(taskId) }
+ activeDesk.freeformTasksInZOrder.add(0, taskId)
// Unminimize the task if it is minimized.
unminimizeTask(displayId, taskId)
if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PERSISTENCE.isTrue()) {
@@ -432,7 +605,11 @@
}
}
- /** Minimizes the task for [taskId] and [displayId] */
+ /**
+ * Minimizes the task for [taskId] and [displayId]'s active display.
+ *
+ * TODO: b/389960283 - add explicit [deskId] argument.
+ */
fun minimizeTask(displayId: Int, taskId: Int) {
if (displayId == INVALID_DISPLAY) {
// When a task vanishes it doesn't have a displayId. Find the display of the task and
@@ -441,7 +618,8 @@
?: logW("Minimize task: No display id found for task: taskId=%d", taskId)
} else {
logD("Minimize Task: display=%d, task=%d", displayId, taskId)
- desktopTaskDataByDisplayId.getOrCreate(displayId).minimizedTasks.add(taskId)
+ desktopData.getActiveDesk(displayId)?.minimizedTasks?.add(taskId)
+ ?: logD("Minimize task: No active desk found for task: taskId=%d", taskId)
}
updateTask(displayId, taskId, isVisible = false)
if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PERSISTENCE.isTrue()) {
@@ -449,26 +627,42 @@
}
}
- /** Unminimizes the task for [taskId] and [displayId] */
+ /**
+ * Unminimizes the task for [taskId] and [displayId].
+ *
+ * TODO: b/389960283 - consider adding an explicit [deskId] argument.
+ */
fun unminimizeTask(displayId: Int, taskId: Int) {
logD("Unminimize Task: display=%d, task=%d", displayId, taskId)
- desktopTaskDataByDisplayId[displayId]?.minimizedTasks?.remove(taskId)
- ?: logW("Unminimize Task: display=%d, task=%d, no task data", displayId, taskId)
+ var removed = false
+ desktopData.forAllDesks(displayId) { desk ->
+ if (desk.minimizedTasks.remove(taskId)) {
+ removed = true
+ }
+ }
+ if (!removed) {
+ logW("Unminimize Task: display=%d, task=%d, no task data", displayId, taskId)
+ }
}
private fun getDisplayIdForTask(taskId: Int): Int? {
- desktopTaskDataByDisplayId.forEach { displayId, data ->
- if (taskId in data.freeformTasksInZOrder) {
- return displayId
+ var displayForTask: Int? = null
+ desktopData.forAllDesks { displayId, desk ->
+ if (taskId in desk.freeformTasksInZOrder) {
+ displayForTask = displayId
}
}
- logW("No display id found for task: taskId=%d", taskId)
- return null
+ if (displayForTask == null) {
+ logW("No display id found for task: taskId=%d", taskId)
+ }
+ return displayForTask
}
/**
* Removes [taskId] from the respective display. If [INVALID_DISPLAY], the original display id
* will be looked up from the task id.
+ *
+ * TODO: b/389960283 - consider adding an explicit [deskId] argument.
*/
fun removeTask(displayId: Int, taskId: Int) {
logD("Removes freeform task: taskId=%d", taskId)
@@ -483,13 +677,17 @@
/** Removes given task from a valid [displayId] and updates the repository state. */
private fun removeTaskFromDisplay(displayId: Int, taskId: Int) {
logD("Removes freeform task: taskId=%d, displayId=%d", taskId, displayId)
- desktopTaskDataByDisplayId[displayId]?.freeformTasksInZOrder?.remove(taskId)
+ desktopData.forAllDesks(displayId) { desk ->
+ if (desk.freeformTasksInZOrder.remove(taskId)) {
+ logD(
+ "Remaining freeform tasks in desk: %d, tasks: %s",
+ desk.deskId,
+ desk.freeformTasksInZOrder.toDumpString(),
+ )
+ }
+ }
boundsBeforeMaximizeByTaskId.remove(taskId)
boundsBeforeFullImmersiveByTaskId.remove(taskId)
- logD(
- "Remaining freeform tasks: %s",
- desktopTaskDataByDisplayId[displayId]?.freeformTasksInZOrder?.toDumpString(),
- )
// Remove task from unminimized task if it is minimized.
unminimizeTask(displayId, taskId)
// Mark task as not in immersive if it was immersive.
@@ -502,15 +700,18 @@
}
/**
- * Removes the desktop for the given [displayId] and returns the active tasks on that desktop.
+ * Removes the active desk for the given [displayId] and returns the active tasks on that desk.
+ *
+ * TODO: b/389960283 - add explicit [deskId] argument.
*/
- fun removeDesktop(displayId: Int): ArraySet<Int> {
- if (!desktopTaskDataByDisplayId.contains(displayId)) {
- logW("Could not find desktop to remove: displayId=%d", displayId)
+ fun removeDesk(displayId: Int): ArraySet<Int> {
+ val desk = desktopData.getActiveDesk(displayId)
+ if (desk == null) {
+ logW("Could not find desk to remove: displayId=%d", displayId)
return ArraySet()
}
- val activeTasks = ArraySet(desktopTaskDataByDisplayId[displayId].activeTasks)
- desktopTaskDataByDisplayId[displayId].clear()
+ val activeTasks = ArraySet(desk.activeTasks)
+ desktopData.remove(desk.deskId)
return activeTasks
}
@@ -564,19 +765,20 @@
fun saveBoundsBeforeFullImmersive(taskId: Int, bounds: Rect) =
boundsBeforeFullImmersiveByTaskId.set(taskId, Rect(bounds))
+ /** TODO: b/389960283 - consider updating only the changing desks. */
private fun updatePersistentRepository(displayId: Int) {
- // Create a deep copy of the data
- desktopTaskDataByDisplayId[displayId]?.deepCopy()?.let { desktopTaskDataByDisplayIdCopy ->
- mainCoroutineScope.launch {
+ val desks = desktopData.desksSequence(displayId).map { desk -> desk.deepCopy() }.toList()
+ mainCoroutineScope.launch {
+ desks.forEach { desk ->
try {
persistentRepository.addOrUpdateDesktop(
- // Use display id as desktop id for now since only once desktop per display
+ // Use display id as desk id for now since only once desk per display
// is supported.
userId = userId,
- desktopId = displayId,
- visibleTasks = desktopTaskDataByDisplayIdCopy.visibleTasks,
- minimizedTasks = desktopTaskDataByDisplayIdCopy.minimizedTasks,
- freeformTasksInZOrder = desktopTaskDataByDisplayIdCopy.freeformTasksInZOrder,
+ desktopId = desk.deskId,
+ visibleTasks = desk.visibleTasks,
+ minimizedTasks = desk.minimizedTasks,
+ freeformTasksInZOrder = desk.freeformTasksInZOrder,
)
} catch (exception: Exception) {
logE(
@@ -598,20 +800,27 @@
private fun dumpDesktopTaskData(pw: PrintWriter, prefix: String) {
val innerPrefix = "$prefix "
- desktopTaskDataByDisplayId.forEach { displayId, data ->
- pw.println("${prefix}Display $displayId:")
- pw.println("${innerPrefix}activeTasks=${data.activeTasks.toDumpString()}")
- pw.println("${innerPrefix}visibleTasks=${data.visibleTasks.toDumpString()}")
- pw.println(
- "${innerPrefix}freeformTasksInZOrder=${data.freeformTasksInZOrder.toDumpString()}"
- )
- pw.println("${innerPrefix}minimizedTasks=${data.minimizedTasks.toDumpString()}")
- pw.println("${innerPrefix}fullImmersiveTaskId=${data.fullImmersiveTaskId}")
- pw.println(
- "${innerPrefix}topTransparentFullscreenTaskId=" +
- "${data.topTransparentFullscreenTaskId}"
- )
- }
+ desktopData
+ .desksSequence()
+ .groupBy { it.displayId }
+ .forEach { (displayId, desks) ->
+ pw.println("${prefix}Display #$displayId:")
+ desks.forEach { desk ->
+ pw.println("${innerPrefix}Desk #${desk.deskId}:")
+ pw.print("$innerPrefix activeTasks=")
+ pw.println(desk.activeTasks.toDumpString())
+ pw.print("$innerPrefix visibleTasks=")
+ pw.println(desk.visibleTasks.toDumpString())
+ pw.print("$innerPrefix freeformTasksInZOrder=")
+ pw.println(desk.freeformTasksInZOrder.toDumpString())
+ pw.print("$innerPrefix minimizedTasks=")
+ pw.println(desk.minimizedTasks.toDumpString())
+ pw.print("$innerPrefix fullImmersiveTaskId=")
+ pw.println(desk.fullImmersiveTaskId)
+ pw.print("$innerPrefix topTransparentFullscreenTaskId=")
+ pw.println(desk.topTransparentFullscreenTaskId)
+ }
+ }
}
/** Listens to changes for active tasks in desktop mode. */
@@ -624,6 +833,227 @@
fun onTasksVisibilityChanged(displayId: Int, visibleTasksCount: Int) {}
}
+ /** An interface for the desktop hierarchy's data managed by this repository. */
+ private interface DesktopData {
+ /**
+ * Returns the existing desk or creates a new entry if needed.
+ *
+ * TODO: 389787966 - consider removing this as it cannot be assumed a desk can be created in
+ * all devices / form-factors.
+ */
+ fun getOrCreateDesk(displayId: Int, deskId: Int): Desk
+
+ /** Returns the desk with the given id, or null if it does not exist. */
+ fun getDesk(deskId: Int): Desk?
+
+ /** Returns the active desk in this diplay, or null if none are active. */
+ fun getActiveDesk(displayId: Int): Desk?
+
+ /** Sets the given desk as the active desk in the given display. */
+ fun setActiveDesk(displayId: Int, deskId: Int)
+
+ /**
+ * Returns the default desk in the given display. Useful when the system wants to activate a
+ * desk but doesn't care about which one it activates (e.g. when putting a window into a
+ * desk using the App Handle). May return null if the display does not support desks.
+ *
+ * TODO: 389787966 - consider removing or renaming. In practice, this is needed for
+ * soon-to-be deprecated IDesktopMode APIs, adb commands or entry-points into the only
+ * desk (single-desk devices) or the most-recent desk (multi-desk devices).
+ */
+ fun getDefaultDesk(displayId: Int): Desk?
+
+ /** Returns all the active desks of all displays. */
+ fun getAllActiveDesks(): Set<Desk>
+
+ /** Returns the number of desks in the given display. */
+ fun getNumberOfDesks(displayId: Int): Int
+
+ /** Applies a function to all desks. */
+ fun forAllDesks(consumer: (Desk) -> Unit)
+
+ /** Applies a function to all desks. */
+ fun forAllDesks(consumer: (displayId: Int, Desk) -> Unit)
+
+ /** Applies a function to all desks under the given display. */
+ fun forAllDesks(displayId: Int, consumer: (Desk) -> Unit)
+
+ /** Returns a sequence of all desks. */
+ fun desksSequence(): Sequence<Desk>
+
+ /** Returns a sequence of all desks under the given display. */
+ fun desksSequence(displayId: Int): Sequence<Desk>
+
+ /** Remove an existing desk if it exists. */
+ fun remove(deskId: Int)
+
+ /** Returns the id of the display where the given desk is located. */
+ fun getDisplayForDesk(deskId: Int): Int
+ }
+
+ /**
+ * A [DesktopData] implementation that only supports one desk per display.
+ *
+ * Internally, it reuses the displayId as that display's single desk's id.
+ */
+ private class SingleDesktopData : DesktopData {
+ private val deskByDisplayId =
+ object : SparseArray<Desk>() {
+ /** Gets [Desk] for existing [displayId] or creates a new one. */
+ fun getOrCreate(displayId: Int): Desk =
+ this[displayId]
+ ?: Desk(deskId = displayId, displayId = displayId).also {
+ this[displayId] = it
+ }
+ }
+
+ override fun getOrCreateDesk(displayId: Int, deskId: Int): Desk {
+ check(displayId == deskId)
+ return deskByDisplayId.getOrCreate(displayId)
+ }
+
+ override fun getDesk(deskId: Int): Desk = getOrCreateDesk(deskId, deskId)
+
+ override fun getActiveDesk(displayId: Int): Desk {
+ // TODO: 389787966 - consider migrating to an "active" state instead of checking the
+ // number of visible active tasks, PIP in desktop, and empty desktop logic. In
+ // practice, existing single-desktop devices are ok with this function returning the
+ // only desktop, even if it's not active.
+ return deskByDisplayId.getOrCreate(displayId)
+ }
+
+ override fun setActiveDesk(displayId: Int, deskId: Int) {
+ // No-op, in single-desk setups, which desktop is "active" is determined by the
+ // existence of visible desktop windows, among other factors.
+ }
+
+ override fun getDefaultDesk(displayId: Int): Desk = getOrCreateDesk(displayId, displayId)
+
+ override fun getAllActiveDesks(): Set<Desk> =
+ deskByDisplayId.valueIterator().asSequence().toSet()
+
+ override fun getNumberOfDesks(displayId: Int): Int = 1
+
+ override fun forAllDesks(consumer: (Desk) -> Unit) {
+ deskByDisplayId.forEach { _, desk -> consumer(desk) }
+ }
+
+ override fun forAllDesks(consumer: (Int, Desk) -> Unit) {
+ deskByDisplayId.forEach { displayId, desk -> consumer(displayId, desk) }
+ }
+
+ override fun forAllDesks(displayId: Int, consumer: (Desk) -> Unit) {
+ consumer(getOrCreateDesk(displayId, displayId))
+ }
+
+ override fun desksSequence(): Sequence<Desk> = deskByDisplayId.valueIterator().asSequence()
+
+ override fun desksSequence(displayId: Int): Sequence<Desk> =
+ deskByDisplayId[displayId]?.let { sequenceOf(it) } ?: emptySequence()
+
+ override fun remove(deskId: Int) {
+ deskByDisplayId[deskId]?.clear()
+ }
+
+ override fun getDisplayForDesk(deskId: Int): Int = deskId
+ }
+
+ /** A [DesktopData] implementation that supports multiple desks. */
+ private class MultiDesktopData : DesktopData {
+ private val desktopDisplays = SparseArray<DesktopDisplay>()
+
+ override fun getOrCreateDesk(displayId: Int, deskId: Int): Desk {
+ val display =
+ desktopDisplays[displayId]
+ ?: DesktopDisplay(displayId).also { desktopDisplays[displayId] = it }
+ val desk =
+ display.orderedDesks.find { desk -> desk.deskId == deskId }
+ ?: Desk(deskId = deskId, displayId = displayId).also {
+ display.orderedDesks.add(it)
+ }
+ return desk
+ }
+
+ override fun getDesk(deskId: Int): Desk? {
+ desktopDisplays.forEach { _, display ->
+ val desk = display.orderedDesks.find { desk -> desk.deskId == deskId }
+ if (desk != null) {
+ return desk
+ }
+ }
+ return null
+ }
+
+ override fun getActiveDesk(displayId: Int): Desk? {
+ val display = desktopDisplays[displayId] ?: return null
+ if (display.activeDeskId == null) return null
+ return display.orderedDesks.find { it.deskId == display.activeDeskId }
+ }
+
+ override fun setActiveDesk(displayId: Int, deskId: Int) {
+ val display =
+ desktopDisplays[displayId] ?: error("Expected display#$displayId to exist")
+ val desk = display.orderedDesks.single { it.deskId == deskId }
+ display.activeDeskId = desk.deskId
+ }
+
+ override fun getDefaultDesk(displayId: Int): Desk? {
+ val display = desktopDisplays[displayId] ?: return null
+ return display.orderedDesks.firstOrNull()
+ }
+
+ override fun getAllActiveDesks(): Set<Desk> {
+ return desktopDisplays
+ .valueIterator()
+ .asSequence()
+ .filter { display -> display.activeDeskId != null }
+ .map { display ->
+ display.orderedDesks.single { it.deskId == display.activeDeskId }
+ }
+ .toSet()
+ }
+
+ override fun getNumberOfDesks(displayId: Int): Int =
+ desktopDisplays[displayId]?.orderedDesks?.size ?: 0
+
+ override fun forAllDesks(consumer: (Desk) -> Unit) {
+ desktopDisplays.forEach { _, display -> display.orderedDesks.forEach { consumer(it) } }
+ }
+
+ override fun forAllDesks(consumer: (Int, Desk) -> Unit) {
+ desktopDisplays.forEach { _, display ->
+ display.orderedDesks.forEach { consumer(display.displayId, it) }
+ }
+ }
+
+ override fun forAllDesks(displayId: Int, consumer: (Desk) -> Unit) {
+ desktopDisplays
+ .valueIterator()
+ .asSequence()
+ .filter { display -> display.displayId == displayId }
+ .flatMap { display -> display.orderedDesks.asSequence() }
+ .forEach { desk -> consumer(desk) }
+ }
+
+ override fun desksSequence(): Sequence<Desk> =
+ desktopDisplays.valueIterator().asSequence().flatMap { display ->
+ display.orderedDesks.asSequence()
+ }
+
+ override fun desksSequence(displayId: Int): Sequence<Desk> =
+ desktopDisplays[displayId]?.orderedDesks?.asSequence() ?: emptySequence()
+
+ override fun remove(deskId: Int) {
+ desktopDisplays.forEach { _, display ->
+ display.orderedDesks.removeIf { it.deskId == deskId }
+ }
+ }
+
+ override fun getDisplayForDesk(deskId: Int): Int =
+ getAllActiveDesks().find { it.deskId == deskId }?.displayId
+ ?: error("Display for desk=$deskId not found")
+ }
+
private fun logD(msg: String, vararg arguments: Any?) {
ProtoLog.d(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments)
}
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 f38957e..172410d 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.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
@@ -86,6 +87,7 @@
import com.android.wm.shell.compatui.isTopActivityExemptFromDesktopWindowing
import com.android.wm.shell.compatui.isTransparentTask
import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.InputMethod
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.MinimizeReason
import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger
import com.android.wm.shell.desktopmode.DesktopModeUiEventLogger.DesktopUiEventEnum
import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.DragStartState
@@ -465,7 +467,9 @@
desktopModeEnterExitTransitionListener?.onEnterDesktopModeTransitionStarted(
FREEFORM_ANIMATION_DURATION
)
- taskIdToMinimize?.let { addPendingMinimizeTransition(transition, it) }
+ taskIdToMinimize?.let {
+ addPendingMinimizeTransition(transition, it, MinimizeReason.TASK_LIMIT)
+ }
exitResult.asExit()?.runOnTransitionStart?.invoke(transition)
return true
}
@@ -511,7 +515,9 @@
desktopModeEnterExitTransitionListener?.onEnterDesktopModeTransitionStarted(
FREEFORM_ANIMATION_DURATION
)
- taskIdToMinimize?.let { addPendingMinimizeTransition(transition, it) }
+ taskIdToMinimize?.let {
+ addPendingMinimizeTransition(transition, it, MinimizeReason.TASK_LIMIT)
+ }
exitResult.asExit()?.runOnTransitionStart?.invoke(transition)
}
@@ -572,7 +578,9 @@
DRAG_TO_DESKTOP_FINISH_ANIM_DURATION_MS.toInt()
)
transition?.let {
- taskIdToMinimize?.let { taskId -> addPendingMinimizeTransition(it, taskId) }
+ taskIdToMinimize?.let { taskId ->
+ addPendingMinimizeTransition(it, taskId, MinimizeReason.TASK_LIMIT)
+ }
exitResult.asExit()?.runOnTransitionStart?.invoke(transition)
}
}
@@ -621,7 +629,7 @@
?.runOnTransitionStart
}
- fun minimizeTask(taskInfo: RunningTaskInfo) {
+ fun minimizeTask(taskInfo: RunningTaskInfo, minimizeReason: MinimizeReason) {
val wct = WindowContainerTransaction()
val isMinimizingToPip = taskInfo.pictureInPictureParams?.isAutoEnterEnabled() ?: false
@@ -641,16 +649,16 @@
freeformTaskTransitionStarter.startPipTransition(wct)
taskRepository.setTaskInPip(taskInfo.displayId, taskInfo.taskId, enterPip = true)
taskRepository.setOnPipAbortedCallback { displayId, taskId ->
- minimizeTaskInner(shellTaskOrganizer.getRunningTaskInfo(taskId)!!)
+ minimizeTaskInner(shellTaskOrganizer.getRunningTaskInfo(taskId)!!, minimizeReason)
taskRepository.setTaskInPip(displayId, taskId, enterPip = false)
}
return
}
- minimizeTaskInner(taskInfo)
+ minimizeTaskInner(taskInfo, minimizeReason)
}
- private fun minimizeTaskInner(taskInfo: RunningTaskInfo) {
+ private fun minimizeTaskInner(taskInfo: RunningTaskInfo, minimizeReason: MinimizeReason) {
val taskId = taskInfo.taskId
val displayId = taskInfo.displayId
val wct = WindowContainerTransaction()
@@ -670,6 +678,7 @@
transition = transition,
displayId = displayId,
taskId = taskId,
+ minimizeReason = minimizeReason,
)
}
exitResult.asExit()?.runOnTransitionStart?.invoke(transition)
@@ -825,7 +834,7 @@
minimizingTaskId = taskIdToMinimize,
exitingImmersiveTask = exitImmersiveResult.asExit()?.exitingTask,
)
- taskIdToMinimize?.let { addPendingMinimizeTransition(t, it) }
+ taskIdToMinimize?.let { addPendingMinimizeTransition(t, it, MinimizeReason.TASK_LIMIT) }
exitImmersiveResult.asExit()?.runOnTransitionStart?.invoke(t)
return t
}
@@ -845,7 +854,7 @@
)
val t = transitions.startTransition(transitionType, wct, remoteTransitionHandler)
remoteTransitionHandler.setTransition(t)
- taskIdToMinimize.let { addPendingMinimizeTransition(t, it) }
+ taskIdToMinimize.let { addPendingMinimizeTransition(t, it, MinimizeReason.TASK_LIMIT) }
exitImmersiveResult.asExit()?.runOnTransitionStart?.invoke(t)
return t
}
@@ -884,6 +893,36 @@
}
/**
+ * 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].
@@ -1867,7 +1906,7 @@
val taskIdToMinimize = addAndGetMinimizeChanges(task.displayId, wct, task.taskId)
addPendingAppLaunchTransition(transition, task.taskId, taskIdToMinimize)
if (taskIdToMinimize != null) {
- addPendingMinimizeTransition(transition, taskIdToMinimize)
+ addPendingMinimizeTransition(transition, taskIdToMinimize, MinimizeReason.TASK_LIMIT)
return wct
}
if (!wct.isEmpty) {
@@ -1901,7 +1940,9 @@
// Desktop Mode is already showing and we're launching a new Task - we might need to
// minimize another Task.
val taskIdToMinimize = addAndGetMinimizeChanges(task.displayId, wct, task.taskId)
- taskIdToMinimize?.let { addPendingMinimizeTransition(transition, it) }
+ taskIdToMinimize?.let {
+ addPendingMinimizeTransition(transition, it, MinimizeReason.TASK_LIMIT)
+ }
addPendingAppLaunchTransition(transition, task.taskId, taskIdToMinimize)
desktopImmersiveController.exitImmersiveIfApplicable(
transition,
@@ -2149,13 +2190,18 @@
.addAndGetMinimizeTaskChanges(displayId, wct, newTaskId, launchingNewIntent)
}
- private fun addPendingMinimizeTransition(transition: IBinder, taskIdToMinimize: Int) {
+ private fun addPendingMinimizeTransition(
+ transition: IBinder,
+ taskIdToMinimize: Int,
+ minimizeReason: MinimizeReason,
+ ) {
val taskToMinimize = shellTaskOrganizer.getRunningTaskInfo(taskIdToMinimize)
desktopTasksLimiter.ifPresent {
it.addPendingMinimizeChange(
transition = transition,
displayId = taskToMinimize?.displayId ?: DEFAULT_DISPLAY,
taskId = taskIdToMinimize,
+ minimizeReason = minimizeReason,
)
}
}
@@ -2185,7 +2231,7 @@
fun removeDesktop(displayId: Int) {
if (!DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue()) return
- val tasksToRemove = taskRepository.removeDesktop(displayId)
+ val tasksToRemove = taskRepository.removeDesk(displayId)
val wct = WindowContainerTransaction()
tasksToRemove.forEach {
val task = shellTaskOrganizer.getRunningTaskInfo(it)
@@ -2425,6 +2471,25 @@
// 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 +2950,12 @@
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/DesktopTasksLimiter.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt
index e4a28e9..204b396 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt
@@ -30,6 +30,7 @@
import com.android.internal.jank.InteractionJankMonitor
import com.android.internal.protolog.ProtoLog
import com.android.wm.shell.ShellTaskOrganizer
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.MinimizeReason
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
import com.android.wm.shell.shared.annotations.ShellMainThread
import com.android.wm.shell.sysui.UserChangeListener
@@ -67,12 +68,21 @@
logV("Starting limiter with a maximum of %d tasks", maxTasksLimit)
}
- private data class TaskDetails(
+ data class TaskDetails(
val displayId: Int,
val taskId: Int,
- var transitionInfo: TransitionInfo?,
+ var transitionInfo: TransitionInfo? = null,
+ val minimizeReason: MinimizeReason? = null,
)
+ /**
+ * Returns the task being minimized in the given transition if that transition is a pending or
+ * active minimize transition.
+ */
+ fun getMinimizingTask(transition: IBinder): TaskDetails? {
+ return minimizeTransitionObserver.getMinimizingTask(transition)
+ }
+
// TODO(b/333018485): replace this observer when implementing the minimize-animation
private inner class MinimizeTransitionObserver : TransitionObserver {
private val pendingTransitionTokensAndTasks = mutableMapOf<IBinder, TaskDetails>()
@@ -82,6 +92,11 @@
pendingTransitionTokensAndTasks[transition] = taskDetails
}
+ fun getMinimizingTask(transition: IBinder): TaskDetails? {
+ return pendingTransitionTokensAndTasks[transition]
+ ?: activeTransitionTokensAndTasks[transition]
+ }
+
override fun onTransitionReady(
transition: IBinder,
info: TransitionInfo,
@@ -89,6 +104,14 @@
finishTransaction: SurfaceControl.Transaction,
) {
val taskRepository = desktopUserRepositories.current
+ handleMinimizeTransition(taskRepository, transition, info)
+ }
+
+ private fun handleMinimizeTransition(
+ taskRepository: DesktopRepository,
+ transition: IBinder,
+ info: TransitionInfo,
+ ) {
val taskToMinimize = pendingTransitionTokensAndTasks.remove(transition) ?: return
if (!taskRepository.isActiveTask(taskToMinimize.taskId)) return
if (!isTaskReadyForMinimize(info, taskToMinimize)) {
@@ -241,10 +264,15 @@
* Add a pending minimize transition change to update the list of minimized apps once the
* transition goes through.
*/
- fun addPendingMinimizeChange(transition: IBinder, displayId: Int, taskId: Int) {
+ fun addPendingMinimizeChange(
+ transition: IBinder,
+ displayId: Int,
+ taskId: Int,
+ minimizeReason: MinimizeReason,
+ ) {
minimizeTransitionObserver.addPendingTransitionToken(
transition,
- TaskDetails(displayId, taskId, transitionInfo = null),
+ TaskDetails(displayId, taskId, transitionInfo = null, minimizeReason = minimizeReason),
)
}
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 fa383cb..54f0312 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 @@
/** 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/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index 9c3e815..912d383 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -1317,14 +1317,14 @@
}
@Override
- public Rect startSwipePipToHome(ComponentName componentName, ActivityInfo activityInfo,
- PictureInPictureParams pictureInPictureParams, int launcherRotation,
- Rect keepClearArea) {
+ public Rect startSwipePipToHome(ActivityManager.RunningTaskInfo taskInfo,
+ int launcherRotation, Rect keepClearArea) {
Rect[] result = new Rect[1];
executeRemoteCallWithTaskPermission(mController, "startSwipePipToHome",
(controller) -> {
- result[0] = controller.startSwipePipToHome(componentName, activityInfo,
- pictureInPictureParams, launcherRotation, keepClearArea);
+ result[0] = controller.startSwipePipToHome(taskInfo.topActivity,
+ taskInfo.topActivityInfo, taskInfo.pictureInPictureParams,
+ launcherRotation, keepClearArea);
}, true /* blocking */);
return result[0];
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipAlphaAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipAlphaAnimator.java
index 63c1512..a033b824 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipAlphaAnimator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipAlphaAnimator.java
@@ -50,6 +50,11 @@
private final SurfaceControl mLeash;
private final SurfaceControl.Transaction mStartTransaction;
+ private final SurfaceControl.Transaction mFinishTransaction;
+
+ private final int mDirection;
+ private final int mCornerRadius;
+ private final int mShadowRadius;
private final Animator.AnimatorListener mAnimatorListener = new AnimatorListenerAdapter() {
@Override
@@ -59,6 +64,7 @@
mAnimationStartCallback.run();
}
if (mStartTransaction != null) {
+ onAlphaAnimationUpdate(getStartAlphaValue(), mStartTransaction);
mStartTransaction.apply();
}
}
@@ -66,6 +72,10 @@
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
+ if (mFinishTransaction != null) {
+ onAlphaAnimationUpdate(getEndAlphaValue(), mFinishTransaction);
+ mFinishTransaction.apply();
+ }
if (mAnimationEndCallback != null) {
mAnimationEndCallback.run();
}
@@ -77,8 +87,9 @@
@Override
public void onAnimationUpdate(@NonNull ValueAnimator animation) {
final float alpha = (Float) animation.getAnimatedValue();
- mSurfaceControlTransactionFactory.getTransaction()
- .setAlpha(mLeash, alpha).apply();
+ final SurfaceControl.Transaction tx =
+ mSurfaceControlTransactionFactory.getTransaction();
+ onAlphaAnimationUpdate(alpha, tx);
}
};
@@ -91,19 +102,21 @@
public PipAlphaAnimator(Context context,
SurfaceControl leash,
- SurfaceControl.Transaction tx,
+ SurfaceControl.Transaction startTransaction,
+ SurfaceControl.Transaction finishTransaction,
@Fade int direction) {
mLeash = leash;
- mStartTransaction = tx;
- if (direction == FADE_IN) {
- setFloatValues(0f, 1f);
- } else { // direction == FADE_OUT
- setFloatValues(1f, 0f);
- }
+ mStartTransaction = startTransaction;
+ mFinishTransaction = finishTransaction;
+
+ mDirection = direction;
+ setFloatValues(getStartAlphaValue(), getEndAlphaValue());
mSurfaceControlTransactionFactory =
new PipSurfaceTransactionHelper.VsyncSurfaceControlTransactionFactory();
final int enterAnimationDuration = context.getResources()
.getInteger(R.integer.config_pipEnterAnimationDuration);
+ mCornerRadius = context.getResources().getDimensionPixelSize(R.dimen.pip_corner_radius);
+ mShadowRadius = context.getResources().getDimensionPixelSize(R.dimen.pip_shadow_radius);
setDuration(enterAnimationDuration);
addListener(mAnimatorListener);
addUpdateListener(mAnimatorUpdateListener);
@@ -117,6 +130,21 @@
mAnimationEndCallback = runnable;
}
+ private void onAlphaAnimationUpdate(float alpha, SurfaceControl.Transaction tx) {
+ tx.setAlpha(mLeash, alpha)
+ .setCornerRadius(mLeash, mCornerRadius)
+ .setShadowRadius(mLeash, mShadowRadius);
+ tx.apply();
+ }
+
+ private float getStartAlphaValue() {
+ return mDirection == FADE_IN ? 0f : 1f;
+ }
+
+ private float getEndAlphaValue() {
+ return mDirection == FADE_IN ? 1f : 0f;
+ }
+
@VisibleForTesting
void setSurfaceControlTransactionFactory(
@NonNull PipSurfaceTransactionHelper.SurfaceControlTransactionFactory factory) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
index 562b260..b1984cc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
@@ -40,6 +40,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.ProtoLog;
import com.android.internal.util.Preconditions;
+import com.android.window.flags.Flags;
import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayChangeController;
@@ -358,10 +359,21 @@
//
private Rect getSwipePipToHomeBounds(ComponentName componentName, ActivityInfo activityInfo,
- PictureInPictureParams pictureInPictureParams,
+ int displayId, PictureInPictureParams pictureInPictureParams,
int launcherRotation, Rect hotseatKeepClearArea) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"getSwipePipToHomeBounds: %s", componentName);
+
+ // If PiP is enabled on Connected Displays, update PipDisplayLayoutState to have the correct
+ // display info that PiP is entering in.
+ if (Flags.enableConnectedDisplaysPip()) {
+ final DisplayLayout displayLayout = mDisplayController.getDisplayLayout(displayId);
+ if (displayLayout != null) {
+ mPipDisplayLayoutState.setDisplayId(displayId);
+ mPipDisplayLayoutState.setDisplayLayout(displayLayout);
+ }
+ }
+
// Preemptively add the keep clear area for Hotseat, so that it is taken into account
// when calculating the entry destination bounds of PiP window.
mPipBoundsState.setNamedUnrestrictedKeepClearArea(
@@ -592,14 +604,14 @@
}
@Override
- public Rect startSwipePipToHome(ComponentName componentName, ActivityInfo activityInfo,
- PictureInPictureParams pictureInPictureParams, int launcherRotation,
- Rect keepClearArea) {
+ public Rect startSwipePipToHome(ActivityManager.RunningTaskInfo taskInfo,
+ int launcherRotation, Rect keepClearArea) {
Rect[] result = new Rect[1];
executeRemoteCallWithTaskPermission(mController, "startSwipePipToHome",
(controller) -> {
- result[0] = controller.getSwipePipToHomeBounds(componentName, activityInfo,
- pictureInPictureParams, launcherRotation, keepClearArea);
+ result[0] = controller.getSwipePipToHomeBounds(taskInfo.topActivity,
+ taskInfo.topActivityInfo, taskInfo.displayId,
+ taskInfo.pictureInPictureParams, launcherRotation, keepClearArea);
}, true /* blocking */);
return result[0];
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
index ed532ca..21b0820 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
@@ -294,7 +294,8 @@
interface PipAlphaAnimatorSupplier {
PipAlphaAnimator get(@NonNull Context context,
SurfaceControl leash,
- SurfaceControl.Transaction tx,
+ SurfaceControl.Transaction startTransaction,
+ SurfaceControl.Transaction finishTransaction,
@PipAlphaAnimator.Fade int direction);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
index 4902455..8cba076 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
@@ -59,6 +59,8 @@
import com.android.window.flags.Flags;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.ComponentUtils;
+import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
import com.android.wm.shell.common.pip.PipBoundsState;
import com.android.wm.shell.common.pip.PipDisplayLayoutState;
@@ -112,6 +114,7 @@
private final PipScheduler mPipScheduler;
private final PipTransitionState mPipTransitionState;
private final PipDisplayLayoutState mPipDisplayLayoutState;
+ private final DisplayController mDisplayController;
private final Optional<DesktopUserRepositories> mDesktopUserRepositoriesOptional;
private final Optional<DesktopWallpaperActivityTokenProvider>
mDesktopWallpaperActivityTokenProviderOptional;
@@ -151,6 +154,7 @@
PipTransitionState pipTransitionState,
PipDisplayLayoutState pipDisplayLayoutState,
PipUiStateChangeController pipUiStateChangeController,
+ DisplayController displayController,
Optional<DesktopUserRepositories> desktopUserRepositoriesOptional,
Optional<DesktopWallpaperActivityTokenProvider>
desktopWallpaperActivityTokenProviderOptional) {
@@ -164,6 +168,7 @@
mPipTransitionState = pipTransitionState;
mPipTransitionState.addPipTransitionStateChangedListener(this);
mPipDisplayLayoutState = pipDisplayLayoutState;
+ mDisplayController = displayController;
mDesktopUserRepositoriesOptional = desktopUserRepositoriesOptional;
mDesktopWallpaperActivityTokenProviderOptional =
desktopWallpaperActivityTokenProviderOptional;
@@ -513,7 +518,7 @@
private void startOverlayFadeoutAnimation(@NonNull SurfaceControl overlayLeash,
@NonNull Runnable onAnimationEnd) {
PipAlphaAnimator animator = new PipAlphaAnimator(mContext, overlayLeash,
- null /* startTx */, PipAlphaAnimator.FADE_OUT);
+ null /* startTx */, null /* finishTx */, PipAlphaAnimator.FADE_OUT);
animator.setDuration(CONTENT_OVERLAY_FADE_OUT_DELAY_MS);
animator.setAnimationEndCallback(onAnimationEnd);
animator.start();
@@ -604,7 +609,7 @@
.setAlpha(pipLeash, 0f);
PipAlphaAnimator animator = new PipAlphaAnimator(mContext, pipLeash, startTransaction,
- PipAlphaAnimator.FADE_IN);
+ finishTransaction, PipAlphaAnimator.FADE_IN);
// This should update the pip transition state accordingly after we stop playing.
animator.setAnimationEndCallback(this::finishTransition);
cacheAndStartTransitionAnimator(animator);
@@ -699,7 +704,7 @@
finishTransaction.setAlpha(pipChange.getLeash(), 0f);
if (mPendingRemoveWithFadeout) {
PipAlphaAnimator animator = new PipAlphaAnimator(mContext, pipChange.getLeash(),
- startTransaction, PipAlphaAnimator.FADE_OUT);
+ startTransaction, finishTransaction, PipAlphaAnimator.FADE_OUT);
animator.setAnimationEndCallback(this::finishTransition);
animator.start();
} else {
@@ -824,6 +829,17 @@
mPipBoundsState.setBoundsStateForEntry(pipTask.topActivity, pipTask.topActivityInfo,
pipParams, mPipBoundsAlgorithm);
+ // If PiP is enabled on Connected Displays, update PipDisplayLayoutState to have the correct
+ // display info that PiP is entering in.
+ if (Flags.enableConnectedDisplaysPip()) {
+ final DisplayLayout displayLayout = mDisplayController.getDisplayLayout(
+ pipTask.displayId);
+ if (displayLayout != null) {
+ mPipDisplayLayoutState.setDisplayId(pipTask.displayId);
+ mPipDisplayLayoutState.setDisplayLayout(displayLayout);
+ }
+ }
+
// calculate the entry bounds and notify core to move task to pinned with final bounds
final Rect entryBounds = mPipBoundsAlgorithm.getEntryDestinationBounds();
mPipBoundsState.setBounds(entryBounds);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentsAnimationRunner.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentsAnimationRunner.aidl
index 32c79a2..8cdb8c4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentsAnimationRunner.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentsAnimationRunner.aidl
@@ -17,9 +17,10 @@
package com.android.wm.shell.recents;
import android.graphics.Rect;
+import android.os.Bundle;
import android.view.RemoteAnimationTarget;
import android.window.TaskSnapshot;
-import android.os.Bundle;
+import android.window.TransitionInfo;
import com.android.wm.shell.recents.IRecentsAnimationController;
@@ -57,7 +58,8 @@
*/
void onAnimationStart(in IRecentsAnimationController controller,
in RemoteAnimationTarget[] apps, in RemoteAnimationTarget[] wallpapers,
- in Rect homeContentInsets, in Rect minimizedHomeBounds, in Bundle extras) = 2;
+ in Rect homeContentInsets, in Rect minimizedHomeBounds, in Bundle extras,
+ in TransitionInfo info) = 2;
/**
* Called when the task of an activity that has been started while the recents animation
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 975b650..2d4d458 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
@@ -43,7 +43,6 @@
import android.os.Bundle;
import android.os.RemoteException;
import android.util.Slog;
-import android.util.SparseArray;
import android.util.SparseIntArray;
import android.window.DesktopModeFlags;
import android.window.WindowContainerToken;
@@ -54,7 +53,6 @@
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;
@@ -84,6 +82,7 @@
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
+import java.util.stream.Collectors;
/**
* Manages the recent task list from the system, caching it as necessary.
@@ -125,6 +124,10 @@
* Cached list of the visible tasks, sorted from top most to bottom most.
*/
private final List<RunningTaskInfo> mVisibleTasks = new ArrayList<>();
+ private final Map<Integer, TaskInfo> mVisibleTasksMap = new HashMap<>();
+
+ // Temporary vars used in `generateList()`
+ private final Map<Integer, TaskInfo> mTmpRemaining = new HashMap<>();
/**
* Creates {@link RecentTasksController}, returns {@code null} if the feature is not
@@ -349,8 +352,11 @@
public void onVisibleTasksChanged(@NonNull List<? extends RunningTaskInfo> visibleTasks) {
mVisibleTasks.clear();
mVisibleTasks.addAll(visibleTasks);
+ mVisibleTasksMap.clear();
+ mVisibleTasksMap.putAll(mVisibleTasks.stream().collect(
+ Collectors.toMap(TaskInfo::getTaskId, task -> task)));
// Notify with all the info and not just the running task info
- notifyVisibleTasksChanged(visibleTasks);
+ notifyVisibleTasksChanged(mVisibleTasks);
}
@VisibleForTesting
@@ -459,7 +465,7 @@
}
try {
// Compute the visible recent tasks in order, and move the task to the top
- mListener.onVisibleTasksChanged(generateList(visibleTasks)
+ mListener.onVisibleTasksChanged(generateList(visibleTasks, "visibleTasksChanged")
.toArray(new GroupedTaskInfo[0]));
} catch (RemoteException e) {
Slog.w(TAG, "Failed call onVisibleTasksChanged", e);
@@ -495,40 +501,87 @@
@VisibleForTesting
ArrayList<GroupedTaskInfo> getRecentTasks(int maxNum, int flags, int userId) {
// Note: the returned task list is ordered from the most-recent to least-recent order
- return generateList(mActivityTaskManager.getRecentTasks(maxNum, flags, userId));
+ return generateList(mActivityTaskManager.getRecentTasks(maxNum, flags, userId),
+ "getRecentTasks");
}
/**
- * Generates a list of GroupedTaskInfos for the given list of tasks.
+ * Returns whether the given task should be excluded from the generated list.
*/
- private <T extends TaskInfo> ArrayList<GroupedTaskInfo> generateList(@NonNull List<T> tasks) {
- // Make a mapping of task id -> task info
- final SparseArray<TaskInfo> rawMapping = new SparseArray<>();
- for (int i = 0; i < tasks.size(); i++) {
- final TaskInfo taskInfo = tasks.get(i);
- rawMapping.put(taskInfo.taskId, taskInfo);
+ private boolean excludeTaskFromGeneratedList(TaskInfo taskInfo) {
+ if (taskInfo.getWindowingMode() == WINDOWING_MODE_PINNED) {
+ // We don't current send pinned tasks as a part of recent or running tasks
+ return true;
+ }
+ if (isWallpaperTask(taskInfo)) {
+ // Don't add the fullscreen wallpaper task as an entry in grouped tasks
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Generates a list of GroupedTaskInfos for the given raw list of tasks (either recents or
+ * running tasks).
+ *
+ * The general flow is:
+ * - Collect the desktop tasks
+ * - Collect the visible tasks (in order), including the desktop tasks if visible
+ * - Construct the final list with the visible tasks, followed by the subsequent tasks
+ * - if enableShellTopTaskTracking() is enabled, the visible tasks will be grouped into
+ * a single mixed task
+ * - if the desktop tasks are not visible, they will be appended to the end of the list
+ *
+ * TODO(346588978): Generate list in per-display order
+ *
+ * @param tasks The list of tasks ordered from most recent to least recent
+ */
+ @VisibleForTesting
+ <T extends TaskInfo> ArrayList<GroupedTaskInfo> generateList(@NonNull List<T> tasks,
+ String reason) {
+ if (tasks.isEmpty()) {
+ return new ArrayList<>();
}
- ArrayList<TaskInfo> freeformTasks = new ArrayList<>();
- Set<Integer> minimizedFreeformTasks = new HashSet<>();
+ if (enableShellTopTaskTracking()) {
+ ProtoLog.v(WM_SHELL_TASK_OBSERVER, "RecentTasksController.generateList(%s)", reason);
+ }
- int mostRecentFreeformTaskIndex = Integer.MAX_VALUE;
+ // Make a mapping of task id -> task info for the remaining tasks to be processed, this
+ // mapping is used to keep track of split tasks that may exist later in the task list that
+ // should be ignored because they've already been grouped
+ mTmpRemaining.clear();
+ mTmpRemaining.putAll(tasks.stream().collect(
+ Collectors.toMap(TaskInfo::getTaskId, task -> task)));
- ArrayList<GroupedTaskInfo> groupedTasks = new ArrayList<>();
- // Pull out the pairs as we iterate back in the list
+ // The final grouped tasks
+ ArrayList<GroupedTaskInfo> groupedTasks = new ArrayList<>(tasks.size());
+ ArrayList<GroupedTaskInfo> visibleGroupedTasks = new ArrayList<>();
+
+ // Phase 1: Extract the desktop and visible fullscreen/split tasks. We make new collections
+ // here as the GroupedTaskInfo can store them without copying
+ ArrayList<TaskInfo> desktopTasks = new ArrayList<>();
+ Set<Integer> minimizedDesktopTasks = new HashSet<>();
+ boolean desktopTasksVisible = false;
for (int i = 0; i < tasks.size(); i++) {
final TaskInfo taskInfo = tasks.get(i);
- if (!rawMapping.contains(taskInfo.taskId)) {
- // If it's not in the mapping, then it was already paired with another task
+ final int taskId = taskInfo.taskId;
+
+ if (!mTmpRemaining.containsKey(taskInfo.taskId)) {
+ // Skip if we've already processed it
continue;
}
+
+ if (excludeTaskFromGeneratedList(taskInfo)) {
+ // Skip and update the list if we are excluding this task
+ mTmpRemaining.remove(taskId);
+ continue;
+ }
+
+ // Desktop tasks
if (DesktopModeStatus.canEnterDesktopMode(mContext) &&
- mDesktopUserRepositories.isPresent()
- && mDesktopUserRepositories.get().getCurrent().isActiveTask(taskInfo.taskId)) {
- // Freeform tasks will be added as a separate entry
- if (mostRecentFreeformTaskIndex == Integer.MAX_VALUE) {
- mostRecentFreeformTaskIndex = groupedTasks.size();
- }
+ mDesktopUserRepositories.isPresent()
+ && mDesktopUserRepositories.get().getCurrent().isActiveTask(taskId)) {
// If task has their app bounds set to null which happens after reboot, set the
// app bounds to persisted lastFullscreenBounds. Also set the position in parent
// to the top left of the bounds.
@@ -539,51 +592,132 @@
taskInfo.positionInParent = new Point(taskInfo.lastNonFullscreenBounds.left,
taskInfo.lastNonFullscreenBounds.top);
}
- freeformTasks.add(taskInfo);
- if (mDesktopUserRepositories.get().getCurrent().isMinimizedTask(taskInfo.taskId)) {
- minimizedFreeformTasks.add(taskInfo.taskId);
+ desktopTasks.add(taskInfo);
+ if (mDesktopUserRepositories.get().getCurrent().isMinimizedTask(taskId)) {
+ minimizedDesktopTasks.add(taskId);
}
+ desktopTasksVisible |= mVisibleTasksMap.containsKey(taskId);
+ mTmpRemaining.remove(taskId);
continue;
}
- final int pairedTaskId = mSplitTasks.get(taskInfo.taskId, INVALID_TASK_ID);
- if (pairedTaskId != INVALID_TASK_ID && rawMapping.contains(pairedTaskId)) {
- final TaskInfo pairedTaskInfo = rawMapping.get(pairedTaskId);
- rawMapping.remove(pairedTaskId);
- groupedTasks.add(GroupedTaskInfo.forSplitTasks(taskInfo, pairedTaskInfo,
- mTaskSplitBoundsMap.get(pairedTaskId)));
+ if (enableShellTopTaskTracking()) {
+ // Visible tasks
+ if (mVisibleTasksMap.containsKey(taskId)) {
+ // Split tasks
+ if (extractAndAddSplitGroupedTask(taskInfo, mTmpRemaining,
+ visibleGroupedTasks)) {
+ continue;
+ }
+
+ // Fullscreen tasks
+ visibleGroupedTasks.add(GroupedTaskInfo.forFullscreenTasks(taskInfo));
+ mTmpRemaining.remove(taskId);
+ }
} else {
- if (
- Flags.enableUseTopVisibleActivityForExcludeFromRecentTask()
- && isWallpaperTask(taskInfo)) {
- // Don't add the wallpaper task as an entry in grouped tasks
+ // Split tasks
+ if (extractAndAddSplitGroupedTask(taskInfo, mTmpRemaining, groupedTasks)) {
continue;
}
- // TODO(346588978): Consolidate multiple visible fullscreen tasks into the same
- // grouped task
+
+ // Fullscreen tasks
groupedTasks.add(GroupedTaskInfo.forFullscreenTasks(taskInfo));
}
}
- // Add a special entry for freeform tasks
- if (!freeformTasks.isEmpty()) {
- groupedTasks.add(mostRecentFreeformTaskIndex,
- GroupedTaskInfo.forFreeformTasks(
- freeformTasks,
- minimizedFreeformTasks));
- }
-
if (enableShellTopTaskTracking()) {
- // We don't current send pinned tasks as a part of recent or running tasks, so remove
- // them from the list here
- groupedTasks.removeIf(
- gti -> gti.getTaskInfo1().getWindowingMode() == WINDOWING_MODE_PINNED);
+ // Phase 2: If there were desktop tasks and they are visible, add them to the visible
+ // list as well (the actual order doesn't matter for Overview)
+ if (!desktopTasks.isEmpty() && desktopTasksVisible) {
+ visibleGroupedTasks.add(
+ GroupedTaskInfo.forFreeformTasks(desktopTasks, minimizedDesktopTasks));
+ }
+
+ if (!visibleGroupedTasks.isEmpty()) {
+ // Phase 3: Combine the visible tasks into a single mixed grouped task, only if
+ // there are > 1 tasks to group, and add them to the final list
+ if (visibleGroupedTasks.size() > 1) {
+ groupedTasks.add(GroupedTaskInfo.forMixed(visibleGroupedTasks));
+ } else {
+ groupedTasks.addAll(visibleGroupedTasks);
+ }
+ }
+ dumpGroupedTasks(groupedTasks, "Phase 3");
+
+ // Phase 4: For the remaining non-visible split and fullscreen tasks, add grouped tasks
+ // in order to the final list
+ for (int i = 0; i < tasks.size(); i++) {
+ final TaskInfo taskInfo = tasks.get(i);
+ if (!mTmpRemaining.containsKey(taskInfo.taskId)) {
+ // Skip if we've already processed it
+ continue;
+ }
+
+ // Split tasks
+ if (extractAndAddSplitGroupedTask(taskInfo, mTmpRemaining, groupedTasks)) {
+ continue;
+ }
+
+ // Fullscreen tasks
+ groupedTasks.add(GroupedTaskInfo.forFullscreenTasks(taskInfo));
+ }
+ dumpGroupedTasks(groupedTasks, "Phase 4");
+
+ // Phase 5: If there were desktop tasks and they are not visible (ie. weren't added
+ // above), add them to the end of the final list (the actual order doesn't
+ // matter for Overview)
+ if (!desktopTasks.isEmpty() && !desktopTasksVisible) {
+ groupedTasks.add(
+ GroupedTaskInfo.forFreeformTasks(desktopTasks, minimizedDesktopTasks));
+ }
+ dumpGroupedTasks(groupedTasks, "Phase 5");
+ } else {
+ // Add the desktop tasks at the end of the list
+ if (!desktopTasks.isEmpty()) {
+ groupedTasks.add(
+ GroupedTaskInfo.forFreeformTasks(desktopTasks, minimizedDesktopTasks));
+ }
}
return groupedTasks;
}
/**
+ * Only to be called from `generateList()`. If the given {@param taskInfo} has a paired task,
+ * then a split grouped task with the pair is added to {@param tasksOut}.
+ *
+ * @return whether a split task was extracted and added to the given list
+ */
+ private boolean extractAndAddSplitGroupedTask(@NonNull TaskInfo taskInfo,
+ @NonNull Map<Integer, TaskInfo> remainingTasks,
+ @NonNull ArrayList<GroupedTaskInfo> tasksOut) {
+ final int pairedTaskId = mSplitTasks.get(taskInfo.taskId, INVALID_TASK_ID);
+ if (pairedTaskId == INVALID_TASK_ID || !remainingTasks.containsKey(pairedTaskId)) {
+ return false;
+ }
+
+ // Add both this task and its pair to the list, and mark the paired task to be
+ // skipped when it is encountered in the list
+ final TaskInfo pairedTaskInfo = remainingTasks.get(pairedTaskId);
+ remainingTasks.remove(taskInfo.taskId);
+ remainingTasks.remove(pairedTaskId);
+ tasksOut.add(GroupedTaskInfo.forSplitTasks(taskInfo, pairedTaskInfo,
+ mTaskSplitBoundsMap.get(pairedTaskId)));
+ return true;
+ }
+
+ /** Dumps the set of tasks to protolog */
+ private void dumpGroupedTasks(List<GroupedTaskInfo> groupedTasks, String reason) {
+ if (!WM_SHELL_TASK_OBSERVER.isEnabled()) {
+ return;
+ }
+ ProtoLog.v(WM_SHELL_TASK_OBSERVER, " Tasks (%s):", reason);
+ for (GroupedTaskInfo task : groupedTasks) {
+ ProtoLog.v(WM_SHELL_TASK_OBSERVER, " %s", task);
+ }
+ }
+
+ /**
* Returns the top running leaf task ignoring {@param ignoreTaskToken} if it is specified.
* NOTE: This path currently makes assumptions that ignoreTaskToken is for the top task.
*/
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
index db582aa..afc6fee 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
@@ -228,7 +228,7 @@
break;
}
}
- final int transitionType = Flags.enableShellTopTaskTracking()
+ final int transitionType = Flags.enableRecentsBookendTransition()
? TRANSIT_START_RECENTS_TRANSITION
: TRANSIT_TO_FRONT;
final IBinder transition = mTransitions.startTransition(transitionType,
@@ -587,7 +587,8 @@
mListener.onAnimationStart(this,
apps.toArray(new RemoteAnimationTarget[apps.size()]),
new RemoteAnimationTarget[0],
- new Rect(0, 0, 0, 0), new Rect(), new Bundle());
+ new Rect(0, 0, 0, 0), new Rect(), new Bundle(),
+ null);
for (int i = 0; i < mStateListeners.size(); i++) {
mStateListeners.get(i).onTransitionStateChanged(TRANSITION_STATE_ANIMATING);
}
@@ -818,7 +819,7 @@
mListener.onAnimationStart(this,
apps.toArray(new RemoteAnimationTarget[apps.size()]),
wallpapers.toArray(new RemoteAnimationTarget[wallpapers.size()]),
- new Rect(0, 0, 0, 0), new Rect(), b);
+ new Rect(0, 0, 0, 0), new Rect(), b, info);
for (int i = 0; i < mStateListeners.size(); i++) {
mStateListeners.get(i).onTransitionStateChanged(TRANSITION_STATE_ANIMATING);
}
@@ -919,7 +920,7 @@
return;
}
- if (Flags.enableShellTopTaskTracking()
+ if (Flags.enableRecentsBookendTransition()
&& info.getType() == TRANSIT_END_RECENTS_TRANSITION
&& mergeTarget == mTransition) {
// This is a pending finish, so merge the end transition to trigger completing the
@@ -1147,9 +1148,12 @@
change, layer, info, t, mLeashMap);
appearedTargets[nextTargetIdx++] = target;
// reparent into the original `mInfo` since that's where we are animating.
- final int rootIdx = TransitionUtil.rootIndexFor(change, mInfo);
+ final TransitionInfo.Root root = TransitionUtil.getRootFor(change, mInfo);
final boolean wasClosing = closingIdx >= 0;
- t.reparent(target.leash, mInfo.getRoot(rootIdx).getLeash());
+ t.reparent(target.leash, root.getLeash());
+ t.setPosition(target.leash,
+ change.getStartAbsBounds().left - root.getOffset().x,
+ change.getStartAbsBounds().top - root.getOffset().y);
t.setLayer(target.leash, layer);
if (wasClosing) {
// App was previously visible and is closing
@@ -1286,8 +1290,8 @@
return;
}
- if (mFinishCB == null
- || (Flags.enableShellTopTaskTracking() && mPendingFinishTransition != null)) {
+ if (mFinishCB == null || (Flags.enableRecentsBookendTransition()
+ && mPendingFinishTransition != null)) {
Slog.e(TAG, "Duplicate call to finish");
if (runnerFinishCb != null) {
try {
@@ -1306,7 +1310,7 @@
&& !mWillFinishToHome
&& mPausingTasks != null
&& mState == STATE_NORMAL;
- if (!Flags.enableShellTopTaskTracking()) {
+ if (!Flags.enableRecentsBookendTransition()) {
// This is only necessary when the recents transition is finished using a finishWCT,
// otherwise a new transition will notify the relevant observers
if (returningToApp && allAppsAreTranslucent(mPausingTasks)) {
@@ -1439,7 +1443,7 @@
// We need to clear the WCT to send finishWCT=null for Recents.
wct.clear();
- if (Flags.enableShellTopTaskTracking()) {
+ if (Flags.enableRecentsBookendTransition()) {
// In this case, we've already started the PIP transition, so we can
// clean up immediately
mPendingRunnerFinishCb = runnerFinishCb;
@@ -1451,7 +1455,7 @@
}
}
- if (Flags.enableShellTopTaskTracking()) {
+ if (Flags.enableRecentsBookendTransition()) {
if (!wct.isEmpty()) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
"[%d] RecentsController.finishInner: "
@@ -1570,7 +1574,7 @@
/**
* A temporary transition handler used with the pending finish transition, which runs the
* cleanup/finish logic once the pending transition is merged/handled.
- * This is only initialized if Flags.enableShellTopTaskTracking() is enabled.
+ * This is only initialized if Flags.enableRecentsBookendTransition() is enabled.
*/
private class PendingFinishTransitionHandler implements Transitions.TransitionHandler {
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/TaskStackTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/TaskStackTransitionObserver.kt
index 93f2e4c..11cd403 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/TaskStackTransitionObserver.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/TaskStackTransitionObserver.kt
@@ -193,16 +193,18 @@
override fun onTransitionMerged(merged: IBinder, playing: IBinder) {}
override fun onTransitionFinished(transition: IBinder, aborted: Boolean) {
- if (enableShellTopTaskTracking()) {
- if (pendingCloseTasks.isNotEmpty()) {
- // Update the visible task list based on the pending close tasks
- for (change in pendingCloseTasks) {
- visibleTasks.removeIf {
- it.taskId == change.taskId
- }
+ if (!enableShellTopTaskTracking()) {
+ return
+ }
+
+ if (pendingCloseTasks.isNotEmpty()) {
+ // Update the visible task list based on the pending close tasks
+ for (change in pendingCloseTasks) {
+ visibleTasks.removeIf {
+ it.taskId == change.taskId
}
- updateVisibleTasksList("transition-finished")
}
+ updateVisibleTasksList("transition-finished")
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index c9136b4..37c9351 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -138,6 +138,7 @@
import com.android.wm.shell.common.LaunchAdjacentController;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.common.pip.PipUtils;
import com.android.wm.shell.common.split.OffscreenTouchZone;
import com.android.wm.shell.common.split.SplitDecorManager;
import com.android.wm.shell.common.split.SplitLayout;
@@ -556,6 +557,13 @@
return true;
}
+ if (PipUtils.isPip2ExperimentEnabled()
+ && request.getPipChange() != null && getSplitPosition(
+ request.getPipChange().getTaskInfo().taskId) != SPLIT_POSITION_UNDEFINED) {
+ // In PiP2, PiP-able task can also come in through the pip change request field.
+ return true;
+ }
+
// If one of the splitting tasks support auto-pip, wm-core might reparent the task to TDA
// and file a TRANSIT_PIP transition when finishing transitions.
// @see com.android.server.wm.RootWindowContainer#moveActivityToPinnedRootTask
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskView.java
index 361d7663..0445add 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskView.java
@@ -74,13 +74,16 @@
private final Rect mTmpRootRect = new Rect();
private final int[] mTmpLocation = new int[2];
private final Rect mBoundsOnScreen = new Rect();
+ private final TaskViewController mTaskViewController;
private final TaskViewTaskController mTaskViewTaskController;
private Region mObscuredTouchRegion;
private Insets mCaptionInsets;
private Handler mHandler;
- public TaskView(Context context, TaskViewTaskController taskViewTaskController) {
+ public TaskView(Context context, TaskViewController taskViewController,
+ TaskViewTaskController taskViewTaskController) {
super(context, null, 0, 0, true /* disableBackgroundLayer */);
+ mTaskViewController = taskViewController;
mTaskViewTaskController = taskViewTaskController;
// TODO(b/266736992): Think about a better way to set the TaskViewBase on the
// TaskViewTaskController and vice-versa
@@ -100,7 +103,8 @@
*/
public void startActivity(@NonNull PendingIntent pendingIntent, @Nullable Intent fillInIntent,
@NonNull ActivityOptions options, @Nullable Rect launchBounds) {
- mTaskViewTaskController.startActivity(pendingIntent, fillInIntent, options, launchBounds);
+ mTaskViewController.startActivity(mTaskViewTaskController, pendingIntent, fillInIntent,
+ options, launchBounds);
}
/**
@@ -115,19 +119,20 @@
*/
public void startShortcutActivity(@NonNull ShortcutInfo shortcut,
@NonNull ActivityOptions options, @Nullable Rect launchBounds) {
- mTaskViewTaskController.startShortcutActivity(shortcut, options, launchBounds);
+ mTaskViewController.startShortcutActivity(mTaskViewTaskController, shortcut, options,
+ launchBounds);
}
/**
* Moves the current task in taskview out of the view and back to fullscreen.
*/
public void moveToFullscreen() {
- mTaskViewTaskController.moveToFullscreen();
+ mTaskViewController.moveTaskViewToFullscreen(mTaskViewTaskController);
}
@Override
public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
- if (mTaskViewTaskController.isUsingShellTransitions()) {
+ if (mTaskViewController.isUsingShellTransitions()) {
// No need for additional work as it is already taken care of during
// prepareOpenAnimation().
return;
@@ -222,14 +227,14 @@
*/
public void onLocationChanged() {
getBoundsOnScreen(mTmpRect);
- mTaskViewTaskController.setWindowBounds(mTmpRect);
+ mTaskViewController.setTaskBounds(mTaskViewTaskController, mTmpRect);
}
/**
* Call to remove the task from window manager. This task will not appear in recents.
*/
public void removeTask() {
- mTaskViewTaskController.removeTask();
+ mTaskViewController.removeTaskView(mTaskViewTaskController, null /* token */);
}
/**
@@ -254,7 +259,7 @@
public void surfaceChanged(@androidx.annotation.NonNull SurfaceHolder holder, int format,
int width, int height) {
getBoundsOnScreen(mTmpRect);
- mTaskViewTaskController.setWindowBounds(mTmpRect);
+ mTaskViewController.setTaskBounds(mTaskViewTaskController, mTmpRect);
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewController.java
new file mode 100644
index 0000000..59becf7
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewController.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.wm.shell.taskview;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.app.ActivityOptions;
+import android.app.PendingIntent;
+import android.content.Intent;
+import android.content.pm.LauncherApps;
+import android.content.pm.ShortcutInfo;
+import android.graphics.Rect;
+import android.view.SurfaceControl;
+import android.window.WindowContainerToken;
+import android.window.WindowContainerTransaction;
+
+import com.android.wm.shell.ShellTaskOrganizer;
+
+/**
+ * Interface which provides methods to control TaskView properties and state.
+ *
+ * <ul>
+ * <li>To start an activity based task view, use {@link #startActivity}</li>
+ *
+ * <li>To start an activity (represented by {@link ShortcutInfo}) based task view, use
+ * {@link #startShortcutActivity}
+ * </li>
+ *
+ * <li>To start a root-task based task view, use {@link #startRootTask}.
+ * This method is special as it doesn't create a root task and instead expects that the
+ * launch root task is already created and started. This method just attaches the taskInfo to
+ * the TaskView.
+ * </li>
+ * </ul>
+ */
+public interface TaskViewController {
+ /** Registers a TaskView with this controller. */
+ void registerTaskView(@NonNull TaskViewTaskController tv);
+
+ /** Un-registers a TaskView from this controller. */
+ void unregisterTaskView(@NonNull TaskViewTaskController tv);
+
+ /**
+ * Launch an activity represented by {@link ShortcutInfo}.
+ * <p>The owner of this container must be allowed to access the shortcut information,
+ * as defined in {@link LauncherApps#hasShortcutHostPermission()} to use this method.
+ *
+ * @param destination the TaskView to start the shortcut into.
+ * @param shortcut the shortcut used to launch the activity.
+ * @param options options for the activity.
+ * @param launchBounds the bounds (window size and position) that the activity should be
+ * launched in, in pixels and in screen coordinates.
+ */
+ void startShortcutActivity(@NonNull TaskViewTaskController destination,
+ @NonNull ShortcutInfo shortcut,
+ @NonNull ActivityOptions options, @Nullable Rect launchBounds);
+
+ /**
+ * Launch a new activity into a TaskView
+ *
+ * @param destination The TaskView to start the activity into.
+ * @param pendingIntent Intent used to launch an activity.
+ * @param fillInIntent Additional Intent data, see {@link Intent#fillIn Intent.fillIn()}
+ * @param options options for the activity.
+ * @param launchBounds the bounds (window size and position) that the activity should be
+ * launched in, in pixels and in screen coordinates.
+ */
+ void startActivity(@NonNull TaskViewTaskController destination,
+ @NonNull PendingIntent pendingIntent, @Nullable Intent fillInIntent,
+ @NonNull ActivityOptions options, @Nullable Rect launchBounds);
+
+ /**
+ * Attaches the given root task {@code taskInfo} in the task view.
+ *
+ * <p> Since {@link ShellTaskOrganizer#createRootTask(int, int,
+ * ShellTaskOrganizer.TaskListener)} does not use the shell transitions flow, this method is
+ * used as an entry point for an already-created root-task in the task view.
+ *
+ * @param destination The TaskView to put the root-task into.
+ * @param taskInfo the task info of the root task.
+ * @param leash the {@link android.content.pm.ShortcutInfo.Surface} of the root task
+ * @param wct The Window container work that should happen as part of this set up.
+ */
+ void startRootTask(@NonNull TaskViewTaskController destination,
+ ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash,
+ @Nullable WindowContainerTransaction wct);
+
+ /**
+ * Closes a taskview and removes the task from window manager. This task will not appear in
+ * recents.
+ */
+ void removeTaskView(@NonNull TaskViewTaskController taskView,
+ @Nullable WindowContainerToken taskToken);
+
+ /**
+ * Moves the current task in TaskView out of the view and back to fullscreen.
+ */
+ void moveTaskViewToFullscreen(@NonNull TaskViewTaskController taskView);
+
+ /**
+ * Starts a new transition to make the given {@code taskView} visible and optionally change
+ * the task order.
+ *
+ * @param taskView the task view which the visibility is being changed for
+ * @param visible the new visibility of the task view
+ */
+ void setTaskViewVisible(TaskViewTaskController taskView, boolean visible);
+
+ /**
+ * Sets the task bounds to {@code boundsOnScreen}.
+ * Usually called when the taskview's position or size has changed.
+ *
+ * @param boundsOnScreen the on screen bounds of the surface view.
+ */
+ void setTaskBounds(TaskViewTaskController taskView, Rect boundsOnScreen);
+
+ /** Whether shell-transitions are currently enabled. */
+ boolean isUsingShellTransitions();
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewFactoryController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewFactoryController.java
index e4fcff0c..b2813bb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewFactoryController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewFactoryController.java
@@ -32,16 +32,16 @@
private final ShellTaskOrganizer mTaskOrganizer;
private final ShellExecutor mShellExecutor;
private final SyncTransactionQueue mSyncQueue;
- private final TaskViewTransitions mTaskViewTransitions;
+ private final TaskViewController mTaskViewController;
private final TaskViewFactory mImpl = new TaskViewFactoryImpl();
public TaskViewFactoryController(ShellTaskOrganizer taskOrganizer,
ShellExecutor shellExecutor, SyncTransactionQueue syncQueue,
- TaskViewTransitions taskViewTransitions) {
+ TaskViewController taskViewController) {
mTaskOrganizer = taskOrganizer;
mShellExecutor = shellExecutor;
mSyncQueue = syncQueue;
- mTaskViewTransitions = taskViewTransitions;
+ mTaskViewController = taskViewController;
}
public TaskViewFactoryController(ShellTaskOrganizer taskOrganizer,
@@ -49,7 +49,7 @@
mTaskOrganizer = taskOrganizer;
mShellExecutor = shellExecutor;
mSyncQueue = syncQueue;
- mTaskViewTransitions = null;
+ mTaskViewController = null;
}
/**
@@ -61,8 +61,8 @@
/** Creates an {@link TaskView} */
public void create(@UiContext Context context, Executor executor, Consumer<TaskView> onCreate) {
- TaskView taskView = new TaskView(context, new TaskViewTaskController(context,
- mTaskOrganizer, mTaskViewTransitions, mSyncQueue));
+ TaskView taskView = new TaskView(context, mTaskViewController, new TaskViewTaskController(
+ context, mTaskOrganizer, mTaskViewController, mSyncQueue));
executor.execute(() -> {
onCreate.accept(taskView);
});
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
index 5c7dd07..d19a7ea 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
@@ -16,32 +16,21 @@
package com.android.wm.shell.taskview;
-import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
-import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-import static android.view.WindowManager.TRANSIT_CHANGE;
-
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
-import android.app.ActivityOptions;
-import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
-import android.content.Intent;
-import android.content.pm.LauncherApps;
-import android.content.pm.ShortcutInfo;
import android.graphics.Rect;
import android.gui.TrustedOverlay;
import android.os.Binder;
import android.util.CloseGuard;
-import android.util.Slog;
import android.view.SurfaceControl;
import android.view.WindowInsets;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.wm.shell.Flags;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.SyncTransactionQueue;
@@ -49,27 +38,10 @@
import java.util.concurrent.Executor;
/**
- * This class implements the core logic to show a task on the {@link TaskView}. All the {@link
+ * This class represents the visible aspect of a task in a {@link TaskView}. All the {@link
* TaskView} to {@link TaskViewTaskController} interactions are done via direct method calls.
*
* The reverse communication is done via the {@link TaskViewBase} interface.
- *
- * <ul>
- * <li>The entry point for an activity based task view is {@link
- * TaskViewTaskController#startActivity(PendingIntent, Intent, ActivityOptions, Rect)}</li>
- *
- * <li>The entry point for an activity (represented by {@link ShortcutInfo}) based task view
- * is {@link TaskViewTaskController#startShortcutActivity(ShortcutInfo, ActivityOptions, Rect)}
- * </li>
- *
- * <li>The entry point for a root-task based task view is {@link
- * TaskViewTaskController#startRootTask(ActivityManager.RunningTaskInfo, SurfaceControl,
- * WindowContainerTransaction)}.
- * This method is special as it doesn't create a root task and instead expects that the
- * launch root task is already created and started. This method just attaches the taskInfo to
- * the TaskView.
- * </li>
- * </ul>
*/
public class TaskViewTaskController implements ShellTaskOrganizer.TaskListener {
@@ -82,7 +54,7 @@
private final ShellTaskOrganizer mTaskOrganizer;
private final Executor mShellExecutor;
private final SyncTransactionQueue mSyncQueue;
- private final TaskViewTransitions mTaskViewTransitions;
+ private final TaskViewController mTaskViewController;
private final Context mContext;
/**
@@ -109,15 +81,15 @@
private Rect mCaptionInsets;
public TaskViewTaskController(Context context, ShellTaskOrganizer organizer,
- TaskViewTransitions taskViewTransitions, SyncTransactionQueue syncQueue) {
+ TaskViewController taskViewController, SyncTransactionQueue syncQueue) {
mContext = context;
mTaskOrganizer = organizer;
mShellExecutor = organizer.getExecutor();
mSyncQueue = syncQueue;
- mTaskViewTransitions = taskViewTransitions;
+ mTaskViewController = taskViewController;
mShellExecutor.execute(() -> {
- if (mTaskViewTransitions != null) {
- mTaskViewTransitions.addTaskView(this);
+ if (mTaskViewController != null) {
+ mTaskViewController.registerTaskView(this);
}
});
mGuard.open("release");
@@ -140,6 +112,10 @@
return mSurfaceControl;
}
+ Context getContext() {
+ return mContext;
+ }
+
/**
* Sets the provided {@link TaskViewBase}, which is used to notify the client part about the
* task related changes and getting the current bounds.
@@ -155,9 +131,12 @@
return mIsInitialized;
}
- /** Until all users are converted, we may have mixed-use (eg. Car). */
- public boolean isUsingShellTransitions() {
- return mTaskViewTransitions != null && mTaskViewTransitions.isEnabled();
+ WindowContainerToken getTaskToken() {
+ return mTaskToken;
+ }
+
+ void setResizeBgColor(SurfaceControl.Transaction t, int bgColor) {
+ mTaskViewBase.setResizeBgColor(t, bgColor);
}
/**
@@ -173,122 +152,6 @@
}
/**
- * Launch an activity represented by {@link ShortcutInfo}.
- * <p>The owner of this container must be allowed to access the shortcut information,
- * as defined in {@link LauncherApps#hasShortcutHostPermission()} to use this method.
- *
- * @param shortcut the shortcut used to launch the activity.
- * @param options options for the activity.
- * @param launchBounds the bounds (window size and position) that the activity should be
- * launched in, in pixels and in screen coordinates.
- */
- public void startShortcutActivity(@NonNull ShortcutInfo shortcut,
- @NonNull ActivityOptions options, @Nullable Rect launchBounds) {
- prepareActivityOptions(options, launchBounds);
- LauncherApps service = mContext.getSystemService(LauncherApps.class);
- if (isUsingShellTransitions()) {
- mShellExecutor.execute(() -> {
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- wct.startShortcut(mContext.getPackageName(), shortcut, options.toBundle());
- mTaskViewTransitions.startTaskView(wct, this, options.getLaunchCookie());
- });
- return;
- }
- try {
- service.startShortcut(shortcut, null /* sourceBounds */, options.toBundle());
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- }
-
- /**
- * Launch a new activity.
- *
- * @param pendingIntent Intent used to launch an activity.
- * @param fillInIntent Additional Intent data, see {@link Intent#fillIn Intent.fillIn()}
- * @param options options for the activity.
- * @param launchBounds the bounds (window size and position) that the activity should be
- * launched in, in pixels and in screen coordinates.
- */
- public void startActivity(@NonNull PendingIntent pendingIntent, @Nullable Intent fillInIntent,
- @NonNull ActivityOptions options, @Nullable Rect launchBounds) {
- prepareActivityOptions(options, launchBounds);
- if (isUsingShellTransitions()) {
- mShellExecutor.execute(() -> {
- WindowContainerTransaction wct = new WindowContainerTransaction();
- wct.sendPendingIntent(pendingIntent, fillInIntent, options.toBundle());
- mTaskViewTransitions.startTaskView(wct, this, options.getLaunchCookie());
- });
- return;
- }
- try {
- pendingIntent.send(mContext, 0 /* code */, fillInIntent,
- null /* onFinished */, null /* handler */, null /* requiredPermission */,
- options.toBundle());
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- }
-
-
- /**
- * Attaches the given root task {@code taskInfo} in the task view.
- *
- * <p> Since {@link ShellTaskOrganizer#createRootTask(int, int,
- * ShellTaskOrganizer.TaskListener)} does not use the shell transitions flow, this method is
- * used as an entry point for an already-created root-task in the task view.
- *
- * @param taskInfo the task info of the root task.
- * @param leash the {@link android.content.pm.ShortcutInfo.Surface} of the root task
- * @param wct The Window container work that should happen as part of this set up.
- */
- public void startRootTask(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash,
- @Nullable WindowContainerTransaction wct) {
- if (wct == null) {
- wct = new WindowContainerTransaction();
- }
- // This method skips the regular flow where an activity task is launched as part of a new
- // transition in taskview and then transition is intercepted using the launchcookie.
- // The task here is already created and running, it just needs to be reparented, resized
- // and tracked correctly inside taskview. Which is done by calling
- // prepareOpenAnimationInternal() and then manually enqueuing the resulting window container
- // transaction.
- prepareOpenAnimationInternal(true /* newTask */, mTransaction /* startTransaction */,
- null /* finishTransaction */, taskInfo, leash, wct);
- mTransaction.apply();
- mTaskViewTransitions.startInstantTransition(TRANSIT_CHANGE, wct);
- }
-
- /**
- * Moves the current task in TaskView out of the view and back to fullscreen.
- */
- public void moveToFullscreen() {
- if (mTaskToken == null) return;
- mShellExecutor.execute(() -> {
- WindowContainerTransaction wct = new WindowContainerTransaction();
- wct.setWindowingMode(mTaskToken, WINDOWING_MODE_UNDEFINED);
- wct.setAlwaysOnTop(mTaskToken, false);
- mTaskOrganizer.setInterceptBackPressedOnTaskRoot(mTaskToken, false);
- mTaskViewTransitions.moveTaskViewToFullscreen(wct, this);
- if (mListener != null) {
- // Task is being "removed" from the clients perspective
- mListener.onTaskRemovalStarted(mTaskInfo.taskId);
- }
- });
- }
-
- private void prepareActivityOptions(ActivityOptions options, Rect launchBounds) {
- final Binder launchCookie = new Binder();
- mShellExecutor.execute(() -> {
- mTaskOrganizer.setPendingLaunchCookieListener(launchCookie, this);
- });
- options.setLaunchBounds(launchBounds);
- options.setLaunchCookie(launchCookie);
- options.setLaunchWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
- options.setRemoveWithTaskOrganizer(true);
- }
-
- /**
* Release this container if it is initialized.
*/
public void release() {
@@ -309,8 +172,8 @@
private void performRelease() {
mShellExecutor.execute(() -> {
- if (mTaskViewTransitions != null) {
- mTaskViewTransitions.removeTaskView(this);
+ if (mTaskViewController != null) {
+ mTaskViewController.unregisterTaskView(this);
}
mTaskOrganizer.removeListener(this);
resetTaskInfo();
@@ -364,7 +227,7 @@
@Override
public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo,
SurfaceControl leash) {
- if (isUsingShellTransitions()) {
+ if (mTaskViewController.isUsingShellTransitions()) {
mPendingInfo = taskInfo;
if (mTaskNotFound) {
// If we were already notified by shell transit that we don't have the
@@ -484,8 +347,8 @@
// Nothing to update, task is not yet available
return;
}
- if (isUsingShellTransitions()) {
- mTaskViewTransitions.setTaskViewVisible(this, true /* visible */);
+ if (mTaskViewController.isUsingShellTransitions()) {
+ mTaskViewController.setTaskViewVisible(this, true /* visible */);
return;
}
// Reparent the task when this surface is created
@@ -497,56 +360,6 @@
}
/**
- * Sets the window bounds to {@code boundsOnScreen}.
- * Call when view position or size has changed. Can also be called before the animation when
- * the final bounds are known.
- * Do not call during the animation.
- *
- * @param boundsOnScreen the on screen bounds of the surface view.
- */
- public void setWindowBounds(Rect boundsOnScreen) {
- if (mTaskToken == null) {
- return;
- }
-
- if (isUsingShellTransitions()) {
- mShellExecutor.execute(() -> {
- // Sync Transactions can't operate simultaneously with shell transition collection.
- mTaskViewTransitions.setTaskBounds(this, boundsOnScreen);
- });
- return;
- }
-
- WindowContainerTransaction wct = new WindowContainerTransaction();
- wct.setBounds(mTaskToken, boundsOnScreen);
- mSyncQueue.queue(wct);
- }
-
- /**
- * Call to remove the task from window manager. This task will not appear in recents.
- */
- void removeTask() {
- if (mTaskToken == null) {
- if (Flags.enableTaskViewControllerCleanup()) {
- // We don't have a task yet. Only clean up the controller
- mTaskViewTransitions.removeTaskView(this);
- } else {
- // Call to remove task before we have one, do nothing
- Slog.w(TAG, "Trying to remove a task that was never added? (no taskToken)");
- }
- return;
- }
- // Cache it to avoid NPE and make sure to remove it from recents history.
- // mTaskToken can be cleared in onTaskVanished() when the task is removed.
- final WindowContainerToken taskToken = mTaskToken;
- mShellExecutor.execute(() -> {
- WindowContainerTransaction wct = new WindowContainerTransaction();
- wct.removeTask(taskToken);
- mTaskViewTransitions.closeTaskView(wct, this);
- });
- }
-
- /**
* Sets a region of the task to inset to allow for a caption bar.
*
* @param captionInsets the rect for the insets in screen coordinates.
@@ -583,8 +396,8 @@
return;
}
- if (isUsingShellTransitions()) {
- mTaskViewTransitions.setTaskViewVisible(this, false /* visible */);
+ if (mTaskViewController.isUsingShellTransitions()) {
+ mTaskViewController.setTaskViewVisible(this, false /* visible */);
return;
}
@@ -604,15 +417,16 @@
}
}
+ void notifyTaskRemovalStarted(@NonNull ActivityManager.RunningTaskInfo taskInfo) {
+ if (mListener == null) return;
+ final int taskId = taskInfo.taskId;
+ mListenerExecutor.execute(() -> mListener.onTaskRemovalStarted(taskId));
+ }
+
/** Notifies listeners of a task being removed and stops intercepting back presses on it. */
private void handleAndNotifyTaskRemoval(ActivityManager.RunningTaskInfo taskInfo) {
if (taskInfo != null) {
- if (mListener != null) {
- final int taskId = taskInfo.taskId;
- mListenerExecutor.execute(() -> {
- mListener.onTaskRemovalStarted(taskId);
- });
- }
+ notifyTaskRemovalStarted(taskInfo);
mTaskViewBase.onTaskVanished(taskInfo);
}
}
@@ -651,9 +465,7 @@
handleAndNotifyTaskRemoval(pendingInfo);
// Make sure the task is removed
- WindowContainerTransaction wct = new WindowContainerTransaction();
- wct.removeTask(pendingInfo.token);
- mTaskViewTransitions.closeTaskView(wct, this);
+ mTaskViewController.removeTaskView(this, pendingInfo.token);
}
resetTaskInfo();
}
@@ -681,72 +493,23 @@
resetTaskInfo();
}
- void prepareOpenAnimation(final boolean newTask,
- @NonNull SurfaceControl.Transaction startTransaction,
- @NonNull SurfaceControl.Transaction finishTransaction,
- ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash,
- WindowContainerTransaction wct) {
- prepareOpenAnimationInternal(newTask, startTransaction, finishTransaction, taskInfo, leash,
- wct);
- }
-
- private TaskViewRepository.TaskViewState getState() {
- return mTaskViewTransitions.getRepository().byTaskView(this);
- }
-
- private void prepareOpenAnimationInternal(final boolean newTask,
- SurfaceControl.Transaction startTransaction,
- SurfaceControl.Transaction finishTransaction,
- ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash,
- WindowContainerTransaction wct) {
+ /**
+ * Prepare this taskview to open {@param taskInfo}.
+ * @return The bounds of the task or {@code null} on failure (surface is destroyed)
+ */
+ Rect prepareOpen(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
mPendingInfo = null;
mTaskInfo = taskInfo;
mTaskToken = mTaskInfo.token;
mTaskLeash = leash;
- if (mSurfaceCreated) {
- // Surface is ready, so just reparent the task to this surface control
- startTransaction.reparent(mTaskLeash, mSurfaceControl)
- .show(mTaskLeash);
- // Also reparent on finishTransaction since the finishTransaction will reparent back
- // to its "original" parent by default.
- Rect boundsOnScreen = mTaskViewBase.getCurrentBoundsOnScreen();
- if (finishTransaction != null) {
- finishTransaction.reparent(mTaskLeash, mSurfaceControl)
- .setPosition(mTaskLeash, 0, 0)
- // TODO: maybe once b/280900002 is fixed this will be unnecessary
- .setWindowCrop(mTaskLeash, boundsOnScreen.width(), boundsOnScreen.height());
- }
- if (TaskViewTransitions.useRepo()) {
- final TaskViewRepository.TaskViewState state = getState();
- if (state != null) {
- state.mBounds.set(boundsOnScreen);
- state.mVisible = true;
- }
- } else {
- mTaskViewTransitions.updateBoundsState(this, boundsOnScreen);
- mTaskViewTransitions.updateVisibilityState(this, true /* visible */);
- }
- wct.setBounds(mTaskToken, boundsOnScreen);
- applyCaptionInsetsIfNeeded();
- } else {
- // The surface has already been destroyed before the task has appeared,
- // so go ahead and hide the task entirely
- wct.setHidden(mTaskToken, true /* hidden */);
- mTaskViewTransitions.updateVisibilityState(this, false /* visible */);
- // listener callback is below
+ if (!mSurfaceCreated) {
+ return null;
}
- if (newTask) {
- mTaskOrganizer.setInterceptBackPressedOnTaskRoot(mTaskToken, true /* intercept */);
- }
+ return mTaskViewBase.getCurrentBoundsOnScreen();
+ }
- if (mTaskInfo.taskDescription != null) {
- int backgroundColor = mTaskInfo.taskDescription.getBackgroundColor();
- mTaskViewBase.setResizeBgColor(startTransaction, backgroundColor);
- }
-
- // After the embedded task has appeared, set it to non-trimmable. This is important
- // to prevent recents from trimming and removing the embedded task.
- wct.setTaskTrimmableFromRecents(taskInfo.token, false /* isTrimmableFromRecents */);
+ /** Notify that the associated task has appeared. This will call appropriate listeners. */
+ void notifyAppeared(final boolean newTask) {
mTaskViewBase.onTaskAppeared(mTaskInfo, mTaskLeash);
if (mListener != null) {
final int taskId = mTaskInfo.taskId;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java
index 0cbb7bd..6c90a90 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java
@@ -16,6 +16,8 @@
package com.android.wm.shell.taskview;
+import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_OPEN;
@@ -25,7 +27,14 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
+import android.app.ActivityOptions;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.LauncherApps;
+import android.content.pm.ShortcutInfo;
import android.graphics.Rect;
+import android.os.Binder;
import android.os.IBinder;
import android.util.ArrayMap;
import android.util.Slog;
@@ -33,11 +42,14 @@
import android.view.WindowManager;
import android.window.TransitionInfo;
import android.window.TransitionRequestInfo;
+import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
import androidx.annotation.VisibleForTesting;
import com.android.wm.shell.Flags;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.shared.TransitionUtil;
import com.android.wm.shell.transition.Transitions;
@@ -45,11 +57,12 @@
import java.util.Map;
import java.util.Objects;
import java.util.WeakHashMap;
+import java.util.concurrent.Executor;
/**
* Handles Shell Transitions that involve TaskView tasks.
*/
-public class TaskViewTransitions implements Transitions.TransitionHandler {
+public class TaskViewTransitions implements Transitions.TransitionHandler, TaskViewController {
static final String TAG = "TaskViewTransitions";
/**
@@ -65,6 +78,12 @@
private final ArrayList<PendingTransition> mPending = new ArrayList<>();
private final Transitions mTransitions;
private final boolean[] mRegistered = new boolean[]{false};
+ private final ShellTaskOrganizer mTaskOrganizer;
+ private final Executor mShellExecutor;
+ private final SyncTransactionQueue mSyncQueue;
+
+ /** A temp transaction used for quick things. */
+ private final SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
/**
* TaskView makes heavy use of startTransition. Only one shell-initiated transition can be
@@ -96,8 +115,12 @@
}
}
- public TaskViewTransitions(Transitions transitions, TaskViewRepository repository) {
+ public TaskViewTransitions(Transitions transitions, TaskViewRepository repository,
+ ShellTaskOrganizer taskOrganizer, SyncTransactionQueue syncQueue) {
mTransitions = transitions;
+ mTaskOrganizer = taskOrganizer;
+ mShellExecutor = taskOrganizer.getExecutor();
+ mSyncQueue = syncQueue;
if (useRepo()) {
mTaskViews = null;
} else if (Flags.enableTaskViewControllerCleanup()) {
@@ -111,7 +134,8 @@
// TODO(210041388): register here once we have an explicit ordering mechanism.
}
- static boolean useRepo() {
+ /** @return whether the shared taskview repository is being used. */
+ public static boolean useRepo() {
return Flags.taskViewRepository() || Flags.enableBubbleAnything();
}
@@ -119,7 +143,8 @@
return mTaskViewRepo;
}
- void addTaskView(TaskViewTaskController tv) {
+ @Override
+ public void registerTaskView(TaskViewTaskController tv) {
synchronized (mRegistered) {
if (!mRegistered[0]) {
mRegistered[0] = true;
@@ -133,7 +158,8 @@
}
}
- void removeTaskView(TaskViewTaskController tv) {
+ @Override
+ public void unregisterTaskView(TaskViewTaskController tv) {
if (useRepo()) {
mTaskViewRepo.remove(tv);
} else {
@@ -142,27 +168,12 @@
// Note: Don't unregister handler since this is a singleton with lifetime bound to Shell
}
- boolean isEnabled() {
+ @Override
+ public boolean isUsingShellTransitions() {
return mTransitions.isRegistered();
}
/**
- * Looks through the pending transitions for a closing transaction that matches the provided
- * `taskView`.
- *
- * @param taskView the pending transition should be for this.
- */
- private PendingTransition findPendingCloseTransition(TaskViewTaskController taskView) {
- for (int i = mPending.size() - 1; i >= 0; --i) {
- if (mPending.get(i).mTaskView != taskView) continue;
- if (TransitionUtil.isClosingType(mPending.get(i).mType)) {
- return mPending.get(i);
- }
- }
- return null;
- }
-
- /**
* Starts a transition outside of the handler associated with {@link TaskViewTransitions}.
*/
public void startInstantTransition(@WindowManager.TransitionType int type,
@@ -264,6 +275,82 @@
return findTaskView(taskInfo) != null;
}
+ private void prepareActivityOptions(ActivityOptions options, Rect launchBounds,
+ @NonNull TaskViewTaskController destination) {
+ final Binder launchCookie = new Binder();
+ mShellExecutor.execute(() -> {
+ mTaskOrganizer.setPendingLaunchCookieListener(launchCookie, destination);
+ });
+ options.setLaunchBounds(launchBounds);
+ options.setLaunchCookie(launchCookie);
+ options.setLaunchWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+ options.setRemoveWithTaskOrganizer(true);
+ }
+
+ @Override
+ public void startShortcutActivity(@NonNull TaskViewTaskController destination,
+ @NonNull ShortcutInfo shortcut, @NonNull ActivityOptions options,
+ @Nullable Rect launchBounds) {
+ prepareActivityOptions(options, launchBounds, destination);
+ final Context context = destination.getContext();
+ if (isUsingShellTransitions()) {
+ mShellExecutor.execute(() -> {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ wct.startShortcut(context.getPackageName(), shortcut, options.toBundle());
+ startTaskView(wct, destination, options.getLaunchCookie());
+ });
+ return;
+ }
+ try {
+ LauncherApps service = context.getSystemService(LauncherApps.class);
+ service.startShortcut(shortcut, null /* sourceBounds */, options.toBundle());
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public void startActivity(@NonNull TaskViewTaskController destination,
+ @NonNull PendingIntent pendingIntent, @Nullable Intent fillInIntent,
+ @NonNull ActivityOptions options, @Nullable Rect launchBounds) {
+ prepareActivityOptions(options, launchBounds, destination);
+ if (isUsingShellTransitions()) {
+ mShellExecutor.execute(() -> {
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ wct.sendPendingIntent(pendingIntent, fillInIntent, options.toBundle());
+ startTaskView(wct, destination, options.getLaunchCookie());
+ });
+ return;
+ }
+ try {
+ pendingIntent.send(destination.getContext(), 0 /* code */, fillInIntent,
+ null /* onFinished */, null /* handler */, null /* requiredPermission */,
+ options.toBundle());
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public void startRootTask(@NonNull TaskViewTaskController destination,
+ ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash,
+ @Nullable WindowContainerTransaction wct) {
+ if (wct == null) {
+ wct = new WindowContainerTransaction();
+ }
+ // This method skips the regular flow where an activity task is launched as part of a new
+ // transition in taskview and then transition is intercepted using the launchcookie.
+ // The task here is already created and running, it just needs to be reparented, resized
+ // and tracked correctly inside taskview. Which is done by calling
+ // prepareOpenAnimationInternal() and then manually enqueuing the resulting window container
+ // transaction.
+ prepareOpenAnimation(destination, true /* newTask */, mTransaction /* startTransaction */,
+ null /* finishTransaction */, taskInfo, leash, wct);
+ mTransaction.apply();
+ mTransitions.startTransition(TRANSIT_CHANGE, wct, null);
+ }
+
+ @VisibleForTesting
void startTaskView(@NonNull WindowContainerTransaction wct,
@NonNull TaskViewTaskController taskView, @NonNull IBinder launchCookie) {
updateVisibilityState(taskView, true /* visible */);
@@ -271,30 +358,53 @@
startNextTransition();
}
- void closeTaskView(@NonNull WindowContainerTransaction wct,
- @NonNull TaskViewTaskController taskView) {
+ @Override
+ public void removeTaskView(@NonNull TaskViewTaskController taskView,
+ @Nullable WindowContainerToken taskToken) {
+ final WindowContainerToken token = taskToken != null ? taskToken : taskView.getTaskToken();
+ if (token == null) {
+ // We don't have a task yet, so just clean up records
+ if (!Flags.enableTaskViewControllerCleanup()) {
+ // Call to remove task before we have one, do nothing
+ Slog.w(TAG, "Trying to remove a task that was never added? (no taskToken)");
+ return;
+ }
+ unregisterTaskView(taskView);
+ return;
+ }
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ wct.removeTask(token);
updateVisibilityState(taskView, false /* visible */);
- mPending.add(new PendingTransition(TRANSIT_CLOSE, wct, taskView, null /* cookie */));
- startNextTransition();
+ mShellExecutor.execute(() -> {
+ mPending.add(new PendingTransition(TRANSIT_CLOSE, wct, taskView, null /* cookie */));
+ startNextTransition();
+ });
}
- void moveTaskViewToFullscreen(@NonNull WindowContainerTransaction wct,
- @NonNull TaskViewTaskController taskView) {
- mPending.add(new PendingTransition(TRANSIT_CHANGE, wct, taskView, null /* cookie */));
- startNextTransition();
+ @Override
+ public void moveTaskViewToFullscreen(@NonNull TaskViewTaskController taskView) {
+ final WindowContainerToken taskToken = taskView.getTaskToken();
+ if (taskToken == null) return;
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ wct.setWindowingMode(taskToken, WINDOWING_MODE_UNDEFINED);
+ wct.setAlwaysOnTop(taskToken, false);
+ mShellExecutor.execute(() -> {
+ mTaskOrganizer.setInterceptBackPressedOnTaskRoot(taskToken, false);
+ mPending.add(new PendingTransition(TRANSIT_CHANGE, wct, taskView, null /* cookie */));
+ startNextTransition();
+ taskView.notifyTaskRemovalStarted(taskView.getTaskInfo());
+ });
}
- /** Starts a new transition to make the given {@code taskView} visible. */
+ @Override
public void setTaskViewVisible(TaskViewTaskController taskView, boolean visible) {
setTaskViewVisible(taskView, visible, false /* reorder */);
}
/**
- * Starts a new transition to make the given {@code taskView} visible and optionally change
- * the task order.
+ * Starts a new transition to make the given {@code taskView} visible and optionally
+ * reordering it.
*
- * @param taskView the task view which the visibility is being changed for
- * @param visible the new visibility of the task view
* @param reorder whether to reorder the task or not. If this is {@code true}, the task will
* be reordered as per the given {@code visible}. For {@code visible = true},
* task will be reordered to top. For {@code visible = false}, task will be
@@ -359,7 +469,26 @@
state.mVisible = visible;
}
- void setTaskBounds(TaskViewTaskController taskView, Rect boundsOnScreen) {
+ @Override
+ public void setTaskBounds(TaskViewTaskController taskView, Rect boundsOnScreen) {
+ if (taskView.getTaskToken() == null) {
+ return;
+ }
+
+ if (isUsingShellTransitions()) {
+ mShellExecutor.execute(() -> {
+ // Sync Transactions can't operate simultaneously with shell transition collection.
+ setTaskBoundsInTransition(taskView, boundsOnScreen);
+ });
+ return;
+ }
+
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ wct.setBounds(taskView.getTaskToken(), boundsOnScreen);
+ mSyncQueue.queue(wct);
+ }
+
+ private void setTaskBoundsInTransition(TaskViewTaskController taskView, Rect boundsOnScreen) {
final TaskViewRepository.TaskViewState state = useRepo()
? mTaskViewRepo.byTaskView(taskView)
: mTaskViews.get(taskView);
@@ -476,7 +605,7 @@
}
}
if (wct == null) wct = new WindowContainerTransaction();
- tv.prepareOpenAnimation(taskIsNew, startTransaction, finishTransaction,
+ prepareOpenAnimation(tv, taskIsNew, startTransaction, finishTransaction,
chg.getTaskInfo(), chg.getLeash(), wct);
changesHandled++;
} else if (chg.getMode() == TRANSIT_CHANGE) {
@@ -510,4 +639,60 @@
startNextTransition();
return true;
}
+
+ @VisibleForTesting
+ void prepareOpenAnimation(TaskViewTaskController taskView,
+ final boolean newTask,
+ SurfaceControl.Transaction startTransaction,
+ SurfaceControl.Transaction finishTransaction,
+ ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash,
+ WindowContainerTransaction wct) {
+ final Rect boundsOnScreen = taskView.prepareOpen(taskInfo, leash);
+ if (boundsOnScreen != null) {
+ final SurfaceControl tvSurface = taskView.getSurfaceControl();
+ // Surface is ready, so just reparent the task to this surface control
+ startTransaction.reparent(leash, tvSurface)
+ .show(leash);
+ // Also reparent on finishTransaction since the finishTransaction will reparent back
+ // to its "original" parent by default.
+ if (finishTransaction != null) {
+ finishTransaction.reparent(leash, tvSurface)
+ .setPosition(leash, 0, 0)
+ // TODO: maybe once b/280900002 is fixed this will be unnecessary
+ .setWindowCrop(leash, boundsOnScreen.width(), boundsOnScreen.height());
+ }
+ if (useRepo()) {
+ final TaskViewRepository.TaskViewState state = mTaskViewRepo.byTaskView(taskView);
+ if (state != null) {
+ state.mBounds.set(boundsOnScreen);
+ state.mVisible = true;
+ }
+ } else {
+ updateBoundsState(taskView, boundsOnScreen);
+ updateVisibilityState(taskView, true /* visible */);
+ }
+ wct.setBounds(taskInfo.token, boundsOnScreen);
+ taskView.applyCaptionInsetsIfNeeded();
+ } else {
+ // The surface has already been destroyed before the task has appeared,
+ // so go ahead and hide the task entirely
+ wct.setHidden(taskInfo.token, true /* hidden */);
+ updateVisibilityState(taskView, false /* visible */);
+ // listener callback is below
+ }
+ if (newTask) {
+ mTaskOrganizer.setInterceptBackPressedOnTaskRoot(taskInfo.token, true /* intercept */);
+ }
+
+ if (taskInfo.taskDescription != null) {
+ int backgroundColor = taskInfo.taskDescription.getBackgroundColor();
+ taskView.setResizeBgColor(startTransaction, backgroundColor);
+ }
+
+ // After the embedded task has appeared, set it to non-trimmable. This is important
+ // to prevent recents from trimming and removing the embedded task.
+ wct.setTaskTrimmableFromRecents(taskInfo.token, false /* isTrimmableFromRecents */);
+
+ taskView.notifyAppeared(newTask);
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHelper.java
index e61929f..2133275 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHelper.java
@@ -17,13 +17,14 @@
package com.android.wm.shell.transition;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.view.WindowManager.TRANSIT_PIP;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
-import static com.android.wm.shell.shared.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR;
-import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
import static com.android.wm.shell.pip.PipAnimationController.ANIM_TYPE_ALPHA;
import static com.android.wm.shell.shared.TransitionUtil.isOpeningMode;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_MAIN;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_SIDE;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
@@ -36,6 +37,7 @@
import android.window.TransitionInfo;
import com.android.internal.protolog.ProtoLog;
+import com.android.wm.shell.common.pip.PipUtils;
import com.android.wm.shell.keyguard.KeyguardTransitionHandler;
import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
@@ -54,6 +56,7 @@
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Animating a mixed transition for "
+ "entering PIP while Split-Screen is foreground.");
TransitionInfo.Change pipChange = null;
+ TransitionInfo.Change pipActivityChange = null;
TransitionInfo.Change wallpaper = null;
final TransitionInfo everythingElse =
subCopy(info, TRANSIT_TO_BACK, true /* changes */);
@@ -68,6 +71,13 @@
pipChange = change;
// going backwards, so remove-by-index is fine.
everythingElse.getChanges().remove(i);
+ } else if (change.getTaskInfo() == null && change.getParent() != null
+ && pipChange != null && change.getParent().equals(pipChange.getContainer())) {
+ // Cache the PiP activity if it's a target and cached pip task change is its parent;
+ // note that we are bottom-to-top, so if such activity has a task
+ // that is also a target, then it must have been cached already as pipChange.
+ pipActivityChange = change;
+ everythingElse.getChanges().remove(i);
} else if (isHomeOpening(change)) {
homeIsOpening = true;
} else if (isWallpaper(change)) {
@@ -138,9 +148,19 @@
}
}
- pipHandler.setEnterAnimationType(ANIM_TYPE_ALPHA);
- pipHandler.startEnterAnimation(pipChange, startTransaction, finishTransaction,
- finishCB);
+ if (PipUtils.isPip2ExperimentEnabled()) {
+ TransitionInfo pipInfo = subCopy(info, TRANSIT_PIP, false /* withChanges */);
+ pipInfo.getChanges().add(pipChange);
+ if (pipActivityChange != null) {
+ pipInfo.getChanges().add(pipActivityChange);
+ }
+ pipHandler.startAnimation(mixed.mTransition, pipInfo, startTransaction,
+ finishTransaction, finishCB);
+ } else {
+ pipHandler.setEnterAnimationType(ANIM_TYPE_ALPHA);
+ pipHandler.startEnterAnimation(pipChange, startTransaction, finishTransaction,
+ finishCB);
+ }
// make a new finishTransaction because pip's startEnterAnimation "consumes" it so
// we need a separate one to send over to launcher.
SurfaceControl.Transaction otherFinishT = new SurfaceControl.Transaction();
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 d5929f0..abfb41b 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 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.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 @@
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/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
index 792f5ca..7aa0037 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
@@ -284,6 +284,10 @@
}
private boolean shouldShowWindowDecor(RunningTaskInfo taskInfo) {
+ if (mDisplayController.getDisplay(taskInfo.displayId) == null) {
+ // If DisplayController doesn't have it tracked, it could be a private/managed display.
+ return false;
+ }
if (taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
return true;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CarWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CarWindowDecorViewModel.java
index 0d75e65..7948ead 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CarWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CarWindowDecorViewModel.java
@@ -110,9 +110,6 @@
SurfaceControl taskSurface,
SurfaceControl.Transaction startT,
SurfaceControl.Transaction finishT) {
- if (!shouldShowWindowDecor(taskInfo)) {
- return false;
- }
createWindowDecoration(taskInfo, taskSurface, startT, finishT);
return true;
}
@@ -125,12 +122,9 @@
return;
}
- if (!shouldShowWindowDecor(taskInfo)) {
- destroyWindowDecoration(taskInfo);
- return;
- }
-
- decoration.relayout(taskInfo, decoration.mHasGlobalFocus, decoration.mExclusionRegion);
+ final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+ decoration.relayout(taskInfo, t, t,
+ /* isCaptionVisible= */ shouldShowWindowDecor(taskInfo));
}
@Override
@@ -221,7 +215,8 @@
mWindowDecorViewHostSupplier,
new ButtonClickListener(taskInfo));
mWindowDecorByTaskId.put(taskInfo.taskId, windowDecoration);
- windowDecoration.relayout(taskInfo, startT, finishT);
+ windowDecoration.relayout(taskInfo, startT, finishT,
+ /* isCaptionVisible= */ shouldShowWindowDecor(taskInfo));
}
private class ButtonClickListener implements View.OnClickListener {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CarWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CarWindowDecoration.java
index 1ca82d2..3943784 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CarWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CarWindowDecoration.java
@@ -20,14 +20,17 @@
import android.annotation.SuppressLint;
import android.app.ActivityManager;
import android.content.Context;
+import android.graphics.Insets;
import android.graphics.Rect;
import android.graphics.Region;
import android.view.InsetsState;
import android.view.SurfaceControl;
import android.view.View;
+import android.view.WindowInsets;
import android.window.WindowContainerTransaction;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
@@ -44,6 +47,7 @@
private WindowDecorLinearLayout mRootView;
private @ShellBackgroundThread final ShellExecutor mBgExecutor;
private final View.OnClickListener mClickListener;
+ private final RelayoutResult<WindowDecorLinearLayout> mResult = new RelayoutResult<>();
CarWindowDecoration(
Context context,
@@ -71,26 +75,32 @@
@SuppressLint("MissingPermission")
void relayout(ActivityManager.RunningTaskInfo taskInfo,
SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT) {
+ relayout(taskInfo, startT, finishT, /* isCaptionVisible= */ true);
+ }
+
+ @SuppressLint("MissingPermission")
+ void relayout(ActivityManager.RunningTaskInfo taskInfo,
+ SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT,
+ boolean isCaptionVisible) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
RelayoutParams relayoutParams = new RelayoutParams();
- RelayoutResult<WindowDecorLinearLayout> outResult = new RelayoutResult<>();
updateRelayoutParams(relayoutParams, taskInfo,
- mDisplayController.getInsetsState(taskInfo.displayId));
+ mDisplayController.getInsetsState(taskInfo.displayId), isCaptionVisible);
- relayout(relayoutParams, startT, finishT, wct, mRootView, outResult);
+ relayout(relayoutParams, startT, finishT, wct, mRootView, mResult);
// After this line, mTaskInfo is up-to-date and should be used instead of taskInfo
mBgExecutor.execute(() -> mTaskOrganizer.applyTransaction(wct));
- if (outResult.mRootView == null) {
+ if (mResult.mRootView == null) {
// This means something blocks the window decor from showing, e.g. the task is hidden.
// Nothing is set up in this case including the decoration surface.
return;
}
- if (mRootView != outResult.mRootView) {
- mRootView = outResult.mRootView;
- setupRootView(outResult.mRootView, mClickListener);
+ if (mRootView != mResult.mRootView) {
+ mRootView = mResult.mRootView;
+ setupRootView(mResult.mRootView, mClickListener);
}
}
@@ -108,18 +118,31 @@
private void updateRelayoutParams(
RelayoutParams relayoutParams,
ActivityManager.RunningTaskInfo taskInfo,
- InsetsState displayInsetsState) {
+ @Nullable InsetsState displayInsetsState,
+ boolean isCaptionVisible) {
relayoutParams.reset();
relayoutParams.mRunningTaskInfo = taskInfo;
// todo(b/382071404): update to car specific UI
relayoutParams.mLayoutResId = R.layout.caption_window_decor;
relayoutParams.mCaptionHeightId = R.dimen.freeform_decor_caption_height;
- relayoutParams.mIsCaptionVisible = mIsStatusBarVisible && !mIsKeyguardVisibleAndOccluded;
- relayoutParams.mCaptionTopPadding = 0;
+ relayoutParams.mIsCaptionVisible =
+ isCaptionVisible && mIsStatusBarVisible && !mIsKeyguardVisibleAndOccluded;
+ if (displayInsetsState != null) {
+ relayoutParams.mCaptionTopPadding = getTopPadding(
+ taskInfo.getConfiguration().windowConfiguration.getBounds(),
+ displayInsetsState);
+ }
relayoutParams.mInsetSourceFlags |= FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR;
relayoutParams.mApplyStartTransactionOnDraw = true;
}
+ private static int getTopPadding(Rect taskBounds, @NonNull InsetsState insetsState) {
+ Insets systemDecor = insetsState.calculateInsets(taskBounds,
+ WindowInsets.Type.systemBars() & ~WindowInsets.Type.captionBar(),
+ false /* ignoreVisibility */);
+ return systemDecor.top;
+ }
+
/**
* Sets up listeners when a new root view is created.
*/
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopHeaderManageWindowsMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopHeaderManageWindowsMenu.kt
index ff52a45..575aac38 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopHeaderManageWindowsMenu.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopHeaderManageWindowsMenu.kt
@@ -74,8 +74,7 @@
override fun addToContainer(menuView: ManageWindowsView) {
val menuPosition = Point(x, y)
val flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or
- WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH or
- WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
+ WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
val desktopRepository = desktopUserRepositories.getProfile(callerTaskInfo.userId)
menuViewContainer = if (Flags.enableFullyImmersiveInDesktop()
&& desktopRepository.isTaskInFullImmersiveState(callerTaskInfo.taskId)) {
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 67dae28..51b0291 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
@@ -34,6 +34,7 @@
import static com.android.window.flags.Flags.enableDisplayFocusInShellTransitions;
import static com.android.wm.shell.compatui.AppCompatUtils.isTopActivityExemptFromDesktopWindowing;
import static com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.InputMethod;
+import static com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.MinimizeReason;
import static com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger;
import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR;
import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_LEFT_INDICATOR;
@@ -99,6 +100,7 @@
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;
@@ -126,6 +128,8 @@
import com.android.wm.shell.desktopmode.education.AppHandleEducationController;
import com.android.wm.shell.desktopmode.education.AppToWebEducationController;
import com.android.wm.shell.freeform.FreeformTaskTransitionStarter;
+import com.android.wm.shell.recents.RecentsTransitionHandler;
+import com.android.wm.shell.recents.RecentsTransitionStateListener;
import com.android.wm.shell.shared.FocusTransitionListener;
import com.android.wm.shell.shared.annotations.ShellBackgroundThread;
import com.android.wm.shell.shared.annotations.ShellMainThread;
@@ -157,8 +161,10 @@
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.HashSet;
import java.util.List;
import java.util.Optional;
+import java.util.Set;
import java.util.function.Supplier;
/**
@@ -247,6 +253,7 @@
private final DesktopModeEventLogger mDesktopModeEventLogger;
private final DesktopModeUiEventLogger mDesktopModeUiEventLogger;
private final WindowDecorTaskResourceLoader mTaskResourceLoader;
+ private final RecentsTransitionHandler mRecentsTransitionHandler;
public DesktopModeWindowDecorViewModel(
Context context,
@@ -282,7 +289,8 @@
FocusTransitionObserver focusTransitionObserver,
DesktopModeEventLogger desktopModeEventLogger,
DesktopModeUiEventLogger desktopModeUiEventLogger,
- WindowDecorTaskResourceLoader taskResourceLoader) {
+ WindowDecorTaskResourceLoader taskResourceLoader,
+ RecentsTransitionHandler recentsTransitionHandler) {
this(
context,
shellExecutor,
@@ -323,7 +331,8 @@
focusTransitionObserver,
desktopModeEventLogger,
desktopModeUiEventLogger,
- taskResourceLoader);
+ taskResourceLoader,
+ recentsTransitionHandler);
}
@VisibleForTesting
@@ -367,7 +376,8 @@
FocusTransitionObserver focusTransitionObserver,
DesktopModeEventLogger desktopModeEventLogger,
DesktopModeUiEventLogger desktopModeUiEventLogger,
- WindowDecorTaskResourceLoader taskResourceLoader) {
+ WindowDecorTaskResourceLoader taskResourceLoader,
+ RecentsTransitionHandler recentsTransitionHandler) {
mContext = context;
mMainExecutor = shellExecutor;
mMainHandler = mainHandler;
@@ -436,6 +446,7 @@
mDesktopModeEventLogger = desktopModeEventLogger;
mDesktopModeUiEventLogger = desktopModeUiEventLogger;
mTaskResourceLoader = taskResourceLoader;
+ mRecentsTransitionHandler = recentsTransitionHandler;
shellInit.addInitCallback(this::onInit, this);
}
@@ -450,6 +461,10 @@
new DesktopModeOnTaskResizeAnimationListener());
mDesktopTasksController.setOnTaskRepositionAnimationListener(
new DesktopModeOnTaskRepositionAnimationListener());
+ if (Flags.enableDesktopRecentsTransitionsCornersBugfix()) {
+ mRecentsTransitionHandler.addTransitionStateListener(
+ new DesktopModeRecentsTransitionStateListener());
+ }
mDisplayController.addDisplayChangingController(mOnDisplayChangingListener);
try {
mWindowManager.registerSystemGestureExclusionListener(mGestureExclusionListener,
@@ -966,7 +981,8 @@
ToggleTaskSizeInteraction.AmbiguousSource.HEADER_BUTTON, mMotionEvent);
}
} else if (id == R.id.minimize_window) {
- mDesktopTasksController.minimizeTask(decoration.mTaskInfo);
+ mDesktopTasksController.minimizeTask(
+ decoration.mTaskInfo, MinimizeReason.MINIMIZE_BUTTON);
}
}
@@ -1627,6 +1643,10 @@
}
private boolean shouldShowWindowDecor(RunningTaskInfo taskInfo) {
+ if (mDisplayController.getDisplay(taskInfo.displayId) == null) {
+ // If DisplayController doesn't have it tracked, it could be a private/managed display.
+ return false;
+ }
if (taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) return true;
if (mSplitScreenController != null
&& mSplitScreenController.isTaskRootOrStageRoot(taskInfo.taskId)) {
@@ -1859,6 +1879,38 @@
}
}
+ private class DesktopModeRecentsTransitionStateListener
+ implements RecentsTransitionStateListener {
+ final Set<Integer> mAnimatingTaskIds = new HashSet<>();
+
+ @Override
+ public void onTransitionStateChanged(int state) {
+ switch (state) {
+ case RecentsTransitionStateListener.TRANSITION_STATE_REQUESTED:
+ for (int n = 0; n < mWindowDecorByTaskId.size(); n++) {
+ int taskId = mWindowDecorByTaskId.keyAt(n);
+ mAnimatingTaskIds.add(taskId);
+ setIsRecentsTransitionRunningForTask(taskId, true);
+ }
+ return;
+ case RecentsTransitionStateListener.TRANSITION_STATE_NOT_RUNNING:
+ // No Recents transition running - clean up window decorations
+ for (int taskId : mAnimatingTaskIds) {
+ setIsRecentsTransitionRunningForTask(taskId, false);
+ }
+ mAnimatingTaskIds.clear();
+ return;
+ default:
+ }
+ }
+
+ private void setIsRecentsTransitionRunningForTask(int taskId, boolean isRecentsRunning) {
+ final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskId);
+ if (decoration == null) return;
+ decoration.setIsRecentsTransitionRunning(isRecentsRunning);
+ }
+ }
+
private class DragEventListenerImpl
implements DragPositioningCallbackUtility.DragEventListener {
@Override
@@ -1881,14 +1933,21 @@
// 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/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index 4ac8954..39a989c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -204,6 +204,7 @@
private final MultiInstanceHelper mMultiInstanceHelper;
private final WindowDecorCaptionHandleRepository mWindowDecorCaptionHandleRepository;
private final DesktopUserRepositories mDesktopUserRepositories;
+ private boolean mIsRecentsTransitionRunning = false;
private Runnable mLoadAppInfoRunnable;
private Runnable mSetAppInfoRunnable;
@@ -498,7 +499,7 @@
applyStartTransactionOnDraw, shouldSetTaskVisibilityPositionAndCrop,
mIsStatusBarVisible, mIsKeyguardVisibleAndOccluded, inFullImmersive,
mDisplayController.getInsetsState(taskInfo.displayId), hasGlobalFocus,
- displayExclusionRegion);
+ displayExclusionRegion, mIsRecentsTransitionRunning);
final WindowDecorLinearLayout oldRootView = mResult.mRootView;
final SurfaceControl oldDecorationSurface = mDecorationContainerSurface;
@@ -869,7 +870,8 @@
boolean inFullImmersiveMode,
@NonNull InsetsState displayInsetsState,
boolean hasGlobalFocus,
- @NonNull Region displayExclusionRegion) {
+ @NonNull Region displayExclusionRegion,
+ boolean shouldIgnoreCornerRadius) {
final int captionLayoutId = getDesktopModeWindowDecorLayoutId(taskInfo.getWindowingMode());
final boolean isAppHeader =
captionLayoutId == R.layout.desktop_mode_app_header;
@@ -1006,13 +1008,19 @@
relayoutParams.mWindowDecorConfig = windowDecorConfig;
if (DesktopModeStatus.useRoundedCorners()) {
- relayoutParams.mCornerRadius = taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM
- ? loadDimensionPixelSize(context.getResources(),
- R.dimen.desktop_windowing_freeform_rounded_corner_radius)
- : INVALID_CORNER_RADIUS;
+ relayoutParams.mCornerRadius = shouldIgnoreCornerRadius ? INVALID_CORNER_RADIUS :
+ getCornerRadius(context, relayoutParams.mLayoutResId);
}
}
+ private static int getCornerRadius(@NonNull Context context, int layoutResId) {
+ if (layoutResId == R.layout.desktop_mode_app_header) {
+ return loadDimensionPixelSize(context.getResources(),
+ R.dimen.desktop_windowing_freeform_rounded_corner_radius);
+ }
+ return INVALID_CORNER_RADIUS;
+ }
+
/**
* If task has focused window decor, return the caption id of the fullscreen caption size
* resource. Otherwise, return ID_NULL and caption width be set to task width.
@@ -1740,6 +1748,17 @@
}
/**
+ * Declares whether a Recents transition is currently active.
+ *
+ * <p> When a Recents transition is active we allow that transition to take ownership of the
+ * corner radius of its task surfaces, so each window decoration should stop updating the corner
+ * radius of its task surface during that time.
+ */
+ void setIsRecentsTransitionRunning(boolean isRecentsTransitionRunning) {
+ mIsRecentsTransitionRunning = isRecentsTransitionRunning;
+ }
+
+ /**
* Called when there is a {@link MotionEvent#ACTION_HOVER_EXIT} on the maximize window button.
*/
void onMaximizeButtonHoverExit() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
index 7d1471f..b531079 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
@@ -17,7 +17,6 @@
package com.android.wm.shell.windowdecor;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
-import static android.view.WindowManager.LayoutParams.FLAG_SPLIT_TOUCH;
import static android.view.WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL;
import static android.view.WindowManager.LayoutParams.INPUT_FEATURE_SPY;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
@@ -122,7 +121,7 @@
mDecorationSurface,
mClientToken,
null /* hostInputToken */,
- FLAG_NOT_FOCUSABLE | FLAG_SPLIT_TOUCH,
+ FLAG_NOT_FOCUSABLE,
PRIVATE_FLAG_TRUSTED_OVERLAY,
INPUT_FEATURE_SPY,
TYPE_APPLICATION,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
index 9d73950..e5c989e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
@@ -245,8 +245,7 @@
width = menuWidth,
height = menuHeight,
flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or
- WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH or
- WindowManager.LayoutParams.FLAG_SPLIT_TOUCH,
+ WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH,
view = handleMenuView.rootView,
forciblyShownTypes = if (forceShowSystemBars) { systemBars() } else { 0 },
ignoreCutouts = Flags.showAppHandleLargeScreens()
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt
index cc54d25..1ce0366 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt
@@ -178,8 +178,7 @@
menuHeight,
WindowManager.LayoutParams.TYPE_APPLICATION,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
- or WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
- or WindowManager.LayoutParams.FLAG_SPLIT_TOUCH,
+ or WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH,
PixelFormat.TRANSPARENT
)
lp.title = "Maximize Menu for Task=" + taskInfo.taskId
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 8dc921c..07496eb 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.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 @@
@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 @@
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 @@
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 @@
}
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/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index 5d1bedb..3fcb093 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -22,7 +22,6 @@
import static android.view.WindowInsets.Type.mandatorySystemGestures;
import static android.view.WindowInsets.Type.statusBars;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
-import static android.view.WindowManager.LayoutParams.FLAG_SPLIT_TOUCH;
import static android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
@@ -333,7 +332,7 @@
outResult.mCaptionWidth,
outResult.mCaptionHeight,
TYPE_APPLICATION,
- FLAG_NOT_FOCUSABLE | FLAG_SPLIT_TOUCH,
+ FLAG_NOT_FOCUSABLE,
PixelFormat.TRANSPARENT);
lp.setTitle("Caption of Task=" + mTaskInfo.taskId);
lp.setTrustedOverlay();
@@ -750,7 +749,7 @@
width,
height,
TYPE_APPLICATION,
- FLAG_NOT_FOCUSABLE | FLAG_WATCH_OUTSIDE_TOUCH | FLAG_SPLIT_TOUCH,
+ FLAG_NOT_FOCUSABLE | FLAG_WATCH_OUTSIDE_TOUCH,
PixelFormat.TRANSPARENT);
lp.setTitle("Additional window of Task=" + mTaskInfo.taskId);
lp.setTrustedOverlay();
@@ -967,4 +966,4 @@
return Objects.hash(mToken, mOwner, mFrame, Arrays.hashCode(mBoundingRects), mFlags);
}
}
-}
\ No newline at end of file
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/ReusableWindowDecorViewHost.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/ReusableWindowDecorViewHost.kt
index da41e1b..4a09614 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/ReusableWindowDecorViewHost.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/ReusableWindowDecorViewHost.kt
@@ -24,7 +24,6 @@
import android.view.View
import android.view.WindowManager
import android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
-import android.view.WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
import android.view.WindowManager.LayoutParams.TYPE_APPLICATION
import android.widget.FrameLayout
import androidx.tracing.Trace
@@ -72,7 +71,7 @@
0 /* width*/,
0 /* height */,
TYPE_APPLICATION,
- FLAG_NOT_FOCUSABLE or FLAG_SPLIT_TOUCH,
+ FLAG_NOT_FOCUSABLE,
PixelFormat.TRANSPARENT,
)
.apply {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManager.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManager.kt
index 5832822..fbbf1a5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManager.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManager.kt
@@ -34,7 +34,6 @@
import android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
import android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
import android.view.WindowManager.LayoutParams.FLAG_SLIPPERY
-import android.view.WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
import android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION
import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY
@@ -240,7 +239,6 @@
FLAG_NOT_FOCUSABLE or
FLAG_NOT_TOUCH_MODAL or
FLAG_WATCH_OUTSIDE_TOUCH or
- FLAG_SPLIT_TOUCH or
FLAG_SLIPPERY,
PixelFormat.TRANSLUCENT,
)
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/CloseAllAppsWithBackNavigationLandscape.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/CloseAllAppsWithBackNavigationLandscape.kt
new file mode 100644
index 0000000..0feb13d
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/CloseAllAppsWithBackNavigationLandscape.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker
+
+import android.tools.Rotation
+import android.tools.flicker.FlickerConfig
+import android.tools.flicker.annotation.ExpectedScenarios
+import android.tools.flicker.annotation.FlickerConfigProvider
+import android.tools.flicker.config.FlickerConfig
+import android.tools.flicker.config.FlickerServiceConfig
+import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.DesktopModeFlickerScenarios.Companion.CLOSE_APP
+import com.android.wm.shell.flicker.DesktopModeFlickerScenarios.Companion.CLOSE_LAST_APP
+import com.android.wm.shell.scenarios.CloseAllAppsWithBackNavigation
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class CloseAllAppsWithBackNavigationLandscape : CloseAllAppsWithBackNavigation(
+ Rotation.ROTATION_90
+) {
+ // TODO(b/390043833): Add verifications that TO_BACK transition is captured when the back
+ // navigation button is pressed.
+ @ExpectedScenarios(["CLOSE_APP", "CLOSE_LAST_APP"])
+ @Test
+ override fun closeAllAppsInDesktop() = super.closeAllAppsInDesktop()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig()
+ .use(FlickerServiceConfig.DEFAULT)
+ .use(CLOSE_APP)
+ .use(CLOSE_LAST_APP)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/CloseAllAppsWithBackNavigationPortrait.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/CloseAllAppsWithBackNavigationPortrait.kt
new file mode 100644
index 0000000..3b77ea5
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/CloseAllAppsWithBackNavigationPortrait.kt
@@ -0,0 +1,48 @@
+/*
+ * 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.flicker
+
+import android.tools.flicker.FlickerConfig
+import android.tools.flicker.annotation.ExpectedScenarios
+import android.tools.flicker.annotation.FlickerConfigProvider
+import android.tools.flicker.config.FlickerConfig
+import android.tools.flicker.config.FlickerServiceConfig
+import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.DesktopModeFlickerScenarios.Companion.CLOSE_APP
+import com.android.wm.shell.flicker.DesktopModeFlickerScenarios.Companion.CLOSE_LAST_APP
+import com.android.wm.shell.scenarios.CloseAllAppsWithBackNavigation
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class CloseAllAppsWithBackNavigationPortrait : CloseAllAppsWithBackNavigation() {
+ // TODO(b/390043833): Add verifications that TO_BACK transition is captured when the back
+ // navigation button is pressed.
+ @ExpectedScenarios(["CLOSE_APP", "CLOSE_LAST_APP"])
+ @Test
+ override fun closeAllAppsInDesktop() = super.closeAllAppsInDesktop()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig()
+ .use(FlickerServiceConfig.DEFAULT)
+ .use(CLOSE_APP)
+ .use(CLOSE_LAST_APP)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/CloseAllAppsWithBackNavigation.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/CloseAllAppsWithBackNavigation.kt
new file mode 100644
index 0000000..319df49
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/CloseAllAppsWithBackNavigation.kt
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.scenarios
+
+import android.app.Instrumentation
+import android.tools.NavBar
+import android.tools.Rotation
+import android.tools.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.DesktopModeAppHelper
+import com.android.server.wm.flicker.helpers.MailAppHelper
+import com.android.server.wm.flicker.helpers.NonResizeableAppHelper
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.window.flags.Flags
+import com.android.wm.shell.Utils
+import org.junit.After
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+@Ignore("Base Test Class")
+abstract class CloseAllAppsWithBackNavigation(val rotation: Rotation = Rotation.ROTATION_0) {
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val tapl = LauncherInstrumentation()
+ private val wmHelper = WindowManagerStateHelper(instrumentation)
+ private val device = UiDevice.getInstance(instrumentation)
+ private val testApp = DesktopModeAppHelper(SimpleAppHelper(instrumentation))
+ private val mailApp = DesktopModeAppHelper(MailAppHelper(instrumentation))
+ private val nonResizeableApp = DesktopModeAppHelper(NonResizeableAppHelper(instrumentation))
+
+ @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
+
+ @Before
+ fun setup() {
+ Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet)
+ Assume.assumeTrue(Flags.enableDesktopWindowingBackNavigation())
+ tapl.setEnableRotation(true)
+ tapl.setExpectedRotation(rotation.value)
+ testApp.enterDesktopMode(wmHelper, device)
+ mailApp.launchViaIntent(wmHelper)
+ nonResizeableApp.launchViaIntent(wmHelper)
+ }
+
+ @Test
+ open fun closeAllAppsInDesktop() {
+ nonResizeableApp.closeDesktopApp(wmHelper, device, usingBackNavigation = true)
+ mailApp.closeDesktopApp(wmHelper, device, usingBackNavigation = true)
+ testApp.closeDesktopApp(wmHelper, device, usingBackNavigation = true)
+ }
+
+ @After
+ fun teardown() {
+ testApp.exit(wmHelper)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/OpenUnlimitedApps.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/OpenUnlimitedApps.kt
index 367c4a4..16a01d5 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/OpenUnlimitedApps.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/OpenUnlimitedApps.kt
@@ -17,6 +17,8 @@
package com.android.wm.shell.scenarios
import android.app.Instrumentation
+import android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK
+import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
import android.tools.traces.parsers.WindowManagerStateHelper
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
@@ -43,7 +45,7 @@
private val device = UiDevice.getInstance(instrumentation)
private val testApp = DesktopModeAppHelper(SimpleAppHelper(instrumentation))
- private val mailApp = DesktopModeAppHelper(MailAppHelper(instrumentation))
+ private val mailApp = MailAppHelper(instrumentation)
private val maxNum = DesktopModeStatus.getMaxTaskLimit(instrumentation.context)
@@ -61,7 +63,12 @@
// Launch new [openTaskNum] tasks.
for (i in 1..openTaskNum) {
- mailApp.launchViaIntent(wmHelper)
+ mailApp.launchViaIntent(
+ wmHelper,
+ mailApp.openAppIntent.apply {
+ addFlags(FLAG_ACTIVITY_MULTIPLE_TASK or FLAG_ACTIVITY_NEW_TASK)
+ }
+ )
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
index ce640b5..ffcc344 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
@@ -445,6 +445,15 @@
assertThat(update.updatedBubble.showFlyout()).isFalse();
}
+ @Test
+ public void getOrCreateBubble_withIntent_usesCorrectUser() {
+ Intent intent = new Intent();
+ intent.setPackage(mContext.getPackageName());
+ Bubble b = mBubbleData.getOrCreateBubble(intent, UserHandle.of(/* userId= */ 10));
+
+ assertThat(b.getUser().getIdentifier()).isEqualTo(10);
+ }
+
//
// Overflow
//
@@ -1441,12 +1450,6 @@
assertWithMessage("selectedBubble").that(update.selectedBubble).isEqualTo(bubble);
}
- private void assertSelectionCleared() {
- BubbleData.Update update = mUpdateCaptor.getValue();
- assertWithMessage("selectionChanged").that(update.selectionChanged).isTrue();
- assertWithMessage("selectedBubble").that(update.selectedBubble).isNull();
- }
-
private void assertExpandedChangedTo(boolean expected) {
BubbleData.Update update = mUpdateCaptor.getValue();
assertWithMessage("expandedChanged").that(update.expandedChanged).isTrue();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt
index eeb83df..417b43a 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt
@@ -46,6 +46,7 @@
import com.android.wm.shell.sysui.ShellController
import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.taskview.TaskView
+import com.android.wm.shell.taskview.TaskViewRepository
import com.android.wm.shell.taskview.TaskViewTransitions
import com.android.wm.shell.transition.Transitions
import com.google.common.truth.Truth.assertThat
@@ -137,6 +138,7 @@
mainExecutor,
mock<Handler>(),
bgExecutor,
+ mock<TaskViewRepository>(),
mock<TaskViewTransitions>(),
mock<Transitions>(),
mock<SyncTransactionQueue>(),
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 0000000..bd924c2
--- /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 0000000..c8bebf1
--- /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/CloseDesktopTaskTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/CloseDesktopTaskTransitionHandlerTest.kt
index 04f9ada..03aad1c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/CloseDesktopTaskTransitionHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/CloseDesktopTaskTransitionHandlerTest.kt
@@ -21,6 +21,7 @@
import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
import android.app.WindowConfiguration.WindowingMode
+import android.os.Handler
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
import android.view.SurfaceControl
@@ -52,6 +53,7 @@
@Mock lateinit var testExecutor: ShellExecutor
@Mock lateinit var closingTaskLeash: SurfaceControl
+ @Mock lateinit var mockHandler: Handler
private val transactionSupplier = Supplier { mock<SurfaceControl.Transaction>() }
@@ -65,6 +67,7 @@
testExecutor,
testExecutor,
transactionSupplier,
+ mockHandler,
)
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandlerTest.kt
index 413e7bc..016e040 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandlerTest.kt
@@ -46,6 +46,7 @@
import com.android.wm.shell.TestShellExecutor
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.DisplayLayout
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.MinimizeReason
import com.android.wm.shell.desktopmode.DesktopTestHelpers.createFreeformTask
import com.android.wm.shell.desktopmode.common.ToggleTaskSizeInteraction
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
@@ -294,7 +295,7 @@
testExecutor.flushAll()
assertThat(result).isTrue()
- verify(desktopTasksController).minimizeTask(task)
+ verify(desktopTasksController).minimizeTask(task, MinimizeReason.KEY_GESTURE)
}
private fun setUpFreeformTask(
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt
index 4317143..a9ebcef 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt
@@ -42,6 +42,7 @@
import androidx.test.filters.SmallTest
import com.android.dx.mockito.inline.extended.ExtendedMockito
import com.android.modules.utils.testing.ExtendedMockitoRule
+import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.EnterReason
@@ -62,6 +63,7 @@
import com.android.wm.shell.transition.TransitionInfoBuilder
import com.android.wm.shell.transition.Transitions
import com.android.wm.shell.transition.Transitions.TRANSIT_MINIMIZE
+import java.util.Optional
import kotlin.test.assertFalse
import kotlin.test.assertTrue
import org.junit.Before
@@ -69,6 +71,7 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
+import org.mockito.Mockito.`when`
import org.mockito.kotlin.any
import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
@@ -102,6 +105,8 @@
private val mockShellInit = mock<ShellInit>()
private val transitions = mock<Transitions>()
private val context = mock<Context>()
+ private val shellTaskOrganizer = mock<ShellTaskOrganizer>()
+ private val desktopTasksLimiter = mock<DesktopTasksLimiter>()
private lateinit var transitionObserver: DesktopModeLoggerTransitionObserver
private lateinit var shellInit: ShellInit
@@ -119,6 +124,8 @@
mockShellInit,
transitions,
desktopModeEventLogger,
+ Optional.of(desktopTasksLimiter),
+ shellTaskOrganizer,
)
val initRunnableCaptor = ArgumentCaptor.forClass(Runnable::class.java)
verify(mockShellInit)
@@ -755,6 +762,39 @@
verify(desktopModeEventLogger, never()).logSessionExit(any())
}
+ @Test
+ fun onTransitionReady_taskIsBeingMinimized_logsTaskMinimized() {
+ transitionObserver.isSessionActive = true
+ transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM, id = 1))
+ val taskInfo2 = createTaskInfo(WINDOWING_MODE_FREEFORM, id = 2)
+ transitionObserver.addTaskInfosToCachedMap(taskInfo2)
+ val transitionInfo =
+ TransitionInfoBuilder(TRANSIT_TO_BACK, 0)
+ .addChange(createChange(TRANSIT_TO_BACK, taskInfo2))
+ .build()
+ `when`(desktopTasksLimiter.getMinimizingTask(any()))
+ .thenReturn(
+ DesktopTasksLimiter.TaskDetails(
+ taskInfo2.displayId,
+ taskInfo2.taskId,
+ minimizeReason = MinimizeReason.TASK_LIMIT,
+ )
+ )
+
+ callOnTransitionReady(transitionInfo)
+
+ verify(desktopModeEventLogger, times(1))
+ .logTaskRemoved(
+ eq(
+ DEFAULT_TASK_UPDATE.copy(
+ instanceId = 2,
+ visibleTaskCount = 1,
+ minimizeReason = MinimizeReason.TASK_LIMIT,
+ )
+ )
+ )
+ }
+
/** Simulate calling the onTransitionReady() method */
private fun callOnTransitionReady(transitionInfo: TransitionInfo) {
val transition = mock<IBinder>()
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt
index 6003a21..8d73f3f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt
@@ -1046,14 +1046,14 @@
}
@Test
- fun removeDesktop_multipleTasks_removesAll() {
+ fun removeDesk_multipleTasks_removesAll() {
// The front-most task will be the one added last through `addTask`.
repo.addTask(displayId = DEFAULT_DISPLAY, taskId = 3, isVisible = true)
repo.addTask(displayId = DEFAULT_DISPLAY, taskId = 2, isVisible = true)
repo.addTask(displayId = DEFAULT_DISPLAY, taskId = 1, isVisible = true)
repo.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = 2)
- val tasksBeforeRemoval = repo.removeDesktop(displayId = DEFAULT_DISPLAY)
+ val tasksBeforeRemoval = repo.removeDesk(displayId = DEFAULT_DISPLAY)
assertThat(tasksBeforeRemoval).containsExactly(1, 2, 3).inOrder()
assertThat(repo.getActiveTasks(displayId = DEFAULT_DISPLAY)).isEmpty()
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 f549b056..6c4f043 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
@@ -102,6 +102,7 @@
import com.android.wm.shell.common.SyncTransactionQueue
import com.android.wm.shell.desktopmode.DesktopImmersiveController.ExitResult
import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.InputMethod
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.MinimizeReason
import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger
import com.android.wm.shell.desktopmode.DesktopTasksController.DesktopModeEntryExitTransitionListener
import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition
@@ -257,6 +258,7 @@
// 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 +318,8 @@
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 +1147,21 @@
}
@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()
@@ -1800,8 +1819,35 @@
}
@Test
+ @EnableFlags(
+ FLAG_ENABLE_PER_DISPLAY_DESKTOP_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER,
+ )
+ fun moveToNextDisplay_wallpaperOnSystemUser_reorderWallpaperToBack() {
+ // Set up two display ids
+ whenever(rootTaskDisplayAreaOrganizer.displayIds)
+ .thenReturn(intArrayOf(DEFAULT_DISPLAY, SECOND_DISPLAY))
+ // Create a mock for the target display area: second display
+ val secondDisplayArea = DisplayAreaInfo(MockToken().token(), SECOND_DISPLAY, 0)
+ whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(SECOND_DISPLAY))
+ .thenReturn(secondDisplayArea)
+ // Add a task and a wallpaper
+ val task = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+
+ controller.moveToNextDisplay(task.taskId)
+
+ with(getLatestWct(type = TRANSIT_CHANGE)) {
+ val wallpaperChange =
+ hierarchyOps.find { op -> op.container == wallpaperToken.asBinder() }
+ assertNotNull(wallpaperChange)
+ assertThat(wallpaperChange.type).isEqualTo(HIERARCHY_OP_TYPE_REORDER)
+ }
+ }
+
+ @Test
@EnableFlags(FLAG_ENABLE_PER_DISPLAY_DESKTOP_WALLPAPER_ACTIVITY)
- fun moveToNextDisplay_removeWallpaper() {
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER)
+ fun moveToNextDisplay_wallpaperNotOnSystemUser_removeWallpaper() {
// Set up two display ids
whenever(rootTaskDisplayAreaOrganizer.displayIds)
.thenReturn(intArrayOf(DEFAULT_DISPLAY, SECOND_DISPLAY))
@@ -2117,7 +2163,7 @@
whenever(transitions.dispatchRequest(any(), any(), anyOrNull()))
.thenReturn(android.util.Pair(handler, WindowContainerTransaction()))
- controller.minimizeTask(pipTask)
+ controller.minimizeTask(pipTask, MinimizeReason.MINIMIZE_BUTTON)
verifyExitDesktopWCTNotExecuted()
taskRepository.setTaskInPip(DEFAULT_DISPLAY, pipTask.taskId, enterPip = false)
@@ -2137,7 +2183,7 @@
whenever(freeformTaskTransitionStarter.startMinimizedModeTransition(any()))
.thenReturn(transition)
- controller.minimizeTask(task)
+ controller.minimizeTask(task, MinimizeReason.MINIMIZE_BUTTON)
val captor = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
verify(freeformTaskTransitionStarter).startMinimizedModeTransition(captor.capture())
@@ -2153,7 +2199,7 @@
whenever(transitions.dispatchRequest(any(), any(), anyOrNull()))
.thenReturn(android.util.Pair(handler, WindowContainerTransaction()))
- controller.minimizeTask(task)
+ controller.minimizeTask(task, MinimizeReason.MINIMIZE_BUTTON)
verify(freeformTaskTransitionStarter).startPipTransition(any())
verify(freeformTaskTransitionStarter, never()).startMinimizedModeTransition(any())
@@ -2165,7 +2211,7 @@
whenever(freeformTaskTransitionStarter.startMinimizedModeTransition(any()))
.thenReturn(Binder())
- controller.minimizeTask(task)
+ controller.minimizeTask(task, MinimizeReason.MINIMIZE_BUTTON)
verify(freeformTaskTransitionStarter).startMinimizedModeTransition(any())
verify(freeformTaskTransitionStarter, never()).startPipTransition(any())
@@ -2178,7 +2224,7 @@
whenever(transitions.dispatchRequest(any(), any(), anyOrNull()))
.thenReturn(android.util.Pair(handler, WindowContainerTransaction()))
- controller.minimizeTask(task)
+ controller.minimizeTask(task, MinimizeReason.MINIMIZE_BUTTON)
val captor = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
verify(freeformTaskTransitionStarter).startPipTransition(captor.capture())
@@ -2194,7 +2240,7 @@
whenever(freeformTaskTransitionStarter.startMinimizedModeTransition(any()))
.thenReturn(transition)
- controller.minimizeTask(task)
+ controller.minimizeTask(task, MinimizeReason.MINIMIZE_BUTTON)
val captor = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
verify(freeformTaskTransitionStarter).startMinimizedModeTransition(captor.capture())
@@ -2210,7 +2256,7 @@
.thenReturn(transition)
// The only active task is being minimized.
- controller.minimizeTask(task)
+ controller.minimizeTask(task, MinimizeReason.MINIMIZE_BUTTON)
val captor = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
verify(freeformTaskTransitionStarter).startMinimizedModeTransition(captor.capture())
@@ -2227,7 +2273,7 @@
taskRepository.minimizeTask(DEFAULT_DISPLAY, task.taskId)
// The only active task is already minimized.
- controller.minimizeTask(task)
+ controller.minimizeTask(task, MinimizeReason.MINIMIZE_BUTTON)
val captor = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
verify(freeformTaskTransitionStarter).startMinimizedModeTransition(captor.capture())
@@ -2244,7 +2290,7 @@
whenever(freeformTaskTransitionStarter.startMinimizedModeTransition(any()))
.thenReturn(transition)
- controller.minimizeTask(task1)
+ controller.minimizeTask(task1, MinimizeReason.MINIMIZE_BUTTON)
val captor = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
verify(freeformTaskTransitionStarter).startMinimizedModeTransition(captor.capture())
@@ -2264,7 +2310,7 @@
taskRepository.minimizeTask(DEFAULT_DISPLAY, task2.taskId)
// task1 is the only visible task as task2 is minimized.
- controller.minimizeTask(task1)
+ controller.minimizeTask(task1, MinimizeReason.MINIMIZE_BUTTON)
// Adds remove wallpaper operation
val captor = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
verify(freeformTaskTransitionStarter).startMinimizedModeTransition(captor.capture())
@@ -2279,7 +2325,7 @@
whenever(freeformTaskTransitionStarter.startMinimizedModeTransition(any()))
.thenReturn(transition)
- controller.minimizeTask(task)
+ controller.minimizeTask(task, MinimizeReason.MINIMIZE_BUTTON)
verify(mMockDesktopImmersiveController).exitImmersiveIfApplicable(any(), eq(task), any())
}
@@ -2296,7 +2342,7 @@
ExitResult.Exit(exitingTask = task.taskId, runOnTransitionStart = runOnTransit)
)
- controller.minimizeTask(task)
+ controller.minimizeTask(task, MinimizeReason.MINIMIZE_BUTTON)
assertThat(runOnTransit.invocations).isEqualTo(1)
assertThat(runOnTransit.lastInvoked).isEqualTo(transition)
@@ -3240,7 +3286,7 @@
whenever(transitions.dispatchRequest(any(), any(), anyOrNull()))
.thenReturn(android.util.Pair(handler, WindowContainerTransaction()))
- controller.minimizeTask(pipTask)
+ controller.minimizeTask(pipTask, MinimizeReason.MINIMIZE_BUTTON)
verifyExitDesktopWCTNotExecuted()
freeformTask.isFocused = true
@@ -3561,6 +3607,45 @@
}
@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)
@@ -5135,7 +5220,7 @@
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/desktopmode/DesktopTasksLimiterTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt
index c8214b3..acfe1e9 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt
@@ -20,6 +20,7 @@
import android.graphics.Rect
import android.os.Binder
import android.os.Handler
+import android.os.IBinder
import android.os.UserManager
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
@@ -43,6 +44,7 @@
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.common.ShellExecutor
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.MinimizeReason
import com.android.wm.shell.desktopmode.DesktopTestHelpers.createFreeformTask
import com.android.wm.shell.desktopmode.persistence.DesktopPersistentRepository
import com.android.wm.shell.desktopmode.persistence.DesktopRepositoryInitializer
@@ -180,7 +182,7 @@
val task = setUpFreeformTask()
markTaskHidden(task)
- desktopTasksLimiter.addPendingMinimizeChange(Binder(), displayId = 1, taskId = task.taskId)
+ addPendingMinimizeChange(Binder(), displayId = 1, taskId = task.taskId)
assertThat(desktopTaskRepo.isMinimizedTask(taskId = task.taskId)).isFalse()
}
@@ -208,11 +210,7 @@
val taskTransition = Binder()
val task = setUpFreeformTask()
markTaskHidden(task)
- desktopTasksLimiter.addPendingMinimizeChange(
- pendingTransition,
- displayId = DEFAULT_DISPLAY,
- taskId = task.taskId,
- )
+ addPendingMinimizeChange(pendingTransition, taskId = task.taskId)
desktopTasksLimiter
.getTransitionObserver()
@@ -231,11 +229,7 @@
val transition = Binder()
val task = setUpFreeformTask()
markTaskVisible(task)
- desktopTasksLimiter.addPendingMinimizeChange(
- transition,
- displayId = DEFAULT_DISPLAY,
- taskId = task.taskId,
- )
+ addPendingMinimizeChange(transition, taskId = task.taskId)
desktopTasksLimiter
.getTransitionObserver()
@@ -254,11 +248,7 @@
val transition = Binder()
val task = setUpFreeformTask()
markTaskHidden(task)
- desktopTasksLimiter.addPendingMinimizeChange(
- transition,
- displayId = DEFAULT_DISPLAY,
- taskId = task.taskId,
- )
+ addPendingMinimizeChange(transition, taskId = task.taskId)
desktopTasksLimiter
.getTransitionObserver()
@@ -276,11 +266,7 @@
fun onTransitionReady_pendingTransition_changeTaskToBack_taskIsMinimized() {
val transition = Binder()
val task = setUpFreeformTask()
- desktopTasksLimiter.addPendingMinimizeChange(
- transition,
- displayId = DEFAULT_DISPLAY,
- taskId = task.taskId,
- )
+ addPendingMinimizeChange(transition, taskId = task.taskId)
desktopTasksLimiter
.getTransitionObserver()
@@ -299,11 +285,7 @@
val bounds = Rect(0, 0, 200, 200)
val transition = Binder()
val task = setUpFreeformTask()
- desktopTasksLimiter.addPendingMinimizeChange(
- transition,
- displayId = DEFAULT_DISPLAY,
- taskId = task.taskId,
- )
+ addPendingMinimizeChange(transition, taskId = task.taskId)
val change =
TransitionInfo.Change(task.token, mock(SurfaceControl::class.java)).apply {
@@ -330,11 +312,7 @@
val mergedTransition = Binder()
val newTransition = Binder()
val task = setUpFreeformTask()
- desktopTasksLimiter.addPendingMinimizeChange(
- mergedTransition,
- displayId = DEFAULT_DISPLAY,
- taskId = task.taskId,
- )
+ addPendingMinimizeChange(mergedTransition, taskId = task.taskId)
desktopTasksLimiter
.getTransitionObserver()
.onTransitionMerged(mergedTransition, newTransition)
@@ -541,11 +519,7 @@
(1..<MAX_TASK_LIMIT).forEach { _ -> setUpFreeformTask() }
val transition = Binder()
val task = setUpFreeformTask()
- desktopTasksLimiter.addPendingMinimizeChange(
- transition,
- displayId = DEFAULT_DISPLAY,
- taskId = task.taskId,
- )
+ addPendingMinimizeChange(transition, taskId = task.taskId)
desktopTasksLimiter
.getTransitionObserver()
@@ -573,11 +547,7 @@
(1..<MAX_TASK_LIMIT).forEach { _ -> setUpFreeformTask() }
val transition = Binder()
val task = setUpFreeformTask()
- desktopTasksLimiter.addPendingMinimizeChange(
- transition,
- displayId = DEFAULT_DISPLAY,
- taskId = task.taskId,
- )
+ addPendingMinimizeChange(transition, taskId = task.taskId)
desktopTasksLimiter
.getTransitionObserver()
@@ -606,11 +576,7 @@
val mergedTransition = Binder()
val newTransition = Binder()
val task = setUpFreeformTask()
- desktopTasksLimiter.addPendingMinimizeChange(
- mergedTransition,
- displayId = DEFAULT_DISPLAY,
- taskId = task.taskId,
- )
+ addPendingMinimizeChange(mergedTransition, taskId = task.taskId)
desktopTasksLimiter
.getTransitionObserver()
@@ -633,6 +599,60 @@
verify(interactionJankMonitor).end(eq(CUJ_DESKTOP_MODE_MINIMIZE_WINDOW))
}
+ @Test
+ fun getMinimizingTask_noPendingTransition_returnsNull() {
+ val transition = Binder()
+
+ assertThat(desktopTasksLimiter.getMinimizingTask(transition)).isNull()
+ }
+
+ @Test
+ fun getMinimizingTask_pendingTaskTransition_returnsTask() {
+ val transition = Binder()
+ val task = setUpFreeformTask()
+ addPendingMinimizeChange(
+ transition,
+ taskId = task.taskId,
+ minimizeReason = MinimizeReason.TASK_LIMIT,
+ )
+
+ assertThat(desktopTasksLimiter.getMinimizingTask(transition))
+ .isEqualTo(
+ createTaskDetails(taskId = task.taskId, minimizeReason = MinimizeReason.TASK_LIMIT)
+ )
+ }
+
+ @Test
+ fun getMinimizingTask_activeTaskTransition_returnsTask() {
+ val transition = Binder()
+ val task = setUpFreeformTask()
+ addPendingMinimizeChange(
+ transition,
+ taskId = task.taskId,
+ minimizeReason = MinimizeReason.TASK_LIMIT,
+ )
+ val transitionInfo =
+ TransitionInfoBuilder(TRANSIT_OPEN).addChange(TRANSIT_TO_BACK, task).build()
+
+ desktopTasksLimiter
+ .getTransitionObserver()
+ .onTransitionReady(
+ transition,
+ transitionInfo,
+ /* startTransaction= */ StubTransaction(),
+ /* finishTransaction= */ StubTransaction(),
+ )
+
+ assertThat(desktopTasksLimiter.getMinimizingTask(transition))
+ .isEqualTo(
+ createTaskDetails(
+ taskId = task.taskId,
+ transitionInfo = transitionInfo,
+ minimizeReason = MinimizeReason.TASK_LIMIT,
+ )
+ )
+ }
+
private fun setUpFreeformTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo {
val task = createFreeformTask(displayId)
`when`(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task)
@@ -640,6 +660,20 @@
return task
}
+ private fun createTaskDetails(
+ displayId: Int = DEFAULT_DISPLAY,
+ taskId: Int,
+ transitionInfo: TransitionInfo? = null,
+ minimizeReason: MinimizeReason? = null,
+ ) = DesktopTasksLimiter.TaskDetails(displayId, taskId, transitionInfo, minimizeReason)
+
+ fun addPendingMinimizeChange(
+ transition: IBinder,
+ displayId: Int = DEFAULT_DISPLAY,
+ taskId: Int,
+ minimizeReason: MinimizeReason = MinimizeReason.TASK_LIMIT,
+ ) = desktopTasksLimiter.addPendingMinimizeChange(transition, displayId, taskId, minimizeReason)
+
private fun markTaskVisible(task: RunningTaskInfo) {
desktopTaskRepo.updateTask(task.displayId, task.taskId, isVisible = true)
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipAlphaAnimatorTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipAlphaAnimatorTest.java
index 9cc18ff..607e6a4 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipAlphaAnimatorTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipAlphaAnimatorTest.java
@@ -19,6 +19,8 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
@@ -33,6 +35,7 @@
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
+import com.android.wm.shell.R;
import com.android.wm.shell.pip2.PipSurfaceTransactionHelper;
import org.junit.Before;
@@ -48,6 +51,8 @@
@TestableLooper.RunWithLooper
@RunWith(AndroidTestingRunner.class)
public class PipAlphaAnimatorTest {
+ private static final float TEST_CORNER_RADIUS = 1f;
+ private static final float TEST_SHADOW_RADIUS = 2f;
@Mock private Context mMockContext;
@@ -55,7 +60,9 @@
@Mock private PipSurfaceTransactionHelper.SurfaceControlTransactionFactory mMockFactory;
- @Mock private SurfaceControl.Transaction mMockTransaction;
+ @Mock private SurfaceControl.Transaction mMockAnimateTransaction;
+ @Mock private SurfaceControl.Transaction mMockStartTransaction;
+ @Mock private SurfaceControl.Transaction mMockFinishTransaction;
@Mock private Runnable mMockStartCallback;
@@ -69,9 +76,15 @@
MockitoAnnotations.initMocks(this);
when(mMockContext.getResources()).thenReturn(mMockResources);
when(mMockResources.getInteger(anyInt())).thenReturn(0);
- when(mMockFactory.getTransaction()).thenReturn(mMockTransaction);
- when(mMockTransaction.setAlpha(any(SurfaceControl.class), anyFloat()))
- .thenReturn(mMockTransaction);
+ when(mMockFactory.getTransaction()).thenReturn(mMockAnimateTransaction);
+ when(mMockResources.getDimensionPixelSize(R.dimen.pip_corner_radius))
+ .thenReturn((int) TEST_CORNER_RADIUS);
+ when(mMockResources.getDimensionPixelSize(R.dimen.pip_shadow_radius))
+ .thenReturn((int) TEST_SHADOW_RADIUS);
+
+ prepareTransaction(mMockAnimateTransaction);
+ prepareTransaction(mMockStartTransaction);
+ prepareTransaction(mMockFinishTransaction);
mTestLeash = new SurfaceControl.Builder()
.setContainerLayer()
@@ -82,8 +95,8 @@
@Test
public void setAnimationStartCallback_fadeInAnimator_callbackStartCallback() {
- mPipAlphaAnimator = new PipAlphaAnimator(mMockContext, mTestLeash, mMockTransaction,
- PipAlphaAnimator.FADE_IN);
+ mPipAlphaAnimator = new PipAlphaAnimator(mMockContext, mTestLeash, mMockStartTransaction,
+ mMockFinishTransaction, PipAlphaAnimator.FADE_IN);
mPipAlphaAnimator.setAnimationStartCallback(mMockStartCallback);
mPipAlphaAnimator.setAnimationEndCallback(mMockEndCallback);
@@ -98,8 +111,8 @@
@Test
public void setAnimationEndCallback_fadeInAnimator_callbackStartAndEndCallback() {
- mPipAlphaAnimator = new PipAlphaAnimator(mMockContext, mTestLeash, mMockTransaction,
- PipAlphaAnimator.FADE_IN);
+ mPipAlphaAnimator = new PipAlphaAnimator(mMockContext, mTestLeash, mMockStartTransaction,
+ mMockFinishTransaction, PipAlphaAnimator.FADE_IN);
mPipAlphaAnimator.setAnimationStartCallback(mMockStartCallback);
mPipAlphaAnimator.setAnimationEndCallback(mMockEndCallback);
@@ -109,36 +122,98 @@
});
verify(mMockStartCallback).run();
- verify(mMockStartCallback).run();
+ verify(mMockEndCallback).run();
+ }
+
+ @Test
+ public void onAnimationStart_setCornerAndShadowRadii() {
+ mPipAlphaAnimator = new PipAlphaAnimator(mMockContext, mTestLeash, mMockStartTransaction,
+ mMockFinishTransaction, PipAlphaAnimator.FADE_IN);
+ mPipAlphaAnimator.setSurfaceControlTransactionFactory(mMockFactory);
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+ mPipAlphaAnimator.start();
+ mPipAlphaAnimator.pause();
+ });
+
+ verify(mMockStartTransaction, atLeastOnce())
+ .setCornerRadius(eq(mTestLeash), eq(TEST_CORNER_RADIUS));
+ verify(mMockStartTransaction, atLeastOnce())
+ .setShadowRadius(eq(mTestLeash), eq(TEST_SHADOW_RADIUS));
+ }
+
+ @Test
+ public void onAnimationUpdate_setCornerAndShadowRadii() {
+ mPipAlphaAnimator = new PipAlphaAnimator(mMockContext, mTestLeash, mMockStartTransaction,
+ mMockFinishTransaction, PipAlphaAnimator.FADE_IN);
+ mPipAlphaAnimator.setSurfaceControlTransactionFactory(mMockFactory);
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+ mPipAlphaAnimator.start();
+ mPipAlphaAnimator.pause();
+ });
+
+ verify(mMockAnimateTransaction, atLeastOnce())
+ .setCornerRadius(eq(mTestLeash), eq(TEST_CORNER_RADIUS));
+ verify(mMockAnimateTransaction, atLeastOnce())
+ .setShadowRadius(eq(mTestLeash), eq(TEST_SHADOW_RADIUS));
+ }
+
+ @Test
+ public void onAnimationEnd_setCornerAndShadowRadii() {
+ mPipAlphaAnimator = new PipAlphaAnimator(mMockContext, mTestLeash, mMockStartTransaction,
+ mMockFinishTransaction, PipAlphaAnimator.FADE_IN);
+ mPipAlphaAnimator.setSurfaceControlTransactionFactory(mMockFactory);
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+ mPipAlphaAnimator.start();
+ mPipAlphaAnimator.end();
+ });
+
+ verify(mMockFinishTransaction, atLeastOnce())
+ .setCornerRadius(eq(mTestLeash), eq(TEST_CORNER_RADIUS));
+ verify(mMockFinishTransaction, atLeastOnce())
+ .setShadowRadius(eq(mTestLeash), eq(TEST_SHADOW_RADIUS));
}
@Test
public void onAnimationEnd_fadeInAnimator_leashVisibleAtEnd() {
- mPipAlphaAnimator = new PipAlphaAnimator(mMockContext, mTestLeash, mMockTransaction,
- PipAlphaAnimator.FADE_IN);
+ mPipAlphaAnimator = new PipAlphaAnimator(mMockContext, mTestLeash, mMockStartTransaction,
+ mMockFinishTransaction, PipAlphaAnimator.FADE_IN);
mPipAlphaAnimator.setSurfaceControlTransactionFactory(mMockFactory);
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
mPipAlphaAnimator.start();
- clearInvocations(mMockTransaction);
+ clearInvocations(mMockAnimateTransaction);
mPipAlphaAnimator.end();
});
- verify(mMockTransaction).setAlpha(mTestLeash, 1.0f);
+ verify(mMockAnimateTransaction).setAlpha(mTestLeash, 1.0f);
}
@Test
public void onAnimationEnd_fadeOutAnimator_leashInvisibleAtEnd() {
- mPipAlphaAnimator = new PipAlphaAnimator(mMockContext, mTestLeash, mMockTransaction,
- PipAlphaAnimator.FADE_OUT);
+ mPipAlphaAnimator = new PipAlphaAnimator(mMockContext, mTestLeash, mMockStartTransaction,
+ mMockFinishTransaction, PipAlphaAnimator.FADE_OUT);
mPipAlphaAnimator.setSurfaceControlTransactionFactory(mMockFactory);
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
mPipAlphaAnimator.start();
- clearInvocations(mMockTransaction);
+ clearInvocations(mMockAnimateTransaction);
mPipAlphaAnimator.end();
});
- verify(mMockTransaction).setAlpha(mTestLeash, 0f);
+ verify(mMockAnimateTransaction).setAlpha(mTestLeash, 0f);
+ }
+
+
+ // set up transaction chaining
+ private void prepareTransaction(SurfaceControl.Transaction tx) {
+ when(tx.setAlpha(any(SurfaceControl.class), anyFloat()))
+ .thenReturn(tx);
+ when(tx.setCornerRadius(any(SurfaceControl.class), anyFloat()))
+ .thenReturn(tx);
+ when(tx.setShadowRadius(any(SurfaceControl.class), anyFloat()))
+ .thenReturn(tx);
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipSchedulerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipSchedulerTest.java
index aef44a4..bd857c7 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipSchedulerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipSchedulerTest.java
@@ -111,7 +111,7 @@
mRootTaskDisplayAreaOrganizer);
mPipScheduler.setPipTransitionController(mMockPipTransitionController);
mPipScheduler.setSurfaceControlTransactionFactory(mMockFactory);
- mPipScheduler.setPipAlphaAnimatorSupplier((context, leash, tx, direction) ->
+ mPipScheduler.setPipAlphaAnimatorSupplier((context, leash, startTx, finishTx, direction) ->
mMockAlphaAnimator);
SurfaceControl testLeash = new SurfaceControl.Builder()
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 7e5d6ce..065fa21 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
@@ -20,9 +20,9 @@
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
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;
@@ -48,9 +48,10 @@
import static org.mockito.Mockito.when;
import static java.lang.Integer.MAX_VALUE;
+import static java.util.stream.Collectors.joining;
-import android.app.ActivityManager;
import android.app.ActivityManager.RecentTaskInfo;
+import android.app.ActivityManager.RunningTaskInfo;
import android.app.ActivityTaskManager;
import android.app.KeyguardManager;
import android.content.ComponentName;
@@ -65,8 +66,8 @@
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;
@@ -247,13 +248,12 @@
ArrayList<GroupedTaskInfo> recentTasks =
mRecentTasksController.getRecentTasks(MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0);
- assertGroupedTasksListEquals(recentTasks,
- t1.taskId, -1,
- t2.taskId, -1,
- t3.taskId, -1);
+ assertGroupedTasksListEquals(recentTasks, List.of(
+ List.of(t1.taskId),
+ List.of(t2.taskId),
+ List.of(t3.taskId)));
}
- @EnableFlags(FLAG_ENABLE_USE_TOP_VISIBLE_ACTIVITY_FOR_EXCLUDE_FROM_RECENT_TASK)
@Test
public void testGetRecentTasks_removesDesktopWallpaperActivity() {
RecentTaskInfo t1 = makeTaskInfo(1);
@@ -263,7 +263,9 @@
ArrayList<GroupedTaskInfo> recentTasks =
mRecentTasksController.getRecentTasks(MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0);
- assertGroupedTasksListEquals(recentTasks, t1.taskId, -1, t3.taskId, -1);
+ assertGroupedTasksListEquals(recentTasks, List.of(
+ List.of(t1.taskId),
+ List.of(t3.taskId)));
}
@Test
@@ -287,11 +289,11 @@
ArrayList<GroupedTaskInfo> recentTasks =
mRecentTasksController.getRecentTasks(MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0);
- assertGroupedTasksListEquals(recentTasks,
- t1.taskId, -1,
- t2.taskId, t4.taskId,
- t3.taskId, t5.taskId,
- t6.taskId, -1);
+ assertGroupedTasksListEquals(recentTasks, List.of(
+ List.of(t1.taskId),
+ List.of(t2.taskId, t4.taskId),
+ List.of(t3.taskId, t5.taskId),
+ List.of(t6.taskId)));
}
@Test
@@ -321,11 +323,11 @@
consumer);
mMainExecutor.flushAll();
- assertGroupedTasksListEquals(recentTasks[0],
- t1.taskId, -1,
- t2.taskId, t4.taskId,
- t3.taskId, t5.taskId,
- t6.taskId, -1);
+ assertGroupedTasksListEquals(recentTasks[0], List.of(
+ List.of(t1.taskId),
+ List.of(t2.taskId, t4.taskId),
+ List.of(t3.taskId, t5.taskId),
+ List.of(t6.taskId)));
}
@Test
@@ -344,9 +346,9 @@
// 2 freeform tasks should be grouped into one, 3 total recents entries
assertEquals(3, recentTasks.size());
- GroupedTaskInfo freeformGroup = recentTasks.get(0);
- GroupedTaskInfo singleGroup1 = recentTasks.get(1);
- GroupedTaskInfo singleGroup2 = recentTasks.get(2);
+ GroupedTaskInfo singleGroup1 = recentTasks.get(0);
+ GroupedTaskInfo singleGroup2 = recentTasks.get(1);
+ GroupedTaskInfo freeformGroup = recentTasks.get(2);
// Check that groups have expected types
assertTrue(freeformGroup.isBaseType(TYPE_FREEFORM));
@@ -384,8 +386,8 @@
// 2 split screen tasks grouped, 2 freeform tasks grouped, 3 total recents entries
assertEquals(3, recentTasks.size());
GroupedTaskInfo splitGroup = recentTasks.get(0);
- GroupedTaskInfo freeformGroup = recentTasks.get(1);
- GroupedTaskInfo singleGroup = recentTasks.get(2);
+ GroupedTaskInfo singleGroup = recentTasks.get(1);
+ GroupedTaskInfo freeformGroup = recentTasks.get(2);
// Check that groups have expected types
assertTrue(splitGroup.isBaseType(TYPE_SPLIT));
@@ -455,9 +457,9 @@
// 3 freeform tasks should be grouped into one, 2 single tasks, 3 total recents entries
assertEquals(3, recentTasks.size());
- GroupedTaskInfo freeformGroup = recentTasks.get(0);
- GroupedTaskInfo singleGroup1 = recentTasks.get(1);
- GroupedTaskInfo singleGroup2 = recentTasks.get(2);
+ GroupedTaskInfo singleGroup1 = recentTasks.get(0);
+ GroupedTaskInfo singleGroup2 = recentTasks.get(1);
+ GroupedTaskInfo freeformGroup = recentTasks.get(2);
// Check that groups have expected types
assertTrue(freeformGroup.isBaseType(TYPE_FREEFORM));
@@ -524,7 +526,7 @@
// Remove one of the tasks and ensure the pair is removed
SurfaceControl mockLeash = mock(SurfaceControl.class);
- ActivityManager.RunningTaskInfo rt2 = makeRunningTaskInfo(2);
+ RunningTaskInfo rt2 = makeRunningTaskInfo(2);
mShellTaskOrganizer.onTaskAppeared(rt2, mockLeash);
mShellTaskOrganizer.onTaskVanished(rt2);
@@ -538,13 +540,13 @@
// Remove one of the tasks and ensure the pair is removed
SurfaceControl mockLeash = mock(SurfaceControl.class);
- ActivityManager.RunningTaskInfo rt2Fullscreen = makeRunningTaskInfo(2);
+ RunningTaskInfo rt2Fullscreen = makeRunningTaskInfo(2);
rt2Fullscreen.configuration.windowConfiguration.setWindowingMode(
WINDOWING_MODE_FULLSCREEN);
mShellTaskOrganizer.onTaskAppeared(rt2Fullscreen, mockLeash);
// Change the windowing mode and ensure the recent tasks change is notified
- ActivityManager.RunningTaskInfo rt2MultiWIndow = makeRunningTaskInfo(2);
+ RunningTaskInfo rt2MultiWIndow = makeRunningTaskInfo(2);
rt2MultiWIndow.configuration.windowConfiguration.setWindowingMode(
WINDOWING_MODE_MULTI_WINDOW);
mShellTaskOrganizer.onTaskInfoChanged(rt2MultiWIndow);
@@ -558,7 +560,7 @@
public void onTaskAdded_desktopModeRunningAppsEnabled_triggersOnRunningTaskAppeared()
throws Exception {
mRecentTasksControllerReal.registerRecentTasksListener(mRecentTasksListener);
- ActivityManager.RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
+ RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
mRecentTasksControllerReal.onTaskAdded(taskInfo);
@@ -571,7 +573,7 @@
public void onTaskAdded_desktopModeRunningAppsDisabled_doesNotTriggerOnRunningTaskAppeared()
throws Exception {
mRecentTasksControllerReal.registerRecentTasksListener(mRecentTasksListener);
- ActivityManager.RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
+ RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
mRecentTasksControllerReal.onTaskAdded(taskInfo);
@@ -584,7 +586,7 @@
public void taskWindowingModeChanged_desktopRunningAppsEnabled_triggersOnRunningTaskChanged()
throws Exception {
mRecentTasksControllerReal.registerRecentTasksListener(mRecentTasksListener);
- ActivityManager.RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
+ RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
mRecentTasksControllerReal.onTaskRunningInfoChanged(taskInfo);
@@ -598,7 +600,7 @@
taskWindowingModeChanged_desktopRunningAppsDisabled_doesNotTriggerOnRunningTaskChanged()
throws Exception {
mRecentTasksControllerReal.registerRecentTasksListener(mRecentTasksListener);
- ActivityManager.RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
+ RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
mRecentTasksControllerReal.onTaskRunningInfoChanged(taskInfo);
@@ -611,7 +613,7 @@
public void onTaskRemoved_desktopModeRunningAppsEnabled_triggersOnRunningTaskVanished()
throws Exception {
mRecentTasksControllerReal.registerRecentTasksListener(mRecentTasksListener);
- ActivityManager.RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
+ RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
mRecentTasksControllerReal.onTaskRemoved(taskInfo);
@@ -624,7 +626,7 @@
public void onTaskRemoved_desktopModeRunningAppsDisabled_doesNotTriggerOnRunningTaskVanished()
throws Exception {
mRecentTasksControllerReal.registerRecentTasksListener(mRecentTasksListener);
- ActivityManager.RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
+ RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
mRecentTasksControllerReal.onTaskRemoved(taskInfo);
@@ -633,10 +635,11 @@
@Test
@EnableFlags(Flags.FLAG_ENABLE_TASK_STACK_OBSERVER_IN_SHELL)
+ @DisableFlags(com.android.wm.shell.Flags.FLAG_ENABLE_SHELL_TOP_TASK_TRACKING)
public void onTaskMovedToFront_TaskStackObserverEnabled_triggersOnTaskMovedToFront()
throws Exception {
mRecentTasksControllerReal.registerRecentTasksListener(mRecentTasksListener);
- ActivityManager.RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
+ RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
mRecentTasksControllerReal.onTaskMovedToFrontThroughTransition(taskInfo);
@@ -649,7 +652,7 @@
public void onTaskMovedToFront_TaskStackObserverEnabled_doesNotTriggersOnTaskMovedToFront()
throws Exception {
mRecentTasksControllerReal.registerRecentTasksListener(mRecentTasksListener);
- ActivityManager.RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
+ RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
mRecentTasksControllerReal.onTaskMovedToFront(taskInfo);
@@ -701,7 +704,7 @@
@EnableFlags(com.android.wm.shell.Flags.FLAG_ENABLE_SHELL_TOP_TASK_TRACKING)
public void shellTopTaskTracker_onTaskRemoved_expectNoRecentsChanged() throws Exception {
mRecentTasksControllerReal.registerRecentTasksListener(mRecentTasksListener);
- ActivityManager.RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
+ RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
mRecentTasksControllerReal.onTaskRemoved(taskInfo);
verify(mRecentTasksListener, never()).onRecentTasksChanged();
}
@@ -711,22 +714,105 @@
@EnableFlags(com.android.wm.shell.Flags.FLAG_ENABLE_SHELL_TOP_TASK_TRACKING)
public void shellTopTaskTracker_onVisibleTasksChanged() throws Exception {
mRecentTasksControllerReal.registerRecentTasksListener(mRecentTasksListener);
- ActivityManager.RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
+ RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
mRecentTasksControllerReal.onVisibleTasksChanged(List.of(taskInfo));
verify(mRecentTasksListener, never()).onVisibleTasksChanged(any());
}
+ @Test
+ public void generateList_emptyTaskList_expectNoGroupedTasks() throws Exception {
+ assertTrue(mRecentTasksControllerReal.generateList(List.of(), "test").isEmpty());
+ }
+
+ @Test
+ public void generateList_excludePipTask() throws Exception {
+ RunningTaskInfo task1 = makeRunningTaskInfo(1);
+ RunningTaskInfo pipTask = makeRunningTaskInfo(2);
+ pipTask.configuration.windowConfiguration.setWindowingMode(
+ WINDOWING_MODE_PINNED);
+
+ ArrayList<GroupedTaskInfo> groupedTasks = mRecentTasksControllerReal.generateList(
+ List.of(task1, pipTask),
+ "test");
+
+ assertGroupedTasksListEquals(groupedTasks, List.of(List.of(task1.taskId)));
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_TASK_STACK_OBSERVER_IN_SHELL)
+ @EnableFlags(com.android.wm.shell.Flags.FLAG_ENABLE_SHELL_TOP_TASK_TRACKING)
+ public void generateList_fullscreen_noVisibleTasks_expectNoGrouping() throws Exception {
+ RunningTaskInfo task1 = makeRunningTaskInfo(1);
+ RunningTaskInfo task2 = makeRunningTaskInfo(2);
+ RunningTaskInfo task3 = makeRunningTaskInfo(3);
+ // Reset visible tasks list
+ mRecentTasksControllerReal.onVisibleTasksChanged(List.of());
+
+ // Generate a list with a number of fullscreen tasks
+ ArrayList<GroupedTaskInfo> groupedTasks = mRecentTasksControllerReal.generateList(
+ List.of(task1, task2, task3),
+ "test");
+
+ assertGroupedTasksListEquals(groupedTasks, List.of(
+ List.of(task1.taskId),
+ List.of(task2.taskId),
+ List.of(task3.taskId)));
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_TASK_STACK_OBSERVER_IN_SHELL)
+ @EnableFlags(com.android.wm.shell.Flags.FLAG_ENABLE_SHELL_TOP_TASK_TRACKING)
+ public void generateList_fullscreen_singleVisibleTask_expectNoGrouping() throws Exception {
+ RunningTaskInfo task1 = makeRunningTaskInfo(1);
+ RunningTaskInfo task2 = makeRunningTaskInfo(2);
+ RunningTaskInfo task3 = makeRunningTaskInfo(3);
+
+ // Reset visible tasks list
+ mRecentTasksControllerReal.onVisibleTasksChanged(List.of(task1));
+
+ // Generate a list with a number of fullscreen tasks
+ ArrayList<GroupedTaskInfo> groupedTasks = mRecentTasksControllerReal.generateList(
+ List.of(task1, task2, task3),
+ "test");
+
+ assertGroupedTasksListEquals(groupedTasks, List.of(
+ List.of(task1.taskId),
+ List.of(task2.taskId),
+ List.of(task3.taskId)));
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_TASK_STACK_OBSERVER_IN_SHELL)
+ @EnableFlags(com.android.wm.shell.Flags.FLAG_ENABLE_SHELL_TOP_TASK_TRACKING)
+ public void generateList_fullscreen_multipleVisibleTasks_expectGrouping() throws Exception {
+ RunningTaskInfo task1 = makeRunningTaskInfo(1);
+ RunningTaskInfo task2 = makeRunningTaskInfo(2);
+ RunningTaskInfo task3 = makeRunningTaskInfo(3);
+
+ // Reset visible tasks list
+ mRecentTasksControllerReal.onVisibleTasksChanged(List.of(task1, task2));
+
+ // Generate a list with a number of fullscreen tasks
+ ArrayList<GroupedTaskInfo> groupedTasks = mRecentTasksControllerReal.generateList(
+ List.of(task1, task2, task3),
+ "test");
+
+ assertGroupedTasksListEquals(groupedTasks, List.of(
+ List.of(task1.taskId),
+ List.of(task2.taskId),
+ List.of(task3.taskId)));
+ }
+
/**
* Helper to create a task with a given task id.
*/
private RecentTaskInfo makeTaskInfo(int taskId) {
RecentTaskInfo info = new RecentTaskInfo();
info.taskId = taskId;
-
+ info.realActivity = new ComponentName("testPackage", "testClass");
Intent intent = new Intent();
intent.setComponent(new ComponentName("com." + taskId, "Activity" + taskId));
info.baseIntent = intent;
-
info.lastNonFullscreenBounds = new Rect();
return info;
}
@@ -743,59 +829,63 @@
/**
* Helper to create a running task with a given task id.
*/
- private ActivityManager.RunningTaskInfo makeRunningTaskInfo(int taskId) {
- ActivityManager.RunningTaskInfo info = new ActivityManager.RunningTaskInfo();
+ private RunningTaskInfo makeRunningTaskInfo(int taskId) {
+ RunningTaskInfo info = new RunningTaskInfo();
info.taskId = taskId;
info.realActivity = new ComponentName("testPackage", "testClass");
+ Intent intent = new Intent();
+ intent.setComponent(new ComponentName("com." + taskId, "Activity" + taskId));
+ info.baseIntent = intent;
+ info.lastNonFullscreenBounds = new Rect();
return info;
}
/**
* 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;
}
/**
* Asserts that the recent tasks matches the given task ids.
+ * TODO(346588978): Separate out specific split verification during the iteration below
*
- * @param expectedTaskIds list of task ids that map to the flattened task ids of the tasks in
- * the grouped task list
+ * @param expectedTaskIds a list of expected grouped task ids (itself a list of ints)
*/
- private void assertGroupedTasksListEquals(List<GroupedTaskInfo> recentTasks,
- int... expectedTaskIds) {
- int[] flattenedTaskIds = new int[recentTasks.size() * 2];
- for (int i = 0; i < recentTasks.size(); i++) {
- GroupedTaskInfo pair = recentTasks.get(i);
- int taskId1 = pair.getTaskInfo1().taskId;
- flattenedTaskIds[2 * i] = taskId1;
- flattenedTaskIds[2 * i + 1] = pair.getTaskInfo2() != null
- ? pair.getTaskInfo2().taskId
- : -1;
+ private void assertGroupedTasksListEquals(List<GroupedTaskInfo> groupedTasks,
+ List<List<Integer>> expectedTaskIds) {
+ List<List<Integer>> foundTaskIds = new ArrayList<>();
+ for (int i = 0; i < groupedTasks.size(); i++) {
+ GroupedTaskInfo groupedTask = groupedTasks.get(i);
+ List<Integer> groupedTaskIds = groupedTask.getTaskInfoList().stream()
+ .map(taskInfo -> taskInfo.taskId)
+ .toList();
+ foundTaskIds.add(groupedTaskIds);
- if (pair.getTaskInfo2() != null) {
- assertNotNull(pair.getSplitBounds());
- int leftTopTaskId = pair.getSplitBounds().leftTopTaskId;
- int bottomRightTaskId = pair.getSplitBounds().rightBottomTaskId;
+ if (groupedTask.isBaseType(TYPE_SPLIT)) {
+ assertNotNull(groupedTask.getSplitBounds());
+ int leftTopTaskId = groupedTask.getSplitBounds().leftTopTaskId;
+ int bottomRightTaskId = groupedTask.getSplitBounds().rightBottomTaskId;
// Unclear if pairs are ordered by split position, most likely not.
- assertTrue(leftTopTaskId == taskId1
- || leftTopTaskId == pair.getTaskInfo2().taskId);
- assertTrue(bottomRightTaskId == taskId1
- || bottomRightTaskId == pair.getTaskInfo2().taskId);
- } else {
- assertNull(pair.getSplitBounds());
+ assertTrue(leftTopTaskId == groupedTaskIds.getFirst()
+ || leftTopTaskId == groupedTaskIds.getLast());
+ assertTrue(bottomRightTaskId == groupedTaskIds.getFirst()
+ || bottomRightTaskId == groupedTaskIds.getLast());
}
}
- assertTrue("Expected: " + Arrays.toString(expectedTaskIds)
- + " Received: " + Arrays.toString(flattenedTaskIds),
- Arrays.equals(flattenedTaskIds, expectedTaskIds));
+ List<Integer> flattenedExpectedTaskIds = expectedTaskIds.stream()
+ .flatMap(List::stream)
+ .toList();
+ List<Integer> flattenedFoundTaskIds = foundTaskIds.stream()
+ .flatMap(List::stream)
+ .toList();
+ assertEquals("Expected: "
+ + flattenedExpectedTaskIds.stream().map(String::valueOf).collect(joining())
+ + " Received: "
+ + flattenedFoundTaskIds.stream().map(String::valueOf).collect(joining()),
+ flattenedExpectedTaskIds,
+ flattenedFoundTaskIds);
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java
index 894d238..ab43119 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java
@@ -169,7 +169,7 @@
final IResultReceiver finishCallback = mock(IResultReceiver.class);
final IBinder transition = startRecentsTransition(/* synthetic= */ true, runner);
- verify(runner).onAnimationStart(any(), any(), any(), any(), any(), any());
+ verify(runner).onAnimationStart(any(), any(), any(), any(), any(), any(), any());
// Finish and verify no transition remains and that the provided finish callback is called
mRecentsTransitionHandler.findController(transition).finish(true /* toHome */,
@@ -184,7 +184,7 @@
final IRecentsAnimationRunner runner = mock(IRecentsAnimationRunner.class);
final IBinder transition = startRecentsTransition(/* synthetic= */ true, runner);
- verify(runner).onAnimationStart(any(), any(), any(), any(), any(), any());
+ verify(runner).onAnimationStart(any(), any(), any(), any(), any(), any(), any());
mRecentsTransitionHandler.findController(transition).cancel("test");
mMainExecutor.flushAll();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java
index 66636c5..6ac34d7 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java
@@ -165,10 +165,11 @@
doReturn(true).when(mTransitions).isRegistered();
}
mTaskViewRepository = new TaskViewRepository();
- mTaskViewTransitions = spy(new TaskViewTransitions(mTransitions, mTaskViewRepository));
+ mTaskViewTransitions = spy(new TaskViewTransitions(mTransitions, mTaskViewRepository,
+ mOrganizer, mSyncQueue));
mTaskViewTaskController = new TaskViewTaskController(mContext, mOrganizer,
mTaskViewTransitions, mSyncQueue);
- mTaskView = new TaskView(mContext, mTaskViewTaskController);
+ mTaskView = new TaskView(mContext, mTaskViewTransitions, mTaskViewTaskController);
mTaskView.setHandler(mViewHandler);
mTaskView.setListener(mExecutor, mViewListener);
}
@@ -182,7 +183,7 @@
@Test
public void testSetPendingListener_throwsException() {
- TaskView taskView = new TaskView(mContext,
+ TaskView taskView = new TaskView(mContext, mTaskViewTransitions,
new TaskViewTaskController(mContext, mOrganizer, mTaskViewTransitions, mSyncQueue));
taskView.setListener(mExecutor, mViewListener);
try {
@@ -326,7 +327,7 @@
public void testOnNewTask_noSurface() {
assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
WindowContainerTransaction wct = new WindowContainerTransaction();
- mTaskViewTaskController.prepareOpenAnimation(true /* newTask */,
+ mTaskViewTransitions.prepareOpenAnimation(mTaskViewTaskController, true /* newTask */,
new SurfaceControl.Transaction(), new SurfaceControl.Transaction(), mTaskInfo,
mLeash, wct);
@@ -354,7 +355,7 @@
assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
mTaskView.surfaceCreated(mock(SurfaceHolder.class));
WindowContainerTransaction wct = new WindowContainerTransaction();
- mTaskViewTaskController.prepareOpenAnimation(true /* newTask */,
+ mTaskViewTransitions.prepareOpenAnimation(mTaskViewTaskController, true /* newTask */,
new SurfaceControl.Transaction(), new SurfaceControl.Transaction(), mTaskInfo,
mLeash, wct);
@@ -366,7 +367,7 @@
public void testSurfaceCreated_withTask() {
assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
WindowContainerTransaction wct = new WindowContainerTransaction();
- mTaskViewTaskController.prepareOpenAnimation(true /* newTask */,
+ mTaskViewTransitions.prepareOpenAnimation(mTaskViewTaskController, true /* newTask */,
new SurfaceControl.Transaction(), new SurfaceControl.Transaction(), mTaskInfo,
mLeash, wct);
mTaskView.surfaceCreated(mock(SurfaceHolder.class));
@@ -374,7 +375,7 @@
verify(mViewListener).onInitialized();
verify(mTaskViewTransitions).setTaskViewVisible(eq(mTaskViewTaskController), eq(true));
- mTaskViewTaskController.prepareOpenAnimation(false /* newTask */,
+ mTaskViewTransitions.prepareOpenAnimation(mTaskViewTaskController, false /* newTask */,
new SurfaceControl.Transaction(), new SurfaceControl.Transaction(), mTaskInfo,
mLeash, wct);
@@ -396,7 +397,7 @@
assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
SurfaceHolder sh = mock(SurfaceHolder.class);
WindowContainerTransaction wct = new WindowContainerTransaction();
- mTaskViewTaskController.prepareOpenAnimation(true /* newTask */,
+ mTaskViewTransitions.prepareOpenAnimation(mTaskViewTaskController, true /* newTask */,
new SurfaceControl.Transaction(), new SurfaceControl.Transaction(), mTaskInfo,
mLeash, wct);
mTaskView.surfaceCreated(sh);
@@ -414,7 +415,7 @@
public void testOnReleased() {
assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
WindowContainerTransaction wct = new WindowContainerTransaction();
- mTaskViewTaskController.prepareOpenAnimation(true /* newTask */,
+ mTaskViewTransitions.prepareOpenAnimation(mTaskViewTaskController, true /* newTask */,
new SurfaceControl.Transaction(), new SurfaceControl.Transaction(), mTaskInfo,
mLeash, wct);
mTaskView.surfaceCreated(mock(SurfaceHolder.class));
@@ -423,14 +424,14 @@
verify(mOrganizer).removeListener(eq(mTaskViewTaskController));
verify(mViewListener).onReleased();
assertThat(mTaskView.isInitialized()).isFalse();
- verify(mTaskViewTransitions).removeTaskView(eq(mTaskViewTaskController));
+ verify(mTaskViewTransitions).unregisterTaskView(eq(mTaskViewTaskController));
}
@Test
public void testOnTaskVanished() {
assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
WindowContainerTransaction wct = new WindowContainerTransaction();
- mTaskViewTaskController.prepareOpenAnimation(true /* newTask */,
+ mTaskViewTransitions.prepareOpenAnimation(mTaskViewTaskController, true /* newTask */,
new SurfaceControl.Transaction(), new SurfaceControl.Transaction(), mTaskInfo,
mLeash, wct);
mTaskView.surfaceCreated(mock(SurfaceHolder.class));
@@ -443,7 +444,7 @@
public void testOnBackPressedOnTaskRoot() {
assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
WindowContainerTransaction wct = new WindowContainerTransaction();
- mTaskViewTaskController.prepareOpenAnimation(true /* newTask */,
+ mTaskViewTransitions.prepareOpenAnimation(mTaskViewTaskController, true /* newTask */,
new SurfaceControl.Transaction(), new SurfaceControl.Transaction(), mTaskInfo,
mLeash, wct);
mTaskViewTaskController.onBackPressedOnTaskRoot(mTaskInfo);
@@ -455,7 +456,7 @@
public void testSetOnBackPressedOnTaskRoot() {
assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
WindowContainerTransaction wct = new WindowContainerTransaction();
- mTaskViewTaskController.prepareOpenAnimation(true /* newTask */,
+ mTaskViewTransitions.prepareOpenAnimation(mTaskViewTaskController, true /* newTask */,
new SurfaceControl.Transaction(), new SurfaceControl.Transaction(), mTaskInfo,
mLeash, wct);
verify(mOrganizer).setInterceptBackPressedOnTaskRoot(eq(mTaskInfo.token), eq(true));
@@ -524,7 +525,7 @@
// Make the task available
WindowContainerTransaction wct = mock(WindowContainerTransaction.class);
- mTaskViewTaskController.startRootTask(mTaskInfo, mLeash, wct);
+ mTaskViewTransitions.startRootTask(mTaskViewTaskController, mTaskInfo, mLeash, wct);
// Bounds got set
verify(wct).setBounds(any(WindowContainerToken.class), eq(bounds));
@@ -564,7 +565,7 @@
// Make the task available / start prepareOpen
WindowContainerTransaction wct = mock(WindowContainerTransaction.class);
- mTaskViewTaskController.prepareOpenAnimation(true /* newTask */,
+ mTaskViewTransitions.prepareOpenAnimation(mTaskViewTaskController, true /* newTask */,
new SurfaceControl.Transaction(), new SurfaceControl.Transaction(), mTaskInfo,
mLeash, wct);
@@ -594,7 +595,7 @@
// Task is available, but the surface was never created
WindowContainerTransaction wct = mock(WindowContainerTransaction.class);
- mTaskViewTaskController.prepareOpenAnimation(true /* newTask */,
+ mTaskViewTransitions.prepareOpenAnimation(mTaskViewTaskController, true /* newTask */,
new SurfaceControl.Transaction(), new SurfaceControl.Transaction(), mTaskInfo,
mLeash, wct);
@@ -619,7 +620,7 @@
assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
mTaskView.removeTask();
- verify(mTaskViewTransitions, never()).closeTaskView(any(), any());
+ assertFalse(mTaskViewTransitions.hasPending());
}
@Test
@@ -628,14 +629,14 @@
mTaskView.surfaceCreated(mock(SurfaceHolder.class));
WindowContainerTransaction wct = new WindowContainerTransaction();
- mTaskViewTaskController.prepareOpenAnimation(true /* newTask */,
+ mTaskViewTransitions.prepareOpenAnimation(mTaskViewTaskController, true /* newTask */,
new SurfaceControl.Transaction(), new SurfaceControl.Transaction(), mTaskInfo,
mLeash, wct);
verify(mViewListener).onTaskCreated(eq(mTaskInfo.taskId), any());
mTaskView.removeTask();
- verify(mTaskViewTransitions).closeTaskView(any(), eq(mTaskViewTaskController));
+ verify(mTaskViewTransitions).removeTaskView(eq(mTaskViewTaskController), any());
}
@Test
@@ -646,7 +647,7 @@
mTaskViewTaskController.onTaskAppeared(mTaskInfo, mLeash);
assertNull(mTaskViewTaskController.getTaskInfo());
- verify(mTaskViewTransitions).closeTaskView(any(), eq(mTaskViewTaskController));
+ verify(mTaskViewTransitions).removeTaskView(eq(mTaskViewTaskController), any());
}
@Test
@@ -655,7 +656,7 @@
mTaskViewTaskController.onTaskAppeared(mTaskInfo, mLeash);
assertEquals(mTaskInfo, mTaskViewTaskController.getPendingInfo());
- verify(mTaskViewTransitions, never()).closeTaskView(any(), any());
+ verify(mTaskViewTransitions, never()).removeTaskView(any(), any());
}
@Test
@@ -671,7 +672,7 @@
mTaskView.surfaceCreated(mock(SurfaceHolder.class));
reset(mOrganizer);
WindowContainerTransaction wct = new WindowContainerTransaction();
- mTaskViewTaskController.prepareOpenAnimation(true /* newTask */,
+ mTaskViewTransitions.prepareOpenAnimation(mTaskViewTaskController, true /* newTask */,
new SurfaceControl.Transaction(), new SurfaceControl.Transaction(), mTaskInfo,
mLeash, wct);
mTaskView.onComputeInternalInsets(new ViewTreeObserver.InternalInsetsInfo());
@@ -688,7 +689,7 @@
mTaskView.surfaceCreated(mock(SurfaceHolder.class));
WindowContainerTransaction wct = new WindowContainerTransaction();
- mTaskViewTaskController.prepareOpenAnimation(true /* newTask */,
+ mTaskViewTransitions.prepareOpenAnimation(mTaskViewTaskController, true /* newTask */,
new SurfaceControl.Transaction(), new SurfaceControl.Transaction(), mTaskInfo,
mLeash, wct);
reset(mOrganizer);
@@ -706,7 +707,7 @@
public void testReleaseInOnTaskRemoval_noNPE() {
mTaskViewTaskController = spy(new TaskViewTaskController(mContext, mOrganizer,
mTaskViewTransitions, mSyncQueue));
- mTaskView = new TaskView(mContext, mTaskViewTaskController);
+ mTaskView = new TaskView(mContext, mTaskViewTransitions, mTaskViewTaskController);
mTaskView.setListener(mExecutor, new TaskView.Listener() {
@Override
public void onTaskRemovalStarted(int taskId) {
@@ -715,7 +716,7 @@
});
WindowContainerTransaction wct = new WindowContainerTransaction();
- mTaskViewTaskController.prepareOpenAnimation(true /* newTask */,
+ mTaskViewTransitions.prepareOpenAnimation(mTaskViewTaskController, true /* newTask */,
new SurfaceControl.Transaction(), new SurfaceControl.Transaction(), mTaskInfo,
mLeash, wct);
mTaskView.surfaceCreated(mock(SurfaceHolder.class));
@@ -763,7 +764,7 @@
@Test
public void testOnAppeared_setsTrimmableTask() {
WindowContainerTransaction wct = new WindowContainerTransaction();
- mTaskViewTaskController.prepareOpenAnimation(true /* newTask */,
+ mTaskViewTransitions.prepareOpenAnimation(mTaskViewTaskController, true /* newTask */,
new SurfaceControl.Transaction(), new SurfaceControl.Transaction(), mTaskInfo,
mLeash, wct);
@@ -773,11 +774,11 @@
@Test
public void testMoveToFullscreen_callsTaskRemovalStarted() {
WindowContainerTransaction wct = new WindowContainerTransaction();
- mTaskViewTaskController.prepareOpenAnimation(true /* newTask */,
+ mTaskViewTransitions.prepareOpenAnimation(mTaskViewTaskController, true /* newTask */,
new SurfaceControl.Transaction(), new SurfaceControl.Transaction(), mTaskInfo,
mLeash, wct);
mTaskView.surfaceCreated(mock(SurfaceHolder.class));
- mTaskViewTaskController.moveToFullscreen();
+ mTaskViewTransitions.moveTaskViewToFullscreen(mTaskViewTaskController);
verify(mViewListener).onTaskRemovalStarted(eq(mTaskInfo.taskId));
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTransitionsTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTransitionsTest.java
index 5f6f18f..326f11e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTransitionsTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTransitionsTest.java
@@ -44,7 +44,9 @@
import androidx.test.filters.SmallTest;
import com.android.wm.shell.Flags;
+import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.transition.Transitions;
import org.junit.Before;
@@ -56,6 +58,7 @@
import java.util.ArrayList;
import java.util.List;
+import java.util.concurrent.Executor;
import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
import platform.test.runner.parameterized.Parameters;
@@ -82,6 +85,12 @@
ActivityManager.RunningTaskInfo mTaskInfo;
@Mock
WindowContainerToken mToken;
+ @Mock
+ ShellTaskOrganizer mOrganizer;
+ @Mock
+ SyncTransactionQueue mSyncQueue;
+
+ Executor mExecutor = command -> command.run();
TaskViewRepository mTaskViewRepository;
TaskViewTransitions mTaskViewTransitions;
@@ -104,9 +113,12 @@
mTaskInfo.taskDescription = mock(ActivityManager.TaskDescription.class);
mTaskViewRepository = new TaskViewRepository();
- mTaskViewTransitions = spy(new TaskViewTransitions(mTransitions, mTaskViewRepository));
- mTaskViewTransitions.addTaskView(mTaskViewTaskController);
+ when(mOrganizer.getExecutor()).thenReturn(mExecutor);
+ mTaskViewTransitions = spy(new TaskViewTransitions(mTransitions, mTaskViewRepository,
+ mOrganizer, mSyncQueue));
+ mTaskViewTransitions.registerTaskView(mTaskViewTaskController);
when(mTaskViewTaskController.getTaskInfo()).thenReturn(mTaskInfo);
+ when(mTaskViewTaskController.getTaskToken()).thenReturn(mToken);
}
@Test
@@ -212,7 +224,7 @@
@Test
public void testSetTaskVisibility_taskRemoved_noNPE() {
- mTaskViewTransitions.removeTaskView(mTaskViewTaskController);
+ mTaskViewTransitions.unregisterTaskView(mTaskViewTaskController);
assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
@@ -221,7 +233,7 @@
@Test
public void testSetTaskBounds_taskRemoved_noNPE() {
- mTaskViewTransitions.removeTaskView(mTaskViewTaskController);
+ mTaskViewTransitions.unregisterTaskView(mTaskViewTaskController);
assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
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 dd645fd..0a19be4 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 @@
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/DesktopModeWindowDecorViewModelTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
index ffe8e71..b479164 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
@@ -59,11 +59,13 @@
import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
import com.android.window.flags.Flags
import com.android.wm.shell.R
-import com.android.wm.shell.desktopmode.common.ToggleTaskSizeInteraction
import com.android.wm.shell.desktopmode.DesktopImmersiveController
import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.InputMethod
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.MinimizeReason
import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger
import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition
+import com.android.wm.shell.desktopmode.common.ToggleTaskSizeInteraction
+import com.android.wm.shell.recents.RecentsTransitionStateListener
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource
import com.android.wm.shell.splitscreen.SplitScreenController
@@ -271,7 +273,7 @@
onClickListenerCaptor.value.onClick(view)
- verify(mockDesktopTasksController).minimizeTask(decor.mTaskInfo)
+ verify(mockDesktopTasksController).minimizeTask(decor.mTaskInfo, MinimizeReason.MINIMIZE_BUTTON)
}
@Test
@@ -539,7 +541,8 @@
onLeftSnapClickListenerCaptor.value.invoke()
verify(mockDesktopTasksController, never())
- .snapToHalfScreen(eq(decor.mTaskInfo), any(), eq(currentBounds), eq(SnapPosition.LEFT),
+ .snapToHalfScreen(
+ eq(decor.mTaskInfo), any(), eq(currentBounds), eq(SnapPosition.LEFT),
eq(ResizeTrigger.MAXIMIZE_BUTTON),
eq(InputMethod.UNKNOWN_INPUT_METHOD),
eq(decor),
@@ -616,11 +619,12 @@
onRightSnapClickListenerCaptor.value.invoke()
verify(mockDesktopTasksController, never())
- .snapToHalfScreen(eq(decor.mTaskInfo), any(), eq(currentBounds), eq(SnapPosition.RIGHT),
+ .snapToHalfScreen(
+ eq(decor.mTaskInfo), any(), eq(currentBounds), eq(SnapPosition.RIGHT),
eq(ResizeTrigger.MAXIMIZE_BUTTON),
eq(InputMethod.UNKNOWN_INPUT_METHOD),
eq(decor),
- )
+ )
}
@Test
@@ -1223,6 +1227,49 @@
verify(task2, never()).onExclusionRegionChanged(newRegion)
}
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_RECENTS_TRANSITIONS_CORNERS_BUGFIX)
+ fun testRecentsTransitionStateListener_requestedState_setsTransitionRunning() {
+ val task = createTask(windowingMode = WINDOWING_MODE_FREEFORM)
+ val decoration = setUpMockDecorationForTask(task)
+ onTaskOpening(task, SurfaceControl())
+
+ desktopModeRecentsTransitionStateListener.onTransitionStateChanged(
+ RecentsTransitionStateListener.TRANSITION_STATE_REQUESTED)
+
+ verify(decoration).setIsRecentsTransitionRunning(true)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_RECENTS_TRANSITIONS_CORNERS_BUGFIX)
+ fun testRecentsTransitionStateListener_nonRunningState_setsTransitionNotRunning() {
+ val task = createTask(windowingMode = WINDOWING_MODE_FREEFORM)
+ val decoration = setUpMockDecorationForTask(task)
+ onTaskOpening(task, SurfaceControl())
+ desktopModeRecentsTransitionStateListener.onTransitionStateChanged(
+ RecentsTransitionStateListener.TRANSITION_STATE_REQUESTED)
+
+ desktopModeRecentsTransitionStateListener.onTransitionStateChanged(
+ RecentsTransitionStateListener.TRANSITION_STATE_NOT_RUNNING)
+
+ verify(decoration).setIsRecentsTransitionRunning(false)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_RECENTS_TRANSITIONS_CORNERS_BUGFIX)
+ fun testRecentsTransitionStateListener_requestedAndAnimating_setsTransitionRunningOnce() {
+ val task = createTask(windowingMode = WINDOWING_MODE_FREEFORM)
+ val decoration = setUpMockDecorationForTask(task)
+ onTaskOpening(task, SurfaceControl())
+
+ desktopModeRecentsTransitionStateListener.onTransitionStateChanged(
+ RecentsTransitionStateListener.TRANSITION_STATE_REQUESTED)
+ desktopModeRecentsTransitionStateListener.onTransitionStateChanged(
+ RecentsTransitionStateListener.TRANSITION_STATE_ANIMATING)
+
+ verify(decoration, times(1)).setIsRecentsTransitionRunning(true)
+ }
+
private fun createOpenTaskDecoration(
@WindowingMode windowingMode: Int,
taskSurface: SurfaceControl = SurfaceControl(),
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt
index 40015ee..908bc99 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt
@@ -31,6 +31,7 @@
import android.testing.TestableContext
import android.util.SparseArray
import android.view.Choreographer
+import android.view.Display
import android.view.Display.DEFAULT_DISPLAY
import android.view.IWindowManager
import android.view.InputChannel
@@ -40,6 +41,7 @@
import android.view.WindowInsets.Type.statusBars
import com.android.dx.mockito.inline.extended.StaticMockitoSession
import com.android.internal.jank.InteractionJankMonitor
+import com.android.window.flags.Flags
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.ShellTestCase
@@ -65,6 +67,8 @@
import com.android.wm.shell.desktopmode.education.AppHandleEducationController
import com.android.wm.shell.desktopmode.education.AppToWebEducationController
import com.android.wm.shell.freeform.FreeformTaskTransitionStarter
+import com.android.wm.shell.recents.RecentsTransitionHandler
+import com.android.wm.shell.recents.RecentsTransitionStateListener
import com.android.wm.shell.splitscreen.SplitScreenController
import com.android.wm.shell.sysui.ShellCommandHandler
import com.android.wm.shell.sysui.ShellController
@@ -151,8 +155,10 @@
protected val mockFocusTransitionObserver = mock<FocusTransitionObserver>()
protected val mockCaptionHandleRepository = mock<WindowDecorCaptionHandleRepository>()
protected val mockDesktopRepository: DesktopRepository = mock<DesktopRepository>()
+ protected val mockRecentsTransitionHandler = mock<RecentsTransitionHandler>()
protected val motionEvent = mock<MotionEvent>()
val displayLayout = mock<DisplayLayout>()
+ val display = mock<Display>()
protected lateinit var spyContext: TestableContext
private lateinit var desktopModeEventLogger: DesktopModeEventLogger
@@ -164,6 +170,7 @@
protected lateinit var mockitoSession: StaticMockitoSession
protected lateinit var shellInit: ShellInit
internal lateinit var desktopModeOnInsetsChangedListener: DesktopModeOnInsetsChangedListener
+ protected lateinit var desktopModeRecentsTransitionStateListener: RecentsTransitionStateListener
protected lateinit var displayChangingListener:
DisplayChangeController.OnDisplayChangingListener
internal lateinit var desktopModeOnKeyguardChangedListener: DesktopModeKeyguardChangeListener
@@ -178,6 +185,7 @@
desktopModeEventLogger = mock<DesktopModeEventLogger>()
whenever(mockDesktopUserRepositories.current).thenReturn(mockDesktopRepository)
whenever(mockDisplayController.getDisplayContext(any())).thenReturn(spyContext)
+ whenever(mockDisplayController.getDisplay(any())).thenReturn(display)
whenever(mockDesktopUserRepositories.getProfile(anyInt()))
.thenReturn(mockDesktopRepository)
desktopModeWindowDecorViewModel = DesktopModeWindowDecorViewModel(
@@ -220,7 +228,8 @@
mockFocusTransitionObserver,
desktopModeEventLogger,
mock<DesktopModeUiEventLogger>(),
- mock<WindowDecorTaskResourceLoader>()
+ mock<WindowDecorTaskResourceLoader>(),
+ mockRecentsTransitionHandler,
)
desktopModeWindowDecorViewModel.setSplitScreenController(mockSplitScreenController)
whenever(mockDisplayController.getDisplayLayout(any())).thenReturn(mockDisplayLayout)
@@ -257,6 +266,13 @@
verify(displayInsetsController)
.addGlobalInsetsChangedListener(insetsChangedCaptor.capture())
desktopModeOnInsetsChangedListener = insetsChangedCaptor.firstValue
+ val recentsTransitionStateListenerCaptor = argumentCaptor<RecentsTransitionStateListener>()
+ if (Flags.enableDesktopRecentsTransitionsCornersBugfix()) {
+ verify(mockRecentsTransitionHandler)
+ .addTransitionStateListener(recentsTransitionStateListenerCaptor.capture())
+ desktopModeRecentsTransitionStateListener =
+ recentsTransitionStateListenerCaptor.firstValue
+ }
val keyguardChangedCaptor =
argumentCaptor<DesktopModeKeyguardChangeListener>()
verify(mockShellController).addKeyguardChangeListener(keyguardChangedCaptor.capture())
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
index 6b02aef..9ea5fd6 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
@@ -169,6 +169,7 @@
private static final boolean DEFAULT_IS_KEYGUARD_VISIBLE_AND_OCCLUDED = false;
private static final boolean DEFAULT_IS_IN_FULL_IMMERSIVE_MODE = false;
private static final boolean DEFAULT_HAS_GLOBAL_FOCUS = true;
+ private static final boolean DEFAULT_SHOULD_IGNORE_CORNER_RADIUS = false;
@Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
@@ -396,6 +397,31 @@
}
@Test
+ public void updateRelayoutParams_shouldIgnoreCornerRadius_roundedCornersNotSet() {
+ final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
+ taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
+ fillRoundedCornersResources(/* fillValue= */ 30);
+ RelayoutParams relayoutParams = new RelayoutParams();
+
+ DesktopModeWindowDecoration.updateRelayoutParams(
+ relayoutParams,
+ mTestableContext,
+ taskInfo,
+ mMockSplitScreenController,
+ DEFAULT_APPLY_START_TRANSACTION_ON_DRAW,
+ DEFAULT_SHOULD_SET_TASK_POSITIONING_AND_CROP,
+ DEFAULT_IS_STATUSBAR_VISIBLE,
+ DEFAULT_IS_KEYGUARD_VISIBLE_AND_OCCLUDED,
+ DEFAULT_IS_IN_FULL_IMMERSIVE_MODE,
+ new InsetsState(),
+ DEFAULT_HAS_GLOBAL_FOCUS,
+ mExclusionRegion,
+ /* shouldIgnoreCornerRadius= */ true);
+
+ assertThat(relayoutParams.mCornerRadius).isEqualTo(INVALID_CORNER_RADIUS);
+ }
+
+ @Test
@EnableFlags(Flags.FLAG_ENABLE_APP_HEADER_WITH_TASK_DENSITY)
public void updateRelayoutParams_appHeader_usesTaskDensity() {
final int systemDensity = mTestableContext.getOrCreateTestableResources().getResources()
@@ -634,7 +660,8 @@
/* inFullImmersiveMode */ true,
insetsState,
DEFAULT_HAS_GLOBAL_FOCUS,
- mExclusionRegion);
+ mExclusionRegion,
+ DEFAULT_SHOULD_IGNORE_CORNER_RADIUS);
// Takes status bar inset as padding, ignores caption bar inset.
assertThat(relayoutParams.mCaptionTopPadding).isEqualTo(50);
@@ -659,7 +686,8 @@
/* inFullImmersiveMode */ true,
new InsetsState(),
DEFAULT_HAS_GLOBAL_FOCUS,
- mExclusionRegion);
+ mExclusionRegion,
+ DEFAULT_SHOULD_IGNORE_CORNER_RADIUS);
assertThat(relayoutParams.mIsInsetSource).isFalse();
}
@@ -683,7 +711,8 @@
DEFAULT_IS_IN_FULL_IMMERSIVE_MODE,
new InsetsState(),
DEFAULT_HAS_GLOBAL_FOCUS,
- mExclusionRegion);
+ mExclusionRegion,
+ DEFAULT_SHOULD_IGNORE_CORNER_RADIUS);
// Header is always shown because it's assumed the status bar is always visible.
assertThat(relayoutParams.mIsCaptionVisible).isTrue();
@@ -707,7 +736,8 @@
DEFAULT_IS_IN_FULL_IMMERSIVE_MODE,
new InsetsState(),
DEFAULT_HAS_GLOBAL_FOCUS,
- mExclusionRegion);
+ mExclusionRegion,
+ DEFAULT_SHOULD_IGNORE_CORNER_RADIUS);
assertThat(relayoutParams.mIsCaptionVisible).isTrue();
}
@@ -730,7 +760,8 @@
DEFAULT_IS_IN_FULL_IMMERSIVE_MODE,
new InsetsState(),
DEFAULT_HAS_GLOBAL_FOCUS,
- mExclusionRegion);
+ mExclusionRegion,
+ DEFAULT_SHOULD_IGNORE_CORNER_RADIUS);
assertThat(relayoutParams.mIsCaptionVisible).isFalse();
}
@@ -753,7 +784,8 @@
DEFAULT_IS_IN_FULL_IMMERSIVE_MODE,
new InsetsState(),
DEFAULT_HAS_GLOBAL_FOCUS,
- mExclusionRegion);
+ mExclusionRegion,
+ DEFAULT_SHOULD_IGNORE_CORNER_RADIUS);
assertThat(relayoutParams.mIsCaptionVisible).isFalse();
}
@@ -777,7 +809,8 @@
/* inFullImmersiveMode */ true,
new InsetsState(),
DEFAULT_HAS_GLOBAL_FOCUS,
- mExclusionRegion);
+ mExclusionRegion,
+ DEFAULT_SHOULD_IGNORE_CORNER_RADIUS);
assertThat(relayoutParams.mIsCaptionVisible).isTrue();
@@ -793,7 +826,8 @@
/* inFullImmersiveMode */ true,
new InsetsState(),
DEFAULT_HAS_GLOBAL_FOCUS,
- mExclusionRegion);
+ mExclusionRegion,
+ DEFAULT_SHOULD_IGNORE_CORNER_RADIUS);
assertThat(relayoutParams.mIsCaptionVisible).isFalse();
}
@@ -817,7 +851,8 @@
/* inFullImmersiveMode */ true,
new InsetsState(),
DEFAULT_HAS_GLOBAL_FOCUS,
- mExclusionRegion);
+ mExclusionRegion,
+ DEFAULT_SHOULD_IGNORE_CORNER_RADIUS);
assertThat(relayoutParams.mIsCaptionVisible).isFalse();
}
@@ -1480,7 +1515,8 @@
DEFAULT_IS_IN_FULL_IMMERSIVE_MODE,
new InsetsState(),
DEFAULT_HAS_GLOBAL_FOCUS,
- mExclusionRegion);
+ mExclusionRegion,
+ DEFAULT_SHOULD_IGNORE_CORNER_RADIUS);
}
private DesktopModeWindowDecoration createWindowDecoration(
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 f179cac..2207c70 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 @@
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.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.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 @@
@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 @@
@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 @@
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 @@
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 @@
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 @@
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 @@
val endBounds =
taskPositioner.onDragPositioningEnd(
- DISPLAY_ID,
+ DISPLAY_ID_0,
STARTING_BOUNDS.left.toFloat() + 70,
STARTING_BOUNDS.top.toFloat() + 20,
)
@@ -221,16 +245,39 @@
}
@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 @@
)
taskPositioner.onDragPositioningEnd(
- DISPLAY_ID,
+ DISPLAY_ID_0,
STARTING_BOUNDS.right.toFloat() + 20,
STARTING_BOUNDS.top.toFloat() + 20,
)
@@ -278,20 +325,20 @@
@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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
)
// 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 @@
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 @@
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 @@
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 @@
}
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 @@
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 7eedfdb..b80c875 100644
--- a/libs/androidfw/ApkParsing.cpp
+++ b/libs/androidfw/ApkParsing.cpp
@@ -33,7 +33,7 @@
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 @@
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 194eaae..b288e15 100644
--- a/libs/androidfw/include/androidfw/ApkParsing.h
+++ b/libs/androidfw/include/androidfw/ApkParsing.h
@@ -24,7 +24,7 @@
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 ac1dc9b..f1f9d71 100644
--- a/libs/androidfw/tests/ApkParsing_test.cpp
+++ b/libs/androidfw/tests/ApkParsing_test.cpp
@@ -27,57 +27,45 @@
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 69fe40c..6ab8e4e 100644
--- a/libs/hwui/renderthread/RenderThread.cpp
+++ b/libs/hwui/renderthread/RenderThread.cpp
@@ -323,6 +323,7 @@
}
void RenderThread::destroyRenderingContext() {
+ ATRACE_CALL();
mFunctorManager.onContextDestroyed();
if (Properties::getRenderPipelineType() == RenderPipelineType::SkiaGL) {
if (mEglManager->hasEglContext()) {
@@ -520,7 +521,10 @@
// 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 @@
} else {
requireVkContext();
}
- if (Properties::earlyPreloadGlContext()) {
- queue().post([]() { GraphicBufferAllocator::getInstance(); });
- }
HardwareBitmapUploader::initialize();
}
diff --git a/media/java/android/media/MediaCodecInfo.java b/media/java/android/media/MediaCodecInfo.java
index 302969f..9bb31d0 100644
--- a/media/java/android/media/MediaCodecInfo.java
+++ b/media/java/android/media/MediaCodecInfo.java
@@ -908,13 +908,13 @@
/** @hide */
public String[] validFeatures() {
Feature[] features = getValidFeatures();
- String[] res = new String[features.length];
- for (int i = 0; i < res.length; i++) {
+ ArrayList<String> res = new ArrayList();
+ for (int i = 0; i < features.length; i++) {
if (!features[i].mInternal) {
- res[i] = features[i].mName;
+ res.add(features[i].mName);
}
}
- return res;
+ return res.toArray(new String[0]);
}
private Feature[] getValidFeatures() {
diff --git a/media/java/android/media/flags/media_better_together.aconfig b/media/java/android/media/flags/media_better_together.aconfig
index c48b5f4..312f78e 100644
--- a/media/java/android/media/flags/media_better_together.aconfig
+++ b/media/java/android/media/flags/media_better_together.aconfig
@@ -173,6 +173,18 @@
bug: "281072508"
}
+
+flag {
+ name: "enable_singleton_audio_manager_route_controller"
+ is_exported: true
+ namespace: "media_solutions"
+ description: "Use singleton AudioManagerRouteController shared across all users."
+ bug: "372868909"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
flag {
name: "enable_use_of_bluetooth_device_get_alias_for_mr2info_get_name"
namespace: "media_solutions"
diff --git a/media/java/android/media/quality/MediaQualityManager.java b/media/java/android/media/quality/MediaQualityManager.java
index aeb028c..b726925 100644
--- a/media/java/android/media/quality/MediaQualityManager.java
+++ b/media/java/android/media/quality/MediaQualityManager.java
@@ -18,6 +18,7 @@
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 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 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/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java
index abfc244..f352a41 100644
--- a/media/java/android/media/tv/TvInputService.java
+++ b/media/java/android/media/tv/TvInputService.java
@@ -161,11 +161,6 @@
new RemoteCallbackList<>();
private TvInputManager mTvInputManager;
- /**
- * @hide
- */
- protected TvInputServiceExtensionManager mTvInputServiceExtensionManager =
- new TvInputServiceExtensionManager();
@Override
public final IBinder onBind(Intent intent) {
@@ -230,12 +225,20 @@
@Override
public IBinder getExtensionInterface(String name) {
- if (tifExtensionStandardization() && name != null) {
- if (TvInputServiceExtensionManager.checkIsStandardizedInterfaces(name)) {
- return mTvInputServiceExtensionManager.getExtensionIBinder(name);
+ IBinder binder = TvInputService.this.getExtensionInterface(name);
+ if (tifExtensionStandardization()) {
+ if (name != null
+ && TvInputServiceExtensionManager.checkIsStandardizedInterfaces(name)) {
+ if (TvInputServiceExtensionManager.checkIsStandardizedIBinder(name,
+ binder)) {
+ return binder;
+ } else {
+ // binder with standardized name is not standardized
+ return null;
+ }
}
}
- return TvInputService.this.getExtensionInterface(name);
+ return binder;
}
@Override
diff --git a/media/java/android/media/tv/TvInputServiceExtensionManager.java b/media/java/android/media/tv/TvInputServiceExtensionManager.java
index d33ac92..02d2616 100644
--- a/media/java/android/media/tv/TvInputServiceExtensionManager.java
+++ b/media/java/android/media/tv/TvInputServiceExtensionManager.java
@@ -16,13 +16,9 @@
package android.media.tv;
-import android.annotation.FlaggedApi;
-import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.RequiresPermission;
import android.annotation.StringDef;
-import android.media.tv.flags.Flags;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
@@ -30,21 +26,17 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
-import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
-import java.util.Map;
import java.util.Set;
/**
* This class provides a list of available standardized TvInputService extension interface names
- * and a container storing IBinder objects that implement these interfaces created by SoC/OEMs.
- * It also provides an API for SoC/OEMs to register implemented IBinder objects.
+ * and checks if IBinder objects created by SoC/OEMs implement these interfaces.
*
* @hide
*/
-@FlaggedApi(Flags.FLAG_TIF_EXTENSION_STANDARDIZATION)
public final class TvInputServiceExtensionManager {
private static final String TAG = "TvInputServiceExtensionManager";
private static final String SCAN_PACKAGE = "android.media.tv.extension.scan.";
@@ -63,33 +55,6 @@
private static final String ANALOG_PACKAGE = "android.media.tv.extension.analog.";
private static final String TUNE_PACKAGE = "android.media.tv.extension.tune.";
- @IntDef(prefix = {"REGISTER_"}, value = {
- REGISTER_SUCCESS,
- REGISTER_FAIL_NAME_NOT_STANDARDIZED,
- REGISTER_FAIL_IMPLEMENTATION_NOT_STANDARDIZED,
- REGISTER_FAIL_REMOTE_EXCEPTION
- })
- @Retention(RetentionPolicy.SOURCE)
- public @interface RegisterResult {}
-
- /**
- * Registering binder returns success when it abides standardized interface structure
- */
- public static final int REGISTER_SUCCESS = 0;
- /**
- * Registering binder returns failure when the extension name is not in the standardization
- * list
- */
- public static final int REGISTER_FAIL_NAME_NOT_STANDARDIZED = 1;
- /**
- * Registering binder returns failure when the IBinder does not implement standardized interface
- */
- public static final int REGISTER_FAIL_IMPLEMENTATION_NOT_STANDARDIZED = 2;
- /**
- * Registering binder returns failure when remote server is not available
- */
- public static final int REGISTER_FAIL_REMOTE_EXCEPTION = 3;
-
@StringDef({
ISCAN_INTERFACE,
ISCAN_SESSION,
@@ -673,12 +638,6 @@
IMUX_TUNE
));
- // Store the mapping between interface names and IBinder
- private Map<String, IBinder> mExtensionInterfaceIBinderMapping = new HashMap<>();
-
- TvInputServiceExtensionManager() {
- }
-
/**
* Function to return available extension interface names
*/
@@ -694,43 +653,18 @@
}
/**
- * Registers IBinder objects that implement standardized AIDL interfaces.
- * <p>This function should be used by SoCs/OEMs
- *
- * @param extensionName Extension Interface Name
- * @param binder IBinder object to be registered
- * @return {@link #REGISTER_SUCCESS} on success of registering IBinder object
- * {@link #REGISTER_FAIL_NAME_NOT_STANDARDIZED} on failure due to registering extension
- * with non-standardized name
- * {@link #REGISTER_FAIL_IMPLEMENTATION_NOT_STANDARDIZED} on failure due to IBinder not
- * implementing standardized AIDL interface
- * {@link #REGISTER_FAIL_REMOTE_EXCEPTION} on failure due to remote exception
+ * Function check if the IBinder object implements standardized interface
*/
- @RequiresPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
- @RegisterResult
- public int registerExtensionIBinder(@StandardizedExtensionName @NonNull String extensionName,
- @NonNull IBinder binder) {
- if (!checkIsStandardizedInterfaces(extensionName)) {
- return REGISTER_FAIL_NAME_NOT_STANDARDIZED;
- }
- try {
- if (binder.getInterfaceDescriptor().equals(extensionName)) {
- mExtensionInterfaceIBinderMapping.put(extensionName, binder);
- return REGISTER_SUCCESS;
- } else {
- return REGISTER_FAIL_IMPLEMENTATION_NOT_STANDARDIZED;
+ public static boolean checkIsStandardizedIBinder(@NonNull String extensionName,
+ @Nullable IBinder binder) {
+ if (binder != null) {
+ try {
+ return binder.getInterfaceDescriptor().equals(extensionName);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Fetching IBinder object failure due to " + e);
}
- } catch (RemoteException e) {
- Log.e(TAG, "Fetching IBinder object failure due to " + e);
- return REGISTER_FAIL_REMOTE_EXCEPTION;
}
- }
-
- /**
- * Function to get corresponding IBinder object
- */
- @Nullable IBinder getExtensionIBinder(@NonNull String extensionName) {
- return mExtensionInterfaceIBinderMapping.get(extensionName);
+ return false;
}
}
diff --git a/packages/CrashRecovery/framework/java/android/service/watchdog/ExplicitHealthCheckService.java b/packages/CrashRecovery/framework/java/android/service/watchdog/ExplicitHealthCheckService.java
new file mode 100644
index 0000000..fdb0fc5
--- /dev/null
+++ b/packages/CrashRecovery/framework/java/android/service/watchdog/ExplicitHealthCheckService.java
@@ -0,0 +1,359 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.watchdog;
+
+import static android.os.Parcelable.Creator;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SdkConstant;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
+import android.app.Service;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.crashrecovery.flags.Flags;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteCallback;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
+
+/**
+ * A service to provide packages supporting explicit health checks and route checks to these
+ * packages on behalf of the package watchdog.
+ *
+ * <p>To extend this class, you must declare the service in your manifest file with the
+ * {@link android.Manifest.permission#BIND_EXPLICIT_HEALTH_CHECK_SERVICE} permission,
+ * and include an intent filter with the {@link #SERVICE_INTERFACE} action. In adddition,
+ * your implementation must live in
+ * {@link PackageManager#getServicesSystemSharedLibraryPackageName()}.
+ * For example:</p>
+ * <pre>
+ * <service android:name=".FooExplicitHealthCheckService"
+ * android:exported="true"
+ * android:priority="100"
+ * android:permission="android.permission.BIND_EXPLICIT_HEALTH_CHECK_SERVICE">
+ * <intent-filter>
+ * <action android:name="android.service.watchdog.ExplicitHealthCheckService" />
+ * </intent-filter>
+ * </service>
+ * </pre>
+ * @hide
+ */
+@SystemApi
+public abstract class ExplicitHealthCheckService extends Service {
+
+ private static final String TAG = "ExplicitHealthCheckService";
+
+ /**
+ * {@link Bundle} key for a {@link List} of {@link PackageConfig} value.
+ *
+ * {@hide}
+ */
+ public static final String EXTRA_SUPPORTED_PACKAGES =
+ "android.service.watchdog.extra.supported_packages";
+
+ /**
+ * {@link Bundle} key for a {@link List} of {@link String} value.
+ *
+ * {@hide}
+ */
+ public static final String EXTRA_REQUESTED_PACKAGES =
+ "android.service.watchdog.extra.requested_packages";
+
+ /**
+ * {@link Bundle} key for a {@link String} value.
+ */
+ @FlaggedApi(Flags.FLAG_ENABLE_CRASHRECOVERY)
+ public static final String EXTRA_HEALTH_CHECK_PASSED_PACKAGE =
+ "android.service.watchdog.extra.HEALTH_CHECK_PASSED_PACKAGE";
+
+ /**
+ * The Intent action that a service must respond to. Add it to the intent filter of the service
+ * in its manifest.
+ */
+ @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
+ public static final String SERVICE_INTERFACE =
+ "android.service.watchdog.ExplicitHealthCheckService";
+
+ /**
+ * The permission that a service must require to ensure that only Android system can bind to it.
+ * If this permission is not enforced in the AndroidManifest of the service, the system will
+ * skip that service.
+ */
+ public static final String BIND_PERMISSION =
+ "android.permission.BIND_EXPLICIT_HEALTH_CHECK_SERVICE";
+
+ private final ExplicitHealthCheckServiceWrapper mWrapper =
+ new ExplicitHealthCheckServiceWrapper();
+
+ /**
+ * Called when the system requests an explicit health check for {@code packageName}.
+ *
+ * <p> When {@code packageName} passes the check, implementors should call
+ * {@link #notifyHealthCheckPassed} to inform the system.
+ *
+ * <p> It could take many hours before a {@code packageName} passes a check and implementors
+ * should never drop requests unless {@link onCancel} is called or the service dies.
+ *
+ * <p> Requests should not be queued and additional calls while expecting a result for
+ * {@code packageName} should have no effect.
+ */
+ public abstract void onRequestHealthCheck(@NonNull String packageName);
+
+ /**
+ * Called when the system cancels the explicit health check request for {@code packageName}.
+ * Should do nothing if there are is no active request for {@code packageName}.
+ */
+ public abstract void onCancelHealthCheck(@NonNull String packageName);
+
+ /**
+ * Called when the system requests for all the packages supporting explicit health checks. The
+ * system may request an explicit health check for any of these packages with
+ * {@link #onRequestHealthCheck}.
+ *
+ * @return all packages supporting explicit health checks
+ */
+ @NonNull public abstract List<PackageConfig> onGetSupportedPackages();
+
+ /**
+ * Called when the system requests for all the packages that it has currently requested
+ * an explicit health check for.
+ *
+ * @return all packages expecting an explicit health check result
+ */
+ @NonNull public abstract List<String> onGetRequestedPackages();
+
+ private final Handler mHandler = Handler.createAsync(Looper.getMainLooper());
+ @Nullable private Consumer<Bundle> mHealthCheckResultCallback;
+ @Nullable private Executor mCallbackExecutor;
+
+ @Override
+ @NonNull
+ public final IBinder onBind(@NonNull Intent intent) {
+ return mWrapper;
+ }
+
+ /**
+ * Sets a callback to be invoked when an explicit health check passes for a package.
+ * <p>
+ * The callback will receive a {@link Bundle} containing the package name that passed the
+ * health check, identified by the key {@link #EXTRA_HEALTH_CHECK_PASSED_PACKAGE}.
+ * <p>
+ * <b>Note:</b> This API is primarily intended for testing purposes. Calling this outside of a
+ * test environment will override the default callback mechanism used to notify the system
+ * about health check results. Use with caution in production code.
+ *
+ * @param executor The executor on which the callback should be invoked. If {@code null}, the
+ * callback will be executed on the main thread.
+ * @param callback A callback that receives a {@link Bundle} containing the package name that
+ * passed the health check.
+ */
+ @FlaggedApi(Flags.FLAG_ENABLE_CRASHRECOVERY)
+ public final void setHealthCheckPassedCallback(@CallbackExecutor @Nullable Executor executor,
+ @Nullable Consumer<Bundle> callback) {
+ mCallbackExecutor = executor;
+ mHealthCheckResultCallback = callback;
+ }
+
+ private void executeCallback(@NonNull String packageName) {
+ if (mHealthCheckResultCallback != null) {
+ Objects.requireNonNull(packageName,
+ "Package passing explicit health check must be non-null");
+ Bundle bundle = new Bundle();
+ bundle.putString(EXTRA_HEALTH_CHECK_PASSED_PACKAGE, packageName);
+ mHealthCheckResultCallback.accept(bundle);
+ } else {
+ Log.wtf(TAG, "System missed explicit health check result for " + packageName);
+ }
+ }
+
+ /**
+ * Implementors should call this to notify the system when explicit health check passes
+ * for {@code packageName};
+ */
+ public final void notifyHealthCheckPassed(@NonNull String packageName) {
+ if (mCallbackExecutor != null) {
+ mCallbackExecutor.execute(() -> executeCallback(packageName));
+ } else {
+ mHandler.post(() -> executeCallback(packageName));
+ }
+ }
+
+ /**
+ * A PackageConfig contains a package supporting explicit health checks and the
+ * timeout in {@link System#uptimeMillis} across reboots after which health
+ * check requests from clients are failed.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final class PackageConfig implements Parcelable {
+ private static final long DEFAULT_HEALTH_CHECK_TIMEOUT_MILLIS = TimeUnit.HOURS.toMillis(1);
+
+ private final String mPackageName;
+ private final long mHealthCheckTimeoutMillis;
+
+ /**
+ * Creates a new instance.
+ *
+ * @param packageName the package name
+ * @param durationMillis the duration in milliseconds, must be greater than or
+ * equal to 0. If it is 0, it will use a system default value.
+ */
+ public PackageConfig(@NonNull String packageName, long healthCheckTimeoutMillis) {
+ mPackageName = Preconditions.checkNotNull(packageName);
+ if (healthCheckTimeoutMillis == 0) {
+ mHealthCheckTimeoutMillis = DEFAULT_HEALTH_CHECK_TIMEOUT_MILLIS;
+ } else {
+ mHealthCheckTimeoutMillis = Preconditions.checkArgumentNonnegative(
+ healthCheckTimeoutMillis);
+ }
+ }
+
+ private PackageConfig(Parcel parcel) {
+ mPackageName = parcel.readString();
+ mHealthCheckTimeoutMillis = parcel.readLong();
+ }
+
+ /**
+ * Gets the package name.
+ *
+ * @return the package name
+ */
+ public @NonNull String getPackageName() {
+ return mPackageName;
+ }
+
+ /**
+ * Gets the timeout in milliseconds to evaluate an explicit health check result after a
+ * request.
+ *
+ * @return the duration in {@link System#uptimeMillis} across reboots
+ */
+ public long getHealthCheckTimeoutMillis() {
+ return mHealthCheckTimeoutMillis;
+ }
+
+ @NonNull
+ @Override
+ public String toString() {
+ return "PackageConfig{" + mPackageName + ", " + mHealthCheckTimeoutMillis + "}";
+ }
+
+ @Override
+ public boolean equals(@Nullable Object other) {
+ if (other == this) {
+ return true;
+ }
+ if (!(other instanceof PackageConfig)) {
+ return false;
+ }
+
+ PackageConfig otherInfo = (PackageConfig) other;
+ return Objects.equals(otherInfo.getHealthCheckTimeoutMillis(),
+ mHealthCheckTimeoutMillis)
+ && Objects.equals(otherInfo.getPackageName(), mPackageName);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mPackageName, mHealthCheckTimeoutMillis);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@SuppressLint({"MissingNullability"}) Parcel parcel, int flags) {
+ parcel.writeString(mPackageName);
+ parcel.writeLong(mHealthCheckTimeoutMillis);
+ }
+
+ public static final @NonNull Creator<PackageConfig> CREATOR = new Creator<PackageConfig>() {
+ @Override
+ public PackageConfig createFromParcel(Parcel source) {
+ return new PackageConfig(source);
+ }
+
+ @Override
+ public PackageConfig[] newArray(int size) {
+ return new PackageConfig[size];
+ }
+ };
+ }
+
+
+ private class ExplicitHealthCheckServiceWrapper extends IExplicitHealthCheckService.Stub {
+ @Override
+ public void setCallback(RemoteCallback callback) throws RemoteException {
+ mHandler.post(() -> mHealthCheckResultCallback = callback::sendResult);
+ }
+
+ @Override
+ public void request(String packageName) throws RemoteException {
+ mHandler.post(() -> ExplicitHealthCheckService.this.onRequestHealthCheck(packageName));
+ }
+
+ @Override
+ public void cancel(String packageName) throws RemoteException {
+ mHandler.post(() -> ExplicitHealthCheckService.this.onCancelHealthCheck(packageName));
+ }
+
+ @Override
+ public void getSupportedPackages(RemoteCallback callback) throws RemoteException {
+ mHandler.post(() -> {
+ List<PackageConfig> packages =
+ ExplicitHealthCheckService.this.onGetSupportedPackages();
+ Objects.requireNonNull(packages, "Supported package list must be non-null");
+ Bundle bundle = new Bundle();
+ bundle.putParcelableArrayList(EXTRA_SUPPORTED_PACKAGES, new ArrayList<>(packages));
+ callback.sendResult(bundle);
+ });
+ }
+
+ @Override
+ public void getRequestedPackages(RemoteCallback callback) throws RemoteException {
+ mHandler.post(() -> {
+ List<String> packages =
+ ExplicitHealthCheckService.this.onGetRequestedPackages();
+ Objects.requireNonNull(packages, "Requested package list must be non-null");
+ Bundle bundle = new Bundle();
+ bundle.putStringArrayList(EXTRA_REQUESTED_PACKAGES, new ArrayList<>(packages));
+ callback.sendResult(bundle);
+ });
+ }
+ }
+}
diff --git a/packages/CrashRecovery/framework/java/android/service/watchdog/IExplicitHealthCheckService.aidl b/packages/CrashRecovery/framework/java/android/service/watchdog/IExplicitHealthCheckService.aidl
new file mode 100644
index 0000000..9096509
--- /dev/null
+++ b/packages/CrashRecovery/framework/java/android/service/watchdog/IExplicitHealthCheckService.aidl
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.watchdog;
+
+import android.os.RemoteCallback;
+
+/**
+ * @hide
+ */
+@PermissionManuallyEnforced
+oneway interface IExplicitHealthCheckService
+{
+ void setCallback(in @nullable RemoteCallback callback);
+ void request(String packageName);
+ void cancel(String packageName);
+ void getSupportedPackages(in RemoteCallback callback);
+ void getRequestedPackages(in RemoteCallback callback);
+}
diff --git a/packages/CrashRecovery/framework/java/android/service/watchdog/OWNERS b/packages/CrashRecovery/framework/java/android/service/watchdog/OWNERS
new file mode 100644
index 0000000..1c045e1
--- /dev/null
+++ b/packages/CrashRecovery/framework/java/android/service/watchdog/OWNERS
@@ -0,0 +1,3 @@
+narayan@google.com
+nandana@google.com
+olilan@google.com
diff --git a/packages/CrashRecovery/framework/java/android/service/watchdog/PackageConfig.aidl b/packages/CrashRecovery/framework/java/android/service/watchdog/PackageConfig.aidl
new file mode 100644
index 0000000..0131586
--- /dev/null
+++ b/packages/CrashRecovery/framework/java/android/service/watchdog/PackageConfig.aidl
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.watchdog;
+
+/**
+ * @hide
+ */
+parcelable PackageConfig;
diff --git a/packages/CrashRecovery/services/module/java/com/android/server/ExplicitHealthCheckController.java b/packages/CrashRecovery/services/module/java/com/android/server/ExplicitHealthCheckController.java
new file mode 100644
index 0000000..da9a139
--- /dev/null
+++ b/packages/CrashRecovery/services/module/java/com/android/server/ExplicitHealthCheckController.java
@@ -0,0 +1,447 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import static android.service.watchdog.ExplicitHealthCheckService.EXTRA_HEALTH_CHECK_PASSED_PACKAGE;
+import static android.service.watchdog.ExplicitHealthCheckService.EXTRA_REQUESTED_PACKAGES;
+import static android.service.watchdog.ExplicitHealthCheckService.EXTRA_SUPPORTED_PACKAGES;
+import static android.service.watchdog.ExplicitHealthCheckService.PackageConfig;
+
+import android.Manifest;
+import android.annotation.MainThread;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.os.IBinder;
+import android.os.RemoteCallback;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.service.watchdog.ExplicitHealthCheckService;
+import android.service.watchdog.IExplicitHealthCheckService;
+import android.text.TextUtils;
+import android.util.ArraySet;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.function.Consumer;
+
+// TODO(b/120598832): Add tests
+/**
+ * Controls the connections with {@link ExplicitHealthCheckService}.
+ */
+class ExplicitHealthCheckController {
+ private static final String TAG = "ExplicitHealthCheckController";
+ private final Object mLock = new Object();
+ private final Context mContext;
+
+ // Called everytime a package passes the health check, so the watchdog is notified of the
+ // passing check. In practice, should never be null after it has been #setEnabled.
+ // To prevent deadlocks between the controller and watchdog threads, we have
+ // a lock invariant to ALWAYS acquire the PackageWatchdog#mLock before #mLock in this class.
+ // It's easier to just NOT hold #mLock when calling into watchdog code on this consumer.
+ @GuardedBy("mLock") @Nullable private Consumer<String> mPassedConsumer;
+ // Called everytime after a successful #syncRequest call, so the watchdog can receive packages
+ // supporting health checks and update its internal state. In practice, should never be null
+ // after it has been #setEnabled.
+ // To prevent deadlocks between the controller and watchdog threads, we have
+ // a lock invariant to ALWAYS acquire the PackageWatchdog#mLock before #mLock in this class.
+ // It's easier to just NOT hold #mLock when calling into watchdog code on this consumer.
+ @GuardedBy("mLock") @Nullable private Consumer<List<PackageConfig>> mSupportedConsumer;
+ // Called everytime we need to notify the watchdog to sync requests between itself and the
+ // health check service. In practice, should never be null after it has been #setEnabled.
+ // To prevent deadlocks between the controller and watchdog threads, we have
+ // a lock invariant to ALWAYS acquire the PackageWatchdog#mLock before #mLock in this class.
+ // It's easier to just NOT hold #mLock when calling into watchdog code on this runnable.
+ @GuardedBy("mLock") @Nullable private Runnable mNotifySyncRunnable;
+ // Actual binder object to the explicit health check service.
+ @GuardedBy("mLock") @Nullable private IExplicitHealthCheckService mRemoteService;
+ // Connection to the explicit health check service, necessary to unbind.
+ // We should only try to bind if mConnection is null, non-null indicates we
+ // are connected or at least connecting.
+ @GuardedBy("mLock") @Nullable private ServiceConnection mConnection;
+ // Bind state of the explicit health check service.
+ @GuardedBy("mLock") private boolean mEnabled;
+
+ ExplicitHealthCheckController(Context context) {
+ mContext = context;
+ }
+
+ /** Enables or disables explicit health checks. */
+ public void setEnabled(boolean enabled) {
+ synchronized (mLock) {
+ Slog.i(TAG, "Explicit health checks " + (enabled ? "enabled." : "disabled."));
+ mEnabled = enabled;
+ }
+ }
+
+ /**
+ * Sets callbacks to listen to important events from the controller.
+ *
+ * <p> Should be called once at initialization before any other calls to the controller to
+ * ensure a happens-before relationship of the set parameters and visibility on other threads.
+ */
+ public void setCallbacks(Consumer<String> passedConsumer,
+ Consumer<List<PackageConfig>> supportedConsumer, Runnable notifySyncRunnable) {
+ synchronized (mLock) {
+ if (mPassedConsumer != null || mSupportedConsumer != null
+ || mNotifySyncRunnable != null) {
+ Slog.wtf(TAG, "Resetting health check controller callbacks");
+ }
+
+ mPassedConsumer = Objects.requireNonNull(passedConsumer);
+ mSupportedConsumer = Objects.requireNonNull(supportedConsumer);
+ mNotifySyncRunnable = Objects.requireNonNull(notifySyncRunnable);
+ }
+ }
+
+ /**
+ * Calls the health check service to request or cancel packages based on
+ * {@code newRequestedPackages}.
+ *
+ * <p> Supported packages in {@code newRequestedPackages} that have not been previously
+ * requested will be requested while supported packages not in {@code newRequestedPackages}
+ * but were previously requested will be cancelled.
+ *
+ * <p> This handles binding and unbinding to the health check service as required.
+ *
+ * <p> Note, calling this may modify {@code newRequestedPackages}.
+ *
+ * <p> Note, this method is not thread safe, all calls should be serialized.
+ */
+ public void syncRequests(Set<String> newRequestedPackages) {
+ boolean enabled;
+ synchronized (mLock) {
+ enabled = mEnabled;
+ }
+
+ if (!enabled) {
+ Slog.i(TAG, "Health checks disabled, no supported packages");
+ // Call outside lock
+ mSupportedConsumer.accept(Collections.emptyList());
+ return;
+ }
+
+ getSupportedPackages(supportedPackageConfigs -> {
+ // Notify the watchdog without lock held
+ mSupportedConsumer.accept(supportedPackageConfigs);
+ getRequestedPackages(previousRequestedPackages -> {
+ synchronized (mLock) {
+ // Hold lock so requests and cancellations are sent atomically.
+ // It is important we don't mix requests from multiple threads.
+
+ Set<String> supportedPackages = new ArraySet<>();
+ for (PackageConfig config : supportedPackageConfigs) {
+ supportedPackages.add(config.getPackageName());
+ }
+ // Note, this may modify newRequestedPackages
+ newRequestedPackages.retainAll(supportedPackages);
+
+ // Cancel packages no longer requested
+ actOnDifference(previousRequestedPackages,
+ newRequestedPackages, p -> cancel(p));
+ // Request packages not yet requested
+ actOnDifference(newRequestedPackages,
+ previousRequestedPackages, p -> request(p));
+
+ if (newRequestedPackages.isEmpty()) {
+ Slog.i(TAG, "No more health check requests, unbinding...");
+ unbindService();
+ return;
+ }
+ }
+ });
+ });
+ }
+
+ private void actOnDifference(Collection<String> collection1, Collection<String> collection2,
+ Consumer<String> action) {
+ Iterator<String> iterator = collection1.iterator();
+ while (iterator.hasNext()) {
+ String packageName = iterator.next();
+ if (!collection2.contains(packageName)) {
+ action.accept(packageName);
+ }
+ }
+ }
+
+ /**
+ * Requests an explicit health check for {@code packageName}.
+ * After this request, the callback registered on {@link #setCallbacks} can receive explicit
+ * health check passed results.
+ */
+ private void request(String packageName) {
+ synchronized (mLock) {
+ if (!prepareServiceLocked("request health check for " + packageName)) {
+ return;
+ }
+
+ Slog.i(TAG, "Requesting health check for package " + packageName);
+ try {
+ mRemoteService.request(packageName);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed to request health check for package " + packageName, e);
+ }
+ }
+ }
+
+ /**
+ * Cancels all explicit health checks for {@code packageName}.
+ * After this request, the callback registered on {@link #setCallbacks} can no longer receive
+ * explicit health check passed results.
+ */
+ private void cancel(String packageName) {
+ synchronized (mLock) {
+ if (!prepareServiceLocked("cancel health check for " + packageName)) {
+ return;
+ }
+
+ Slog.i(TAG, "Cancelling health check for package " + packageName);
+ try {
+ mRemoteService.cancel(packageName);
+ } catch (RemoteException e) {
+ // Do nothing, if the service is down, when it comes up, we will sync requests,
+ // if there's some other error, retrying wouldn't fix anyways.
+ Slog.w(TAG, "Failed to cancel health check for package " + packageName, e);
+ }
+ }
+ }
+
+ /**
+ * Returns the packages that we can request explicit health checks for.
+ * The packages will be returned to the {@code consumer}.
+ */
+ private void getSupportedPackages(Consumer<List<PackageConfig>> consumer) {
+ synchronized (mLock) {
+ if (!prepareServiceLocked("get health check supported packages")) {
+ return;
+ }
+
+ Slog.d(TAG, "Getting health check supported packages");
+ try {
+ mRemoteService.getSupportedPackages(new RemoteCallback(result -> {
+ List<PackageConfig> packages =
+ result.getParcelableArrayList(EXTRA_SUPPORTED_PACKAGES, android.service.watchdog.ExplicitHealthCheckService.PackageConfig.class);
+ Slog.i(TAG, "Explicit health check supported packages " + packages);
+ consumer.accept(packages);
+ }));
+ } catch (RemoteException e) {
+ // Request failed, treat as if all observed packages are supported, if any packages
+ // expire during this period, we may incorrectly treat it as failing health checks
+ // even if we don't support health checks for the package.
+ Slog.w(TAG, "Failed to get health check supported packages", e);
+ }
+ }
+ }
+
+ /**
+ * Returns the packages for which health checks are currently in progress.
+ * The packages will be returned to the {@code consumer}.
+ */
+ private void getRequestedPackages(Consumer<List<String>> consumer) {
+ synchronized (mLock) {
+ if (!prepareServiceLocked("get health check requested packages")) {
+ return;
+ }
+
+ Slog.d(TAG, "Getting health check requested packages");
+ try {
+ mRemoteService.getRequestedPackages(new RemoteCallback(result -> {
+ List<String> packages = result.getStringArrayList(EXTRA_REQUESTED_PACKAGES);
+ Slog.i(TAG, "Explicit health check requested packages " + packages);
+ consumer.accept(packages);
+ }));
+ } catch (RemoteException e) {
+ // Request failed, treat as if we haven't requested any packages, if any packages
+ // were actually requested, they will not be cancelled now. May be cancelled later
+ Slog.w(TAG, "Failed to get health check requested packages", e);
+ }
+ }
+ }
+
+ /**
+ * Binds to the explicit health check service if the controller is enabled and
+ * not already bound.
+ */
+ private void bindService() {
+ synchronized (mLock) {
+ if (!mEnabled || mConnection != null || mRemoteService != null) {
+ if (!mEnabled) {
+ Slog.i(TAG, "Not binding to service, service disabled");
+ } else if (mRemoteService != null) {
+ Slog.i(TAG, "Not binding to service, service already connected");
+ } else {
+ Slog.i(TAG, "Not binding to service, service already connecting");
+ }
+ return;
+ }
+ ComponentName component = getServiceComponentNameLocked();
+ if (component == null) {
+ Slog.wtf(TAG, "Explicit health check service not found");
+ return;
+ }
+
+ Intent intent = new Intent();
+ intent.setComponent(component);
+ mConnection = new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ Slog.i(TAG, "Explicit health check service is connected " + name);
+ initState(service);
+ }
+
+ @Override
+ @MainThread
+ public void onServiceDisconnected(ComponentName name) {
+ // Service crashed or process was killed, #onServiceConnected will be called.
+ // Don't need to re-bind.
+ Slog.i(TAG, "Explicit health check service is disconnected " + name);
+ synchronized (mLock) {
+ mRemoteService = null;
+ }
+ }
+
+ @Override
+ public void onBindingDied(ComponentName name) {
+ // Application hosting service probably got updated
+ // Need to re-bind.
+ Slog.i(TAG, "Explicit health check service binding is dead. Rebind: " + name);
+ unbindService();
+ bindService();
+ }
+
+ @Override
+ public void onNullBinding(ComponentName name) {
+ // Should never happen. Service returned null from #onBind.
+ Slog.wtf(TAG, "Explicit health check service binding is null?? " + name);
+ }
+ };
+
+ mContext.bindServiceAsUser(intent, mConnection,
+ Context.BIND_AUTO_CREATE, UserHandle.SYSTEM);
+ Slog.i(TAG, "Explicit health check service is bound");
+ }
+ }
+
+ /** Unbinds the explicit health check service. */
+ private void unbindService() {
+ synchronized (mLock) {
+ if (mRemoteService != null) {
+ mContext.unbindService(mConnection);
+ mRemoteService = null;
+ mConnection = null;
+ }
+ Slog.i(TAG, "Explicit health check service is unbound");
+ }
+ }
+
+ @GuardedBy("mLock")
+ @Nullable
+ private ServiceInfo getServiceInfoLocked() {
+ final Intent intent = new Intent(ExplicitHealthCheckService.SERVICE_INTERFACE);
+ final ResolveInfo resolveInfo = mContext.getPackageManager().resolveService(intent,
+ PackageManager.GET_SERVICES | PackageManager.GET_META_DATA
+ | PackageManager.MATCH_SYSTEM_ONLY);
+ if (resolveInfo == null || resolveInfo.serviceInfo == null) {
+ Slog.w(TAG, "No valid components found.");
+ return null;
+ }
+ return resolveInfo.serviceInfo;
+ }
+
+ @GuardedBy("mLock")
+ @Nullable
+ private ComponentName getServiceComponentNameLocked() {
+ final ServiceInfo serviceInfo = getServiceInfoLocked();
+ if (serviceInfo == null) {
+ return null;
+ }
+
+ final ComponentName name = new ComponentName(serviceInfo.packageName, serviceInfo.name);
+ if (!Manifest.permission.BIND_EXPLICIT_HEALTH_CHECK_SERVICE
+ .equals(serviceInfo.permission)) {
+ Slog.w(TAG, name.flattenToShortString() + " does not require permission "
+ + Manifest.permission.BIND_EXPLICIT_HEALTH_CHECK_SERVICE);
+ return null;
+ }
+ return name;
+ }
+
+ private void initState(IBinder service) {
+ synchronized (mLock) {
+ if (!mEnabled) {
+ Slog.w(TAG, "Attempting to connect disabled service?? Unbinding...");
+ // Very unlikely, but we disabled the service after binding but before we connected
+ unbindService();
+ return;
+ }
+ mRemoteService = IExplicitHealthCheckService.Stub.asInterface(service);
+ try {
+ mRemoteService.setCallback(new RemoteCallback(result -> {
+ String packageName = result.getString(EXTRA_HEALTH_CHECK_PASSED_PACKAGE);
+ if (!TextUtils.isEmpty(packageName)) {
+ if (mPassedConsumer == null) {
+ Slog.wtf(TAG, "Health check passed for package " + packageName
+ + "but no consumer registered.");
+ } else {
+ // Call without lock held
+ mPassedConsumer.accept(packageName);
+ }
+ } else {
+ Slog.wtf(TAG, "Empty package passed explicit health check?");
+ }
+ }));
+ Slog.i(TAG, "Service initialized, syncing requests");
+ } catch (RemoteException e) {
+ Slog.wtf(TAG, "Could not setCallback on explicit health check service");
+ }
+ }
+ // Calling outside lock
+ mNotifySyncRunnable.run();
+ }
+
+ /**
+ * Prepares the health check service to receive requests.
+ *
+ * @return {@code true} if it is ready and we can proceed with a request,
+ * {@code false} otherwise. If it is not ready, and the service is enabled,
+ * we will bind and the request should be automatically attempted later.
+ */
+ @GuardedBy("mLock")
+ private boolean prepareServiceLocked(String action) {
+ if (mRemoteService != null && mEnabled) {
+ return true;
+ }
+ Slog.i(TAG, "Service not ready to " + action
+ + (mEnabled ? ". Binding..." : ". Disabled"));
+ if (mEnabled) {
+ bindService();
+ }
+ return false;
+ }
+}
diff --git a/packages/CrashRecovery/services/module/java/com/android/server/PackageWatchdog.java b/packages/CrashRecovery/services/module/java/com/android/server/PackageWatchdog.java
new file mode 100644
index 0000000..e4f07f9
--- /dev/null
+++ b/packages/CrashRecovery/services/module/java/com/android/server/PackageWatchdog.java
@@ -0,0 +1,2253 @@
+/*
+ * Copyright (C) 2018 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;
+
+import static android.content.Intent.ACTION_REBOOT;
+import static android.content.Intent.ACTION_SHUTDOWN;
+import static android.service.watchdog.ExplicitHealthCheckService.PackageConfig;
+import static android.util.Xml.Encoding.UTF_8;
+
+import static com.android.server.crashrecovery.CrashRecoveryUtils.dumpCrashRecoveryEvents;
+
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.VersionedPackage;
+import android.crashrecovery.flags.Flags;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Process;
+import android.os.SystemProperties;
+import android.provider.DeviceConfig;
+import android.sysprop.CrashRecoveryProperties;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.AtomicFile;
+import android.util.EventLog;
+import android.util.IndentingPrintWriter;
+import android.util.LongArrayQueue;
+import android.util.Slog;
+import android.util.Xml;
+import android.util.XmlUtils;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.FastXmlSerializer;
+import com.android.modules.utils.BackgroundThread;
+
+import libcore.io.IoUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.Set;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Monitors the health of packages on the system and notifies interested observers when packages
+ * fail. On failure, the registered observer with the least user impacting mitigation will
+ * be notified.
+ * @hide
+ */
+@FlaggedApi(Flags.FLAG_ENABLE_CRASHRECOVERY)
+@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
+public class PackageWatchdog {
+ private static final String TAG = "PackageWatchdog";
+
+ static final String PROPERTY_WATCHDOG_TRIGGER_DURATION_MILLIS =
+ "watchdog_trigger_failure_duration_millis";
+ static final String PROPERTY_WATCHDOG_TRIGGER_FAILURE_COUNT =
+ "watchdog_trigger_failure_count";
+ static final String PROPERTY_WATCHDOG_EXPLICIT_HEALTH_CHECK_ENABLED =
+ "watchdog_explicit_health_check_enabled";
+
+ // TODO: make the following values configurable via DeviceConfig
+ private static final long NATIVE_CRASH_POLLING_INTERVAL_MILLIS =
+ TimeUnit.SECONDS.toMillis(30);
+ private static final long NUMBER_OF_NATIVE_CRASH_POLLS = 10;
+
+
+ /** Reason for package failure could not be determined. */
+ public static final int FAILURE_REASON_UNKNOWN = 0;
+
+ /** The package had a native crash. */
+ public static final int FAILURE_REASON_NATIVE_CRASH = 1;
+
+ /** The package failed an explicit health check. */
+ public static final int FAILURE_REASON_EXPLICIT_HEALTH_CHECK = 2;
+
+ /** The app crashed. */
+ public static final int FAILURE_REASON_APP_CRASH = 3;
+
+ /** The app was not responding. */
+ public static final int FAILURE_REASON_APP_NOT_RESPONDING = 4;
+
+ /** The device was boot looping. */
+ public static final int FAILURE_REASON_BOOT_LOOP = 5;
+
+ /** @hide */
+ @IntDef(prefix = { "FAILURE_REASON_" }, value = {
+ FAILURE_REASON_UNKNOWN,
+ FAILURE_REASON_NATIVE_CRASH,
+ FAILURE_REASON_EXPLICIT_HEALTH_CHECK,
+ FAILURE_REASON_APP_CRASH,
+ FAILURE_REASON_APP_NOT_RESPONDING,
+ FAILURE_REASON_BOOT_LOOP
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface FailureReasons {}
+
+ // Duration to count package failures before it resets to 0
+ @VisibleForTesting
+ static final int DEFAULT_TRIGGER_FAILURE_DURATION_MS =
+ (int) TimeUnit.MINUTES.toMillis(1);
+ // Number of package failures within the duration above before we notify observers
+ @VisibleForTesting
+ static final int DEFAULT_TRIGGER_FAILURE_COUNT = 5;
+ @VisibleForTesting
+ static final long DEFAULT_OBSERVING_DURATION_MS = TimeUnit.DAYS.toMillis(2);
+ // Sliding window for tracking how many mitigation calls were made for a package.
+ @VisibleForTesting
+ static final long DEFAULT_DEESCALATION_WINDOW_MS = TimeUnit.HOURS.toMillis(1);
+ // Whether explicit health checks are enabled or not
+ private static final boolean DEFAULT_EXPLICIT_HEALTH_CHECK_ENABLED = true;
+
+ @VisibleForTesting
+ static final int DEFAULT_BOOT_LOOP_TRIGGER_COUNT = 5;
+
+ static final long DEFAULT_BOOT_LOOP_TRIGGER_WINDOW_MS = TimeUnit.MINUTES.toMillis(10);
+
+ // Time needed to apply mitigation
+ private static final String MITIGATION_WINDOW_MS =
+ "persist.device_config.configuration.mitigation_window_ms";
+ @VisibleForTesting
+ static final long DEFAULT_MITIGATION_WINDOW_MS = TimeUnit.SECONDS.toMillis(5);
+
+ // Threshold level at which or above user might experience significant disruption.
+ private static final String MAJOR_USER_IMPACT_LEVEL_THRESHOLD =
+ "persist.device_config.configuration.major_user_impact_level_threshold";
+ private static final int DEFAULT_MAJOR_USER_IMPACT_LEVEL_THRESHOLD =
+ PackageHealthObserverImpact.USER_IMPACT_LEVEL_71;
+
+ // Comma separated list of all packages exempt from user impact level threshold. If a package
+ // in the list is crash looping, all the mitigations including factory reset will be performed.
+ private static final String PACKAGES_EXEMPT_FROM_IMPACT_LEVEL_THRESHOLD =
+ "persist.device_config.configuration.packages_exempt_from_impact_level_threshold";
+
+ // Comma separated list of default packages exempt from user impact level threshold.
+ private static final String DEFAULT_PACKAGES_EXEMPT_FROM_IMPACT_LEVEL_THRESHOLD =
+ "com.android.systemui";
+
+ private long mNumberOfNativeCrashPollsRemaining;
+
+ private static final int DB_VERSION = 1;
+ private static final String TAG_PACKAGE_WATCHDOG = "package-watchdog";
+ private static final String TAG_PACKAGE = "package";
+ private static final String TAG_OBSERVER = "observer";
+ private static final String ATTR_VERSION = "version";
+ private static final String ATTR_NAME = "name";
+ private static final String ATTR_DURATION = "duration";
+ private static final String ATTR_EXPLICIT_HEALTH_CHECK_DURATION = "health-check-duration";
+ private static final String ATTR_PASSED_HEALTH_CHECK = "passed-health-check";
+ private static final String ATTR_MITIGATION_CALLS = "mitigation-calls";
+ private static final String ATTR_MITIGATION_COUNT = "mitigation-count";
+
+ // A file containing information about the current mitigation count in the case of a boot loop.
+ // This allows boot loop information to persist in the case of an fs-checkpoint being
+ // aborted.
+ private static final String METADATA_FILE = "/metadata/watchdog/mitigation_count.txt";
+
+ /**
+ * EventLog tags used when logging into the event log. Note the values must be sync with
+ * frameworks/base/services/core/java/com/android/server/EventLogTags.logtags to get correct
+ * name translation.
+ */
+ private static final int LOG_TAG_RESCUE_NOTE = 2900;
+
+ private static final Object sPackageWatchdogLock = new Object();
+ @GuardedBy("sPackageWatchdogLock")
+ private static PackageWatchdog sPackageWatchdog;
+
+ private static final Object sLock = new Object();
+ // System server context
+ private final Context mContext;
+ // Handler to run short running tasks
+ private final Handler mShortTaskHandler;
+ // Handler for processing IO and long running tasks
+ private final Handler mLongTaskHandler;
+ // Contains (observer-name -> observer-handle) that have ever been registered from
+ // previous boots. Observers with all packages expired are periodically pruned.
+ // It is saved to disk on system shutdown and repouplated on startup so it survives reboots.
+ @GuardedBy("sLock")
+ private final ArrayMap<String, ObserverInternal> mAllObservers = new ArrayMap<>();
+ // File containing the XML data of monitored packages /data/system/package-watchdog.xml
+ private final AtomicFile mPolicyFile;
+ private final ExplicitHealthCheckController mHealthCheckController;
+ private final Runnable mSyncRequests = this::syncRequests;
+ private final Runnable mSyncStateWithScheduledReason = this::syncStateWithScheduledReason;
+ private final Runnable mSaveToFile = this::saveToFile;
+ private final SystemClock mSystemClock;
+ private final BootThreshold mBootThreshold;
+ private final DeviceConfig.OnPropertiesChangedListener
+ mOnPropertyChangedListener = this::onPropertyChanged;
+
+ private final Set<String> mPackagesExemptFromImpactLevelThreshold = new ArraySet<>();
+
+ // The set of packages that have been synced with the ExplicitHealthCheckController
+ @GuardedBy("sLock")
+ private Set<String> mRequestedHealthCheckPackages = new ArraySet<>();
+ @GuardedBy("sLock")
+ private boolean mIsPackagesReady;
+ // Flag to control whether explicit health checks are supported or not
+ @GuardedBy("sLock")
+ private boolean mIsHealthCheckEnabled = DEFAULT_EXPLICIT_HEALTH_CHECK_ENABLED;
+ @GuardedBy("sLock")
+ private int mTriggerFailureDurationMs = DEFAULT_TRIGGER_FAILURE_DURATION_MS;
+ @GuardedBy("sLock")
+ private int mTriggerFailureCount = DEFAULT_TRIGGER_FAILURE_COUNT;
+ // SystemClock#uptimeMillis when we last executed #syncState
+ // 0 if no prune is scheduled.
+ @GuardedBy("sLock")
+ private long mUptimeAtLastStateSync;
+ // If true, sync explicit health check packages with the ExplicitHealthCheckController.
+ @GuardedBy("sLock")
+ private boolean mSyncRequired = false;
+
+ @GuardedBy("sLock")
+ private long mLastMitigation = -1000000;
+
+ @FunctionalInterface
+ @VisibleForTesting
+ interface SystemClock {
+ long uptimeMillis();
+ }
+
+ private PackageWatchdog(Context context) {
+ // Needs to be constructed inline
+ this(context, new AtomicFile(
+ new File(new File(Environment.getDataDirectory(), "system"),
+ "package-watchdog.xml")),
+ new Handler(Looper.myLooper()), BackgroundThread.getHandler(),
+ new ExplicitHealthCheckController(context),
+ android.os.SystemClock::uptimeMillis);
+ }
+
+ /**
+ * Creates a PackageWatchdog that allows injecting dependencies.
+ */
+ @VisibleForTesting
+ PackageWatchdog(Context context, AtomicFile policyFile, Handler shortTaskHandler,
+ Handler longTaskHandler, ExplicitHealthCheckController controller,
+ SystemClock clock) {
+ mContext = context;
+ mPolicyFile = policyFile;
+ mShortTaskHandler = shortTaskHandler;
+ mLongTaskHandler = longTaskHandler;
+ mHealthCheckController = controller;
+ mSystemClock = clock;
+ mNumberOfNativeCrashPollsRemaining = NUMBER_OF_NATIVE_CRASH_POLLS;
+ mBootThreshold = new BootThreshold(DEFAULT_BOOT_LOOP_TRIGGER_COUNT,
+ DEFAULT_BOOT_LOOP_TRIGGER_WINDOW_MS);
+
+ loadFromFile();
+ sPackageWatchdog = this;
+ }
+
+ /**
+ * Creates or gets singleton instance of PackageWatchdog.
+ *
+ * @param context The system server context.
+ */
+ public static @NonNull PackageWatchdog getInstance(@NonNull Context context) {
+ synchronized (sPackageWatchdogLock) {
+ if (sPackageWatchdog == null) {
+ new PackageWatchdog(context);
+ }
+ return sPackageWatchdog;
+ }
+ }
+
+ /**
+ * Called during boot to notify when packages are ready on the device so we can start
+ * binding.
+ * @hide
+ */
+ public void onPackagesReady() {
+ synchronized (sLock) {
+ mIsPackagesReady = true;
+ mHealthCheckController.setCallbacks(packageName -> onHealthCheckPassed(packageName),
+ packages -> onSupportedPackages(packages),
+ this::onSyncRequestNotified);
+ setPropertyChangedListenerLocked();
+ updateConfigs();
+ }
+ }
+
+ /**
+ * Registers {@code observer} to listen for package failures. Add a new ObserverInternal for
+ * this observer if it does not already exist.
+ * For executing mitigations observers will receive callback on the given executor.
+ *
+ * <p>Observers are expected to call this on boot. It does not specify any packages but
+ * it will resume observing any packages requested from a previous boot.
+ *
+ * @param observer instance of {@link PackageHealthObserver} for observing package failures
+ * and boot loops.
+ * @param executor Executor for the thread on which observers would receive callbacks
+ */
+ public void registerHealthObserver(@NonNull @CallbackExecutor Executor executor,
+ @NonNull PackageHealthObserver observer) {
+ synchronized (sLock) {
+ ObserverInternal internalObserver = mAllObservers.get(observer.getUniqueIdentifier());
+ if (internalObserver != null) {
+ internalObserver.registeredObserver = observer;
+ internalObserver.observerExecutor = executor;
+ } else {
+ internalObserver = new ObserverInternal(observer.getUniqueIdentifier(),
+ new ArrayList<>());
+ internalObserver.registeredObserver = observer;
+ internalObserver.observerExecutor = executor;
+ mAllObservers.put(observer.getUniqueIdentifier(), internalObserver);
+ syncState("added new observer");
+ }
+ }
+ }
+
+ /**
+ * Starts observing the health of the {@code packages} for {@code observer}.
+ * Note: Observer needs to be registered with {@link #registerHealthObserver} before calling
+ * this API.
+ *
+ * <p>If monitoring a package supporting explicit health check, at the end of the monitoring
+ * duration if {@link #onHealthCheckPassed} was never called,
+ * {@link PackageHealthObserver#onExecuteHealthCheckMitigation} will be called as if the
+ * package failed.
+ *
+ * <p>If {@code observer} is already monitoring a package in {@code packageNames},
+ * the monitoring window of that package will be reset to {@code durationMs} and the health
+ * check state will be reset to a default.
+ *
+ * <p>The {@code observer} must be registered with {@link #registerHealthObserver} before
+ * calling this method.
+ *
+ * @param packageNames The list of packages to check. If this is empty, the call will be a
+ * no-op.
+ *
+ * @param timeoutMs The timeout after which Explicit Health Checks would not run. If this is
+ * less than 1, a default monitoring duration 2 days will be used.
+ *
+ * @throws IllegalStateException if the observer was not previously registered
+ */
+ public void startExplicitHealthCheck(@NonNull List<String> packageNames, long timeoutMs,
+ @NonNull PackageHealthObserver observer) {
+ synchronized (sLock) {
+ if (!mAllObservers.containsKey(observer.getUniqueIdentifier())) {
+ Slog.wtf(TAG, "No observer found, need to register the observer: "
+ + observer.getUniqueIdentifier());
+ throw new IllegalStateException("Observer not registered");
+ }
+ }
+ if (packageNames.isEmpty()) {
+ Slog.wtf(TAG, "No packages to observe, " + observer.getUniqueIdentifier());
+ return;
+ }
+ if (timeoutMs < 1) {
+ Slog.wtf(TAG, "Invalid duration " + timeoutMs + "ms for observer "
+ + observer.getUniqueIdentifier() + ". Not observing packages " + packageNames);
+ timeoutMs = DEFAULT_OBSERVING_DURATION_MS;
+ }
+
+ List<MonitoredPackage> packages = new ArrayList<>();
+ for (int i = 0; i < packageNames.size(); i++) {
+ // Health checks not available yet so health check state will start INACTIVE
+ MonitoredPackage pkg = newMonitoredPackage(packageNames.get(i), timeoutMs, false);
+ if (pkg != null) {
+ packages.add(pkg);
+ } else {
+ Slog.w(TAG, "Failed to create MonitoredPackage for pkg=" + packageNames.get(i));
+ }
+ }
+
+ if (packages.isEmpty()) {
+ return;
+ }
+
+ // Sync before we add the new packages to the observers. This will #pruneObservers,
+ // causing any elapsed time to be deducted from all existing packages before we add new
+ // packages. This maintains the invariant that the elapsed time for ALL (new and existing)
+ // packages is the same.
+ mLongTaskHandler.post(() -> {
+ syncState("observing new packages");
+
+ synchronized (sLock) {
+ ObserverInternal oldObserver = mAllObservers.get(observer.getUniqueIdentifier());
+ if (oldObserver == null) {
+ Slog.d(TAG, observer.getUniqueIdentifier() + " started monitoring health "
+ + "of packages " + packageNames);
+ mAllObservers.put(observer.getUniqueIdentifier(),
+ new ObserverInternal(observer.getUniqueIdentifier(), packages));
+ } else {
+ Slog.d(TAG, observer.getUniqueIdentifier() + " added the following "
+ + "packages to monitor " + packageNames);
+ oldObserver.updatePackagesLocked(packages);
+ }
+ }
+
+ // Sync after we add the new packages to the observers. We may have received packges
+ // requiring an earlier schedule than we are currently scheduled for.
+ syncState("updated observers");
+ });
+
+ }
+
+ /**
+ * Unregisters {@code observer} from listening to package failure.
+ * Additionally, this stops observing any packages that may have previously been observed
+ * even from a previous boot.
+ */
+ public void unregisterHealthObserver(@NonNull PackageHealthObserver observer) {
+ mLongTaskHandler.post(() -> {
+ synchronized (sLock) {
+ mAllObservers.remove(observer.getUniqueIdentifier());
+ }
+ syncState("unregistering observer: " + observer.getUniqueIdentifier());
+ });
+ }
+
+ /**
+ * Called when a process fails due to a crash, ANR or explicit health check.
+ *
+ * <p>For each package contained in the process, one registered observer with the least user
+ * impact will be notified for mitigation.
+ *
+ * <p>This method could be called frequently if there is a severe problem on the device.
+ */
+ public void notifyPackageFailure(@NonNull List<VersionedPackage> packages,
+ @FailureReasons int failureReason) {
+ if (packages == null) {
+ Slog.w(TAG, "Could not resolve a list of failing packages");
+ return;
+ }
+ synchronized (sLock) {
+ final long now = mSystemClock.uptimeMillis();
+ if (Flags.recoverabilityDetection()) {
+ if (now >= mLastMitigation
+ && (now - mLastMitigation) < getMitigationWindowMs()) {
+ Slog.i(TAG, "Skipping notifyPackageFailure mitigation");
+ return;
+ }
+ }
+ }
+ mLongTaskHandler.post(() -> {
+ synchronized (sLock) {
+ if (mAllObservers.isEmpty()) {
+ return;
+ }
+ boolean requiresImmediateAction = (failureReason == FAILURE_REASON_NATIVE_CRASH
+ || failureReason == FAILURE_REASON_EXPLICIT_HEALTH_CHECK);
+ if (requiresImmediateAction) {
+ handleFailureImmediately(packages, failureReason);
+ } else {
+ for (int pIndex = 0; pIndex < packages.size(); pIndex++) {
+ VersionedPackage versionedPackage = packages.get(pIndex);
+ // Observer that will receive failure for versionedPackage
+ ObserverInternal currentObserverToNotify = null;
+ int currentObserverImpact = Integer.MAX_VALUE;
+ MonitoredPackage currentMonitoredPackage = null;
+
+ // Find observer with least user impact
+ for (int oIndex = 0; oIndex < mAllObservers.size(); oIndex++) {
+ ObserverInternal observer = mAllObservers.valueAt(oIndex);
+ PackageHealthObserver registeredObserver = observer.registeredObserver;
+ if (registeredObserver != null
+ && observer.notifyPackageFailureLocked(
+ versionedPackage.getPackageName())) {
+ MonitoredPackage p = observer.getMonitoredPackage(
+ versionedPackage.getPackageName());
+ int mitigationCount = 1;
+ if (p != null) {
+ mitigationCount = p.getMitigationCountLocked() + 1;
+ }
+ int impact = registeredObserver.onHealthCheckFailed(
+ versionedPackage, failureReason, mitigationCount);
+ if (impact != PackageHealthObserverImpact.USER_IMPACT_LEVEL_0
+ && impact < currentObserverImpact) {
+ currentObserverToNotify = observer;
+ currentObserverImpact = impact;
+ currentMonitoredPackage = p;
+ }
+ }
+ }
+
+ // Execute action with least user impact
+ if (currentObserverToNotify != null) {
+ int mitigationCount;
+ if (currentMonitoredPackage != null) {
+ currentMonitoredPackage.noteMitigationCallLocked();
+ mitigationCount =
+ currentMonitoredPackage.getMitigationCountLocked();
+ } else {
+ mitigationCount = 1;
+ }
+ if (Flags.recoverabilityDetection()) {
+ maybeExecute(currentObserverToNotify, versionedPackage,
+ failureReason, currentObserverImpact, mitigationCount);
+ } else {
+ PackageHealthObserver registeredObserver =
+ currentObserverToNotify.registeredObserver;
+ currentObserverToNotify.observerExecutor.execute(() ->
+ registeredObserver.onExecuteHealthCheckMitigation(
+ versionedPackage, failureReason, mitigationCount));
+ }
+ }
+ }
+ }
+ }
+ });
+ }
+
+ /**
+ * For native crashes or explicit health check failures, call directly into each observer to
+ * mitigate the error without going through failure threshold logic.
+ */
+ @GuardedBy("sLock")
+ private void handleFailureImmediately(List<VersionedPackage> packages,
+ @FailureReasons int failureReason) {
+ VersionedPackage failingPackage = packages.size() > 0 ? packages.get(0) : null;
+ ObserverInternal currentObserverToNotify = null;
+ int currentObserverImpact = Integer.MAX_VALUE;
+ for (ObserverInternal observer: mAllObservers.values()) {
+ PackageHealthObserver registeredObserver = observer.registeredObserver;
+ if (registeredObserver != null) {
+ int impact = registeredObserver.onHealthCheckFailed(
+ failingPackage, failureReason, 1);
+ if (impact != PackageHealthObserverImpact.USER_IMPACT_LEVEL_0
+ && impact < currentObserverImpact) {
+ currentObserverToNotify = observer;
+ currentObserverImpact = impact;
+ }
+ }
+ }
+ if (currentObserverToNotify != null) {
+ if (Flags.recoverabilityDetection()) {
+ maybeExecute(currentObserverToNotify, failingPackage, failureReason,
+ currentObserverImpact, /*mitigationCount=*/ 1);
+ } else {
+ PackageHealthObserver registeredObserver =
+ currentObserverToNotify.registeredObserver;
+ currentObserverToNotify.observerExecutor.execute(() ->
+ registeredObserver.onExecuteHealthCheckMitigation(failingPackage,
+ failureReason, 1));
+
+ }
+ }
+ }
+
+ private void maybeExecute(ObserverInternal currentObserverToNotify,
+ VersionedPackage versionedPackage,
+ @FailureReasons int failureReason,
+ int currentObserverImpact,
+ int mitigationCount) {
+ if (allowMitigations(currentObserverImpact, versionedPackage)) {
+ PackageHealthObserver registeredObserver;
+ synchronized (sLock) {
+ mLastMitigation = mSystemClock.uptimeMillis();
+ registeredObserver = currentObserverToNotify.registeredObserver;
+ }
+ currentObserverToNotify.observerExecutor.execute(() ->
+ registeredObserver.onExecuteHealthCheckMitigation(versionedPackage,
+ failureReason, mitigationCount));
+ }
+ }
+
+ private boolean allowMitigations(int currentObserverImpact,
+ VersionedPackage versionedPackage) {
+ return currentObserverImpact < getUserImpactLevelLimit()
+ || getPackagesExemptFromImpactLevelThreshold().contains(
+ versionedPackage.getPackageName());
+ }
+
+ private long getMitigationWindowMs() {
+ return SystemProperties.getLong(MITIGATION_WINDOW_MS, DEFAULT_MITIGATION_WINDOW_MS);
+ }
+
+
+ /**
+ * Called when the system server boots. If the system server is detected to be in a boot loop,
+ * query each observer and perform the mitigation action with the lowest user impact.
+ *
+ * Note: PackageWatchdog considers system_server restart loop as bootloop. Full reboots
+ * are not counted in bootloop.
+ * @hide
+ */
+ @SuppressWarnings("GuardedBy")
+ public void noteBoot() {
+ synchronized (sLock) {
+ // if boot count has reached threshold, start mitigation.
+ // We wait until threshold number of restarts only for the first time. Perform
+ // mitigations for every restart after that.
+ boolean mitigate = mBootThreshold.incrementAndTest();
+ if (mitigate) {
+ if (!Flags.recoverabilityDetection()) {
+ mBootThreshold.reset();
+ }
+ int mitigationCount = mBootThreshold.getMitigationCount() + 1;
+ ObserverInternal currentObserverToNotify = null;
+ int currentObserverImpact = Integer.MAX_VALUE;
+ for (int i = 0; i < mAllObservers.size(); i++) {
+ final ObserverInternal observer = mAllObservers.valueAt(i);
+ PackageHealthObserver registeredObserver = observer.registeredObserver;
+ if (registeredObserver != null) {
+ int impact = Flags.recoverabilityDetection()
+ ? registeredObserver.onBootLoop(
+ observer.getBootMitigationCount() + 1)
+ : registeredObserver.onBootLoop(mitigationCount);
+ if (impact != PackageHealthObserverImpact.USER_IMPACT_LEVEL_0
+ && impact < currentObserverImpact) {
+ currentObserverToNotify = observer;
+ currentObserverImpact = impact;
+ }
+ }
+ }
+
+ if (currentObserverToNotify != null) {
+ PackageHealthObserver registeredObserver =
+ currentObserverToNotify.registeredObserver;
+ if (Flags.recoverabilityDetection()) {
+ int currentObserverMitigationCount =
+ currentObserverToNotify.getBootMitigationCount() + 1;
+ currentObserverToNotify.setBootMitigationCount(
+ currentObserverMitigationCount);
+ saveAllObserversBootMitigationCountToMetadata(METADATA_FILE);
+ currentObserverToNotify.observerExecutor
+ .execute(() -> registeredObserver.onExecuteBootLoopMitigation(
+ currentObserverMitigationCount));
+ } else {
+ mBootThreshold.setMitigationCount(mitigationCount);
+ mBootThreshold.saveMitigationCountToMetadata();
+ currentObserverToNotify.observerExecutor
+ .execute(() -> registeredObserver.onExecuteBootLoopMitigation(
+ mitigationCount));
+
+ }
+ }
+ }
+ }
+ }
+
+ // TODO(b/120598832): Optimize write? Maybe only write a separate smaller file? Also
+ // avoid holding lock?
+ // This currently adds about 7ms extra to shutdown thread
+ /** @hide Writes the package information to file during shutdown. */
+ public void writeNow() {
+ synchronized (sLock) {
+ // Must only run synchronous tasks as this runs on the ShutdownThread and no other
+ // thread is guaranteed to run during shutdown.
+ if (!mAllObservers.isEmpty()) {
+ mLongTaskHandler.removeCallbacks(mSaveToFile);
+ pruneObserversLocked();
+ saveToFile();
+ Slog.i(TAG, "Last write to update package durations");
+ }
+ }
+ }
+
+ /**
+ * Enables or disables explicit health checks.
+ * <p> If explicit health checks are enabled, the health check service is started.
+ * <p> If explicit health checks are disabled, pending explicit health check requests are
+ * passed and the health check service is stopped.
+ */
+ private void setExplicitHealthCheckEnabled(boolean enabled) {
+ synchronized (sLock) {
+ mIsHealthCheckEnabled = enabled;
+ mHealthCheckController.setEnabled(enabled);
+ mSyncRequired = true;
+ // Prune to update internal state whenever health check is enabled/disabled
+ syncState("health check state " + (enabled ? "enabled" : "disabled"));
+ }
+ }
+
+ /**
+ * This method should be only called on mShortTaskHandler, since it modifies
+ * {@link #mNumberOfNativeCrashPollsRemaining}.
+ */
+ private void checkAndMitigateNativeCrashes() {
+ mNumberOfNativeCrashPollsRemaining--;
+ // Check if native watchdog reported a crash
+ if ("1".equals(SystemProperties.get("sys.init.updatable_crashing"))) {
+ // We rollback all available low impact rollbacks when crash is unattributable
+ notifyPackageFailure(Collections.EMPTY_LIST, FAILURE_REASON_NATIVE_CRASH);
+ // we stop polling after an attempt to execute rollback, regardless of whether the
+ // attempt succeeds or not
+ } else {
+ if (mNumberOfNativeCrashPollsRemaining > 0) {
+ mShortTaskHandler.postDelayed(() -> checkAndMitigateNativeCrashes(),
+ NATIVE_CRASH_POLLING_INTERVAL_MILLIS);
+ }
+ }
+ }
+
+ /**
+ * Since this method can eventually trigger a rollback, it should be called
+ * only once boot has completed {@code onBootCompleted} and not earlier, because the install
+ * session must be entirely completed before we try to rollback.
+ * @hide
+ */
+ public void scheduleCheckAndMitigateNativeCrashes() {
+ Slog.i(TAG, "Scheduling " + mNumberOfNativeCrashPollsRemaining + " polls to check "
+ + "and mitigate native crashes");
+ mShortTaskHandler.post(()->checkAndMitigateNativeCrashes());
+ }
+
+ private int getUserImpactLevelLimit() {
+ return SystemProperties.getInt(MAJOR_USER_IMPACT_LEVEL_THRESHOLD,
+ DEFAULT_MAJOR_USER_IMPACT_LEVEL_THRESHOLD);
+ }
+
+ private Set<String> getPackagesExemptFromImpactLevelThreshold() {
+ if (mPackagesExemptFromImpactLevelThreshold.isEmpty()) {
+ String packageNames = SystemProperties.get(PACKAGES_EXEMPT_FROM_IMPACT_LEVEL_THRESHOLD,
+ DEFAULT_PACKAGES_EXEMPT_FROM_IMPACT_LEVEL_THRESHOLD);
+ return Set.of(packageNames.split("\\s*,\\s*"));
+ }
+ return mPackagesExemptFromImpactLevelThreshold;
+ }
+
+ /**
+ * Indicates that a mitigation was successfully triggered or executed during
+ * {@link PackageHealthObserver#onExecuteHealthCheckMitigation} or
+ * {@link PackageHealthObserver#onExecuteBootLoopMitigation}.
+ */
+ public static final int MITIGATION_RESULT_SUCCESS =
+ ObserverMitigationResult.MITIGATION_RESULT_SUCCESS;
+
+ /**
+ * Indicates that a mitigation executed during
+ * {@link PackageHealthObserver#onExecuteHealthCheckMitigation} or
+ * {@link PackageHealthObserver#onExecuteBootLoopMitigation} was skipped.
+ */
+ public static final int MITIGATION_RESULT_SKIPPED =
+ ObserverMitigationResult.MITIGATION_RESULT_SKIPPED;
+
+
+ /**
+ * Possible return values of the for mitigations executed during
+ * {@link PackageHealthObserver#onExecuteHealthCheckMitigation} and
+ * {@link PackageHealthObserver#onExecuteBootLoopMitigation}.
+ * @hide
+ */
+ @Retention(SOURCE)
+ @IntDef(prefix = "MITIGATION_RESULT_", value = {
+ ObserverMitigationResult.MITIGATION_RESULT_SUCCESS,
+ ObserverMitigationResult.MITIGATION_RESULT_SKIPPED,
+ })
+ public @interface ObserverMitigationResult {
+ int MITIGATION_RESULT_SUCCESS = 1;
+ int MITIGATION_RESULT_SKIPPED = 2;
+ }
+
+ /**
+ * The minimum value that can be returned by any observer.
+ * It represents that no mitigations were available.
+ */
+ public static final int USER_IMPACT_THRESHOLD_NONE =
+ PackageHealthObserverImpact.USER_IMPACT_LEVEL_0;
+
+ /**
+ * The mitigation impact beyond which the user will start noticing the mitigations.
+ */
+ public static final int USER_IMPACT_THRESHOLD_MEDIUM =
+ PackageHealthObserverImpact.USER_IMPACT_LEVEL_20;
+
+ /**
+ * The mitigation impact beyond which the user impact is severely high.
+ */
+ public static final int USER_IMPACT_THRESHOLD_HIGH =
+ PackageHealthObserverImpact.USER_IMPACT_LEVEL_71;
+
+ /**
+ * Possible severity values of the user impact of a
+ * {@link PackageHealthObserver#onExecuteHealthCheckMitigation}.
+ * @hide
+ */
+ @Retention(SOURCE)
+ @IntDef(value = {PackageHealthObserverImpact.USER_IMPACT_LEVEL_0,
+ PackageHealthObserverImpact.USER_IMPACT_LEVEL_10,
+ PackageHealthObserverImpact.USER_IMPACT_LEVEL_20,
+ PackageHealthObserverImpact.USER_IMPACT_LEVEL_30,
+ PackageHealthObserverImpact.USER_IMPACT_LEVEL_40,
+ PackageHealthObserverImpact.USER_IMPACT_LEVEL_50,
+ PackageHealthObserverImpact.USER_IMPACT_LEVEL_70,
+ PackageHealthObserverImpact.USER_IMPACT_LEVEL_71,
+ PackageHealthObserverImpact.USER_IMPACT_LEVEL_75,
+ PackageHealthObserverImpact.USER_IMPACT_LEVEL_80,
+ PackageHealthObserverImpact.USER_IMPACT_LEVEL_90,
+ PackageHealthObserverImpact.USER_IMPACT_LEVEL_100})
+ public @interface PackageHealthObserverImpact {
+ /** No action to take. */
+ int USER_IMPACT_LEVEL_0 = 0;
+ /* Action has low user impact, user of a device will barely notice. */
+ int USER_IMPACT_LEVEL_10 = 10;
+ /* Actions having medium user impact, user of a device will likely notice. */
+ int USER_IMPACT_LEVEL_20 = 20;
+ int USER_IMPACT_LEVEL_30 = 30;
+ int USER_IMPACT_LEVEL_40 = 40;
+ int USER_IMPACT_LEVEL_50 = 50;
+ int USER_IMPACT_LEVEL_70 = 70;
+ /* Action has high user impact, a last resort, user of a device will be very frustrated. */
+ int USER_IMPACT_LEVEL_71 = 71;
+ int USER_IMPACT_LEVEL_75 = 75;
+ int USER_IMPACT_LEVEL_80 = 80;
+ int USER_IMPACT_LEVEL_90 = 90;
+ int USER_IMPACT_LEVEL_100 = 100;
+ }
+
+ /** Register instances of this interface to receive notifications on package failure. */
+ @SuppressLint({"CallbackName"})
+ public interface PackageHealthObserver {
+ /**
+ * Called when health check fails for the {@code versionedPackage}.
+ * Note: if the returned user impact is higher than {@link #USER_IMPACT_THRESHOLD_HIGH},
+ * then {@link #onExecuteHealthCheckMitigation} would be called only in severe device
+ * conditions like boot-loop or network failure.
+ *
+ * @param versionedPackage the package that is failing. This may be null if a native
+ * service is crashing.
+ * @param failureReason the type of failure that is occurring.
+ * @param mitigationCount the number of times mitigation has been called for this package
+ * (including this time).
+ *
+ * @return any value greater than {@link #USER_IMPACT_THRESHOLD_NONE} to express
+ * the impact of mitigation on the user in {@link #onExecuteHealthCheckMitigation}.
+ * Returning {@link #USER_IMPACT_THRESHOLD_NONE} would indicate no mitigations available.
+ */
+ @PackageHealthObserverImpact int onHealthCheckFailed(
+ @Nullable VersionedPackage versionedPackage,
+ @FailureReasons int failureReason,
+ int mitigationCount);
+
+ /**
+ * This would be called after {@link #onHealthCheckFailed}.
+ * This is called only if current observer returned least impact mitigation for failed
+ * health check.
+ *
+ * @param versionedPackage the package that is failing. This may be null if a native
+ * service is crashing.
+ * @param failureReason the type of failure that is occurring.
+ * @param mitigationCount the number of times mitigation has been called for this package
+ * (including this time).
+ * @return {@link #MITIGATION_RESULT_SUCCESS} if the mitigation was successful,
+ * or {@link #MITIGATION_RESULT_SKIPPED} if the mitigation was skipped.
+ */
+ @ObserverMitigationResult int onExecuteHealthCheckMitigation(
+ @Nullable VersionedPackage versionedPackage,
+ @FailureReasons int failureReason, int mitigationCount);
+
+
+ /**
+ * Called when the system server has booted several times within a window of time, defined
+ * by {@link #mBootThreshold}
+ *
+ * @param mitigationCount the number of times mitigation has been attempted for this
+ * boot loop (including this time).
+ *
+ * @return any value greater than {@link #USER_IMPACT_THRESHOLD_NONE} to express
+ * the impact of mitigation on the user in {@link #onExecuteBootLoopMitigation}.
+ * Returning {@link #USER_IMPACT_THRESHOLD_NONE} would indicate no mitigations available.
+ */
+ default @PackageHealthObserverImpact int onBootLoop(int mitigationCount) {
+ return PackageHealthObserverImpact.USER_IMPACT_LEVEL_0;
+ }
+
+ /**
+ * This would be called after {@link #onBootLoop}.
+ * This is called only if current observer returned least impact mitigation for fixing
+ * boot loop.
+ *
+ * @param mitigationCount the number of times mitigation has been attempted for this
+ * boot loop (including this time).
+ *
+ * @return {@link #MITIGATION_RESULT_SUCCESS} if the mitigation was successful,
+ * or {@link #MITIGATION_RESULT_SKIPPED} if the mitigation was skipped.
+ */
+ default @ObserverMitigationResult int onExecuteBootLoopMitigation(int mitigationCount) {
+ return ObserverMitigationResult.MITIGATION_RESULT_SKIPPED;
+ }
+
+ // TODO(b/120598832): Ensure uniqueness?
+ /**
+ * Identifier for the observer, should not change across device updates otherwise the
+ * watchdog may drop observing packages with the old name.
+ */
+ @NonNull String getUniqueIdentifier();
+
+ /**
+ * An observer will not be pruned if this is set, even if the observer is not explicitly
+ * monitoring any packages.
+ */
+ default boolean isPersistent() {
+ return false;
+ }
+
+ /**
+ * Returns {@code true} if this observer wishes to observe the given package, {@code false}
+ * otherwise.
+ * Any failing package can be passed on to the observer. Currently the packages that have
+ * ANRs and perform {@link android.service.watchdog.ExplicitHealthCheckService} are being
+ * passed to observers in these API.
+ *
+ * <p> A persistent observer may choose to start observing certain failing packages, even if
+ * it has not explicitly asked to watch the package with {@link #startExplicitHealthCheck}.
+ */
+ default boolean mayObservePackage(@NonNull String packageName) {
+ return false;
+ }
+ }
+
+ @VisibleForTesting
+ long getTriggerFailureCount() {
+ synchronized (sLock) {
+ return mTriggerFailureCount;
+ }
+ }
+
+ @VisibleForTesting
+ long getTriggerFailureDurationMs() {
+ synchronized (sLock) {
+ return mTriggerFailureDurationMs;
+ }
+ }
+
+ /**
+ * Serializes and syncs health check requests with the {@link ExplicitHealthCheckController}.
+ */
+ private void syncRequestsAsync() {
+ mShortTaskHandler.removeCallbacks(mSyncRequests);
+ mShortTaskHandler.post(mSyncRequests);
+ }
+
+ /**
+ * Syncs health check requests with the {@link ExplicitHealthCheckController}.
+ * Calls to this must be serialized.
+ *
+ * @see #syncRequestsAsync
+ */
+ private void syncRequests() {
+ boolean syncRequired = false;
+ synchronized (sLock) {
+ if (mIsPackagesReady) {
+ Set<String> packages = getPackagesPendingHealthChecksLocked();
+ if (mSyncRequired || !packages.equals(mRequestedHealthCheckPackages)
+ || packages.isEmpty()) {
+ syncRequired = true;
+ mRequestedHealthCheckPackages = packages;
+ }
+ } // else, we will sync requests when packages become ready
+ }
+
+ // Call outside lock to avoid holding lock when calling into the controller.
+ if (syncRequired) {
+ Slog.i(TAG, "Syncing health check requests for packages: "
+ + mRequestedHealthCheckPackages);
+ mHealthCheckController.syncRequests(mRequestedHealthCheckPackages);
+ mSyncRequired = false;
+ }
+ }
+
+ /**
+ * Updates the observers monitoring {@code packageName} that explicit health check has passed.
+ *
+ * <p> This update is strictly for registered observers at the time of the call
+ * Observers that register after this signal will have no knowledge of prior signals and will
+ * effectively behave as if the explicit health check hasn't passed for {@code packageName}.
+ *
+ * <p> {@code packageName} can still be considered failed if reported by
+ * {@link #notifyPackageFailureLocked} before the package expires.
+ *
+ * <p> Triggered by components outside the system server when they are fully functional after an
+ * update.
+ */
+ private void onHealthCheckPassed(String packageName) {
+ Slog.i(TAG, "Health check passed for package: " + packageName);
+ boolean isStateChanged = false;
+
+ synchronized (sLock) {
+ for (int observerIdx = 0; observerIdx < mAllObservers.size(); observerIdx++) {
+ ObserverInternal observer = mAllObservers.valueAt(observerIdx);
+ MonitoredPackage monitoredPackage = observer.getMonitoredPackage(packageName);
+
+ if (monitoredPackage != null) {
+ int oldState = monitoredPackage.getHealthCheckStateLocked();
+ int newState = monitoredPackage.tryPassHealthCheckLocked();
+ isStateChanged |= oldState != newState;
+ }
+ }
+ }
+
+ if (isStateChanged) {
+ syncState("health check passed for " + packageName);
+ }
+ }
+
+ private void onSupportedPackages(List<PackageConfig> supportedPackages) {
+ boolean isStateChanged = false;
+
+ Map<String, Long> supportedPackageTimeouts = new ArrayMap<>();
+ Iterator<PackageConfig> it = supportedPackages.iterator();
+ while (it.hasNext()) {
+ PackageConfig info = it.next();
+ supportedPackageTimeouts.put(info.getPackageName(), info.getHealthCheckTimeoutMillis());
+ }
+
+ synchronized (sLock) {
+ Slog.d(TAG, "Received supported packages " + supportedPackages);
+ Iterator<ObserverInternal> oit = mAllObservers.values().iterator();
+ while (oit.hasNext()) {
+ Iterator<MonitoredPackage> pit = oit.next().getMonitoredPackages()
+ .values().iterator();
+ while (pit.hasNext()) {
+ MonitoredPackage monitoredPackage = pit.next();
+ String packageName = monitoredPackage.getName();
+ int oldState = monitoredPackage.getHealthCheckStateLocked();
+ int newState;
+
+ if (supportedPackageTimeouts.containsKey(packageName)) {
+ // Supported packages become ACTIVE if currently INACTIVE
+ newState = monitoredPackage.setHealthCheckActiveLocked(
+ supportedPackageTimeouts.get(packageName));
+ } else {
+ // Unsupported packages are marked as PASSED unless already FAILED
+ newState = monitoredPackage.tryPassHealthCheckLocked();
+ }
+ isStateChanged |= oldState != newState;
+ }
+ }
+ }
+
+ if (isStateChanged) {
+ syncState("updated health check supported packages " + supportedPackages);
+ }
+ }
+
+ private void onSyncRequestNotified() {
+ synchronized (sLock) {
+ mSyncRequired = true;
+ syncRequestsAsync();
+ }
+ }
+
+ @GuardedBy("sLock")
+ private Set<String> getPackagesPendingHealthChecksLocked() {
+ Set<String> packages = new ArraySet<>();
+ Iterator<ObserverInternal> oit = mAllObservers.values().iterator();
+ while (oit.hasNext()) {
+ ObserverInternal observer = oit.next();
+ Iterator<MonitoredPackage> pit =
+ observer.getMonitoredPackages().values().iterator();
+ while (pit.hasNext()) {
+ MonitoredPackage monitoredPackage = pit.next();
+ String packageName = monitoredPackage.getName();
+ if (monitoredPackage.isPendingHealthChecksLocked()) {
+ packages.add(packageName);
+ }
+ }
+ }
+ return packages;
+ }
+
+ /**
+ * Syncs the state of the observers.
+ *
+ * <p> Prunes all observers, saves new state to disk, syncs health check requests with the
+ * health check service and schedules the next state sync.
+ */
+ private void syncState(String reason) {
+ synchronized (sLock) {
+ Slog.i(TAG, "Syncing state, reason: " + reason);
+ pruneObserversLocked();
+
+ saveToFileAsync();
+ syncRequestsAsync();
+
+ // Done syncing state, schedule the next state sync
+ scheduleNextSyncStateLocked();
+ }
+ }
+
+ private void syncStateWithScheduledReason() {
+ syncState("scheduled");
+ }
+
+ @GuardedBy("sLock")
+ private void scheduleNextSyncStateLocked() {
+ long durationMs = getNextStateSyncMillisLocked();
+ mShortTaskHandler.removeCallbacks(mSyncStateWithScheduledReason);
+ if (durationMs == Long.MAX_VALUE) {
+ Slog.i(TAG, "Cancelling state sync, nothing to sync");
+ mUptimeAtLastStateSync = 0;
+ } else {
+ mUptimeAtLastStateSync = mSystemClock.uptimeMillis();
+ mShortTaskHandler.postDelayed(mSyncStateWithScheduledReason, durationMs);
+ }
+ }
+
+ /**
+ * Returns the next duration in millis to sync the watchdog state.
+ *
+ * @returns Long#MAX_VALUE if there are no observed packages.
+ */
+ @GuardedBy("sLock")
+ private long getNextStateSyncMillisLocked() {
+ long shortestDurationMs = Long.MAX_VALUE;
+ for (int oIndex = 0; oIndex < mAllObservers.size(); oIndex++) {
+ ArrayMap<String, MonitoredPackage> packages = mAllObservers.valueAt(oIndex)
+ .getMonitoredPackages();
+ for (int pIndex = 0; pIndex < packages.size(); pIndex++) {
+ MonitoredPackage mp = packages.valueAt(pIndex);
+ long duration = mp.getShortestScheduleDurationMsLocked();
+ if (duration < shortestDurationMs) {
+ shortestDurationMs = duration;
+ }
+ }
+ }
+ return shortestDurationMs;
+ }
+
+ /**
+ * Removes {@code elapsedMs} milliseconds from all durations on monitored packages
+ * and updates other internal state.
+ */
+ @GuardedBy("sLock")
+ private void pruneObserversLocked() {
+ long elapsedMs = mUptimeAtLastStateSync == 0
+ ? 0 : mSystemClock.uptimeMillis() - mUptimeAtLastStateSync;
+ if (elapsedMs <= 0) {
+ Slog.i(TAG, "Not pruning observers, elapsed time: " + elapsedMs + "ms");
+ return;
+ }
+
+ Iterator<ObserverInternal> it = mAllObservers.values().iterator();
+ while (it.hasNext()) {
+ ObserverInternal observer = it.next();
+ Set<MonitoredPackage> failedPackages =
+ observer.prunePackagesLocked(elapsedMs);
+ if (!failedPackages.isEmpty()) {
+ onHealthCheckFailed(observer, failedPackages);
+ }
+ if (observer.getMonitoredPackages().isEmpty() && (observer.registeredObserver == null
+ || !observer.registeredObserver.isPersistent())) {
+ Slog.i(TAG, "Discarding observer " + observer.name + ". All packages expired");
+ it.remove();
+ }
+ }
+ }
+
+ private void onHealthCheckFailed(ObserverInternal observer,
+ Set<MonitoredPackage> failedPackages) {
+ mLongTaskHandler.post(() -> {
+ synchronized (sLock) {
+ PackageHealthObserver registeredObserver = observer.registeredObserver;
+ if (registeredObserver != null) {
+ Iterator<MonitoredPackage> it = failedPackages.iterator();
+ while (it.hasNext()) {
+ VersionedPackage versionedPkg = getVersionedPackage(it.next().getName());
+ if (versionedPkg != null) {
+ Slog.i(TAG,
+ "Explicit health check failed for package " + versionedPkg);
+ observer.observerExecutor.execute(() ->
+ registeredObserver.onExecuteHealthCheckMitigation(versionedPkg,
+ PackageWatchdog.FAILURE_REASON_EXPLICIT_HEALTH_CHECK,
+ 1));
+ }
+ }
+ }
+ }
+ });
+ }
+
+ /**
+ * Gets PackageInfo for the given package. Matches any user and apex.
+ *
+ * @throws PackageManager.NameNotFoundException if no such package is installed.
+ */
+ private PackageInfo getPackageInfo(String packageName)
+ throws PackageManager.NameNotFoundException {
+ PackageManager pm = mContext.getPackageManager();
+ try {
+ // The MATCH_ANY_USER flag doesn't mix well with the MATCH_APEX
+ // flag, so make two separate attempts to get the package info.
+ // We don't need both flags at the same time because we assume
+ // apex files are always installed for all users.
+ return pm.getPackageInfo(packageName, PackageManager.MATCH_ANY_USER);
+ } catch (PackageManager.NameNotFoundException e) {
+ return pm.getPackageInfo(packageName, PackageManager.MATCH_APEX);
+ }
+ }
+
+ @Nullable
+ private VersionedPackage getVersionedPackage(String packageName) {
+ final PackageManager pm = mContext.getPackageManager();
+ if (pm == null || TextUtils.isEmpty(packageName)) {
+ return null;
+ }
+ try {
+ final long versionCode = getPackageInfo(packageName).getLongVersionCode();
+ return new VersionedPackage(packageName, versionCode);
+ } catch (PackageManager.NameNotFoundException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Loads mAllObservers from file.
+ *
+ * <p>Note that this is <b>not</b> thread safe and should only called be called
+ * from the constructor.
+ */
+ private void loadFromFile() {
+ InputStream infile = null;
+ mAllObservers.clear();
+ try {
+ infile = mPolicyFile.openRead();
+ final XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(infile, UTF_8.name());
+ XmlUtils.beginDocument(parser, TAG_PACKAGE_WATCHDOG);
+ int outerDepth = parser.getDepth();
+ while (XmlUtils.nextElementWithin(parser, outerDepth)) {
+ ObserverInternal observer = ObserverInternal.read(parser, this);
+ if (observer != null) {
+ mAllObservers.put(observer.name, observer);
+ }
+ }
+ } catch (FileNotFoundException e) {
+ // Nothing to monitor
+ } catch (Exception e) {
+ Slog.wtf(TAG, "Unable to read monitored packages, deleting file", e);
+ mPolicyFile.delete();
+ } finally {
+ IoUtils.closeQuietly(infile);
+ }
+ }
+
+ private void onPropertyChanged(DeviceConfig.Properties properties) {
+ try {
+ updateConfigs();
+ } catch (Exception ignore) {
+ Slog.w(TAG, "Failed to reload device config changes");
+ }
+ }
+
+ /** Adds a {@link DeviceConfig#OnPropertiesChangedListener}. */
+ private void setPropertyChangedListenerLocked() {
+ DeviceConfig.addOnPropertiesChangedListener(
+ DeviceConfig.NAMESPACE_ROLLBACK,
+ mContext.getMainExecutor(),
+ mOnPropertyChangedListener);
+ }
+
+ @VisibleForTesting
+ void removePropertyChangedListener() {
+ DeviceConfig.removeOnPropertiesChangedListener(mOnPropertyChangedListener);
+ }
+
+ /**
+ * Health check is enabled or disabled after reading the flags
+ * from DeviceConfig.
+ */
+ @VisibleForTesting
+ void updateConfigs() {
+ synchronized (sLock) {
+ mTriggerFailureCount = DeviceConfig.getInt(
+ DeviceConfig.NAMESPACE_ROLLBACK,
+ PROPERTY_WATCHDOG_TRIGGER_FAILURE_COUNT,
+ DEFAULT_TRIGGER_FAILURE_COUNT);
+ if (mTriggerFailureCount <= 0) {
+ mTriggerFailureCount = DEFAULT_TRIGGER_FAILURE_COUNT;
+ }
+
+ mTriggerFailureDurationMs = DeviceConfig.getInt(
+ DeviceConfig.NAMESPACE_ROLLBACK,
+ PROPERTY_WATCHDOG_TRIGGER_DURATION_MILLIS,
+ DEFAULT_TRIGGER_FAILURE_DURATION_MS);
+ if (mTriggerFailureDurationMs <= 0) {
+ mTriggerFailureDurationMs = DEFAULT_TRIGGER_FAILURE_DURATION_MS;
+ }
+
+ setExplicitHealthCheckEnabled(DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_ROLLBACK,
+ PROPERTY_WATCHDOG_EXPLICIT_HEALTH_CHECK_ENABLED,
+ DEFAULT_EXPLICIT_HEALTH_CHECK_ENABLED));
+ }
+ }
+
+ /**
+ * Persists mAllObservers to file. Threshold information is ignored.
+ */
+ private boolean saveToFile() {
+ Slog.i(TAG, "Saving observer state to file");
+ synchronized (sLock) {
+ FileOutputStream stream;
+ try {
+ stream = mPolicyFile.startWrite();
+ } catch (IOException e) {
+ Slog.w(TAG, "Cannot update monitored packages", e);
+ return false;
+ }
+
+ try {
+ XmlSerializer out = new FastXmlSerializer();
+ out.setOutput(stream, UTF_8.name());
+ out.startDocument(null, true);
+ out.startTag(null, TAG_PACKAGE_WATCHDOG);
+ out.attribute(null, ATTR_VERSION, Integer.toString(DB_VERSION));
+ for (int oIndex = 0; oIndex < mAllObservers.size(); oIndex++) {
+ mAllObservers.valueAt(oIndex).writeLocked(out);
+ }
+ out.endTag(null, TAG_PACKAGE_WATCHDOG);
+ out.endDocument();
+ mPolicyFile.finishWrite(stream);
+ return true;
+ } catch (IOException e) {
+ Slog.w(TAG, "Failed to save monitored packages, restoring backup", e);
+ mPolicyFile.failWrite(stream);
+ return false;
+ }
+ }
+ }
+
+ private void saveToFileAsync() {
+ if (!mLongTaskHandler.hasCallbacks(mSaveToFile)) {
+ mLongTaskHandler.post(mSaveToFile);
+ }
+ }
+
+ /** @hide Convert a {@code LongArrayQueue} to a String of comma-separated values. */
+ public static String longArrayQueueToString(LongArrayQueue queue) {
+ if (queue.size() > 0) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(queue.get(0));
+ for (int i = 1; i < queue.size(); i++) {
+ sb.append(",");
+ sb.append(queue.get(i));
+ }
+ return sb.toString();
+ }
+ return "";
+ }
+
+ /** @hide Parse a comma-separated String of longs into a LongArrayQueue. */
+ public static LongArrayQueue parseLongArrayQueue(String commaSeparatedValues) {
+ LongArrayQueue result = new LongArrayQueue();
+ if (!TextUtils.isEmpty(commaSeparatedValues)) {
+ String[] values = commaSeparatedValues.split(",");
+ for (String value : values) {
+ result.addLast(Long.parseLong(value));
+ }
+ }
+ return result;
+ }
+
+
+ /** Dump status of every observer in mAllObservers. */
+ public void dump(@NonNull PrintWriter pw) {
+ if (Flags.synchronousRebootInRescueParty() && isRecoveryTriggeredReboot()) {
+ dumpInternal(pw);
+ } else {
+ synchronized (sLock) {
+ dumpInternal(pw);
+ }
+ }
+ }
+
+ /**
+ * Check if we're currently attempting to reboot during mitigation. This method must return
+ * true if triggered reboot early during a boot loop, since the device will not be fully booted
+ * at this time.
+ */
+ public static boolean isRecoveryTriggeredReboot() {
+ return isFactoryResetPropertySet() || isRebootPropertySet();
+ }
+
+ private static boolean isFactoryResetPropertySet() {
+ return CrashRecoveryProperties.attemptingFactoryReset().orElse(false);
+ }
+
+ private static boolean isRebootPropertySet() {
+ return CrashRecoveryProperties.attemptingReboot().orElse(false);
+ }
+
+ private void dumpInternal(@NonNull PrintWriter pw) {
+ IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ");
+ ipw.println("Package Watchdog status");
+ ipw.increaseIndent();
+ synchronized (sLock) {
+ for (String observerName : mAllObservers.keySet()) {
+ ipw.println("Observer name: " + observerName);
+ ipw.increaseIndent();
+ ObserverInternal observerInternal = mAllObservers.get(observerName);
+ observerInternal.dump(ipw);
+ ipw.decreaseIndent();
+ }
+ }
+ ipw.decreaseIndent();
+ dumpCrashRecoveryEvents(ipw);
+ }
+
+ @VisibleForTesting
+ @GuardedBy("sLock")
+ void registerObserverInternal(ObserverInternal observerInternal) {
+ mAllObservers.put(observerInternal.name, observerInternal);
+ }
+
+ /**
+ * Represents an observer monitoring a set of packages along with the failure thresholds for
+ * each package.
+ *
+ * <p> Note, the PackageWatchdog#sLock must always be held when reading or writing
+ * instances of this class.
+ */
+ static class ObserverInternal {
+ public final String name;
+ @GuardedBy("sLock")
+ private final ArrayMap<String, MonitoredPackage> mPackages = new ArrayMap<>();
+ @Nullable
+ @GuardedBy("sLock")
+ public PackageHealthObserver registeredObserver;
+ public Executor observerExecutor;
+ private int mMitigationCount;
+
+ ObserverInternal(String name, List<MonitoredPackage> packages) {
+ this(name, packages, /*mitigationCount=*/ 0);
+ }
+
+ ObserverInternal(String name, List<MonitoredPackage> packages, int mitigationCount) {
+ this.name = name;
+ updatePackagesLocked(packages);
+ this.mMitigationCount = mitigationCount;
+ }
+
+ /**
+ * Writes important {@link MonitoredPackage} details for this observer to file.
+ * Does not persist any package failure thresholds.
+ */
+ @GuardedBy("sLock")
+ public boolean writeLocked(XmlSerializer out) {
+ try {
+ out.startTag(null, TAG_OBSERVER);
+ out.attribute(null, ATTR_NAME, name);
+ if (Flags.recoverabilityDetection()) {
+ out.attribute(null, ATTR_MITIGATION_COUNT, Integer.toString(mMitigationCount));
+ }
+ for (int i = 0; i < mPackages.size(); i++) {
+ MonitoredPackage p = mPackages.valueAt(i);
+ p.writeLocked(out);
+ }
+ out.endTag(null, TAG_OBSERVER);
+ return true;
+ } catch (IOException e) {
+ Slog.w(TAG, "Cannot save observer", e);
+ return false;
+ }
+ }
+
+ public int getBootMitigationCount() {
+ return mMitigationCount;
+ }
+
+ public void setBootMitigationCount(int mitigationCount) {
+ mMitigationCount = mitigationCount;
+ }
+
+ @GuardedBy("sLock")
+ public void updatePackagesLocked(List<MonitoredPackage> packages) {
+ for (int pIndex = 0; pIndex < packages.size(); pIndex++) {
+ MonitoredPackage p = packages.get(pIndex);
+ MonitoredPackage existingPackage = getMonitoredPackage(p.getName());
+ if (existingPackage != null) {
+ existingPackage.updateHealthCheckDuration(p.mDurationMs);
+ } else {
+ putMonitoredPackage(p);
+ }
+ }
+ }
+
+ /**
+ * Reduces the monitoring durations of all packages observed by this observer by
+ * {@code elapsedMs}. If any duration is less than 0, the package is removed from
+ * observation. If any health check duration is less than 0, the health check result
+ * is evaluated.
+ *
+ * @return a {@link Set} of packages that were removed from the observer without explicit
+ * health check passing, or an empty list if no package expired for which an explicit health
+ * check was still pending
+ */
+ @GuardedBy("sLock")
+ private Set<MonitoredPackage> prunePackagesLocked(long elapsedMs) {
+ Set<MonitoredPackage> failedPackages = new ArraySet<>();
+ Iterator<MonitoredPackage> it = mPackages.values().iterator();
+ while (it.hasNext()) {
+ MonitoredPackage p = it.next();
+ int oldState = p.getHealthCheckStateLocked();
+ int newState = p.handleElapsedTimeLocked(elapsedMs);
+ if (oldState != HealthCheckState.FAILED
+ && newState == HealthCheckState.FAILED) {
+ Slog.i(TAG, "Package " + p.getName() + " failed health check");
+ failedPackages.add(p);
+ }
+ if (p.isExpiredLocked()) {
+ it.remove();
+ }
+ }
+ return failedPackages;
+ }
+
+ /**
+ * Increments failure counts of {@code packageName}.
+ * @returns {@code true} if failure threshold is exceeded, {@code false} otherwise
+ * @hide
+ */
+ @GuardedBy("sLock")
+ public boolean notifyPackageFailureLocked(String packageName) {
+ if (getMonitoredPackage(packageName) == null && registeredObserver.isPersistent()
+ && registeredObserver.mayObservePackage(packageName)) {
+ putMonitoredPackage(sPackageWatchdog.newMonitoredPackage(
+ packageName, DEFAULT_OBSERVING_DURATION_MS, false));
+ }
+ MonitoredPackage p = getMonitoredPackage(packageName);
+ if (p != null) {
+ return p.onFailureLocked();
+ }
+ return false;
+ }
+
+ /**
+ * Returns the map of packages monitored by this observer.
+ *
+ * @return a mapping of package names to {@link MonitoredPackage} objects.
+ */
+ @GuardedBy("sLock")
+ public ArrayMap<String, MonitoredPackage> getMonitoredPackages() {
+ return mPackages;
+ }
+
+ /**
+ * Returns the {@link MonitoredPackage} associated with a given package name if the
+ * package is being monitored by this observer.
+ *
+ * @param packageName: the name of the package.
+ * @return the {@link MonitoredPackage} object associated with the package name if one
+ * exists, {@code null} otherwise.
+ */
+ @GuardedBy("sLock")
+ @Nullable
+ public MonitoredPackage getMonitoredPackage(String packageName) {
+ return mPackages.get(packageName);
+ }
+
+ /**
+ * Associates a {@link MonitoredPackage} with the observer.
+ *
+ * @param p: the {@link MonitoredPackage} to store.
+ */
+ @GuardedBy("sLock")
+ public void putMonitoredPackage(MonitoredPackage p) {
+ mPackages.put(p.getName(), p);
+ }
+
+ /**
+ * Returns one ObserverInternal from the {@code parser} and advances its state.
+ *
+ * <p>Note that this method is <b>not</b> thread safe. It should only be called from
+ * #loadFromFile which in turn is only called on construction of the
+ * singleton PackageWatchdog.
+ **/
+ public static ObserverInternal read(XmlPullParser parser, PackageWatchdog watchdog) {
+ String observerName = null;
+ int observerMitigationCount = 0;
+ if (TAG_OBSERVER.equals(parser.getName())) {
+ observerName = parser.getAttributeValue(null, ATTR_NAME);
+ if (TextUtils.isEmpty(observerName)) {
+ Slog.wtf(TAG, "Unable to read observer name");
+ return null;
+ }
+ }
+ List<MonitoredPackage> packages = new ArrayList<>();
+ int innerDepth = parser.getDepth();
+ try {
+ if (Flags.recoverabilityDetection()) {
+ try {
+ observerMitigationCount = Integer.parseInt(
+ parser.getAttributeValue(null, ATTR_MITIGATION_COUNT));
+ } catch (Exception e) {
+ Slog.i(
+ TAG,
+ "ObserverInternal mitigation count was not present.");
+ }
+ }
+ while (XmlUtils.nextElementWithin(parser, innerDepth)) {
+ if (TAG_PACKAGE.equals(parser.getName())) {
+ try {
+ MonitoredPackage pkg = watchdog.parseMonitoredPackage(parser);
+ if (pkg != null) {
+ packages.add(pkg);
+ }
+ } catch (NumberFormatException e) {
+ Slog.wtf(TAG, "Skipping package for observer " + observerName, e);
+ continue;
+ }
+ }
+ }
+ } catch (XmlPullParserException | IOException e) {
+ Slog.wtf(TAG, "Unable to read observer " + observerName, e);
+ return null;
+ }
+ if (packages.isEmpty()) {
+ return null;
+ }
+ return new ObserverInternal(observerName, packages, observerMitigationCount);
+ }
+
+ /** Dumps information about this observer and the packages it watches. */
+ public void dump(IndentingPrintWriter pw) {
+ boolean isPersistent = registeredObserver != null && registeredObserver.isPersistent();
+ pw.println("Persistent: " + isPersistent);
+ for (String packageName : mPackages.keySet()) {
+ MonitoredPackage p = getMonitoredPackage(packageName);
+ pw.println(packageName + ": ");
+ pw.increaseIndent();
+ pw.println("# Failures: " + p.mFailureHistory.size());
+ pw.println("Monitoring duration remaining: " + p.mDurationMs + "ms");
+ pw.println("Explicit health check duration: " + p.mHealthCheckDurationMs + "ms");
+ pw.println("Health check state: " + p.toString(p.mHealthCheckState));
+ pw.decreaseIndent();
+ }
+ }
+ }
+
+ /** @hide */
+ @Retention(SOURCE)
+ @IntDef(value = {
+ HealthCheckState.ACTIVE,
+ HealthCheckState.INACTIVE,
+ HealthCheckState.PASSED,
+ HealthCheckState.FAILED})
+ public @interface HealthCheckState {
+ // The package has not passed health check but has requested a health check
+ int ACTIVE = 0;
+ // The package has not passed health check and has not requested a health check
+ int INACTIVE = 1;
+ // The package has passed health check
+ int PASSED = 2;
+ // The package has failed health check
+ int FAILED = 3;
+ }
+
+ MonitoredPackage newMonitoredPackage(
+ String name, long durationMs, boolean hasPassedHealthCheck) {
+ return newMonitoredPackage(name, durationMs, Long.MAX_VALUE, hasPassedHealthCheck,
+ new LongArrayQueue());
+ }
+
+ MonitoredPackage newMonitoredPackage(String name, long durationMs, long healthCheckDurationMs,
+ boolean hasPassedHealthCheck, LongArrayQueue mitigationCalls) {
+ return new MonitoredPackage(name, durationMs, healthCheckDurationMs,
+ hasPassedHealthCheck, mitigationCalls);
+ }
+
+ MonitoredPackage parseMonitoredPackage(XmlPullParser parser)
+ throws XmlPullParserException {
+ String packageName = parser.getAttributeValue(null, ATTR_NAME);
+ long duration = Long.parseLong(parser.getAttributeValue(null, ATTR_DURATION));
+ long healthCheckDuration = Long.parseLong(parser.getAttributeValue(null,
+ ATTR_EXPLICIT_HEALTH_CHECK_DURATION));
+ boolean hasPassedHealthCheck = Boolean.parseBoolean(parser.getAttributeValue(null,
+ ATTR_PASSED_HEALTH_CHECK));
+ LongArrayQueue mitigationCalls = parseLongArrayQueue(
+ parser.getAttributeValue(null, ATTR_MITIGATION_CALLS));
+ return newMonitoredPackage(packageName,
+ duration, healthCheckDuration, hasPassedHealthCheck, mitigationCalls);
+ }
+
+ /**
+ * Represents a package and its health check state along with the time
+ * it should be monitored for.
+ *
+ * <p> Note, the PackageWatchdog#sLock must always be held when reading or writing
+ * instances of this class.
+ */
+ class MonitoredPackage {
+ private final String mPackageName;
+ // Times when package failures happen sorted in ascending order
+ @GuardedBy("sLock")
+ private final LongArrayQueue mFailureHistory = new LongArrayQueue();
+ // Times when an observer was called to mitigate this package's failure. Sorted in
+ // ascending order.
+ @GuardedBy("sLock")
+ private final LongArrayQueue mMitigationCalls;
+ // One of STATE_[ACTIVE|INACTIVE|PASSED|FAILED]. Updated on construction and after
+ // methods that could change the health check state: handleElapsedTimeLocked and
+ // tryPassHealthCheckLocked
+ private int mHealthCheckState = HealthCheckState.INACTIVE;
+ // Whether an explicit health check has passed.
+ // This value in addition with mHealthCheckDurationMs determines the health check state
+ // of the package, see #getHealthCheckStateLocked
+ @GuardedBy("sLock")
+ private boolean mHasPassedHealthCheck;
+ // System uptime duration to monitor package.
+ @GuardedBy("sLock")
+ private long mDurationMs;
+ // System uptime duration to check the result of an explicit health check
+ // Initially, MAX_VALUE until we get a value from the health check service
+ // and request health checks.
+ // This value in addition with mHasPassedHealthCheck determines the health check state
+ // of the package, see #getHealthCheckStateLocked
+ @GuardedBy("sLock")
+ private long mHealthCheckDurationMs = Long.MAX_VALUE;
+
+ MonitoredPackage(String packageName, long durationMs,
+ long healthCheckDurationMs, boolean hasPassedHealthCheck,
+ LongArrayQueue mitigationCalls) {
+ mPackageName = packageName;
+ mDurationMs = durationMs;
+ mHealthCheckDurationMs = healthCheckDurationMs;
+ mHasPassedHealthCheck = hasPassedHealthCheck;
+ mMitigationCalls = mitigationCalls;
+ updateHealthCheckStateLocked();
+ }
+
+ /** Writes the salient fields to disk using {@code out}.
+ * @hide
+ */
+ @GuardedBy("sLock")
+ public void writeLocked(XmlSerializer out) throws IOException {
+ out.startTag(null, TAG_PACKAGE);
+ out.attribute(null, ATTR_NAME, getName());
+ out.attribute(null, ATTR_DURATION, Long.toString(mDurationMs));
+ out.attribute(null, ATTR_EXPLICIT_HEALTH_CHECK_DURATION,
+ Long.toString(mHealthCheckDurationMs));
+ out.attribute(null, ATTR_PASSED_HEALTH_CHECK, Boolean.toString(mHasPassedHealthCheck));
+ LongArrayQueue normalizedCalls = normalizeMitigationCalls();
+ out.attribute(null, ATTR_MITIGATION_CALLS, longArrayQueueToString(normalizedCalls));
+ out.endTag(null, TAG_PACKAGE);
+ }
+
+ /**
+ * Increment package failures or resets failure count depending on the last package failure.
+ *
+ * @return {@code true} if failure count exceeds a threshold, {@code false} otherwise
+ */
+ @GuardedBy("sLock")
+ public boolean onFailureLocked() {
+ // Sliding window algorithm: find out if there exists a window containing failures >=
+ // mTriggerFailureCount.
+ final long now = mSystemClock.uptimeMillis();
+ mFailureHistory.addLast(now);
+ while (now - mFailureHistory.peekFirst() > mTriggerFailureDurationMs) {
+ // Prune values falling out of the window
+ mFailureHistory.removeFirst();
+ }
+ boolean failed = mFailureHistory.size() >= mTriggerFailureCount;
+ if (failed) {
+ mFailureHistory.clear();
+ }
+ return failed;
+ }
+
+ /**
+ * Notes the timestamp of a mitigation call into the observer.
+ */
+ @GuardedBy("sLock")
+ public void noteMitigationCallLocked() {
+ mMitigationCalls.addLast(mSystemClock.uptimeMillis());
+ }
+
+ /**
+ * Prunes any mitigation calls outside of the de-escalation window, and returns the
+ * number of calls that are in the window afterwards.
+ *
+ * @return the number of mitigation calls made in the de-escalation window.
+ */
+ @GuardedBy("sLock")
+ public int getMitigationCountLocked() {
+ try {
+ final long now = mSystemClock.uptimeMillis();
+ while (now - mMitigationCalls.peekFirst() > DEFAULT_DEESCALATION_WINDOW_MS) {
+ mMitigationCalls.removeFirst();
+ }
+ } catch (NoSuchElementException ignore) {
+ }
+
+ return mMitigationCalls.size();
+ }
+
+ /**
+ * Before writing to disk, make the mitigation call timestamps relative to the current
+ * system uptime. This is because they need to be relative to the uptime which will reset
+ * at the next boot.
+ *
+ * @return a LongArrayQueue of the mitigation calls relative to the current system uptime.
+ */
+ @GuardedBy("sLock")
+ public LongArrayQueue normalizeMitigationCalls() {
+ LongArrayQueue normalized = new LongArrayQueue();
+ final long now = mSystemClock.uptimeMillis();
+ for (int i = 0; i < mMitigationCalls.size(); i++) {
+ normalized.addLast(mMitigationCalls.get(i) - now);
+ }
+ return normalized;
+ }
+
+ /**
+ * Sets the initial health check duration.
+ *
+ * @return the new health check state
+ */
+ @GuardedBy("sLock")
+ public int setHealthCheckActiveLocked(long initialHealthCheckDurationMs) {
+ if (initialHealthCheckDurationMs <= 0) {
+ Slog.wtf(TAG, "Cannot set non-positive health check duration "
+ + initialHealthCheckDurationMs + "ms for package " + getName()
+ + ". Using total duration " + mDurationMs + "ms instead");
+ initialHealthCheckDurationMs = mDurationMs;
+ }
+ if (mHealthCheckState == HealthCheckState.INACTIVE) {
+ // Transitions to ACTIVE
+ mHealthCheckDurationMs = initialHealthCheckDurationMs;
+ }
+ return updateHealthCheckStateLocked();
+ }
+
+ /**
+ * Updates the monitoring durations of the package.
+ *
+ * @return the new health check state
+ */
+ @GuardedBy("sLock")
+ public int handleElapsedTimeLocked(long elapsedMs) {
+ if (elapsedMs <= 0) {
+ Slog.w(TAG, "Cannot handle non-positive elapsed time for package " + getName());
+ return mHealthCheckState;
+ }
+ // Transitions to FAILED if now <= 0 and health check not passed
+ mDurationMs -= elapsedMs;
+ if (mHealthCheckState == HealthCheckState.ACTIVE) {
+ // We only update health check durations if we have #setHealthCheckActiveLocked
+ // This ensures we don't leave the INACTIVE state for an unexpected elapsed time
+ // Transitions to FAILED if now <= 0 and health check not passed
+ mHealthCheckDurationMs -= elapsedMs;
+ }
+ return updateHealthCheckStateLocked();
+ }
+
+ /** Explicitly update the monitoring duration of the package. */
+ @GuardedBy("sLock")
+ public void updateHealthCheckDuration(long newDurationMs) {
+ mDurationMs = newDurationMs;
+ }
+
+ /**
+ * Marks the health check as passed and transitions to {@link HealthCheckState.PASSED}
+ * if not yet {@link HealthCheckState.FAILED}.
+ *
+ * @return the new {@link HealthCheckState health check state}
+ */
+ @GuardedBy("sLock")
+ @HealthCheckState
+ public int tryPassHealthCheckLocked() {
+ if (mHealthCheckState != HealthCheckState.FAILED) {
+ // FAILED is a final state so only pass if we haven't failed
+ // Transition to PASSED
+ mHasPassedHealthCheck = true;
+ }
+ return updateHealthCheckStateLocked();
+ }
+
+ /** Returns the monitored package name. */
+ private String getName() {
+ return mPackageName;
+ }
+
+ /**
+ * Returns the current {@link HealthCheckState health check state}.
+ */
+ @GuardedBy("sLock")
+ @HealthCheckState
+ public int getHealthCheckStateLocked() {
+ return mHealthCheckState;
+ }
+
+ /**
+ * Returns the shortest duration before the package should be scheduled for a prune.
+ *
+ * @return the duration or {@link Long#MAX_VALUE} if the package should not be scheduled
+ */
+ @GuardedBy("sLock")
+ public long getShortestScheduleDurationMsLocked() {
+ // Consider health check duration only if #isPendingHealthChecksLocked is true
+ return Math.min(toPositive(mDurationMs),
+ isPendingHealthChecksLocked()
+ ? toPositive(mHealthCheckDurationMs) : Long.MAX_VALUE);
+ }
+
+ /**
+ * Returns {@code true} if the total duration left to monitor the package is less than or
+ * equal to 0 {@code false} otherwise.
+ */
+ @GuardedBy("sLock")
+ public boolean isExpiredLocked() {
+ return mDurationMs <= 0;
+ }
+
+ /**
+ * Returns {@code true} if the package, {@link #getName} is expecting health check results
+ * {@code false} otherwise.
+ */
+ @GuardedBy("sLock")
+ public boolean isPendingHealthChecksLocked() {
+ return mHealthCheckState == HealthCheckState.ACTIVE
+ || mHealthCheckState == HealthCheckState.INACTIVE;
+ }
+
+ /**
+ * Updates the health check state based on {@link #mHasPassedHealthCheck}
+ * and {@link #mHealthCheckDurationMs}.
+ *
+ * @return the new {@link HealthCheckState health check state}
+ */
+ @GuardedBy("sLock")
+ @HealthCheckState
+ private int updateHealthCheckStateLocked() {
+ int oldState = mHealthCheckState;
+ if (mHasPassedHealthCheck) {
+ // Set final state first to avoid ambiguity
+ mHealthCheckState = HealthCheckState.PASSED;
+ } else if (mHealthCheckDurationMs <= 0 || mDurationMs <= 0) {
+ // Set final state first to avoid ambiguity
+ mHealthCheckState = HealthCheckState.FAILED;
+ } else if (mHealthCheckDurationMs == Long.MAX_VALUE) {
+ mHealthCheckState = HealthCheckState.INACTIVE;
+ } else {
+ mHealthCheckState = HealthCheckState.ACTIVE;
+ }
+
+ if (oldState != mHealthCheckState) {
+ Slog.i(TAG, "Updated health check state for package " + getName() + ": "
+ + toString(oldState) + " -> " + toString(mHealthCheckState));
+ }
+ return mHealthCheckState;
+ }
+
+ /** Returns a {@link String} representation of the current health check state. */
+ private String toString(@HealthCheckState int state) {
+ switch (state) {
+ case HealthCheckState.ACTIVE:
+ return "ACTIVE";
+ case HealthCheckState.INACTIVE:
+ return "INACTIVE";
+ case HealthCheckState.PASSED:
+ return "PASSED";
+ case HealthCheckState.FAILED:
+ return "FAILED";
+ default:
+ return "UNKNOWN";
+ }
+ }
+
+ /** Returns {@code value} if it is greater than 0 or {@link Long#MAX_VALUE} otherwise. */
+ private long toPositive(long value) {
+ return value > 0 ? value : Long.MAX_VALUE;
+ }
+
+ /** Compares the equality of this object with another {@link MonitoredPackage}. */
+ @VisibleForTesting
+ boolean isEqualTo(MonitoredPackage pkg) {
+ return (getName().equals(pkg.getName()))
+ && mDurationMs == pkg.mDurationMs
+ && mHasPassedHealthCheck == pkg.mHasPassedHealthCheck
+ && mHealthCheckDurationMs == pkg.mHealthCheckDurationMs
+ && (mMitigationCalls.toString()).equals(pkg.mMitigationCalls.toString());
+ }
+ }
+
+ @GuardedBy("sLock")
+ @SuppressWarnings("GuardedBy")
+ void saveAllObserversBootMitigationCountToMetadata(String filePath) {
+ HashMap<String, Integer> bootMitigationCounts = new HashMap<>();
+ for (int i = 0; i < mAllObservers.size(); i++) {
+ final ObserverInternal observer = mAllObservers.valueAt(i);
+ bootMitigationCounts.put(observer.name, observer.getBootMitigationCount());
+ }
+
+ FileOutputStream fileStream = null;
+ ObjectOutputStream objectStream = null;
+ try {
+ fileStream = new FileOutputStream(new File(filePath));
+ objectStream = new ObjectOutputStream(fileStream);
+ objectStream.writeObject(bootMitigationCounts);
+ objectStream.flush();
+ } catch (Exception e) {
+ Slog.i(TAG, "Could not save observers metadata to file: " + e);
+ return;
+ } finally {
+ IoUtils.closeQuietly(objectStream);
+ IoUtils.closeQuietly(fileStream);
+ }
+ }
+
+ /**
+ * Handles the thresholding logic for system server boots.
+ */
+ class BootThreshold {
+
+ private final int mBootTriggerCount;
+ private final long mTriggerWindow;
+
+ BootThreshold(int bootTriggerCount, long triggerWindow) {
+ this.mBootTriggerCount = bootTriggerCount;
+ this.mTriggerWindow = triggerWindow;
+ }
+
+ public void reset() {
+ setStart(0);
+ setCount(0);
+ }
+
+ protected int getCount() {
+ return CrashRecoveryProperties.rescueBootCount().orElse(0);
+ }
+
+ protected void setCount(int count) {
+ CrashRecoveryProperties.rescueBootCount(count);
+ }
+
+ public long getStart() {
+ return CrashRecoveryProperties.rescueBootStart().orElse(0L);
+ }
+
+ public int getMitigationCount() {
+ return CrashRecoveryProperties.bootMitigationCount().orElse(0);
+ }
+
+ public void setStart(long start) {
+ CrashRecoveryProperties.rescueBootStart(getStartTime(start));
+ }
+
+ public void setMitigationStart(long start) {
+ CrashRecoveryProperties.bootMitigationStart(getStartTime(start));
+ }
+
+ public long getMitigationStart() {
+ return CrashRecoveryProperties.bootMitigationStart().orElse(0L);
+ }
+
+ public void setMitigationCount(int count) {
+ CrashRecoveryProperties.bootMitigationCount(count);
+ }
+
+ private static long constrain(long amount, long low, long high) {
+ return amount < low ? low : (amount > high ? high : amount);
+ }
+
+ public long getStartTime(long start) {
+ final long now = mSystemClock.uptimeMillis();
+ return constrain(start, 0, now);
+ }
+
+ public void saveMitigationCountToMetadata() {
+ try (BufferedWriter writer = new BufferedWriter(new FileWriter(METADATA_FILE))) {
+ writer.write(String.valueOf(getMitigationCount()));
+ } catch (Exception e) {
+ Slog.e(TAG, "Could not save metadata to file: " + e);
+ }
+ }
+
+ public void readMitigationCountFromMetadataIfNecessary() {
+ File bootPropsFile = new File(METADATA_FILE);
+ if (bootPropsFile.exists()) {
+ try (BufferedReader reader = new BufferedReader(new FileReader(METADATA_FILE))) {
+ String mitigationCount = reader.readLine();
+ setMitigationCount(Integer.parseInt(mitigationCount));
+ bootPropsFile.delete();
+ } catch (Exception e) {
+ Slog.i(TAG, "Could not read metadata file: " + e);
+ }
+ }
+ }
+
+
+ /** Increments the boot counter, and returns whether the device is bootlooping. */
+ @GuardedBy("sLock")
+ public boolean incrementAndTest() {
+ if (Flags.recoverabilityDetection()) {
+ readAllObserversBootMitigationCountIfNecessary(METADATA_FILE);
+ } else {
+ readMitigationCountFromMetadataIfNecessary();
+ }
+
+ final long now = mSystemClock.uptimeMillis();
+ if (now - getStart() < 0) {
+ Slog.e(TAG, "Window was less than zero. Resetting start to current time.");
+ setStart(now);
+ setMitigationStart(now);
+ }
+ if (now - getMitigationStart() > DEFAULT_DEESCALATION_WINDOW_MS) {
+ setMitigationStart(now);
+ if (Flags.recoverabilityDetection()) {
+ resetAllObserversBootMitigationCount();
+ } else {
+ setMitigationCount(0);
+ }
+ }
+ final long window = now - getStart();
+ if (window >= mTriggerWindow) {
+ setCount(1);
+ setStart(now);
+ return false;
+ } else {
+ int count = getCount() + 1;
+ setCount(count);
+ EventLog.writeEvent(LOG_TAG_RESCUE_NOTE, Process.ROOT_UID, count, window);
+ if (Flags.recoverabilityDetection()) {
+ // After a reboot (e.g. by WARM_REBOOT or mainline rollback) we apply
+ // mitigations without waiting for DEFAULT_BOOT_LOOP_TRIGGER_COUNT.
+ return (count >= mBootTriggerCount)
+ || (performedMitigationsDuringWindow() && count > 1);
+ }
+ return count >= mBootTriggerCount;
+ }
+ }
+
+ @GuardedBy("sLock")
+ private boolean performedMitigationsDuringWindow() {
+ for (ObserverInternal observerInternal: mAllObservers.values()) {
+ if (observerInternal.getBootMitigationCount() > 0) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @GuardedBy("sLock")
+ private void resetAllObserversBootMitigationCount() {
+ for (int i = 0; i < mAllObservers.size(); i++) {
+ final ObserverInternal observer = mAllObservers.valueAt(i);
+ observer.setBootMitigationCount(0);
+ }
+ saveAllObserversBootMitigationCountToMetadata(METADATA_FILE);
+ }
+
+ @GuardedBy("sLock")
+ @SuppressWarnings("GuardedBy")
+ void readAllObserversBootMitigationCountIfNecessary(String filePath) {
+ File metadataFile = new File(filePath);
+ if (metadataFile.exists()) {
+ FileInputStream fileStream = null;
+ ObjectInputStream objectStream = null;
+ HashMap<String, Integer> bootMitigationCounts = null;
+ try {
+ fileStream = new FileInputStream(metadataFile);
+ objectStream = new ObjectInputStream(fileStream);
+ bootMitigationCounts =
+ (HashMap<String, Integer>) objectStream.readObject();
+ } catch (Exception e) {
+ Slog.i(TAG, "Could not read observer metadata file: " + e);
+ return;
+ } finally {
+ IoUtils.closeQuietly(objectStream);
+ IoUtils.closeQuietly(fileStream);
+ }
+
+ if (bootMitigationCounts == null || bootMitigationCounts.isEmpty()) {
+ Slog.i(TAG, "No observer in metadata file");
+ return;
+ }
+ for (int i = 0; i < mAllObservers.size(); i++) {
+ final ObserverInternal observer = mAllObservers.valueAt(i);
+ if (bootMitigationCounts.containsKey(observer.name)) {
+ observer.setBootMitigationCount(
+ bootMitigationCounts.get(observer.name));
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Register broadcast receiver for shutdown.
+ * We would save the observer state to persist across boots.
+ *
+ * @hide
+ */
+ public void registerShutdownBroadcastReceiver() {
+ BroadcastReceiver shutdownEventReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ // Only write if intent is relevant to device reboot or shutdown.
+ String intentAction = intent.getAction();
+ if (ACTION_REBOOT.equals(intentAction)
+ || ACTION_SHUTDOWN.equals(intentAction)) {
+ writeNow();
+ }
+ }
+ };
+
+ // Setup receiver for device reboots or shutdowns.
+ IntentFilter filter = new IntentFilter(ACTION_REBOOT);
+ filter.addAction(ACTION_SHUTDOWN);
+ mContext.registerReceiverForAllUsers(shutdownEventReceiver, filter, null,
+ /* run on main thread */ null);
+ }
+}
diff --git a/packages/CrashRecovery/services/module/java/com/android/server/RescueParty.java b/packages/CrashRecovery/services/module/java/com/android/server/RescueParty.java
new file mode 100644
index 0000000..846da19
--- /dev/null
+++ b/packages/CrashRecovery/services/module/java/com/android/server/RescueParty.java
@@ -0,0 +1,861 @@
+/*
+ * Copyright (C) 2017 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;
+
+import static com.android.server.PackageWatchdog.MITIGATION_RESULT_SKIPPED;
+import static com.android.server.PackageWatchdog.MITIGATION_RESULT_SUCCESS;
+import static com.android.server.crashrecovery.CrashRecoveryUtils.logCrashRecoveryEvent;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.VersionedPackage;
+import android.crashrecovery.flags.Flags;
+import android.os.Build;
+import android.os.PowerManager;
+import android.os.RecoverySystem;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.provider.Settings;
+import android.sysprop.CrashRecoveryProperties;
+import android.text.TextUtils;
+import android.util.EventLog;
+import android.util.FileUtils;
+import android.util.Log;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.PackageWatchdog.FailureReasons;
+import com.android.server.PackageWatchdog.PackageHealthObserver;
+import com.android.server.PackageWatchdog.PackageHealthObserverImpact;
+import com.android.server.crashrecovery.proto.CrashRecoveryStatsLog;
+
+import java.io.File;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Utilities to help rescue the system from crash loops. Callers are expected to
+ * report boot events and persistent app crashes, and if they happen frequently
+ * enough this class will slowly escalate through several rescue operations
+ * before finally rebooting and prompting the user if they want to wipe data as
+ * a last resort.
+ *
+ * @hide
+ */
+public class RescueParty {
+ @VisibleForTesting
+ static final String PROP_ENABLE_RESCUE = "persist.sys.enable_rescue";
+ @VisibleForTesting
+ static final int LEVEL_NONE = 0;
+ @VisibleForTesting
+ static final int LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS = 1;
+ @VisibleForTesting
+ static final int LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES = 2;
+ @VisibleForTesting
+ static final int LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS = 3;
+ @VisibleForTesting
+ static final int LEVEL_WARM_REBOOT = 4;
+ @VisibleForTesting
+ static final int LEVEL_FACTORY_RESET = 5;
+ @VisibleForTesting
+ static final int RESCUE_LEVEL_NONE = 0;
+ @VisibleForTesting
+ static final int RESCUE_LEVEL_SCOPED_DEVICE_CONFIG_RESET = 1;
+ @VisibleForTesting
+ static final int RESCUE_LEVEL_ALL_DEVICE_CONFIG_RESET = 2;
+ @VisibleForTesting
+ static final int RESCUE_LEVEL_WARM_REBOOT = 3;
+ @VisibleForTesting
+ static final int RESCUE_LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS = 4;
+ @VisibleForTesting
+ static final int RESCUE_LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES = 5;
+ @VisibleForTesting
+ static final int RESCUE_LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS = 6;
+ @VisibleForTesting
+ static final int RESCUE_LEVEL_FACTORY_RESET = 7;
+
+ @IntDef(prefix = { "RESCUE_LEVEL_" }, value = {
+ RESCUE_LEVEL_NONE,
+ RESCUE_LEVEL_SCOPED_DEVICE_CONFIG_RESET,
+ RESCUE_LEVEL_ALL_DEVICE_CONFIG_RESET,
+ RESCUE_LEVEL_WARM_REBOOT,
+ RESCUE_LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS,
+ RESCUE_LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES,
+ RESCUE_LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS,
+ RESCUE_LEVEL_FACTORY_RESET
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface RescueLevels {}
+
+ @VisibleForTesting
+ static final String RESCUE_NON_REBOOT_LEVEL_LIMIT = "persist.sys.rescue_non_reboot_level_limit";
+ @VisibleForTesting
+ static final int DEFAULT_RESCUE_NON_REBOOT_LEVEL_LIMIT = RESCUE_LEVEL_WARM_REBOOT - 1;
+ @VisibleForTesting
+ static final String TAG = "RescueParty";
+ @VisibleForTesting
+ static final long DEFAULT_OBSERVING_DURATION_MS = TimeUnit.DAYS.toMillis(2);
+ @VisibleForTesting
+ static final int DEVICE_CONFIG_RESET_MODE = Settings.RESET_MODE_TRUSTED_DEFAULTS;
+ // The DeviceConfig namespace containing all RescueParty switches.
+ @VisibleForTesting
+ static final String NAMESPACE_CONFIGURATION = "configuration";
+ @VisibleForTesting
+ static final String NAMESPACE_TO_PACKAGE_MAPPING_FLAG =
+ "namespace_to_package_mapping";
+ @VisibleForTesting
+ static final long DEFAULT_FACTORY_RESET_THROTTLE_DURATION_MIN = 1440;
+
+ private static final String NAME = "rescue-party-observer";
+
+ private static final String PROP_DISABLE_RESCUE = "persist.sys.disable_rescue";
+ private static final String PROP_VIRTUAL_DEVICE = "ro.hardware.virtual_device";
+ private static final String PROP_DEVICE_CONFIG_DISABLE_FLAG =
+ "persist.device_config.configuration.disable_rescue_party";
+ private static final String PROP_DISABLE_FACTORY_RESET_FLAG =
+ "persist.device_config.configuration.disable_rescue_party_factory_reset";
+ private static final String PROP_THROTTLE_DURATION_MIN_FLAG =
+ "persist.device_config.configuration.rescue_party_throttle_duration_min";
+
+ private static final int PERSISTENT_MASK = ApplicationInfo.FLAG_PERSISTENT
+ | ApplicationInfo.FLAG_SYSTEM;
+
+ /**
+ * EventLog tags used when logging into the event log. Note the values must be sync with
+ * frameworks/base/services/core/java/com/android/server/EventLogTags.logtags to get correct
+ * name translation.
+ */
+ private static final int LOG_TAG_RESCUE_SUCCESS = 2902;
+ private static final int LOG_TAG_RESCUE_FAILURE = 2903;
+
+ /** Register the Rescue Party observer as a Package Watchdog health observer */
+ public static void registerHealthObserver(Context context) {
+ PackageWatchdog.getInstance(context).registerHealthObserver(
+ context.getMainExecutor(), RescuePartyObserver.getInstance(context));
+ }
+
+ private static boolean isDisabled() {
+ // Check if we're explicitly enabled for testing
+ if (SystemProperties.getBoolean(PROP_ENABLE_RESCUE, false)) {
+ return false;
+ }
+
+ // We're disabled if the DeviceConfig disable flag is set to true.
+ // This is in case that an emergency rollback of the feature is needed.
+ if (SystemProperties.getBoolean(PROP_DEVICE_CONFIG_DISABLE_FLAG, false)) {
+ Slog.v(TAG, "Disabled because of DeviceConfig flag");
+ return true;
+ }
+
+ // We're disabled on all engineering devices
+ if (Build.TYPE.equals("eng")) {
+ Slog.v(TAG, "Disabled because of eng build");
+ return true;
+ }
+
+ // We're disabled on userdebug devices connected over USB, since that's
+ // a decent signal that someone is actively trying to debug the device,
+ // or that it's in a lab environment.
+ if (Build.TYPE.equals("userdebug") && isUsbActive()) {
+ Slog.v(TAG, "Disabled because of active USB connection");
+ return true;
+ }
+
+ // One last-ditch check
+ if (SystemProperties.getBoolean(PROP_DISABLE_RESCUE, false)) {
+ Slog.v(TAG, "Disabled because of manual property");
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Check if we're currently attempting to reboot for a factory reset. This method must
+ * return true if RescueParty tries to reboot early during a boot loop, since the device
+ * will not be fully booted at this time.
+ */
+ public static boolean isRecoveryTriggeredReboot() {
+ return isFactoryResetPropertySet() || isRebootPropertySet();
+ }
+
+ static boolean isFactoryResetPropertySet() {
+ return CrashRecoveryProperties.attemptingFactoryReset().orElse(false);
+ }
+
+ static boolean isRebootPropertySet() {
+ return CrashRecoveryProperties.attemptingReboot().orElse(false);
+ }
+
+ protected static long getLastFactoryResetTimeMs() {
+ return CrashRecoveryProperties.lastFactoryResetTimeMs().orElse(0L);
+ }
+
+ protected static int getMaxRescueLevelAttempted() {
+ return CrashRecoveryProperties.maxRescueLevelAttempted().orElse(LEVEL_NONE);
+ }
+
+ protected static void setFactoryResetProperty(boolean value) {
+ CrashRecoveryProperties.attemptingFactoryReset(value);
+ }
+ protected static void setRebootProperty(boolean value) {
+ CrashRecoveryProperties.attemptingReboot(value);
+ }
+
+ protected static void setLastFactoryResetTimeMs(long value) {
+ CrashRecoveryProperties.lastFactoryResetTimeMs(value);
+ }
+
+ protected static void setMaxRescueLevelAttempted(int level) {
+ CrashRecoveryProperties.maxRescueLevelAttempted(level);
+ }
+
+ @VisibleForTesting
+ static long getElapsedRealtime() {
+ return SystemClock.elapsedRealtime();
+ }
+
+ private static int getMaxRescueLevel(boolean mayPerformReboot) {
+ if (Flags.recoverabilityDetection()) {
+ if (!mayPerformReboot
+ || SystemProperties.getBoolean(PROP_DISABLE_FACTORY_RESET_FLAG, false)) {
+ return SystemProperties.getInt(RESCUE_NON_REBOOT_LEVEL_LIMIT,
+ DEFAULT_RESCUE_NON_REBOOT_LEVEL_LIMIT);
+ }
+ return RESCUE_LEVEL_FACTORY_RESET;
+ } else {
+ if (!mayPerformReboot
+ || SystemProperties.getBoolean(PROP_DISABLE_FACTORY_RESET_FLAG, false)) {
+ return LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS;
+ }
+ return LEVEL_FACTORY_RESET;
+ }
+ }
+
+ private static int getMaxRescueLevel() {
+ if (!SystemProperties.getBoolean(PROP_DISABLE_FACTORY_RESET_FLAG, false)) {
+ return Level.factoryReset();
+ }
+ return Level.reboot();
+ }
+
+ /**
+ * Get the rescue level to perform if this is the n-th attempt at mitigating failure.
+ *
+ * @param mitigationCount: the mitigation attempt number (1 = first attempt etc.)
+ * @param mayPerformReboot: whether or not a reboot and factory reset may be performed
+ * for the given failure.
+ * @return the rescue level for the n-th mitigation attempt.
+ */
+ private static int getRescueLevel(int mitigationCount, boolean mayPerformReboot) {
+ if (!Flags.deprecateFlagsAndSettingsResets()) {
+ if (mitigationCount == 1) {
+ return LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS;
+ } else if (mitigationCount == 2) {
+ return LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES;
+ } else if (mitigationCount == 3) {
+ return LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS;
+ } else if (mitigationCount == 4) {
+ return Math.min(getMaxRescueLevel(mayPerformReboot), LEVEL_WARM_REBOOT);
+ } else if (mitigationCount >= 5) {
+ return Math.min(getMaxRescueLevel(mayPerformReboot), LEVEL_FACTORY_RESET);
+ } else {
+ Slog.w(TAG, "Expected positive mitigation count, was " + mitigationCount);
+ return LEVEL_NONE;
+ }
+ } else {
+ if (mitigationCount == 1) {
+ return Level.reboot();
+ } else if (mitigationCount >= 2) {
+ return Math.min(getMaxRescueLevel(), Level.factoryReset());
+ } else {
+ Slog.w(TAG, "Expected positive mitigation count, was " + mitigationCount);
+ return LEVEL_NONE;
+ }
+ }
+ }
+
+ /**
+ * Get the rescue level to perform if this is the n-th attempt at mitigating failure.
+ * When failedPackage is null then 1st and 2nd mitigation counts are redundant (scoped and
+ * all device config reset). Behaves as if one mitigation attempt was already done.
+ *
+ * @param mitigationCount the mitigation attempt number (1 = first attempt etc.).
+ * @param mayPerformReboot whether or not a reboot and factory reset may be performed
+ * for the given failure.
+ * @param failedPackage in case of bootloop this is null.
+ * @return the rescue level for the n-th mitigation attempt.
+ */
+ private static @RescueLevels int getRescueLevel(int mitigationCount, boolean mayPerformReboot,
+ @Nullable VersionedPackage failedPackage) {
+ // Skipping RESCUE_LEVEL_SCOPED_DEVICE_CONFIG_RESET since it's not defined without a failed
+ // package.
+ if (failedPackage == null && mitigationCount > 0) {
+ mitigationCount += 1;
+ }
+ if (mitigationCount == 1) {
+ return RESCUE_LEVEL_SCOPED_DEVICE_CONFIG_RESET;
+ } else if (mitigationCount == 2) {
+ return RESCUE_LEVEL_ALL_DEVICE_CONFIG_RESET;
+ } else if (mitigationCount == 3) {
+ return Math.min(getMaxRescueLevel(mayPerformReboot), RESCUE_LEVEL_WARM_REBOOT);
+ } else if (mitigationCount == 4) {
+ return Math.min(getMaxRescueLevel(mayPerformReboot),
+ RESCUE_LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS);
+ } else if (mitigationCount == 5) {
+ return Math.min(getMaxRescueLevel(mayPerformReboot),
+ RESCUE_LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES);
+ } else if (mitigationCount == 6) {
+ return Math.min(getMaxRescueLevel(mayPerformReboot),
+ RESCUE_LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS);
+ } else if (mitigationCount >= 7) {
+ return Math.min(getMaxRescueLevel(mayPerformReboot), RESCUE_LEVEL_FACTORY_RESET);
+ } else {
+ return RESCUE_LEVEL_NONE;
+ }
+ }
+
+ /**
+ * Get the rescue level to perform if this is the n-th attempt at mitigating failure.
+ *
+ * @param mitigationCount the mitigation attempt number (1 = first attempt etc.).
+ * @return the rescue level for the n-th mitigation attempt.
+ */
+ private static @RescueLevels int getRescueLevel(int mitigationCount) {
+ if (mitigationCount == 1) {
+ return Level.reboot();
+ } else if (mitigationCount >= 2) {
+ return Math.min(getMaxRescueLevel(), Level.factoryReset());
+ } else {
+ return Level.none();
+ }
+ }
+
+ private static void executeRescueLevel(Context context, @Nullable String failedPackage,
+ int level) {
+ Slog.w(TAG, "Attempting rescue level " + levelToString(level));
+ try {
+ executeRescueLevelInternal(context, level, failedPackage);
+ EventLog.writeEvent(LOG_TAG_RESCUE_SUCCESS, level);
+ String successMsg = "Finished rescue level " + levelToString(level);
+ if (!TextUtils.isEmpty(failedPackage)) {
+ successMsg += " for package " + failedPackage;
+ }
+ logCrashRecoveryEvent(Log.DEBUG, successMsg);
+ } catch (Throwable t) {
+ logRescueException(level, failedPackage, t);
+ }
+ }
+
+ private static void executeRescueLevelInternal(Context context, int level, @Nullable
+ String failedPackage) throws Exception {
+ if (Flags.recoverabilityDetection()) {
+ executeRescueLevelInternalNew(context, level, failedPackage);
+ } else {
+ executeRescueLevelInternalOld(context, level, failedPackage);
+ }
+ }
+
+ private static void executeRescueLevelInternalOld(Context context, int level, @Nullable
+ String failedPackage) throws Exception {
+ CrashRecoveryStatsLog.write(CrashRecoveryStatsLog.RESCUE_PARTY_RESET_REPORTED,
+ level, levelToString(level));
+ // Try our best to reset all settings possible, and once finished
+ // rethrow any exception that we encountered
+ Exception res = null;
+ switch (level) {
+ case LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS:
+ break;
+ case LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES:
+ break;
+ case LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS:
+ break;
+ case LEVEL_WARM_REBOOT:
+ executeWarmReboot(context, level, failedPackage);
+ break;
+ case LEVEL_FACTORY_RESET:
+ // Before the completion of Reboot, if any crash happens then PackageWatchdog
+ // escalates to next level i.e. factory reset, as they happen in separate threads.
+ // Adding a check to prevent factory reset to execute before above reboot completes.
+ // Note: this reboot property is not persistent resets after reboot is completed.
+ if (isRebootPropertySet()) {
+ return;
+ }
+ executeFactoryReset(context, level, failedPackage);
+ break;
+ }
+
+ if (res != null) {
+ throw res;
+ }
+ }
+
+ private static void executeRescueLevelInternalNew(Context context, @RescueLevels int level,
+ @Nullable String failedPackage) throws Exception {
+ CrashRecoveryStatsLog.write(CrashRecoveryStatsLog.RESCUE_PARTY_RESET_REPORTED,
+ level, levelToString(level));
+ switch (level) {
+ case RESCUE_LEVEL_SCOPED_DEVICE_CONFIG_RESET:
+ break;
+ case RESCUE_LEVEL_ALL_DEVICE_CONFIG_RESET:
+ break;
+ case RESCUE_LEVEL_WARM_REBOOT:
+ executeWarmReboot(context, level, failedPackage);
+ break;
+ case RESCUE_LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS:
+ // do nothing
+ break;
+ case RESCUE_LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES:
+ // do nothing
+ break;
+ case RESCUE_LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS:
+ // do nothing
+ break;
+ case RESCUE_LEVEL_FACTORY_RESET:
+ // Before the completion of Reboot, if any crash happens then PackageWatchdog
+ // escalates to next level i.e. factory reset, as they happen in separate threads.
+ // Adding a check to prevent factory reset to execute before above reboot completes.
+ // Note: this reboot property is not persistent resets after reboot is completed.
+ if (isRebootPropertySet()) {
+ return;
+ }
+ executeFactoryReset(context, level, failedPackage);
+ break;
+ }
+ }
+
+ private static void executeWarmReboot(Context context, int level,
+ @Nullable String failedPackage) {
+ if (Flags.deprecateFlagsAndSettingsResets()) {
+ if (shouldThrottleReboot()) {
+ return;
+ }
+ }
+
+ // Request the reboot from a separate thread to avoid deadlock on PackageWatchdog
+ // when device shutting down.
+ setRebootProperty(true);
+
+ if (Flags.synchronousRebootInRescueParty()) {
+ try {
+ PowerManager pm = context.getSystemService(PowerManager.class);
+ if (pm != null) {
+ pm.reboot(TAG);
+ }
+ } catch (Throwable t) {
+ logRescueException(level, failedPackage, t);
+ }
+ } else {
+ Runnable runnable = () -> {
+ try {
+ PowerManager pm = context.getSystemService(PowerManager.class);
+ if (pm != null) {
+ pm.reboot(TAG);
+ }
+ } catch (Throwable t) {
+ logRescueException(level, failedPackage, t);
+ }
+ };
+ Thread thread = new Thread(runnable);
+ thread.start();
+ }
+ }
+
+ private static void executeFactoryReset(Context context, int level,
+ @Nullable String failedPackage) {
+ if (Flags.deprecateFlagsAndSettingsResets()) {
+ if (shouldThrottleReboot()) {
+ return;
+ }
+ }
+ setFactoryResetProperty(true);
+ long now = System.currentTimeMillis();
+ setLastFactoryResetTimeMs(now);
+
+ if (Flags.synchronousRebootInRescueParty()) {
+ try {
+ RecoverySystem.rebootPromptAndWipeUserData(context, TAG + "," + failedPackage);
+ } catch (Throwable t) {
+ logRescueException(level, failedPackage, t);
+ }
+ } else {
+ Runnable runnable = new Runnable() {
+ @Override
+ public void run() {
+ try {
+ RecoverySystem.rebootPromptAndWipeUserData(context,
+ TAG + "," + failedPackage);
+ } catch (Throwable t) {
+ logRescueException(level, failedPackage, t);
+ }
+ }
+ };
+ Thread thread = new Thread(runnable);
+ thread.start();
+ }
+ }
+
+
+ private static String getCompleteMessage(Throwable t) {
+ final StringBuilder builder = new StringBuilder();
+ builder.append(t.getMessage());
+ while ((t = t.getCause()) != null) {
+ builder.append(": ").append(t.getMessage());
+ }
+ return builder.toString();
+ }
+
+ private static void logRescueException(int level, @Nullable String failedPackageName,
+ Throwable t) {
+ final String msg = getCompleteMessage(t);
+ EventLog.writeEvent(LOG_TAG_RESCUE_FAILURE, level, msg);
+ String failureMsg = "Failed rescue level " + levelToString(level);
+ if (!TextUtils.isEmpty(failedPackageName)) {
+ failureMsg += " for package " + failedPackageName;
+ }
+ logCrashRecoveryEvent(Log.ERROR, failureMsg + ": " + msg);
+ }
+
+ private static int mapRescueLevelToUserImpact(int rescueLevel) {
+ if (Flags.recoverabilityDetection()) {
+ switch (rescueLevel) {
+ case RESCUE_LEVEL_SCOPED_DEVICE_CONFIG_RESET:
+ return PackageHealthObserverImpact.USER_IMPACT_LEVEL_10;
+ case RESCUE_LEVEL_ALL_DEVICE_CONFIG_RESET:
+ return PackageHealthObserverImpact.USER_IMPACT_LEVEL_40;
+ case RESCUE_LEVEL_WARM_REBOOT:
+ return PackageHealthObserverImpact.USER_IMPACT_LEVEL_50;
+ case RESCUE_LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS:
+ return PackageHealthObserverImpact.USER_IMPACT_LEVEL_71;
+ case RESCUE_LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES:
+ return PackageHealthObserverImpact.USER_IMPACT_LEVEL_75;
+ case RESCUE_LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS:
+ return PackageHealthObserverImpact.USER_IMPACT_LEVEL_80;
+ case RESCUE_LEVEL_FACTORY_RESET:
+ return PackageHealthObserverImpact.USER_IMPACT_LEVEL_100;
+ default:
+ return PackageHealthObserverImpact.USER_IMPACT_LEVEL_0;
+ }
+ } else {
+ switch (rescueLevel) {
+ case LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS:
+ case LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES:
+ return PackageHealthObserverImpact.USER_IMPACT_LEVEL_10;
+ case LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS:
+ case LEVEL_WARM_REBOOT:
+ return PackageHealthObserverImpact.USER_IMPACT_LEVEL_50;
+ case LEVEL_FACTORY_RESET:
+ return PackageHealthObserverImpact.USER_IMPACT_LEVEL_100;
+ default:
+ return PackageHealthObserverImpact.USER_IMPACT_LEVEL_0;
+ }
+ }
+ }
+
+ /**
+ * Handle mitigation action for package failures. This observer will be register to Package
+ * Watchdog and will receive calls about package failures. This observer is persistent so it
+ * may choose to mitigate failures for packages it has not explicitly asked to observe.
+ */
+ public static class RescuePartyObserver implements PackageHealthObserver {
+
+ private final Context mContext;
+ private final Map<String, Set<String>> mCallingPackageNamespaceSetMap = new HashMap<>();
+ private final Map<String, Set<String>> mNamespaceCallingPackageSetMap = new HashMap<>();
+
+ @GuardedBy("RescuePartyObserver.class")
+ static RescuePartyObserver sRescuePartyObserver;
+
+ private RescuePartyObserver(Context context) {
+ mContext = context;
+ }
+
+ /** Creates or gets singleton instance of RescueParty. */
+ public static RescuePartyObserver getInstance(Context context) {
+ synchronized (RescuePartyObserver.class) {
+ if (sRescuePartyObserver == null) {
+ sRescuePartyObserver = new RescuePartyObserver(context);
+ }
+ return sRescuePartyObserver;
+ }
+ }
+
+ /** Gets singleton instance. It returns null if the instance is not created yet.*/
+ @Nullable
+ public static RescuePartyObserver getInstanceIfCreated() {
+ synchronized (RescuePartyObserver.class) {
+ return sRescuePartyObserver;
+ }
+ }
+
+ @VisibleForTesting
+ static void reset() {
+ synchronized (RescuePartyObserver.class) {
+ sRescuePartyObserver = null;
+ }
+ }
+
+ @Override
+ public int onHealthCheckFailed(@Nullable VersionedPackage failedPackage,
+ @FailureReasons int failureReason, int mitigationCount) {
+ int impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_0;
+ if (!isDisabled() && (failureReason == PackageWatchdog.FAILURE_REASON_APP_CRASH
+ || failureReason == PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING)) {
+ if (Flags.recoverabilityDetection()) {
+ if (!Flags.deprecateFlagsAndSettingsResets()) {
+ impact = mapRescueLevelToUserImpact(getRescueLevel(mitigationCount,
+ mayPerformReboot(failedPackage), failedPackage));
+ } else {
+ impact = mapRescueLevelToUserImpact(getRescueLevel(mitigationCount));
+ }
+ } else {
+ impact = mapRescueLevelToUserImpact(getRescueLevel(mitigationCount,
+ mayPerformReboot(failedPackage)));
+ }
+ }
+
+ Slog.i(TAG, "Checking available remediations for health check failure."
+ + " failedPackage: "
+ + (failedPackage == null ? null : failedPackage.getPackageName())
+ + " failureReason: " + failureReason
+ + " available impact: " + impact);
+ return impact;
+ }
+
+ @Override
+ public int onExecuteHealthCheckMitigation(@Nullable VersionedPackage failedPackage,
+ @FailureReasons int failureReason, int mitigationCount) {
+ if (isDisabled()) {
+ return MITIGATION_RESULT_SKIPPED;
+ }
+ Slog.i(TAG, "Executing remediation."
+ + " failedPackage: "
+ + (failedPackage == null ? null : failedPackage.getPackageName())
+ + " failureReason: " + failureReason
+ + " mitigationCount: " + mitigationCount);
+ if (failureReason == PackageWatchdog.FAILURE_REASON_APP_CRASH
+ || failureReason == PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING) {
+ final int level;
+ if (Flags.recoverabilityDetection()) {
+ if (!Flags.deprecateFlagsAndSettingsResets()) {
+ level = getRescueLevel(mitigationCount, mayPerformReboot(failedPackage),
+ failedPackage);
+ } else {
+ level = getRescueLevel(mitigationCount);
+ }
+ } else {
+ level = getRescueLevel(mitigationCount, mayPerformReboot(failedPackage));
+ }
+ executeRescueLevel(mContext,
+ failedPackage == null ? null : failedPackage.getPackageName(), level);
+ return MITIGATION_RESULT_SUCCESS;
+ } else {
+ return MITIGATION_RESULT_SKIPPED;
+ }
+ }
+
+ @Override
+ public boolean isPersistent() {
+ return true;
+ }
+
+ @Override
+ public boolean mayObservePackage(String packageName) {
+ PackageManager pm = mContext.getPackageManager();
+ try {
+ // A package is a module if this is non-null
+ if (pm.getModuleInfo(packageName, 0) != null) {
+ return true;
+ }
+ } catch (PackageManager.NameNotFoundException | IllegalStateException ignore) {
+ }
+
+ return isPersistentSystemApp(packageName);
+ }
+
+ @Override
+ public int onBootLoop(int mitigationCount) {
+ if (isDisabled()) {
+ return PackageHealthObserverImpact.USER_IMPACT_LEVEL_0;
+ }
+ if (Flags.recoverabilityDetection()) {
+ if (!Flags.deprecateFlagsAndSettingsResets()) {
+ return mapRescueLevelToUserImpact(getRescueLevel(mitigationCount,
+ true, /*failedPackage=*/ null));
+ } else {
+ return mapRescueLevelToUserImpact(getRescueLevel(mitigationCount));
+ }
+ } else {
+ return mapRescueLevelToUserImpact(getRescueLevel(mitigationCount, true));
+ }
+ }
+
+ @Override
+ public int onExecuteBootLoopMitigation(int mitigationCount) {
+ if (isDisabled()) {
+ return MITIGATION_RESULT_SKIPPED;
+ }
+ boolean mayPerformReboot = !shouldThrottleReboot();
+ final int level;
+ if (Flags.recoverabilityDetection()) {
+ if (!Flags.deprecateFlagsAndSettingsResets()) {
+ level = getRescueLevel(mitigationCount, mayPerformReboot,
+ /*failedPackage=*/ null);
+ } else {
+ level = getRescueLevel(mitigationCount);
+ }
+ } else {
+ level = getRescueLevel(mitigationCount, mayPerformReboot);
+ }
+ executeRescueLevel(mContext, /*failedPackage=*/ null, level);
+ return MITIGATION_RESULT_SUCCESS;
+ }
+
+ @Override
+ public String getUniqueIdentifier() {
+ return NAME;
+ }
+
+ /**
+ * Returns {@code true} if the failing package is non-null and performing a reboot or
+ * prompting a factory reset is an acceptable mitigation strategy for the package's
+ * failure, {@code false} otherwise.
+ */
+ private boolean mayPerformReboot(@Nullable VersionedPackage failingPackage) {
+ if (failingPackage == null) {
+ return false;
+ }
+ if (shouldThrottleReboot()) {
+ return false;
+ }
+
+ return isPersistentSystemApp(failingPackage.getPackageName());
+ }
+
+ private boolean isPersistentSystemApp(@NonNull String packageName) {
+ PackageManager pm = mContext.getPackageManager();
+ try {
+ ApplicationInfo info = pm.getApplicationInfo(packageName, 0);
+ return (info.flags & PERSISTENT_MASK) == PERSISTENT_MASK;
+ } catch (PackageManager.NameNotFoundException e) {
+ return false;
+ }
+ }
+
+ private synchronized Set<String> getCallingPackagesSet(String namespace) {
+ return mNamespaceCallingPackageSetMap.get(namespace);
+ }
+ }
+
+ /**
+ * Returns {@code true} if Rescue Party is allowed to attempt a reboot or factory reset.
+ * Will return {@code false} if a factory reset was already offered recently.
+ */
+ private static boolean shouldThrottleReboot() {
+ Long lastResetTime = getLastFactoryResetTimeMs();
+ long now = System.currentTimeMillis();
+ long throttleDurationMin = SystemProperties.getLong(PROP_THROTTLE_DURATION_MIN_FLAG,
+ DEFAULT_FACTORY_RESET_THROTTLE_DURATION_MIN);
+ return now < lastResetTime + TimeUnit.MINUTES.toMillis(throttleDurationMin);
+ }
+
+ /**
+ * Hacky test to check if the device has an active USB connection, which is
+ * a good proxy for someone doing local development work.
+ */
+ private static boolean isUsbActive() {
+ if (SystemProperties.getBoolean(PROP_VIRTUAL_DEVICE, false)) {
+ Slog.v(TAG, "Assuming virtual device is connected over USB");
+ return true;
+ }
+ try {
+ final String state = FileUtils
+ .readTextFile(new File("/sys/class/android_usb/android0/state"), 128, "");
+ return "CONFIGURED".equals(state.trim());
+ } catch (Throwable t) {
+ Slog.w(TAG, "Failed to determine if device was on USB", t);
+ return false;
+ }
+ }
+
+ private static class Level {
+ static int none() {
+ return Flags.recoverabilityDetection() ? RESCUE_LEVEL_NONE : LEVEL_NONE;
+ }
+
+ static int reboot() {
+ return Flags.recoverabilityDetection() ? RESCUE_LEVEL_WARM_REBOOT : LEVEL_WARM_REBOOT;
+ }
+
+ static int factoryReset() {
+ return Flags.recoverabilityDetection()
+ ? RESCUE_LEVEL_FACTORY_RESET
+ : LEVEL_FACTORY_RESET;
+ }
+ }
+
+ private static String levelToString(int level) {
+ if (Flags.recoverabilityDetection()) {
+ switch (level) {
+ case RESCUE_LEVEL_NONE:
+ return "NONE";
+ case RESCUE_LEVEL_SCOPED_DEVICE_CONFIG_RESET:
+ return "SCOPED_DEVICE_CONFIG_RESET";
+ case RESCUE_LEVEL_ALL_DEVICE_CONFIG_RESET:
+ return "ALL_DEVICE_CONFIG_RESET";
+ case RESCUE_LEVEL_WARM_REBOOT:
+ return "WARM_REBOOT";
+ case RESCUE_LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS:
+ return "RESET_SETTINGS_UNTRUSTED_DEFAULTS";
+ case RESCUE_LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES:
+ return "RESET_SETTINGS_UNTRUSTED_CHANGES";
+ case RESCUE_LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS:
+ return "RESET_SETTINGS_TRUSTED_DEFAULTS";
+ case RESCUE_LEVEL_FACTORY_RESET:
+ return "FACTORY_RESET";
+ default:
+ return Integer.toString(level);
+ }
+ } else {
+ switch (level) {
+ case LEVEL_NONE:
+ return "NONE";
+ case LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS:
+ return "RESET_SETTINGS_UNTRUSTED_DEFAULTS";
+ case LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES:
+ return "RESET_SETTINGS_UNTRUSTED_CHANGES";
+ case LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS:
+ return "RESET_SETTINGS_TRUSTED_DEFAULTS";
+ case LEVEL_WARM_REBOOT:
+ return "WARM_REBOOT";
+ case LEVEL_FACTORY_RESET:
+ return "FACTORY_RESET";
+ default:
+ return Integer.toString(level);
+ }
+ }
+ }
+}
diff --git a/packages/CrashRecovery/services/module/java/com/android/server/crashrecovery/CrashRecoveryModule.java b/packages/CrashRecovery/services/module/java/com/android/server/crashrecovery/CrashRecoveryModule.java
new file mode 100644
index 0000000..8a81aaa
--- /dev/null
+++ b/packages/CrashRecovery/services/module/java/com/android/server/crashrecovery/CrashRecoveryModule.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.crashrecovery;
+
+import android.content.Context;
+
+import com.android.server.PackageWatchdog;
+import com.android.server.RescueParty;
+import com.android.server.SystemService;
+
+
+/** This class encapsulate the lifecycle methods of CrashRecovery module.
+ *
+ * @hide
+ */
+public class CrashRecoveryModule {
+ private static final String TAG = "CrashRecoveryModule";
+
+ /** Lifecycle definition for CrashRecovery module. */
+ public static class Lifecycle extends SystemService {
+ private Context mSystemContext;
+ private PackageWatchdog mPackageWatchdog;
+
+ public Lifecycle(Context context) {
+ super(context);
+ mSystemContext = context;
+ mPackageWatchdog = PackageWatchdog.getInstance(context);
+ }
+
+ @Override
+ public void onStart() {
+ RescueParty.registerHealthObserver(mSystemContext);
+ mPackageWatchdog.registerShutdownBroadcastReceiver();
+ mPackageWatchdog.noteBoot();
+ }
+
+ @Override
+ public void onBootPhase(int phase) {
+ if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
+ mPackageWatchdog.onPackagesReady();
+ }
+ }
+ }
+}
diff --git a/packages/CrashRecovery/services/module/java/com/android/server/crashrecovery/CrashRecoveryUtils.java b/packages/CrashRecovery/services/module/java/com/android/server/crashrecovery/CrashRecoveryUtils.java
new file mode 100644
index 0000000..2e2a937
--- /dev/null
+++ b/packages/CrashRecovery/services/module/java/com/android/server/crashrecovery/CrashRecoveryUtils.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.crashrecovery;
+
+import android.os.Environment;
+import android.util.IndentingPrintWriter;
+import android.util.Log;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+
+/**
+ * Class containing helper methods for the CrashRecoveryModule.
+ *
+ * @hide
+ */
+public class CrashRecoveryUtils {
+ private static final String TAG = "CrashRecoveryUtils";
+ private static final long MAX_CRITICAL_INFO_DUMP_SIZE = 1000 * 1000; // ~1MB
+ private static final Object sFileLock = new Object();
+
+ /** Persist recovery related events in crashrecovery events file.**/
+ public static void logCrashRecoveryEvent(int priority, String msg) {
+ Log.println(priority, TAG, msg);
+ try {
+ File fname = getCrashRecoveryEventsFile();
+ synchronized (sFileLock) {
+ FileOutputStream out = new FileOutputStream(fname, true);
+ PrintWriter pw = new PrintWriter(out);
+ String dateString = LocalDateTime.now(ZoneId.systemDefault()).toString();
+ pw.println(dateString + ": " + msg);
+ pw.close();
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "Unable to log CrashRecoveryEvents " + e.getMessage());
+ }
+ }
+
+ /** Dump recovery related events from crashrecovery events file.**/
+ public static void dumpCrashRecoveryEvents(IndentingPrintWriter pw) {
+ pw.println("CrashRecovery Events: ");
+ pw.increaseIndent();
+ final File file = getCrashRecoveryEventsFile();
+ final long skipSize = file.length() - MAX_CRITICAL_INFO_DUMP_SIZE;
+ synchronized (sFileLock) {
+ try (BufferedReader in = new BufferedReader(new FileReader(file))) {
+ if (skipSize > 0) {
+ in.skip(skipSize);
+ }
+ String line;
+ while ((line = in.readLine()) != null) {
+ pw.println(line);
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "Unable to dump CrashRecoveryEvents " + e.getMessage());
+ }
+ }
+ pw.decreaseIndent();
+ }
+
+ private static File getCrashRecoveryEventsFile() {
+ File systemDir = new File(Environment.getDataDirectory(), "system");
+ return new File(systemDir, "crashrecovery-events.txt");
+ }
+}
diff --git a/packages/CrashRecovery/services/module/java/com/android/server/rollback/RollbackPackageHealthObserver.java b/packages/CrashRecovery/services/module/java/com/android/server/rollback/RollbackPackageHealthObserver.java
new file mode 100644
index 0000000..4978df4
--- /dev/null
+++ b/packages/CrashRecovery/services/module/java/com/android/server/rollback/RollbackPackageHealthObserver.java
@@ -0,0 +1,785 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.rollback;
+
+import static com.android.server.PackageWatchdog.MITIGATION_RESULT_SKIPPED;
+import static com.android.server.PackageWatchdog.MITIGATION_RESULT_SUCCESS;
+import static com.android.server.crashrecovery.CrashRecoveryUtils.logCrashRecoveryEvent;
+
+import android.annotation.AnyThread;
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
+import android.annotation.WorkerThread;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.VersionedPackage;
+import android.content.rollback.PackageRollbackInfo;
+import android.content.rollback.RollbackInfo;
+import android.content.rollback.RollbackManager;
+import android.crashrecovery.flags.Flags;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.PowerManager;
+import android.os.SystemProperties;
+import android.sysprop.CrashRecoveryProperties;
+import android.util.ArraySet;
+import android.util.FileUtils;
+import android.util.Log;
+import android.util.Slog;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.Preconditions;
+import com.android.server.PackageWatchdog;
+import com.android.server.PackageWatchdog.FailureReasons;
+import com.android.server.PackageWatchdog.PackageHealthObserver;
+import com.android.server.PackageWatchdog.PackageHealthObserverImpact;
+import com.android.server.crashrecovery.proto.CrashRecoveryStatsLog;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Set;
+import java.util.function.Consumer;
+
+/**
+ * {@link PackageHealthObserver} for {@link RollbackManagerService}.
+ * This class monitors crashes and triggers RollbackManager rollback accordingly.
+ * It also monitors native crashes for some short while after boot.
+ *
+ * @hide
+ */
+@FlaggedApi(Flags.FLAG_ENABLE_CRASHRECOVERY)
+@SuppressLint({"CallbackName"})
+@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
+public final class RollbackPackageHealthObserver implements PackageHealthObserver {
+ private static final String TAG = "RollbackPackageHealthObserver";
+ private static final String NAME = "rollback-observer";
+ private static final String CLASS_NAME = RollbackPackageHealthObserver.class.getName();
+
+ private static final int PERSISTENT_MASK = ApplicationInfo.FLAG_PERSISTENT
+ | ApplicationInfo.FLAG_SYSTEM;
+
+ private static final String PROP_DISABLE_HIGH_IMPACT_ROLLBACK_FLAG =
+ "persist.device_config.configuration.disable_high_impact_rollback";
+
+ private final Context mContext;
+ private final Handler mHandler;
+ private final File mLastStagedRollbackIdsFile;
+ private final File mTwoPhaseRollbackEnabledFile;
+ // Staged rollback ids that have been committed but their session is not yet ready
+ private final Set<Integer> mPendingStagedRollbackIds = new ArraySet<>();
+ // True if needing to roll back only rebootless apexes when native crash happens
+ private boolean mTwoPhaseRollbackEnabled;
+
+ @VisibleForTesting
+ public RollbackPackageHealthObserver(@NonNull Context context) {
+ mContext = context;
+ HandlerThread handlerThread = new HandlerThread("RollbackPackageHealthObserver");
+ handlerThread.start();
+ mHandler = new Handler(handlerThread.getLooper());
+ File dataDir = new File(Environment.getDataDirectory(), "rollback-observer");
+ dataDir.mkdirs();
+ mLastStagedRollbackIdsFile = new File(dataDir, "last-staged-rollback-ids");
+ mTwoPhaseRollbackEnabledFile = new File(dataDir, "two-phase-rollback-enabled");
+ PackageWatchdog.getInstance(mContext).registerHealthObserver(context.getMainExecutor(),
+ this);
+
+ if (SystemProperties.getBoolean("sys.boot_completed", false)) {
+ // Load the value from the file if system server has crashed and restarted
+ mTwoPhaseRollbackEnabled = readBoolean(mTwoPhaseRollbackEnabledFile);
+ } else {
+ // Disable two-phase rollback for a normal reboot. We assume the rebootless apex
+ // installed before reboot is stable if native crash didn't happen.
+ mTwoPhaseRollbackEnabled = false;
+ writeBoolean(mTwoPhaseRollbackEnabledFile, false);
+ }
+ }
+
+ @Override
+ public int onHealthCheckFailed(@Nullable VersionedPackage failedPackage,
+ @FailureReasons int failureReason, int mitigationCount) {
+ int impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_0;
+ if (Flags.recoverabilityDetection()) {
+ List<RollbackInfo> availableRollbacks = getAvailableRollbacks();
+ List<RollbackInfo> lowImpactRollbacks = getRollbacksAvailableForImpactLevel(
+ availableRollbacks, PackageManager.ROLLBACK_USER_IMPACT_LOW);
+ if (!lowImpactRollbacks.isEmpty()) {
+ if (failureReason == PackageWatchdog.FAILURE_REASON_NATIVE_CRASH) {
+ // For native crashes, we will directly roll back any available rollbacks at low
+ // impact level
+ impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_30;
+ } else if (getRollbackForPackage(failedPackage, lowImpactRollbacks) != null) {
+ // Rollback is available for crashing low impact package
+ impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_30;
+ } else {
+ impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_70;
+ }
+ }
+ } else {
+ boolean anyRollbackAvailable = !mContext.getSystemService(RollbackManager.class)
+ .getAvailableRollbacks().isEmpty();
+
+ if (failureReason == PackageWatchdog.FAILURE_REASON_NATIVE_CRASH
+ && anyRollbackAvailable) {
+ // For native crashes, we will directly roll back any available rollbacks
+ // Note: For non-native crashes the rollback-all step has higher impact
+ impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_30;
+ } else if (getAvailableRollback(failedPackage) != null) {
+ // Rollback is available, we may get a callback into #onExecuteHealthCheckMitigation
+ impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_30;
+ } else if (anyRollbackAvailable) {
+ // If any rollbacks are available, we will commit them
+ impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_70;
+ }
+ }
+
+ Slog.i(TAG, "Checking available remediations for health check failure."
+ + " failedPackage: "
+ + (failedPackage == null ? null : failedPackage.getPackageName())
+ + " failureReason: " + failureReason
+ + " available impact: " + impact);
+ return impact;
+ }
+
+ @Override
+ public int onExecuteHealthCheckMitigation(@Nullable VersionedPackage failedPackage,
+ @FailureReasons int rollbackReason, int mitigationCount) {
+ Slog.i(TAG, "Executing remediation."
+ + " failedPackage: "
+ + (failedPackage == null ? null : failedPackage.getPackageName())
+ + " rollbackReason: " + rollbackReason
+ + " mitigationCount: " + mitigationCount);
+ if (Flags.recoverabilityDetection()) {
+ List<RollbackInfo> availableRollbacks = getAvailableRollbacks();
+ if (rollbackReason == PackageWatchdog.FAILURE_REASON_NATIVE_CRASH) {
+ mHandler.post(() -> rollbackAllLowImpact(availableRollbacks, rollbackReason));
+ return MITIGATION_RESULT_SUCCESS;
+ }
+
+ List<RollbackInfo> lowImpactRollbacks = getRollbacksAvailableForImpactLevel(
+ availableRollbacks, PackageManager.ROLLBACK_USER_IMPACT_LOW);
+ RollbackInfo rollback = getRollbackForPackage(failedPackage, lowImpactRollbacks);
+ if (rollback != null) {
+ mHandler.post(() -> rollbackPackage(rollback, failedPackage, rollbackReason));
+ } else if (!lowImpactRollbacks.isEmpty()) {
+ // Apply all available low impact rollbacks.
+ mHandler.post(() -> rollbackAllLowImpact(availableRollbacks, rollbackReason));
+ }
+ } else {
+ if (rollbackReason == PackageWatchdog.FAILURE_REASON_NATIVE_CRASH) {
+ mHandler.post(() -> rollbackAll(rollbackReason));
+ return MITIGATION_RESULT_SUCCESS;
+ }
+
+ RollbackInfo rollback = getAvailableRollback(failedPackage);
+ if (rollback != null) {
+ mHandler.post(() -> rollbackPackage(rollback, failedPackage, rollbackReason));
+ } else {
+ mHandler.post(() -> rollbackAll(rollbackReason));
+ }
+ }
+
+ // Assume rollbacks executed successfully
+ return MITIGATION_RESULT_SUCCESS;
+ }
+
+ @Override
+ public int onBootLoop(int mitigationCount) {
+ int impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_0;
+ if (Flags.recoverabilityDetection()) {
+ List<RollbackInfo> availableRollbacks = getAvailableRollbacks();
+ if (!availableRollbacks.isEmpty()) {
+ impact = getUserImpactBasedOnRollbackImpactLevel(availableRollbacks);
+ }
+ }
+ return impact;
+ }
+
+ @Override
+ public int onExecuteBootLoopMitigation(int mitigationCount) {
+ if (Flags.recoverabilityDetection()) {
+ List<RollbackInfo> availableRollbacks = getAvailableRollbacks();
+
+ triggerLeastImpactLevelRollback(availableRollbacks,
+ PackageWatchdog.FAILURE_REASON_BOOT_LOOP);
+ return MITIGATION_RESULT_SUCCESS;
+ }
+ return MITIGATION_RESULT_SKIPPED;
+ }
+
+ @Override
+ @NonNull
+ public String getUniqueIdentifier() {
+ return NAME;
+ }
+
+ @Override
+ public boolean isPersistent() {
+ return true;
+ }
+
+ @Override
+ public boolean mayObservePackage(@NonNull String packageName) {
+ if (getAvailableRollbacks().isEmpty()) {
+ return false;
+ }
+ return isPersistentSystemApp(packageName);
+ }
+
+ private List<RollbackInfo> getAvailableRollbacks() {
+ return mContext.getSystemService(RollbackManager.class).getAvailableRollbacks();
+ }
+
+ private boolean isPersistentSystemApp(@NonNull String packageName) {
+ PackageManager pm = mContext.getPackageManager();
+ try {
+ ApplicationInfo info = pm.getApplicationInfo(packageName, 0);
+ return (info.flags & PERSISTENT_MASK) == PERSISTENT_MASK;
+ } catch (PackageManager.NameNotFoundException e) {
+ return false;
+ }
+ }
+
+ private void assertInWorkerThread() {
+ Preconditions.checkState(mHandler.getLooper().isCurrentThread());
+ }
+
+ @AnyThread
+ @NonNull
+ public void notifyRollbackAvailable(@NonNull RollbackInfo rollback) {
+ mHandler.post(() -> {
+ // Enable two-phase rollback when a rebootless apex rollback is made available.
+ // We assume the rebootless apex is stable and is less likely to be the cause
+ // if native crash doesn't happen before reboot. So we will clear the flag and disable
+ // two-phase rollback after reboot.
+ if (isRebootlessApex(rollback)) {
+ mTwoPhaseRollbackEnabled = true;
+ writeBoolean(mTwoPhaseRollbackEnabledFile, true);
+ }
+ });
+ }
+
+ private static boolean isRebootlessApex(RollbackInfo rollback) {
+ if (!rollback.isStaged()) {
+ for (PackageRollbackInfo info : rollback.getPackages()) {
+ if (info.isApex()) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /** Verifies the rollback state after a reboot and schedules polling for sometime after reboot
+ * to check for native crashes and mitigate them if needed.
+ */
+ @AnyThread
+ public void onBootCompletedAsync() {
+ mHandler.post(()->onBootCompleted());
+ }
+
+ @WorkerThread
+ private void onBootCompleted() {
+ assertInWorkerThread();
+
+ RollbackManager rollbackManager = mContext.getSystemService(RollbackManager.class);
+ if (!rollbackManager.getAvailableRollbacks().isEmpty()) {
+ // TODO(gavincorkery): Call into Package Watchdog from outside the observer
+ PackageWatchdog.getInstance(mContext).scheduleCheckAndMitigateNativeCrashes();
+ }
+
+ SparseArray<String> rollbackIds = popLastStagedRollbackIds();
+ for (int i = 0; i < rollbackIds.size(); i++) {
+ WatchdogRollbackLogger.logRollbackStatusOnBoot(mContext,
+ rollbackIds.keyAt(i), rollbackIds.valueAt(i),
+ rollbackManager.getRecentlyCommittedRollbacks());
+ }
+ }
+
+ @AnyThread
+ private RollbackInfo getAvailableRollback(VersionedPackage failedPackage) {
+ RollbackManager rollbackManager = mContext.getSystemService(RollbackManager.class);
+ for (RollbackInfo rollback : rollbackManager.getAvailableRollbacks()) {
+ for (PackageRollbackInfo packageRollback : rollback.getPackages()) {
+ if (packageRollback.getVersionRolledBackFrom().equals(failedPackage)) {
+ return rollback;
+ }
+ // TODO(b/147666157): Extract version number of apk-in-apex so that we don't have
+ // to rely on complicated reasoning as below
+
+ // Due to b/147666157, for apk in apex, we do not know the version we are rolling
+ // back from. But if a package X is embedded in apex A exclusively (not embedded in
+ // any other apex), which is not guaranteed, then it is sufficient to check only
+ // package names here, as the version of failedPackage and the PackageRollbackInfo
+ // can't be different. If failedPackage has a higher version, then it must have
+ // been updated somehow. There are two ways: it was updated by an update of apex A
+ // or updated directly as apk. In both cases, this rollback would have gotten
+ // expired when onPackageReplaced() was called. Since the rollback exists, it has
+ // same version as failedPackage.
+ if (packageRollback.isApkInApex()
+ && packageRollback.getVersionRolledBackFrom().getPackageName()
+ .equals(failedPackage.getPackageName())) {
+ return rollback;
+ }
+ }
+ }
+ return null;
+ }
+
+ @AnyThread
+ private RollbackInfo getRollbackForPackage(@Nullable VersionedPackage failedPackage,
+ List<RollbackInfo> availableRollbacks) {
+ if (failedPackage == null) {
+ return null;
+ }
+
+ for (RollbackInfo rollback : availableRollbacks) {
+ for (PackageRollbackInfo packageRollback : rollback.getPackages()) {
+ if (packageRollback.getVersionRolledBackFrom().equals(failedPackage)) {
+ return rollback;
+ }
+ // TODO(b/147666157): Extract version number of apk-in-apex so that we don't have
+ // to rely on complicated reasoning as below
+
+ // Due to b/147666157, for apk in apex, we do not know the version we are rolling
+ // back from. But if a package X is embedded in apex A exclusively (not embedded in
+ // any other apex), which is not guaranteed, then it is sufficient to check only
+ // package names here, as the version of failedPackage and the PackageRollbackInfo
+ // can't be different. If failedPackage has a higher version, then it must have
+ // been updated somehow. There are two ways: it was updated by an update of apex A
+ // or updated directly as apk. In both cases, this rollback would have gotten
+ // expired when onPackageReplaced() was called. Since the rollback exists, it has
+ // same version as failedPackage.
+ if (packageRollback.isApkInApex()
+ && packageRollback.getVersionRolledBackFrom().getPackageName()
+ .equals(failedPackage.getPackageName())) {
+ return rollback;
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns {@code true} if staged session associated with {@code rollbackId} was marked
+ * as handled, {@code false} if already handled.
+ */
+ @WorkerThread
+ private boolean markStagedSessionHandled(int rollbackId) {
+ assertInWorkerThread();
+ return mPendingStagedRollbackIds.remove(rollbackId);
+ }
+
+ /**
+ * Returns {@code true} if all pending staged rollback sessions were marked as handled,
+ * {@code false} if there is any left.
+ */
+ @WorkerThread
+ private boolean isPendingStagedSessionsEmpty() {
+ assertInWorkerThread();
+ return mPendingStagedRollbackIds.isEmpty();
+ }
+
+ private static boolean readBoolean(File file) {
+ try (FileInputStream fis = new FileInputStream(file)) {
+ return fis.read() == 1;
+ } catch (IOException ignore) {
+ return false;
+ }
+ }
+
+ private static void writeBoolean(File file, boolean value) {
+ try (FileOutputStream fos = new FileOutputStream(file)) {
+ fos.write(value ? 1 : 0);
+ fos.flush();
+ FileUtils.sync(fos);
+ } catch (IOException ignore) {
+ }
+ }
+
+ @WorkerThread
+ private void saveStagedRollbackId(int stagedRollbackId, @Nullable VersionedPackage logPackage) {
+ assertInWorkerThread();
+ writeStagedRollbackId(mLastStagedRollbackIdsFile, stagedRollbackId, logPackage);
+ }
+
+ static void writeStagedRollbackId(File file, int stagedRollbackId,
+ @Nullable VersionedPackage logPackage) {
+ try {
+ FileOutputStream fos = new FileOutputStream(file, true);
+ PrintWriter pw = new PrintWriter(fos);
+ String logPackageName = logPackage != null ? logPackage.getPackageName() : "";
+ pw.append(String.valueOf(stagedRollbackId)).append(",").append(logPackageName);
+ pw.println();
+ pw.flush();
+ FileUtils.sync(fos);
+ pw.close();
+ } catch (IOException e) {
+ Slog.e(TAG, "Failed to save last staged rollback id", e);
+ file.delete();
+ }
+ }
+
+ @WorkerThread
+ private SparseArray<String> popLastStagedRollbackIds() {
+ assertInWorkerThread();
+ try {
+ return readStagedRollbackIds(mLastStagedRollbackIdsFile);
+ } finally {
+ mLastStagedRollbackIdsFile.delete();
+ }
+ }
+
+ static SparseArray<String> readStagedRollbackIds(File file) {
+ SparseArray<String> result = new SparseArray<>();
+ try {
+ String line;
+ BufferedReader reader = new BufferedReader(new FileReader(file));
+ while ((line = reader.readLine()) != null) {
+ // Each line is of the format: "id,logging_package"
+ String[] values = line.trim().split(",");
+ String rollbackId = values[0];
+ String logPackageName = "";
+ if (values.length > 1) {
+ logPackageName = values[1];
+ }
+ result.put(Integer.parseInt(rollbackId), logPackageName);
+ }
+ } catch (Exception ignore) {
+ return new SparseArray<>();
+ }
+ return result;
+ }
+
+
+ /**
+ * Returns true if the package name is the name of a module.
+ */
+ @AnyThread
+ private boolean isModule(String packageName) {
+ // Check if the package is listed among the system modules or is an
+ // APK inside an updatable APEX.
+ try {
+ PackageManager pm = mContext.getPackageManager();
+ final PackageInfo pkg = pm.getPackageInfo(packageName, 0 /* flags */);
+ String apexPackageName = pkg.getApexPackageName();
+ if (apexPackageName != null) {
+ packageName = apexPackageName;
+ }
+
+ return pm.getModuleInfo(packageName, 0 /* flags */) != null;
+ } catch (PackageManager.NameNotFoundException e) {
+ return false;
+ }
+ }
+
+ /**
+ * Rolls back the session that owns {@code failedPackage}
+ *
+ * @param rollback {@code rollbackInfo} of the {@code failedPackage}
+ * @param failedPackage the package that needs to be rolled back
+ */
+ @WorkerThread
+ private void rollbackPackage(RollbackInfo rollback, VersionedPackage failedPackage,
+ @FailureReasons int rollbackReason) {
+ assertInWorkerThread();
+ String failedPackageName = (failedPackage == null ? null : failedPackage.getPackageName());
+
+ Slog.i(TAG, "Rolling back package. RollbackId: " + rollback.getRollbackId()
+ + " failedPackage: " + failedPackageName
+ + " rollbackReason: " + rollbackReason);
+ logCrashRecoveryEvent(Log.DEBUG, String.format("Rolling back %s. Reason: %s",
+ failedPackageName, rollbackReason));
+ final RollbackManager rollbackManager = mContext.getSystemService(RollbackManager.class);
+ int reasonToLog = WatchdogRollbackLogger.mapFailureReasonToMetric(rollbackReason);
+ final String failedPackageToLog;
+ if (rollbackReason == PackageWatchdog.FAILURE_REASON_NATIVE_CRASH) {
+ failedPackageToLog = SystemProperties.get(
+ "sys.init.updatable_crashing_process_name", "");
+ } else {
+ failedPackageToLog = failedPackage.getPackageName();
+ }
+ VersionedPackage logPackageTemp = null;
+ if (isModule(failedPackage.getPackageName())) {
+ logPackageTemp = WatchdogRollbackLogger.getLogPackage(mContext, failedPackage);
+ }
+
+ final VersionedPackage logPackage = logPackageTemp;
+ WatchdogRollbackLogger.logEvent(logPackage,
+ CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_INITIATE,
+ reasonToLog, failedPackageToLog);
+
+ Consumer<Intent> onResult = result -> {
+ assertInWorkerThread();
+ int status = result.getIntExtra(RollbackManager.EXTRA_STATUS,
+ RollbackManager.STATUS_FAILURE);
+ if (status == RollbackManager.STATUS_SUCCESS) {
+ if (rollback.isStaged()) {
+ int rollbackId = rollback.getRollbackId();
+ saveStagedRollbackId(rollbackId, logPackage);
+ WatchdogRollbackLogger.logEvent(logPackage,
+ CrashRecoveryStatsLog
+ .WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_BOOT_TRIGGERED,
+ reasonToLog, failedPackageToLog);
+
+ } else {
+ WatchdogRollbackLogger.logEvent(logPackage,
+ CrashRecoveryStatsLog
+ .WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_SUCCESS,
+ reasonToLog, failedPackageToLog);
+ }
+ } else {
+ WatchdogRollbackLogger.logEvent(logPackage,
+ CrashRecoveryStatsLog
+ .WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_FAILURE,
+ reasonToLog, failedPackageToLog);
+ }
+ if (rollback.isStaged()) {
+ markStagedSessionHandled(rollback.getRollbackId());
+ // Wait for all pending staged sessions to get handled before rebooting.
+ if (isPendingStagedSessionsEmpty()) {
+ CrashRecoveryProperties.attemptingReboot(true);
+ mContext.getSystemService(PowerManager.class).reboot("Rollback staged install");
+ }
+ }
+ };
+
+ // Define a BroadcastReceiver to handle the result
+ BroadcastReceiver rollbackReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent result) {
+ mHandler.post(() -> onResult.accept(result));
+ }
+ };
+
+ String intentActionName = CLASS_NAME + rollback.getRollbackId();
+ // Register the BroadcastReceiver
+ mContext.registerReceiver(rollbackReceiver,
+ new IntentFilter(intentActionName),
+ Context.RECEIVER_NOT_EXPORTED);
+
+ Intent intentReceiver = new Intent(intentActionName);
+ intentReceiver.putExtra("rollbackId", rollback.getRollbackId());
+ intentReceiver.setPackage(mContext.getPackageName());
+ intentReceiver.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+
+ PendingIntent rollbackPendingIntent = PendingIntent.getBroadcast(mContext,
+ rollback.getRollbackId(),
+ intentReceiver,
+ PendingIntent.FLAG_MUTABLE);
+
+ rollbackManager.commitRollback(rollback.getRollbackId(),
+ Collections.singletonList(failedPackage),
+ rollbackPendingIntent.getIntentSender());
+ }
+
+ /**
+ * Two-phase rollback:
+ * 1. roll back rebootless apexes first
+ * 2. roll back all remaining rollbacks if native crash doesn't stop after (1) is done
+ *
+ * This approach gives us a better chance to correctly attribute native crash to rebootless
+ * apex update without rolling back Mainline updates which might contains critical security
+ * fixes.
+ */
+ @WorkerThread
+ private boolean useTwoPhaseRollback(List<RollbackInfo> rollbacks) {
+ assertInWorkerThread();
+ if (!mTwoPhaseRollbackEnabled) {
+ return false;
+ }
+
+ Slog.i(TAG, "Rolling back all rebootless APEX rollbacks");
+ boolean found = false;
+ for (RollbackInfo rollback : rollbacks) {
+ if (isRebootlessApex(rollback)) {
+ VersionedPackage firstRollback =
+ rollback.getPackages().get(0).getVersionRolledBackFrom();
+ rollbackPackage(rollback, firstRollback,
+ PackageWatchdog.FAILURE_REASON_NATIVE_CRASH);
+ found = true;
+ }
+ }
+ return found;
+ }
+
+ /**
+ * Rollback the package that has minimum rollback impact level.
+ * @param availableRollbacks all available rollbacks
+ * @param rollbackReason reason to rollback
+ */
+ private void triggerLeastImpactLevelRollback(List<RollbackInfo> availableRollbacks,
+ @FailureReasons int rollbackReason) {
+ int minRollbackImpactLevel = getMinRollbackImpactLevel(availableRollbacks);
+
+ if (minRollbackImpactLevel == PackageManager.ROLLBACK_USER_IMPACT_LOW) {
+ // Apply all available low impact rollbacks.
+ mHandler.post(() -> rollbackAllLowImpact(availableRollbacks, rollbackReason));
+ } else if (minRollbackImpactLevel == PackageManager.ROLLBACK_USER_IMPACT_HIGH) {
+ // Check disable_high_impact_rollback device config before performing rollback
+ if (SystemProperties.getBoolean(PROP_DISABLE_HIGH_IMPACT_ROLLBACK_FLAG, false)) {
+ return;
+ }
+ // Rollback one package at a time. If that doesn't resolve the issue, rollback
+ // next with same impact level.
+ mHandler.post(() -> rollbackHighImpact(availableRollbacks, rollbackReason));
+ }
+ }
+
+ /**
+ * sort the available high impact rollbacks by first package name to have a deterministic order.
+ * Apply the first available rollback.
+ * @param availableRollbacks all available rollbacks
+ * @param rollbackReason reason to rollback
+ */
+ @WorkerThread
+ private void rollbackHighImpact(List<RollbackInfo> availableRollbacks,
+ @FailureReasons int rollbackReason) {
+ assertInWorkerThread();
+ List<RollbackInfo> highImpactRollbacks =
+ getRollbacksAvailableForImpactLevel(
+ availableRollbacks, PackageManager.ROLLBACK_USER_IMPACT_HIGH);
+
+ // sort rollbacks based on package name of the first package. This is to have a
+ // deterministic order of rollbacks.
+ List<RollbackInfo> sortedHighImpactRollbacks = highImpactRollbacks.stream().sorted(
+ Comparator.comparing(a -> a.getPackages().get(0).getPackageName())).toList();
+ VersionedPackage firstRollback =
+ sortedHighImpactRollbacks
+ .get(0)
+ .getPackages()
+ .get(0)
+ .getVersionRolledBackFrom();
+ Slog.i(TAG, "Rolling back high impact rollback for package: "
+ + firstRollback.getPackageName());
+ rollbackPackage(sortedHighImpactRollbacks.get(0), firstRollback, rollbackReason);
+ }
+
+ @WorkerThread
+ private void rollbackAll(@FailureReasons int rollbackReason) {
+ assertInWorkerThread();
+ RollbackManager rollbackManager = mContext.getSystemService(RollbackManager.class);
+ List<RollbackInfo> rollbacks = rollbackManager.getAvailableRollbacks();
+ if (useTwoPhaseRollback(rollbacks)) {
+ return;
+ }
+
+ Slog.i(TAG, "Rolling back all available rollbacks");
+ // Add all rollback ids to mPendingStagedRollbackIds, so that we do not reboot before all
+ // pending staged rollbacks are handled.
+ for (RollbackInfo rollback : rollbacks) {
+ if (rollback.isStaged()) {
+ mPendingStagedRollbackIds.add(rollback.getRollbackId());
+ }
+ }
+
+ for (RollbackInfo rollback : rollbacks) {
+ VersionedPackage firstRollback =
+ rollback.getPackages().get(0).getVersionRolledBackFrom();
+ rollbackPackage(rollback, firstRollback, rollbackReason);
+ }
+ }
+
+ /**
+ * Rollback all available low impact rollbacks
+ * @param availableRollbacks all available rollbacks
+ * @param rollbackReason reason to rollbacks
+ */
+ @WorkerThread
+ private void rollbackAllLowImpact(
+ List<RollbackInfo> availableRollbacks, @FailureReasons int rollbackReason) {
+ assertInWorkerThread();
+
+ List<RollbackInfo> lowImpactRollbacks = getRollbacksAvailableForImpactLevel(
+ availableRollbacks,
+ PackageManager.ROLLBACK_USER_IMPACT_LOW);
+ if (useTwoPhaseRollback(lowImpactRollbacks)) {
+ return;
+ }
+
+ Slog.i(TAG, "Rolling back all available low impact rollbacks");
+ logCrashRecoveryEvent(Log.DEBUG, "Rolling back all available. Reason: " + rollbackReason);
+ // Add all rollback ids to mPendingStagedRollbackIds, so that we do not reboot before all
+ // pending staged rollbacks are handled.
+ for (RollbackInfo rollback : lowImpactRollbacks) {
+ if (rollback.isStaged()) {
+ mPendingStagedRollbackIds.add(rollback.getRollbackId());
+ }
+ }
+
+ for (RollbackInfo rollback : lowImpactRollbacks) {
+ VersionedPackage firstRollback =
+ rollback.getPackages().get(0).getVersionRolledBackFrom();
+ rollbackPackage(rollback, firstRollback, rollbackReason);
+ }
+ }
+
+ private List<RollbackInfo> getRollbacksAvailableForImpactLevel(
+ List<RollbackInfo> availableRollbacks, int impactLevel) {
+ return availableRollbacks.stream()
+ .filter(rollbackInfo -> rollbackInfo.getRollbackImpactLevel() == impactLevel)
+ .toList();
+ }
+
+ private int getMinRollbackImpactLevel(List<RollbackInfo> availableRollbacks) {
+ return availableRollbacks.stream()
+ .mapToInt(RollbackInfo::getRollbackImpactLevel)
+ .min()
+ .orElse(-1);
+ }
+
+ private int getUserImpactBasedOnRollbackImpactLevel(List<RollbackInfo> availableRollbacks) {
+ int impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_0;
+ int minImpact = getMinRollbackImpactLevel(availableRollbacks);
+ switch (minImpact) {
+ case PackageManager.ROLLBACK_USER_IMPACT_LOW:
+ impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_70;
+ break;
+ case PackageManager.ROLLBACK_USER_IMPACT_HIGH:
+ if (!SystemProperties.getBoolean(PROP_DISABLE_HIGH_IMPACT_ROLLBACK_FLAG, false)) {
+ impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_90;
+ }
+ break;
+ default:
+ impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_0;
+ }
+ return impact;
+ }
+
+ @VisibleForTesting
+ Handler getHandler() {
+ return mHandler;
+ }
+}
diff --git a/packages/CrashRecovery/services/module/java/com/android/server/rollback/WatchdogRollbackLogger.java b/packages/CrashRecovery/services/module/java/com/android/server/rollback/WatchdogRollbackLogger.java
new file mode 100644
index 0000000..9cfed02
--- /dev/null
+++ b/packages/CrashRecovery/services/module/java/com/android/server/rollback/WatchdogRollbackLogger.java
@@ -0,0 +1,255 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.rollback;
+
+import static com.android.server.crashrecovery.CrashRecoveryUtils.logCrashRecoveryEvent;
+import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_APP_CRASH;
+import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_APP_NOT_RESPONDING;
+import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_BOOT_LOOPING;
+import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_EXPLICIT_HEALTH_CHECK;
+import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_NATIVE_CRASH;
+import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_NATIVE_CRASH_DURING_BOOT;
+import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_UNKNOWN;
+import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_BOOT_TRIGGERED;
+import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_FAILURE;
+import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_INITIATE;
+import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_SUCCESS;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInstaller;
+import android.content.pm.PackageManager;
+import android.content.pm.VersionedPackage;
+import android.content.rollback.PackageRollbackInfo;
+import android.content.rollback.RollbackInfo;
+import android.os.SystemProperties;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.PackageWatchdog;
+import com.android.server.crashrecovery.proto.CrashRecoveryStatsLog;
+
+import java.util.List;
+
+/**
+ * This class handles the logic for logging Watchdog-triggered rollback events.
+ * @hide
+ */
+public final class WatchdogRollbackLogger {
+ private static final String TAG = "WatchdogRollbackLogger";
+
+ private static final String LOGGING_PARENT_KEY = "android.content.pm.LOGGING_PARENT";
+
+ private WatchdogRollbackLogger() {
+ }
+
+ @Nullable
+ private static String getLoggingParentName(Context context, @NonNull String packageName) {
+ PackageManager packageManager = context.getPackageManager();
+ try {
+ int flags = PackageManager.MATCH_APEX | PackageManager.GET_META_DATA;
+ ApplicationInfo ai = packageManager.getPackageInfo(packageName, flags).applicationInfo;
+ if (ai.metaData == null) {
+ return null;
+ }
+ return ai.metaData.getString(LOGGING_PARENT_KEY);
+ } catch (Exception e) {
+ Slog.w(TAG, "Unable to discover logging parent package: " + packageName, e);
+ return null;
+ }
+ }
+
+ /**
+ * Returns the logging parent of a given package if it exists, {@code null} otherwise.
+ *
+ * The logging parent is defined by the {@code android.content.pm.LOGGING_PARENT} field in the
+ * metadata of a package's AndroidManifest.xml.
+ */
+ @VisibleForTesting
+ @Nullable
+ static VersionedPackage getLogPackage(Context context,
+ @NonNull VersionedPackage failingPackage) {
+ String logPackageName;
+ VersionedPackage loggingParent;
+ logPackageName = getLoggingParentName(context, failingPackage.getPackageName());
+ if (logPackageName == null) {
+ return null;
+ }
+ try {
+ loggingParent = new VersionedPackage(logPackageName, context.getPackageManager()
+ .getPackageInfo(logPackageName, 0 /* flags */).getLongVersionCode());
+ } catch (PackageManager.NameNotFoundException e) {
+ return null;
+ }
+ return loggingParent;
+ }
+
+ static void logRollbackStatusOnBoot(Context context, int rollbackId, String logPackageName,
+ List<RollbackInfo> recentlyCommittedRollbacks) {
+ PackageInstaller packageInstaller = context.getPackageManager().getPackageInstaller();
+
+ RollbackInfo rollback = null;
+ for (RollbackInfo info : recentlyCommittedRollbacks) {
+ if (rollbackId == info.getRollbackId()) {
+ rollback = info;
+ break;
+ }
+ }
+
+ if (rollback == null) {
+ Slog.e(TAG, "rollback info not found for last staged rollback: " + rollbackId);
+ return;
+ }
+
+ // Use the version of the logging parent that was installed before
+ // we rolled back for logging purposes.
+ VersionedPackage oldLoggingPackage = null;
+ if (!TextUtils.isEmpty(logPackageName)) {
+ for (PackageRollbackInfo packageRollback : rollback.getPackages()) {
+ if (logPackageName.equals(packageRollback.getPackageName())) {
+ oldLoggingPackage = packageRollback.getVersionRolledBackFrom();
+ break;
+ }
+ }
+ }
+
+ int sessionId = rollback.getCommittedSessionId();
+ PackageInstaller.SessionInfo sessionInfo = packageInstaller.getSessionInfo(sessionId);
+ if (sessionInfo == null) {
+ Slog.e(TAG, "On boot completed, could not load session id " + sessionId);
+ return;
+ }
+
+ if (sessionInfo.isStagedSessionApplied()) {
+ logEvent(oldLoggingPackage,
+ WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_SUCCESS,
+ WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_UNKNOWN, "");
+ } else if (sessionInfo.isStagedSessionFailed()) {
+ logEvent(oldLoggingPackage,
+ WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_FAILURE,
+ WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_UNKNOWN, "");
+ }
+ }
+
+ /**
+ * Log a Watchdog rollback event to statsd.
+ *
+ * @param logPackage the package to associate the rollback with.
+ * @param type the state of the rollback.
+ * @param rollbackReason the reason Watchdog triggered a rollback, if known.
+ * @param failingPackageName the failing package or process which triggered the rollback.
+ */
+ public static void logEvent(@Nullable VersionedPackage logPackage, int type,
+ int rollbackReason, @NonNull String failingPackageName) {
+ String logMsg = "Watchdog event occurred with type: " + rollbackTypeToString(type)
+ + " logPackage: " + logPackage
+ + " rollbackReason: " + rollbackReasonToString(rollbackReason)
+ + " failedPackageName: " + failingPackageName;
+ Slog.i(TAG, logMsg);
+ if (logPackage != null) {
+ CrashRecoveryStatsLog.write(
+ CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED,
+ type,
+ logPackage.getPackageName(),
+ logPackage.getVersionCode(),
+ rollbackReason,
+ failingPackageName,
+ new byte[]{});
+ } else {
+ // In the case that the log package is null, still log an empty string as an
+ // indication that retrieving the logging parent failed.
+ CrashRecoveryStatsLog.write(
+ CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED,
+ type,
+ "",
+ 0,
+ rollbackReason,
+ failingPackageName,
+ new byte[]{});
+ }
+
+ logTestProperties(logMsg);
+ }
+
+ /**
+ * Writes properties which will be used by rollback tests to check if particular rollback
+ * events have occurred.
+ */
+ private static void logTestProperties(String logMsg) {
+ // This property should be on only during the tests
+ if (!SystemProperties.getBoolean("persist.sys.rollbacktest.enabled", false)) {
+ return;
+ }
+ logCrashRecoveryEvent(Log.DEBUG, logMsg);
+ }
+
+ @VisibleForTesting
+ static int mapFailureReasonToMetric(@PackageWatchdog.FailureReasons int failureReason) {
+ switch (failureReason) {
+ case PackageWatchdog.FAILURE_REASON_NATIVE_CRASH:
+ return WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_NATIVE_CRASH;
+ case PackageWatchdog.FAILURE_REASON_EXPLICIT_HEALTH_CHECK:
+ return WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_EXPLICIT_HEALTH_CHECK;
+ case PackageWatchdog.FAILURE_REASON_APP_CRASH:
+ return WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_APP_CRASH;
+ case PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING:
+ return WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_APP_NOT_RESPONDING;
+ case PackageWatchdog.FAILURE_REASON_BOOT_LOOP:
+ return WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_BOOT_LOOPING;
+ default:
+ return WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_UNKNOWN;
+ }
+ }
+
+ private static String rollbackTypeToString(int type) {
+ switch (type) {
+ case WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_INITIATE:
+ return "ROLLBACK_INITIATE";
+ case WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_SUCCESS:
+ return "ROLLBACK_SUCCESS";
+ case WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_FAILURE:
+ return "ROLLBACK_FAILURE";
+ case WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_BOOT_TRIGGERED:
+ return "ROLLBACK_BOOT_TRIGGERED";
+ default:
+ return "UNKNOWN";
+ }
+ }
+
+ private static String rollbackReasonToString(int reason) {
+ switch (reason) {
+ case WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_NATIVE_CRASH:
+ return "REASON_NATIVE_CRASH";
+ case WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_EXPLICIT_HEALTH_CHECK:
+ return "REASON_EXPLICIT_HEALTH_CHECK";
+ case WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_APP_CRASH:
+ return "REASON_APP_CRASH";
+ case WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_APP_NOT_RESPONDING:
+ return "REASON_APP_NOT_RESPONDING";
+ case WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_NATIVE_CRASH_DURING_BOOT:
+ return "REASON_NATIVE_CRASH_DURING_BOOT";
+ case WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_BOOT_LOOPING:
+ return "REASON_BOOT_LOOP";
+ default:
+ return "UNKNOWN";
+ }
+ }
+}
diff --git a/packages/CrashRecovery/services/module/java/com/android/util/ArrayUtils.java b/packages/CrashRecovery/services/module/java/com/android/util/ArrayUtils.java
new file mode 100644
index 0000000..29ff7cc
--- /dev/null
+++ b/packages/CrashRecovery/services/module/java/com/android/util/ArrayUtils.java
@@ -0,0 +1,42 @@
+/*
+ * 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 android.util;
+
+import android.annotation.Nullable;
+
+/**
+ * Copied over from frameworks/base/core/java/com/android/internal/util/ArrayUtils.java
+ *
+ * @hide
+ */
+public class ArrayUtils {
+ private ArrayUtils() { /* cannot be instantiated */ }
+
+ /**
+ * Checks if given array is null or has zero elements.
+ */
+ public static boolean isEmpty(@Nullable int[] array) {
+ return array == null || array.length == 0;
+ }
+
+ /**
+ * True if the byte array is null or has length 0.
+ */
+ public static boolean isEmpty(@Nullable byte[] array) {
+ return array == null || array.length == 0;
+ }
+}
diff --git a/packages/CrashRecovery/services/module/java/com/android/util/FileUtils.java b/packages/CrashRecovery/services/module/java/com/android/util/FileUtils.java
new file mode 100644
index 0000000..d60a9b9
--- /dev/null
+++ b/packages/CrashRecovery/services/module/java/com/android/util/FileUtils.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.util;
+
+import android.annotation.Nullable;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Bits and pieces copied from hidden API of android.os.FileUtils.
+ *
+ * @hide
+ */
+public class FileUtils {
+ /**
+ * Read a text file into a String, optionally limiting the length.
+ *
+ * @param file to read (will not seek, so things like /proc files are OK)
+ * @param max length (positive for head, negative of tail, 0 for no limit)
+ * @param ellipsis to add of the file was truncated (can be null)
+ * @return the contents of the file, possibly truncated
+ * @throws IOException if something goes wrong reading the file
+ * @hide
+ */
+ public static @Nullable String readTextFile(@Nullable File file, @Nullable int max,
+ @Nullable String ellipsis) throws IOException {
+ InputStream input = new FileInputStream(file);
+ // wrapping a BufferedInputStream around it because when reading /proc with unbuffered
+ // input stream, bytes read not equal to buffer size is not necessarily the correct
+ // indication for EOF; but it is true for BufferedInputStream due to its implementation.
+ BufferedInputStream bis = new BufferedInputStream(input);
+ try {
+ long size = file.length();
+ if (max > 0 || (size > 0 && max == 0)) { // "head" mode: read the first N bytes
+ if (size > 0 && (max == 0 || size < max)) max = (int) size;
+ byte[] data = new byte[max + 1];
+ int length = bis.read(data);
+ if (length <= 0) return "";
+ if (length <= max) return new String(data, 0, length);
+ if (ellipsis == null) return new String(data, 0, max);
+ return new String(data, 0, max) + ellipsis;
+ } else if (max < 0) { // "tail" mode: keep the last N
+ int len;
+ boolean rolled = false;
+ byte[] last = null;
+ byte[] data = null;
+ do {
+ if (last != null) rolled = true;
+ byte[] tmp = last;
+ last = data;
+ data = tmp;
+ if (data == null) data = new byte[-max];
+ len = bis.read(data);
+ } while (len == data.length);
+
+ if (last == null && len <= 0) return "";
+ if (last == null) return new String(data, 0, len);
+ if (len > 0) {
+ rolled = true;
+ System.arraycopy(last, len, last, 0, last.length - len);
+ System.arraycopy(data, 0, last, last.length - len, len);
+ }
+ if (ellipsis == null || !rolled) return new String(last);
+ return ellipsis + new String(last);
+ } else { // "cat" mode: size unknown, read it all in streaming fashion
+ ByteArrayOutputStream contents = new ByteArrayOutputStream();
+ int len;
+ byte[] data = new byte[1024];
+ do {
+ len = bis.read(data);
+ if (len > 0) contents.write(data, 0, len);
+ } while (len == data.length);
+ return contents.toString();
+ }
+ } finally {
+ bis.close();
+ input.close();
+ }
+ }
+
+ /**
+ * Perform an fsync on the given FileOutputStream. The stream at this
+ * point must be flushed but not yet closed.
+ *
+ * @hide
+ */
+ public static boolean sync(FileOutputStream stream) {
+ try {
+ if (stream != null) {
+ stream.getFD().sync();
+ }
+ return true;
+ } catch (IOException e) {
+ }
+ return false;
+ }
+}
diff --git a/packages/CrashRecovery/services/module/java/com/android/util/LongArrayQueue.java b/packages/CrashRecovery/services/module/java/com/android/util/LongArrayQueue.java
new file mode 100644
index 0000000..9a24ada
--- /dev/null
+++ b/packages/CrashRecovery/services/module/java/com/android/util/LongArrayQueue.java
@@ -0,0 +1,188 @@
+/*
+ * 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 android.util;
+
+import libcore.util.EmptyArray;
+
+import java.util.NoSuchElementException;
+
+/**
+ * Copied from frameworks/base/core/java/android/util/LongArrayQueue.java
+ *
+ * @hide
+ */
+public class LongArrayQueue {
+
+ private long[] mValues;
+ private int mSize;
+ private int mHead;
+ private int mTail;
+
+ private long[] newUnpaddedLongArray(int num) {
+ return new long[num];
+ }
+ /**
+ * Initializes a queue with the given starting capacity.
+ *
+ * @param initialCapacity the capacity.
+ */
+ public LongArrayQueue(int initialCapacity) {
+ if (initialCapacity == 0) {
+ mValues = EmptyArray.LONG;
+ } else {
+ mValues = newUnpaddedLongArray(initialCapacity);
+ }
+ mSize = 0;
+ mHead = mTail = 0;
+ }
+
+ /**
+ * Initializes a queue with default starting capacity.
+ */
+ public LongArrayQueue() {
+ this(16);
+ }
+
+ /** @hide */
+ public static int growSize(int currentSize) {
+ return currentSize <= 4 ? 8 : currentSize * 2;
+ }
+
+ private void grow() {
+ if (mSize < mValues.length) {
+ throw new IllegalStateException("Queue not full yet!");
+ }
+ final int newSize = growSize(mSize);
+ final long[] newArray = newUnpaddedLongArray(newSize);
+ final int r = mValues.length - mHead; // Number of elements on and to the right of head.
+ System.arraycopy(mValues, mHead, newArray, 0, r);
+ System.arraycopy(mValues, 0, newArray, r, mHead);
+ mValues = newArray;
+ mHead = 0;
+ mTail = mSize;
+ }
+
+ /**
+ * Returns the number of elements in the queue.
+ */
+ public int size() {
+ return mSize;
+ }
+
+ /**
+ * Removes all elements from this queue.
+ */
+ public void clear() {
+ mSize = 0;
+ mHead = mTail = 0;
+ }
+
+ /**
+ * Adds a value to the tail of the queue.
+ *
+ * @param value the value to be added.
+ */
+ public void addLast(long value) {
+ if (mSize == mValues.length) {
+ grow();
+ }
+ mValues[mTail] = value;
+ mTail = (mTail + 1) % mValues.length;
+ mSize++;
+ }
+
+ /**
+ * Removes an element from the head of the queue.
+ *
+ * @return the element at the head of the queue.
+ * @throws NoSuchElementException if the queue is empty.
+ */
+ public long removeFirst() {
+ if (mSize == 0) {
+ throw new NoSuchElementException("Queue is empty!");
+ }
+ final long ret = mValues[mHead];
+ mHead = (mHead + 1) % mValues.length;
+ mSize--;
+ return ret;
+ }
+
+ /**
+ * Returns the element at the given position from the head of the queue, where 0 represents the
+ * head of the queue.
+ *
+ * @param position the position from the head of the queue.
+ * @return the element found at the given position.
+ * @throws IndexOutOfBoundsException if {@code position} < {@code 0} or
+ * {@code position} >= {@link #size()}
+ */
+ public long get(int position) {
+ if (position < 0 || position >= mSize) {
+ throw new IndexOutOfBoundsException("Index " + position
+ + " not valid for a queue of size " + mSize);
+ }
+ final int index = (mHead + position) % mValues.length;
+ return mValues[index];
+ }
+
+ /**
+ * Returns the element at the head of the queue, without removing it.
+ *
+ * @return the element at the head of the queue.
+ * @throws NoSuchElementException if the queue is empty
+ */
+ public long peekFirst() {
+ if (mSize == 0) {
+ throw new NoSuchElementException("Queue is empty!");
+ }
+ return mValues[mHead];
+ }
+
+ /**
+ * Returns the element at the tail of the queue.
+ *
+ * @return the element at the tail of the queue.
+ * @throws NoSuchElementException if the queue is empty.
+ */
+ public long peekLast() {
+ if (mSize == 0) {
+ throw new NoSuchElementException("Queue is empty!");
+ }
+ final int index = (mTail == 0) ? mValues.length - 1 : mTail - 1;
+ return mValues[index];
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toString() {
+ if (mSize <= 0) {
+ return "{}";
+ }
+
+ final StringBuilder buffer = new StringBuilder(mSize * 64);
+ buffer.append('{');
+ buffer.append(get(0));
+ for (int i = 1; i < mSize; i++) {
+ buffer.append(", ");
+ buffer.append(get(i));
+ }
+ buffer.append('}');
+ return buffer.toString();
+ }
+}
diff --git a/packages/CrashRecovery/services/module/java/com/android/util/XmlUtils.java b/packages/CrashRecovery/services/module/java/com/android/util/XmlUtils.java
new file mode 100644
index 0000000..488b531
--- /dev/null
+++ b/packages/CrashRecovery/services/module/java/com/android/util/XmlUtils.java
@@ -0,0 +1,66 @@
+/*
+ * 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 android.util;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+
+/**
+ * Bits and pieces copied from hidden API of
+ * frameworks/base/core/java/com/android/internal/util/XmlUtils.java
+ *
+ * @hide
+ */
+public class XmlUtils {
+
+ /** @hide */
+ public static final void beginDocument(XmlPullParser parser, String firstElementName)
+ throws XmlPullParserException, IOException {
+ int type;
+ while ((type = parser.next()) != parser.START_TAG
+ && type != parser.END_DOCUMENT) {
+ // Do nothing
+ }
+
+ if (type != parser.START_TAG) {
+ throw new XmlPullParserException("No start tag found");
+ }
+
+ if (!parser.getName().equals(firstElementName)) {
+ throw new XmlPullParserException("Unexpected start tag: found " + parser.getName()
+ + ", expected " + firstElementName);
+ }
+ }
+
+ /** @hide */
+ public static boolean nextElementWithin(XmlPullParser parser, int outerDepth)
+ throws IOException, XmlPullParserException {
+ for (;;) {
+ int type = parser.next();
+ if (type == XmlPullParser.END_DOCUMENT
+ || (type == XmlPullParser.END_TAG && parser.getDepth() == outerDepth)) {
+ return false;
+ }
+ if (type == XmlPullParser.START_TAG
+ && parser.getDepth() == outerDepth + 1) {
+ return true;
+ }
+ }
+ }
+}
diff --git a/packages/SettingsLib/ActionButtonsPreference/res/color/settingslib_expressive_actionbutton_background.xml b/packages/SettingsLib/ActionButtonsPreference/res/color/settingslib_expressive_actionbutton_background.xml
new file mode 100644
index 0000000..ec9ee22
--- /dev/null
+++ b/packages/SettingsLib/ActionButtonsPreference/res/color/settingslib_expressive_actionbutton_background.xml
@@ -0,0 +1,24 @@
+<?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.
+ -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_enabled="false"
+ android:color="@color/settingslib_materialColorSurface"/>
+ <item android:state_checked="true" android:color="?attr/colorContainerChecked"/>
+ <item android:state_checkable="true" android:color="?attr/colorContainerUnchecked"/>
+ <item android:color="@color/settingslib_materialColorPrimaryContainer" />
+</selector>
\ No newline at end of file
diff --git a/packages/SettingsLib/ActionButtonsPreference/res/color/settingslib_expressive_actionbutton_content_color.xml b/packages/SettingsLib/ActionButtonsPreference/res/color/settingslib_expressive_actionbutton_content_color.xml
new file mode 100644
index 0000000..0488cba
--- /dev/null
+++ b/packages/SettingsLib/ActionButtonsPreference/res/color/settingslib_expressive_actionbutton_content_color.xml
@@ -0,0 +1,25 @@
+<?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.
+ -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_enabled="false"
+ android:alpha="@dimen/material_emphasis_disabled" android:color="?attr/colorOnSurface"/>
+ <item android:state_checkable="true" android:state_checked="true"
+ android:color="?attr/colorOnContainerChecked"/>
+ <item android:state_checkable="true" android:color="?attr/colorOnContainerUnchecked"/>
+ <item android:color="@color/settingslib_materialColorOnPrimaryContainer"/>
+</selector>
\ No newline at end of file
diff --git a/packages/SettingsLib/ActionButtonsPreference/res/values-v35/styles_expressive.xml b/packages/SettingsLib/ActionButtonsPreference/res/values-v35/styles_expressive.xml
index fd8cecb..267c9f6 100644
--- a/packages/SettingsLib/ActionButtonsPreference/res/values-v35/styles_expressive.xml
+++ b/packages/SettingsLib/ActionButtonsPreference/res/values-v35/styles_expressive.xml
@@ -17,9 +17,14 @@
<resources>
<style name="SettingsLibActionButton.Expressive" parent="SettingsLibButtonStyle.Expressive.Tonal">
- <item name="android:backgroundTint">@color/settingslib_materialColorPrimaryContainer</item>
- <item name="iconTint">@color/settingslib_materialColorOnPrimaryContainer</item>
- <item name="iconGravity">textTop</item>
+ <item name="android:backgroundTint">@color/settingslib_expressive_actionbutton_background</item>
+ <item name="android:textColor">@color/settingslib_expressive_actionbutton_content_color</item>
+ <item name="android:insetTop">@dimen/settingslib_expressive_space_none</item>
+ <item name="android:insetBottom">@dimen/settingslib_expressive_space_none</item>
+ <item name="iconTint">@color/settingslib_expressive_actionbutton_content_color</item>
+ <item name="iconSize">@dimen/settingslib_expressive_space_small4</item>
+ <item name="iconPadding">@dimen/settingslib_expressive_space_none</item>"
+ <item name="iconGravity">textStart</item>
</style>
<style name="SettingsLibActionButton.Expressive.Label" parent="">
diff --git a/packages/SettingsLib/ActionButtonsPreference/src/com/android/settingslib/widget/ActionButtonsPreference.java b/packages/SettingsLib/ActionButtonsPreference/src/com/android/settingslib/widget/ActionButtonsPreference.java
index 601e001..0027d63 100644
--- a/packages/SettingsLib/ActionButtonsPreference/src/com/android/settingslib/widget/ActionButtonsPreference.java
+++ b/packages/SettingsLib/ActionButtonsPreference/src/com/android/settingslib/widget/ActionButtonsPreference.java
@@ -549,7 +549,7 @@
((MaterialButton) mButton).setIcon(mIcon);
}
mButton.setEnabled(mIsEnabled);
- mActionLayout.setOnClickListener(mListener);
+ mButton.setOnClickListener(mListener);
mActionLayout.setEnabled(mIsEnabled);
mActionLayout.setContentDescription(mText);
} else {
diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp
index 933c512..e5b5837 100644
--- a/packages/SettingsLib/Android.bp
+++ b/packages/SettingsLib/Android.bp
@@ -46,6 +46,8 @@
"SettingsLibIntroPreference",
"SettingsLibLayoutPreference",
"SettingsLibMainSwitchPreference",
+ "SettingsLibMetadata",
+ "SettingsLibPreference",
"SettingsLibProfileSelector",
"SettingsLibProgressBar",
"SettingsLibRestrictedLockUtils",
@@ -77,6 +79,7 @@
"src/**/*.kt",
"src/**/I*.aidl",
],
+ kotlincflags: ["-Xjvm-default=all"],
}
// defaults for lint option
diff --git a/packages/SettingsLib/BannerMessagePreference/Android.bp b/packages/SettingsLib/BannerMessagePreference/Android.bp
index 3f671b9..77e2cc7 100644
--- a/packages/SettingsLib/BannerMessagePreference/Android.bp
+++ b/packages/SettingsLib/BannerMessagePreference/Android.bp
@@ -14,12 +14,16 @@
"SettingsLintDefaults",
],
- srcs: ["src/**/*.java"],
+ srcs: [
+ "src/**/*.java",
+ "src/**/*.kt",
+ ],
resource_dirs: ["res"],
static_libs: [
- "androidx.preference_preference",
+ "SettingsLibButtonPreference",
"SettingsLibSettingsTheme",
+ "androidx.preference_preference",
],
sdk_version: "system_current",
diff --git a/packages/SettingsLib/BannerMessagePreference/res/color/settingslib_banner_button_background_high.xml b/packages/SettingsLib/BannerMessagePreference/res/color/settingslib_banner_button_background_high.xml
new file mode 100644
index 0000000..d113b547
--- /dev/null
+++ b/packages/SettingsLib/BannerMessagePreference/res/color/settingslib_banner_button_background_high.xml
@@ -0,0 +1,24 @@
+<?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.
+ -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_enabled="false"
+ android:alpha="@dimen/material_emphasis_disabled_background" android:color="@color/settingslib_colorBackgroundLevel_high"/>
+ <item android:state_checked="true" android:color="?attr/colorContainerChecked"/>
+ <item android:state_checkable="true" android:color="?attr/colorContainerUnchecked"/>
+ <item android:color="@color/settingslib_colorBackgroundLevel_high" />
+</selector>
\ No newline at end of file
diff --git a/packages/SettingsLib/BannerMessagePreference/res/color/settingslib_banner_button_background_low.xml b/packages/SettingsLib/BannerMessagePreference/res/color/settingslib_banner_button_background_low.xml
new file mode 100644
index 0000000..cb89d9a
--- /dev/null
+++ b/packages/SettingsLib/BannerMessagePreference/res/color/settingslib_banner_button_background_low.xml
@@ -0,0 +1,24 @@
+<?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.
+ -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_enabled="false"
+ android:alpha="@dimen/material_emphasis_disabled_background" android:color="@color/settingslib_colorBackgroundLevel_low"/>
+ <item android:state_checked="true" android:color="?attr/colorContainerChecked"/>
+ <item android:state_checkable="true" android:color="?attr/colorContainerUnchecked"/>
+ <item android:color="@color/settingslib_colorBackgroundLevel_low" />
+</selector>
\ No newline at end of file
diff --git a/packages/SettingsLib/BannerMessagePreference/res/color/settingslib_banner_button_background_medium.xml b/packages/SettingsLib/BannerMessagePreference/res/color/settingslib_banner_button_background_medium.xml
new file mode 100644
index 0000000..f820c35
--- /dev/null
+++ b/packages/SettingsLib/BannerMessagePreference/res/color/settingslib_banner_button_background_medium.xml
@@ -0,0 +1,24 @@
+<?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.
+ -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_enabled="false"
+ android:alpha="@dimen/material_emphasis_disabled_background" android:color="@color/settingslib_colorBackgroundLevel_medium"/>
+ <item android:state_checked="true" android:color="?attr/colorContainerChecked"/>
+ <item android:state_checkable="true" android:color="?attr/colorContainerUnchecked"/>
+ <item android:color="@color/settingslib_colorBackgroundLevel_medium" />
+</selector>
\ No newline at end of file
diff --git a/packages/SettingsLib/BannerMessagePreference/res/color/settingslib_banner_button_background_normal.xml b/packages/SettingsLib/BannerMessagePreference/res/color/settingslib_banner_button_background_normal.xml
new file mode 100644
index 0000000..8037a8b
--- /dev/null
+++ b/packages/SettingsLib/BannerMessagePreference/res/color/settingslib_banner_button_background_normal.xml
@@ -0,0 +1,24 @@
+<?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.
+ -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_enabled="false"
+ android:alpha="@dimen/material_emphasis_disabled_background" android:color="?attr/colorOnSurface"/>
+ <item android:state_checked="true" android:color="?attr/colorContainerChecked"/>
+ <item android:state_checkable="true" android:color="?attr/colorContainerUnchecked"/>
+ <item android:color="?attr/colorContainer" />
+</selector>
\ No newline at end of file
diff --git a/packages/SettingsLib/BannerMessagePreference/res/drawable-v31/settingslib_card_background.xml b/packages/SettingsLib/BannerMessagePreference/res/drawable-v31/settingslib_card_background.xml
index 072eb58..3f806e1 100644
--- a/packages/SettingsLib/BannerMessagePreference/res/drawable-v31/settingslib_card_background.xml
+++ b/packages/SettingsLib/BannerMessagePreference/res/drawable-v31/settingslib_card_background.xml
@@ -16,6 +16,6 @@
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android">
- <solid android:color="?android:attr/background" />
+ <solid android:color="@android:color/white" />
<corners android:radius="28dp"/>
</shape>
\ No newline at end of file
diff --git a/packages/SettingsLib/BannerMessagePreference/res/drawable-v35/settingslib_expressive_card_background.xml b/packages/SettingsLib/BannerMessagePreference/res/drawable-v35/settingslib_expressive_card_background.xml
new file mode 100644
index 0000000..a677a66
--- /dev/null
+++ b/packages/SettingsLib/BannerMessagePreference/res/drawable-v35/settingslib_expressive_card_background.xml
@@ -0,0 +1,21 @@
+<?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.
+ -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+ <solid android:color="@color/settingslib_materialColorSurfaceBright" />
+ <corners android:radius="28dp"/>
+</shape>
\ No newline at end of file
diff --git a/packages/SettingsLib/BannerMessagePreference/res/layout-v31/settingslib_banner_message.xml b/packages/SettingsLib/BannerMessagePreference/res/layout-v31/settingslib_banner_message.xml
index 9d53e39..ca596d8 100644
--- a/packages/SettingsLib/BannerMessagePreference/res/layout-v31/settingslib_banner_message.xml
+++ b/packages/SettingsLib/BannerMessagePreference/res/layout-v31/settingslib_banner_message.xml
@@ -15,85 +15,92 @@
limitations under the License.
-->
-<com.android.settingslib.widget.BannerMessageView
- xmlns:android="http://schemas.android.com/apk/res/android"
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:orientation="vertical"
- style="@style/Banner.Preference.SettingsLib">
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart">
- <RelativeLayout
- android:id="@+id/top_row"
+ <com.android.settingslib.widget.BannerMessageView
+ android:id="@+id/banner_background"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:paddingBottom="8dp"
- android:orientation="horizontal">
+ android:orientation="vertical"
+ style="@style/Banner.Preference.SettingsLib">
- <ImageView
- android:id="@+id/banner_icon"
- android:layout_width="24dp"
- android:layout_height="24dp"
- android:layout_alignParentStart="true"
- android:importantForAccessibility="no" />
+ <RelativeLayout
+ android:id="@+id/top_row"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingBottom="8dp"
+ android:orientation="horizontal">
- <ImageButton
- android:id="@+id/banner_dismiss_btn"
+ <ImageView
+ android:id="@+id/banner_icon"
+ android:layout_width="24dp"
+ android:layout_height="24dp"
+ android:layout_alignParentStart="true"
+ android:importantForAccessibility="no" />
+
+ <ImageButton
+ android:id="@+id/banner_dismiss_btn"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/settingslib_ic_cross"
+ android:layout_alignParentEnd="true"
+ android:contentDescription="@string/accessibility_banner_message_dismiss"
+ style="@style/Banner.Dismiss.SettingsLib" />
+ </RelativeLayout>
+
+ <TextView
+ android:id="@+id/banner_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:src="@drawable/settingslib_ic_cross"
- android:layout_alignParentEnd="true"
- android:contentDescription="@string/accessibility_banner_message_dismiss"
- style="@style/Banner.Dismiss.SettingsLib" />
- </RelativeLayout>
+ android:layout_gravity="start"
+ android:textAlignment="viewStart"
+ android:paddingTop="0dp"
+ android:paddingBottom="4dp"
+ android:textAppearance="@style/Banner.Title.SettingsLib"/>
- <TextView
- android:id="@+id/banner_title"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="start"
- android:textAlignment="viewStart"
- android:paddingTop="0dp"
- android:paddingBottom="4dp"
- android:textAppearance="@style/Banner.Title.SettingsLib"/>
-
- <TextView
- android:id="@+id/banner_subtitle"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="start"
- android:textAlignment="viewStart"
- android:paddingTop="0dp"
- android:paddingBottom="4dp"
- android:textAppearance="@style/Banner.Subtitle.SettingsLib"
- android:visibility="gone"/>
-
- <TextView
- android:id="@+id/banner_summary"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="start"
- android:textAlignment="viewStart"
- android:paddingTop="4dp"
- android:paddingBottom="8dp"
- android:textAppearance="@style/Banner.Summary.SettingsLib"/>
-
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="horizontal"
- android:minHeight="8dp"
- android:gravity="end">
-
- <Button
- android:id="@+id/banner_negative_btn"
+ <TextView
+ android:id="@+id/banner_subtitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- style="@style/Banner.ButtonText.SettingsLib"/>
+ android:layout_gravity="start"
+ android:textAlignment="viewStart"
+ android:paddingTop="0dp"
+ android:paddingBottom="4dp"
+ android:textAppearance="@style/Banner.Subtitle.SettingsLib"
+ android:visibility="gone"/>
- <Button
- android:id="@+id/banner_positive_btn"
+ <TextView
+ android:id="@+id/banner_summary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- style="@style/Banner.ButtonText.SettingsLib"/>
- </LinearLayout>
-</com.android.settingslib.widget.BannerMessageView>
\ No newline at end of file
+ android:layout_gravity="start"
+ android:textAlignment="viewStart"
+ android:paddingTop="4dp"
+ android:paddingBottom="8dp"
+ android:textAppearance="@style/Banner.Summary.SettingsLib"/>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:minHeight="8dp"
+ android:gravity="end">
+
+ <Button
+ android:id="@+id/banner_negative_btn"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ style="@style/Banner.ButtonText.SettingsLib"/>
+
+ <Button
+ android:id="@+id/banner_positive_btn"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ style="@style/Banner.ButtonText.SettingsLib"/>
+ </LinearLayout>
+ </com.android.settingslib.widget.BannerMessageView>
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/SettingsLib/BannerMessagePreference/res/layout-v35/settingslib_expressive_banner_message.xml b/packages/SettingsLib/BannerMessagePreference/res/layout-v35/settingslib_expressive_banner_message.xml
new file mode 100644
index 0000000..b10ef6e
--- /dev/null
+++ b/packages/SettingsLib/BannerMessagePreference/res/layout-v35/settingslib_expressive_banner_message.xml
@@ -0,0 +1,108 @@
+<?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.
+ -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart">
+
+ <com.android.settingslib.widget.BannerMessageView
+ android:id="@+id/banner_background"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ style="@style/Banner.Preference.SettingsLib.Expressive">
+
+ <RelativeLayout
+ android:id="@+id/top_row"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_alignParentStart="true"
+ android:layout_marginEnd="@dimen/settingslib_expressive_space_medium4"
+ android:orientation="vertical">
+ <TextView
+ android:id="@+id/banner_header"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ style="@style/Banner.Header.SettingsLib.Expressive"/>
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content">
+
+ <ImageView
+ android:id="@+id/banner_icon"
+ android:layout_width="@dimen/settingslib_expressive_space_small3"
+ android:layout_height="@dimen/settingslib_expressive_space_small3"
+ android:layout_gravity="center_vertical"
+ android:importantForAccessibility="no"
+ android:scaleType="fitCenter" />
+
+ <TextView
+ android:id="@+id/banner_title"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ style="@style/Banner.Title.SettingsLib.Expressive" />
+ </LinearLayout>
+
+ <TextView
+ android:id="@+id/banner_subtitle"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ style="@style/Banner.Subtitle.SettingsLib.Expressive"/>
+ </LinearLayout>
+
+ <ImageButton
+ android:id="@+id/banner_dismiss_btn"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ style="@style/Banner.Dismiss.SettingsLib.Expressive" />
+ </RelativeLayout>
+
+ <TextView
+ android:id="@+id/banner_summary"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ style="@style/Banner.Summary.SettingsLib.Expressive"/>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:id="@+id/banner_buttons_frame"
+ android:paddingTop="@dimen/settingslib_expressive_space_extrasmall6"
+ android:orientation="horizontal">
+
+ <com.google.android.material.button.MaterialButton
+ android:id="@+id/banner_negative_btn"
+ android:layout_weight="1"
+ style="@style/Banner.NegativeButton.SettingsLib.Expressive"/>
+ <Space
+ android:layout_width="@dimen/settingslib_expressive_space_extrasmall4"
+ android:layout_height="@dimen/settingslib_expressive_space_small1"/>
+ <com.google.android.material.button.MaterialButton
+ android:id="@+id/banner_positive_btn"
+ android:layout_weight="1"
+ style="@style/Banner.PositiveButton.SettingsLib.Expressive"/>
+ </LinearLayout>
+ </com.android.settingslib.widget.BannerMessageView>
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/SettingsLib/BannerMessagePreference/res/layout/settingslib_banner_message_preference_group.xml b/packages/SettingsLib/BannerMessagePreference/res/layout/settingslib_banner_message_preference_group.xml
new file mode 100644
index 0000000..c74e391
--- /dev/null
+++ b/packages/SettingsLib/BannerMessagePreference/res/layout/settingslib_banner_message_preference_group.xml
@@ -0,0 +1,27 @@
+<?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.
+-->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:baselineAligned="false"
+ android:id="@+id/banner_group_layout"
+ android:importantForAccessibility="no"
+ android:filterTouchesWhenObscured="false"
+ android:orientation="horizontal">
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/SettingsLib/BannerMessagePreference/res/values-v31/styles.xml b/packages/SettingsLib/BannerMessagePreference/res/values-v31/styles.xml
index fede44f..5909f8e 100644
--- a/packages/SettingsLib/BannerMessagePreference/res/values-v31/styles.xml
+++ b/packages/SettingsLib/BannerMessagePreference/res/values-v31/styles.xml
@@ -24,9 +24,7 @@
<item name="android:paddingTop">20dp</item>
<item name="android:paddingBottom">8dp</item>
<item name="android:layout_marginTop">8dp</item>
- <item name="android:layout_marginStart">16dp</item>
<item name="android:layout_marginBottom">8dp</item>
- <item name="android:layout_marginEnd">16dp</item>
<item name="android:background">@drawable/settingslib_card_background</item>
</style>
diff --git a/packages/SettingsLib/BannerMessagePreference/res/values-v35/styles_expressive.xml b/packages/SettingsLib/BannerMessagePreference/res/values-v35/styles_expressive.xml
new file mode 100644
index 0000000..b864311
--- /dev/null
+++ b/packages/SettingsLib/BannerMessagePreference/res/values-v35/styles_expressive.xml
@@ -0,0 +1,76 @@
+<?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.
+ -->
+
+<resources>
+
+ <style name="Banner.Preference.SettingsLib.Expressive">
+ <item name="android:padding">@dimen/settingslib_expressive_space_small1</item>
+ <item name="android:background">@drawable/settingslib_expressive_card_background</item>
+ </style>
+
+ <style name="Banner.Header.SettingsLib.Expressive"
+ parent="">
+ <item name="android:textAlignment">viewStart</item>
+ <item name="android:paddingBottom">@dimen/settingslib_expressive_space_extrasmall4</item>
+ <item name="android:textAppearance">@style/TextAppearance.SettingsLib.LabelMedium</item>
+ <item name="android:textColor">?android:attr/textColorPrimary</item>
+ </style>
+
+ <style name="Banner.Title.SettingsLib.Expressive"
+ parent="">
+ <item name="android:layout_gravity">start</item>
+ <item name="android:layout_marginLeft">@dimen/settingslib_expressive_space_extrasmall4</item>
+ <item name="android:textAlignment">viewStart</item>
+ <item name="android:textAppearance">@style/TextAppearance.SettingsLib.TitleLarge.Emphasized</item>
+ <item name="android:textColor">?android:attr/textColorPrimary</item>
+ </style>
+
+ <style name="Banner.Subtitle.SettingsLib.Expressive"
+ parent="">
+ <item name="android:layout_gravity">start</item>
+ <item name="android:textAlignment">viewStart</item>
+ <item name="android:paddingTop">@dimen/settingslib_expressive_space_extrasmall4</item>
+ <item name="android:textAppearance">@style/TextAppearance.SettingsLib.BodyMedium</item>
+ <item name="android:textColor">?android:attr/textColorPrimary</item>
+ </style>
+
+ <style name="Banner.Summary.SettingsLib.Expressive"
+ parent="">
+ <item name="android:layout_gravity">start</item>
+ <item name="android:textAlignment">viewStart</item>
+ <item name="android:paddingTop">@dimen/settingslib_expressive_space_extrasmall6</item>
+ <item name="android:textAppearance">@style/TextAppearance.SettingsLib.BodyMedium</item>
+ <item name="android:textColor">?android:attr/textColorPrimary</item>
+ </style>
+
+ <style name="Banner.Dismiss.SettingsLib.Expressive">
+ <item name="android:src">@drawable/settingslib_expressive_icon_cross</item>
+ <item name="android:layout_alignParentEnd">true</item>
+ <item name="android:contentDescription">@string/accessibility_banner_message_dismiss</item>
+ </style>
+
+ <style name="Banner.PositiveButton.SettingsLib.Expressive"
+ parent="@style/SettingsLibButtonStyle.Expressive.Filled.Extra">
+ <item name="android:textColor">?android:attr/textColorPrimaryInverse</item>
+ <item name="materialSizeOverlay">@style/SizeOverlay.Material3Expressive.Button.Small</item>
+ </style>
+
+ <style name="Banner.NegativeButton.SettingsLib.Expressive"
+ parent="@style/SettingsLibButtonStyle.Expressive.Outline.Extra">
+ <item name="materialSizeOverlay">@style/SizeOverlay.Material3Expressive.Button.Small</item>
+ </style>
+</resources>
diff --git a/packages/SettingsLib/BannerMessagePreference/res/values/attrs.xml b/packages/SettingsLib/BannerMessagePreference/res/values/attrs.xml
index 96634a5..86d5f47 100644
--- a/packages/SettingsLib/BannerMessagePreference/res/values/attrs.xml
+++ b/packages/SettingsLib/BannerMessagePreference/res/values/attrs.xml
@@ -21,7 +21,18 @@
<enum name="high" value="0"/>
<enum name="medium" value="1"/>
<enum name="low" value="2"/>
+ <enum name="normal" value="3"/>
</attr>
<attr format="string" name="subtitle" />
+ <attr format="string" name="bannerHeader" />
+ <attr format="integer" name="buttonOrientation" />
+ </declare-styleable>
+
+ <declare-styleable name="BannerMessagePreferenceGroup">
+ <attr format="string" name="expandKey" />
+ <attr format="string" name="expandTitle" />
+ <attr format="string" name="collapseKey" />
+ <attr format="string" name="collapseTitle" />
+ <attr format="reference" name="collapseIcon" />
</declare-styleable>
</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/BannerMessagePreference/res/values/colors.xml b/packages/SettingsLib/BannerMessagePreference/res/values/colors.xml
index 53d72d1..891def1 100644
--- a/packages/SettingsLib/BannerMessagePreference/res/values/colors.xml
+++ b/packages/SettingsLib/BannerMessagePreference/res/values/colors.xml
@@ -19,7 +19,9 @@
<color name="banner_background_attention_high">#FFDAD5</color> <!-- red card background -->
<color name="banner_background_attention_medium">#F0E3A8</color> <!-- yellow card background -->
<color name="banner_background_attention_low">#CFEBC0</color> <!-- green card background -->
+ <color name="banner_background_attention_normal">@color/settingslib_materialColorSurfaceBright</color> <!-- normal card background -->
<color name="banner_accent_attention_high">#BB3322</color> <!-- red accent color -->
<color name="banner_accent_attention_medium">#895900</color> <!-- yellow accent color -->
<color name="banner_accent_attention_low">#1D7233</color> <!-- green accent color -->
+ <color name="banner_accent_attention_normal">@color/settingslib_materialColorPrimary</color> <!-- normal accent color -->
</resources>
diff --git a/packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/BannerMessagePreference.java b/packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/BannerMessagePreference.java
index 10769ec..60a9ebd 100644
--- a/packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/BannerMessagePreference.java
+++ b/packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/BannerMessagePreference.java
@@ -17,6 +17,7 @@
package com.android.settingslib.widget;
import android.content.Context;
+import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.PorterDuff;
@@ -29,6 +30,7 @@
import android.widget.Button;
import android.widget.ImageButton;
import android.widget.ImageView;
+import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.ColorInt;
@@ -39,6 +41,8 @@
import androidx.preference.PreferenceViewHolder;
import com.android.settingslib.widget.preference.banner.R;
+
+import com.google.android.material.button.MaterialButton;
/**
* Banner message is a banner displaying important information (permission request, page error etc),
* and provide actions for user to address. It requires a user action to be dismissed.
@@ -46,22 +50,36 @@
public class BannerMessagePreference extends Preference implements GroupSectionDividerMixin {
public enum AttentionLevel {
- HIGH(0, R.color.banner_background_attention_high, R.color.banner_accent_attention_high),
+ HIGH(0,
+ R.color.banner_background_attention_high,
+ R.color.banner_accent_attention_high,
+ R.color.settingslib_banner_button_background_high),
MEDIUM(1,
- R.color.banner_background_attention_medium,
- R.color.banner_accent_attention_medium),
- LOW(2, R.color.banner_background_attention_low, R.color.banner_accent_attention_low);
+ R.color.banner_background_attention_medium,
+ R.color.banner_accent_attention_medium,
+ R.color.settingslib_banner_button_background_medium),
+ LOW(2,
+ R.color.banner_background_attention_low,
+ R.color.banner_accent_attention_low,
+ R.color.settingslib_banner_button_background_low),
+ NORMAL(3,
+ R.color.banner_background_attention_normal,
+ R.color.banner_accent_attention_normal,
+ R.color.settingslib_banner_button_background_normal);
// Corresponds to the enum valye of R.attr.attentionLevel
private final int mAttrValue;
@ColorRes private final int mBackgroundColorResId;
@ColorRes private final int mAccentColorResId;
+ @ColorRes private final int mButtonBackgroundColorResId;
AttentionLevel(int attrValue, @ColorRes int backgroundColorResId,
- @ColorRes int accentColorResId) {
+ @ColorRes int accentColorResId,
+ @ColorRes int buttonBackgroundColorResId) {
mAttrValue = attrValue;
mBackgroundColorResId = backgroundColorResId;
mAccentColorResId = accentColorResId;
+ mButtonBackgroundColorResId = buttonBackgroundColorResId;
}
static AttentionLevel fromAttr(int attrValue) {
@@ -80,6 +98,10 @@
public @ColorRes int getBackgroundColorResId() {
return mBackgroundColorResId;
}
+
+ public @ColorRes int getButtonBackgroundColorResId() {
+ return mButtonBackgroundColorResId;
+ }
}
private static final String TAG = "BannerPreference";
@@ -95,6 +117,8 @@
// Default attention level is High.
private AttentionLevel mAttentionLevel = AttentionLevel.HIGH;
private String mSubtitle;
+ private String mHeader;
+ private int mButtonOrientation;
public BannerMessagePreference(Context context) {
super(context);
@@ -119,7 +143,10 @@
private void init(Context context, AttributeSet attrs) {
setSelectable(false);
- setLayoutResource(R.layout.settingslib_banner_message);
+ int resId = SettingsThemeHelper.isExpressiveTheme(context)
+ ? R.layout.settingslib_expressive_banner_message
+ : R.layout.settingslib_banner_message;
+ setLayoutResource(resId);
if (IS_AT_LEAST_S) {
if (attrs != null) {
@@ -130,6 +157,9 @@
a.getInt(R.styleable.BannerMessagePreference_attentionLevel, 0);
mAttentionLevel = AttentionLevel.fromAttr(mAttentionLevelValue);
mSubtitle = a.getString(R.styleable.BannerMessagePreference_subtitle);
+ mHeader = a.getString(R.styleable.BannerMessagePreference_bannerHeader);
+ mButtonOrientation = a.getInt(R.styleable.BannerMessagePreference_buttonOrientation,
+ LinearLayout.HORIZONTAL);
a.recycle();
}
}
@@ -142,11 +172,16 @@
final TextView titleView = (TextView) holder.findViewById(R.id.banner_title);
CharSequence title = getTitle();
- titleView.setText(title);
- titleView.setVisibility(title == null ? View.GONE : View.VISIBLE);
+ if (titleView != null) {
+ titleView.setText(title);
+ titleView.setVisibility(title == null ? View.GONE : View.VISIBLE);
+ }
final TextView summaryView = (TextView) holder.findViewById(R.id.banner_summary);
- summaryView.setText(getSummary());
+ if (summaryView != null) {
+ summaryView.setText(getSummary());
+ summaryView.setVisibility(TextUtils.isEmpty(getSummary()) ? View.GONE : View.VISIBLE);
+ }
mPositiveButtonInfo.mButton = (Button) holder.findViewById(R.id.banner_positive_btn);
mNegativeButtonInfo.mButton = (Button) holder.findViewById(R.id.banner_negative_btn);
@@ -162,8 +197,11 @@
icon == null
? getContext().getDrawable(R.drawable.ic_warning)
: icon);
- iconView.setColorFilter(
- new PorterDuffColorFilter(accentColor, PorterDuff.Mode.SRC_IN));
+ if (mAttentionLevel != AttentionLevel.NORMAL
+ && !SettingsThemeHelper.isExpressiveTheme(context)) {
+ iconView.setColorFilter(
+ new PorterDuffColorFilter(accentColor, PorterDuff.Mode.SRC_IN));
+ }
}
if (IS_AT_LEAST_S) {
@@ -171,12 +209,25 @@
context.getResources().getColor(
mAttentionLevel.getBackgroundColorResId(), theme);
+ @ColorInt final int btnBackgroundColor =
+ context.getResources().getColor(mAttentionLevel.getButtonBackgroundColorResId(),
+ theme);
+ ColorStateList strokeColor = context.getResources().getColorStateList(
+ mAttentionLevel.getButtonBackgroundColorResId(), theme);
+
holder.setDividerAllowedAbove(false);
holder.setDividerAllowedBelow(false);
- holder.itemView.getBackground().setTint(backgroundColor);
+ View backgroundView = holder.findViewById(R.id.banner_background);
+ if (backgroundView != null && !SettingsThemeHelper.isExpressiveTheme(context)) {
+ backgroundView.getBackground().setTint(backgroundColor);
+ }
mPositiveButtonInfo.mColor = accentColor;
mNegativeButtonInfo.mColor = accentColor;
+ if (mAttentionLevel != AttentionLevel.NORMAL) {
+ mPositiveButtonInfo.mBackgroundColor = btnBackgroundColor;
+ mNegativeButtonInfo.mStrokeColor = strokeColor;
+ }
mDismissButtonInfo.mButton = (ImageButton) holder.findViewById(R.id.banner_dismiss_btn);
mDismissButtonInfo.setUpButton();
@@ -185,6 +236,13 @@
subtitleView.setText(mSubtitle);
subtitleView.setVisibility(mSubtitle == null ? View.GONE : View.VISIBLE);
+ TextView headerView = (TextView) holder.findViewById(R.id.banner_header);
+ if (headerView != null) {
+ headerView.setText(mHeader);
+ headerView.setVisibility(TextUtils.isEmpty(mHeader) ? View.GONE : View.VISIBLE);
+ }
+
+
} else {
holder.setDividerAllowedAbove(true);
holder.setDividerAllowedBelow(true);
@@ -192,6 +250,24 @@
mPositiveButtonInfo.setUpButton();
mNegativeButtonInfo.setUpButton();
+ View buttonFrame = holder.findViewById(R.id.banner_buttons_frame);
+ if (buttonFrame != null) {
+ buttonFrame.setVisibility(
+ mPositiveButtonInfo.shouldBeVisible() || mNegativeButtonInfo.shouldBeVisible()
+ ? View.VISIBLE : View.GONE);
+
+ LinearLayout linearLayout = (LinearLayout) buttonFrame;
+ if (mButtonOrientation != linearLayout.getOrientation()) {
+ int childCount = linearLayout.getChildCount();
+ //reverse the order of the buttons
+ for (int i = childCount - 1; i >= 0; i--) {
+ View child = linearLayout.getChildAt(i);
+ linearLayout.removeViewAt(i);
+ linearLayout.addView(child);
+ }
+ linearLayout.setOrientation(mButtonOrientation);
+ }
+ }
}
/**
@@ -302,6 +378,18 @@
}
/**
+ * Sets button orientation.
+ */
+ @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ public BannerMessagePreference setButtonOrientation(int orientation) {
+ if (mButtonOrientation != orientation) {
+ mButtonOrientation = orientation;
+ notifyChanged();
+ }
+ return this;
+ }
+
+ /**
* Sets the subtitle.
*/
@RequiresApi(Build.VERSION_CODES.S)
@@ -322,6 +410,26 @@
}
/**
+ * Sets the header.
+ */
+ @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ public BannerMessagePreference setHeader(@StringRes int textResId) {
+ return setHeader(getContext().getString(textResId));
+ }
+
+ /**
+ * Sets the header.
+ */
+ @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ public BannerMessagePreference setHeader(String header) {
+ if (!TextUtils.equals(header, mSubtitle)) {
+ mHeader = header;
+ notifyChanged();
+ }
+ return this;
+ }
+
+ /**
* Sets the attention level. This will update the color theme of the preference.
*/
public BannerMessagePreference setAttentionLevel(AttentionLevel attentionLevel) {
@@ -342,13 +450,29 @@
private View.OnClickListener mListener;
private boolean mIsVisible = true;
@ColorInt private int mColor;
+ @ColorInt private int mBackgroundColor;
+ private ColorStateList mStrokeColor;
void setUpButton() {
mButton.setText(mText);
mButton.setOnClickListener(mListener);
+ MaterialButton btn = null;
+ if (mButton instanceof MaterialButton) {
+ btn = (MaterialButton) mButton;
+ }
+
if (IS_AT_LEAST_S) {
- mButton.setTextColor(mColor);
+ if (btn != null && SettingsThemeHelper.isExpressiveTheme(btn.getContext())) {
+ if (mBackgroundColor != 0) {
+ btn.setBackgroundColor(mBackgroundColor);
+ }
+ if (mStrokeColor != null) {
+ btn.setStrokeColor(mStrokeColor);
+ }
+ } else {
+ mButton.setTextColor(mColor);
+ }
}
if (shouldBeVisible()) {
diff --git a/packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/BannerMessagePreferenceGroup.kt b/packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/BannerMessagePreferenceGroup.kt
new file mode 100644
index 0000000..7545563
--- /dev/null
+++ b/packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/BannerMessagePreferenceGroup.kt
@@ -0,0 +1,149 @@
+/*
+ * 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.widget
+
+import android.content.Context
+import android.graphics.drawable.Drawable
+import android.util.AttributeSet
+import android.view.View
+import androidx.preference.Preference
+import androidx.preference.PreferenceGroup
+import androidx.preference.PreferenceViewHolder
+
+import com.android.settingslib.widget.preference.banner.R
+
+/**
+ * Custom PreferenceGroup that allows expanding and collapsing child preferences.
+ */
+class BannerMessagePreferenceGroup @JvmOverloads constructor(
+ context: Context,
+ attrs: AttributeSet? = null,
+ defStyleAttr: Int = 0,
+ defStyleRes: Int = 0
+) : PreferenceGroup(context, attrs, defStyleAttr, defStyleRes), GroupSectionDividerMixin {
+
+ private var isExpanded = false
+ private var expandPreference: NumberButtonPreference? = null
+ private var collapsePreference: SectionButtonPreference? = null
+ private val childPreferences = mutableListOf<BannerMessagePreference>()
+ private var expandKey: String? = null
+ private var expandTitle: String? = null
+ private var collapseKey: String? = null
+ private var collapseTitle: String? = null
+ private var collapseIcon: Drawable? = null
+ var expandContentDescription: Int = 0
+ set(value) {
+ field = value
+ expandPreference?.btnContentDescription = expandContentDescription
+ }
+
+ init {
+ isPersistent = false // This group doesn't store data
+ layoutResource = R.layout.settingslib_banner_message_preference_group
+
+ initAttributes(context, attrs, defStyleAttr)
+ }
+
+ override fun addPreference(preference: Preference): Boolean {
+ if (preference !is BannerMessagePreference) {
+ return false
+ }
+
+ if (childPreferences.size >= MAX_CHILDREN) {
+ return false
+ }
+
+ childPreferences.add(preference)
+ return super.addPreference(preference)
+ }
+
+ override fun removePreference(preference: Preference): Boolean {
+ if (preference !is BannerMessagePreference) {
+ return false
+ }
+ childPreferences.remove(preference)
+ return super.removePreference(preference)
+ }
+
+ override fun onBindViewHolder(holder: PreferenceViewHolder) {
+ super.onBindViewHolder(holder)
+ if (childPreferences.size >= MAX_CHILDREN - 1) {
+ if (expandPreference == null) {
+ expandPreference = NumberButtonPreference(context).apply {
+ key = expandKey
+ title = expandTitle
+ count = childPreferences.size - 1
+ btnContentDescription = expandContentDescription
+ clickListener = View.OnClickListener {
+ toggleExpansion()
+ }
+ }
+ super.addPreference(expandPreference!!)
+ }
+ if (collapsePreference == null) {
+ collapsePreference = SectionButtonPreference(context)
+ .apply {
+ key = collapseKey
+ title = collapseTitle
+ icon = collapseIcon
+ setOnClickListener {
+ toggleExpansion()
+ }
+ }
+ super.addPreference(collapsePreference!!)
+ }
+ }
+ updateExpandCollapsePreference()
+ updateChildrenVisibility()
+ }
+
+ private fun updateExpandCollapsePreference() {
+ expandPreference?.isVisible = !isExpanded
+ collapsePreference?.isVisible = isExpanded
+ }
+
+ private fun updateChildrenVisibility() {
+ for (i in 1 until childPreferences.size) {
+ val child = childPreferences[i]
+ child.isVisible = isExpanded
+ }
+ }
+
+ private fun toggleExpansion() {
+ isExpanded = !isExpanded
+ updateExpandCollapsePreference()
+ updateChildrenVisibility()
+ }
+
+ private fun initAttributes(context: Context, attrs: AttributeSet?, defStyleAttr: Int) {
+ context.obtainStyledAttributes(
+ attrs,
+ R.styleable.BannerMessagePreferenceGroup, defStyleAttr, 0
+ ).apply {
+ expandKey = getString(R.styleable.BannerMessagePreferenceGroup_expandKey)
+ expandTitle = getString(R.styleable.BannerMessagePreferenceGroup_expandTitle)
+ collapseKey = getString(R.styleable.BannerMessagePreferenceGroup_collapseKey)
+ collapseTitle = getString(R.styleable.BannerMessagePreferenceGroup_collapseTitle)
+ collapseIcon = getDrawable(R.styleable.BannerMessagePreferenceGroup_collapseIcon)
+ recycle()
+ }
+ }
+
+ companion object {
+ private const val MAX_CHILDREN = 3
+ }
+}
\ No newline at end of file
diff --git a/packages/SettingsLib/ButtonPreference/Android.bp b/packages/SettingsLib/ButtonPreference/Android.bp
index 08dd27f..a377f31 100644
--- a/packages/SettingsLib/ButtonPreference/Android.bp
+++ b/packages/SettingsLib/ButtonPreference/Android.bp
@@ -14,12 +14,15 @@
"SettingsLintDefaults",
],
- srcs: ["src/**/*.java"],
+ srcs: [
+ "src/**/*.java",
+ "src/**/*.kt",
+ ],
resource_dirs: ["res"],
static_libs: [
- "androidx.preference_preference",
"SettingsLibSettingsTheme",
+ "androidx.preference_preference",
],
sdk_version: "system_current",
diff --git a/packages/SettingsLib/ButtonPreference/res/color/settingslib_section_button_background.xml b/packages/SettingsLib/ButtonPreference/res/color/settingslib_section_button_background.xml
new file mode 100644
index 0000000..0972b62
--- /dev/null
+++ b/packages/SettingsLib/ButtonPreference/res/color/settingslib_section_button_background.xml
@@ -0,0 +1,22 @@
+<?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.
+ -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_enabled="false"
+ android:alpha="@dimen/material_emphasis_disabled_background" android:color="@color/settingslib_materialColorSurfaceBright"/>
+ <item android:color="@color/settingslib_materialColorSurfaceBright" />
+</selector>
\ No newline at end of file
diff --git a/packages/SettingsLib/ButtonPreference/res/drawable/settingslib_number_button_background.xml b/packages/SettingsLib/ButtonPreference/res/drawable/settingslib_number_button_background.xml
new file mode 100644
index 0000000..9bf5c43
--- /dev/null
+++ b/packages/SettingsLib/ButtonPreference/res/drawable/settingslib_number_button_background.xml
@@ -0,0 +1,21 @@
+<?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.
+ -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+ <solid android:color="@color/settingslib_materialColorSurfaceBright" />
+ <corners android:radius="@dimen/settingslib_expressive_radius_extralarge2"/>
+</shape>
\ No newline at end of file
diff --git a/packages/SettingsLib/ButtonPreference/res/drawable/settingslib_number_count_background.xml b/packages/SettingsLib/ButtonPreference/res/drawable/settingslib_number_count_background.xml
new file mode 100644
index 0000000..b993811
--- /dev/null
+++ b/packages/SettingsLib/ButtonPreference/res/drawable/settingslib_number_count_background.xml
@@ -0,0 +1,21 @@
+<?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.
+ -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+ <solid android:color="@color/settingslib_materialColorSecondaryContainer" />
+ <corners android:radius="100dp"/>
+</shape>
\ No newline at end of file
diff --git a/packages/SettingsLib/ButtonPreference/res/layout/settingslib_number_button.xml b/packages/SettingsLib/ButtonPreference/res/layout/settingslib_number_button.xml
new file mode 100644
index 0000000..fa13b41
--- /dev/null
+++ b/packages/SettingsLib/ButtonPreference/res/layout/settingslib_number_button.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.
+ -->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+ android:paddingVertical="@dimen/settingslib_expressive_space_extrasmall4">
+
+ <LinearLayout
+ android:id="@+id/settingslib_number_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingVertical="@dimen/settingslib_expressive_space_small1"
+ android:paddingHorizontal="@dimen/settingslib_expressive_space_small4"
+ android:background="@drawable/settingslib_number_button_background">
+ <TextView
+ android:id="@+id/settingslib_number_count"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ style="@style/SettingsLibNumberButtonStyle.Number"/>
+ <TextView
+ android:id="@+id/settingslib_number_title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ style="@style/SettingsLibNumberButtonStyle.Title"/>
+ </LinearLayout>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/SettingsLib/ButtonPreference/res/layout/settingslib_section_button.xml b/packages/SettingsLib/ButtonPreference/res/layout/settingslib_section_button.xml
new file mode 100644
index 0000000..e7fb572
--- /dev/null
+++ b/packages/SettingsLib/ButtonPreference/res/layout/settingslib_section_button.xml
@@ -0,0 +1,30 @@
+<?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.
+-->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+ android:paddingVertical="@dimen/settingslib_expressive_space_extrasmall4">
+
+ <com.google.android.material.button.MaterialButton
+ android:id="@+id/settingslib_section_button"
+ style="@style/SettingsLibSectionButtonStyle.Expressive" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/SettingsLib/ButtonPreference/res/values/styles.xml b/packages/SettingsLib/ButtonPreference/res/values/styles.xml
index 3963732..5208e20 100644
--- a/packages/SettingsLib/ButtonPreference/res/values/styles.xml
+++ b/packages/SettingsLib/ButtonPreference/res/values/styles.xml
@@ -30,4 +30,33 @@
<item name="android:textColor">@color/settingslib_btn_colored_text_material</item>
<item name="android:background">@drawable/settingslib_btn_colored_material</item>
</style>
+
+ <style name="SettingsLibSectionButtonStyle.Expressive"
+ parent="@style/SettingsLibButtonStyle.Expressive.Filled.Large">
+ <item name="android:textColor">?android:attr/textColorPrimary</item>
+ <item name="backgroundTint">@color/settingslib_section_button_background</item>
+ <item name="iconTint">?android:attr/textColorPrimary</item>
+ </style>
+
+ <style name="SettingsLibNumberButtonStyle.Number"
+ parent="">
+ <item name="android:minWidth">@dimen/settingslib_expressive_space_small4</item>
+ <item name="android:minHeight">@dimen/settingslib_expressive_space_small4</item>
+ <item name="android:gravity">center</item>
+ <item name="android:background">@drawable/settingslib_number_count_background</item>
+ <item name="android:paddingStart">@dimen/settingslib_expressive_radius_extrasmall2</item>
+ <item name="android:paddingEnd">@dimen/settingslib_expressive_radius_extrasmall2</item>
+ <item name="android:layout_marginEnd">@dimen/settingslib_expressive_space_extrasmall4</item>
+ <item name="android:textAppearance">@style/TextAppearance.SettingsLib.TitleMedium</item>
+ <item name="android:textColor">@color/settingslib_materialColorOnSecondaryContainer</item>
+ <item name="android:importantForAccessibility">no</item>
+ </style>
+
+ <style name="SettingsLibNumberButtonStyle.Title"
+ parent="">
+ <item name="android:gravity">center</item>
+ <item name="android:textAppearance">@style/TextAppearance.SettingsLib.LabelLarge</item>
+ <item name="android:textColor">@color/settingslib_materialColorOnSurface</item>
+ <item name="android:importantForAccessibility">no</item>
+ </style>
</resources>
diff --git a/packages/SettingsLib/ButtonPreference/src/com/android/settingslib/widget/NumberButtonPreference.kt b/packages/SettingsLib/ButtonPreference/src/com/android/settingslib/widget/NumberButtonPreference.kt
new file mode 100644
index 0000000..a1772d5
--- /dev/null
+++ b/packages/SettingsLib/ButtonPreference/src/com/android/settingslib/widget/NumberButtonPreference.kt
@@ -0,0 +1,70 @@
+/*
+ * 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.widget
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.View
+import android.widget.TextView
+import androidx.preference.Preference
+import androidx.preference.PreferenceViewHolder
+
+import com.android.settingslib.widget.preference.button.R
+
+class NumberButtonPreference @JvmOverloads constructor(
+ context: Context,
+ attrs: AttributeSet? = null,
+ defStyleAttr: Int = 0,
+ defStyleRes: Int = 0
+) : Preference(context, attrs, defStyleAttr, defStyleRes), GroupSectionDividerMixin {
+
+ var clickListener: View.OnClickListener? = null
+
+ var count: Int = 0
+ set(value) {
+ field = value
+ notifyChanged()
+ }
+
+ var btnContentDescription: Int = 0
+ set(value) {
+ field = value
+ notifyChanged()
+ }
+
+ init {
+ isPersistent = false // This preference doesn't store data
+ order = Int.MAX_VALUE
+ layoutResource = R.layout.settingslib_number_button
+ }
+
+ override fun onBindViewHolder(holder: PreferenceViewHolder) {
+ super.onBindViewHolder(holder)
+ holder.isDividerAllowedAbove = false
+ holder.isDividerAllowedBelow = false
+
+ holder.findViewById(R.id.settingslib_number_button)?.apply {
+ setOnClickListener(clickListener)
+ if (btnContentDescription != 0) {
+ setContentDescription(context.getString(btnContentDescription, count))
+ }
+ }
+ (holder.findViewById(R.id.settingslib_number_title) as? TextView)?.text = title
+
+ (holder.findViewById(R.id.settingslib_number_count) as? TextView)?.text = "$count"
+ }
+}
\ No newline at end of file
diff --git a/packages/SettingsLib/ButtonPreference/src/com/android/settingslib/widget/SectionButtonPreference.kt b/packages/SettingsLib/ButtonPreference/src/com/android/settingslib/widget/SectionButtonPreference.kt
new file mode 100644
index 0000000..b374dea
--- /dev/null
+++ b/packages/SettingsLib/ButtonPreference/src/com/android/settingslib/widget/SectionButtonPreference.kt
@@ -0,0 +1,71 @@
+/*
+ * 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.widget
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.View
+import androidx.preference.Preference
+import androidx.preference.PreferenceViewHolder
+import com.android.settingslib.widget.preference.button.R
+import com.google.android.material.button.MaterialButton
+
+/**
+ * A Preference that displays a button with an optional icon.
+ */
+class SectionButtonPreference @JvmOverloads constructor(
+ context: Context,
+ attrs: AttributeSet? = null,
+ defStyleAttr: Int = 0,
+ defStyleRes: Int = 0
+) : Preference(context, attrs, defStyleAttr, defStyleRes), GroupSectionDividerMixin {
+
+ private var clickListener: ((View) -> Unit)? = null
+ set(value) {
+ field = value
+ notifyChanged()
+ }
+ private var button: MaterialButton? = null
+ init {
+ isPersistent = false // This preference doesn't store data
+ order = Int.MAX_VALUE
+ layoutResource = R.layout.settingslib_section_button
+ }
+
+ override fun onBindViewHolder(holder: PreferenceViewHolder) {
+ super.onBindViewHolder(holder)
+ holder.isDividerAllowedAbove = false
+ holder.isDividerAllowedBelow = false
+
+ button = holder.findViewById(R.id.settingslib_section_button) as? MaterialButton
+ button?.apply{
+ text = title
+ isFocusable = isSelectable
+ isClickable = isSelectable
+ setOnClickListener { view -> clickListener?.let { it(view) } }
+ }
+ button?.isEnabled = isEnabled
+ button?.icon = icon
+ }
+
+ /**
+ * Set a listener for button click
+ */
+ fun setOnClickListener(listener: (View) -> Unit) {
+ clickListener = listener
+ }
+}
\ No newline at end of file
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 f001fad..ce66a36 100644
--- a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt
+++ b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt
@@ -40,8 +40,6 @@
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.BooleanValue
-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,26 +408,33 @@
val storage = metadata.storage(context)
value = preferenceValueProto {
when (metadata) {
- is BooleanValue -> storage.getBoolean(metadata.key)?.let { booleanValue = it }
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 }
+ }
}
}
if (flags.includeValueDescriptor()) {
valueDescriptor = preferenceValueDescriptorProto {
when (metadata) {
- is BooleanValue -> booleanType = true
is RangeValue -> rangeValue = rangeValueProto {
min = metadata.getMinValue(context)
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/Graph/src/com/android/settingslib/graph/PreferenceSetterApi.kt b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceSetterApi.kt
index ea79554..4719064 100644
--- a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceSetterApi.kt
+++ b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceSetterApi.kt
@@ -26,7 +26,6 @@
import com.android.settingslib.ipc.ApiPermissionChecker
import com.android.settingslib.ipc.IntMessageCodec
import com.android.settingslib.ipc.MessageCodec
-import com.android.settingslib.metadata.BooleanValue
import com.android.settingslib.metadata.PersistentPreference
import com.android.settingslib.metadata.PreferenceAvailabilityProvider
import com.android.settingslib.metadata.PreferenceMetadata
@@ -146,7 +145,9 @@
val value = request.value
try {
if (value.hasBooleanValue()) {
- if (metadata !is BooleanValue) return PreferenceSetterResult.INVALID_REQUEST
+ if (metadata.valueType != Boolean::class.javaObjectType) {
+ return PreferenceSetterResult.INVALID_REQUEST
+ }
val booleanValue = value.booleanValue
val resultCode = metadata.checkWritePermit(booleanValue)
if (resultCode != PreferenceSetterResult.OK) return resultCode
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 0000000..7323488
--- /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 83725aa..be705b5 100644
--- a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PersistentPreference.kt
+++ b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PersistentPreference.kt
@@ -76,7 +76,15 @@
}
/** Preference interface that has a value persisted in datastore. */
-interface PersistentPreference<T> {
+interface PersistentPreference<T> : PreferenceMetadata {
+
+ /**
+ * The value type the preference is associated with.
+ *
+ * TODO(b/388167302): Remove the default implementation once all subclasses are migrated.
+ */
+ val valueType: Class<T>?
+ get() = null
/**
* Returns the key-value storage of the preference.
@@ -85,7 +93,7 @@
* [PreferenceScreenRegistry.getKeyValueStore].
*/
fun storage(context: Context): KeyValueStore =
- PreferenceScreenRegistry.getKeyValueStore(context, this as PreferenceMetadata)!!
+ PreferenceScreenRegistry.getKeyValueStore(context, this)!!
/** Returns the required permissions to read preference value. */
fun getReadPermissions(context: Context): Permissions? = null
@@ -103,7 +111,7 @@
context,
callingPid,
callingUid,
- this as PreferenceMetadata,
+ this,
)
/** Returns the required permissions to write preference value. */
@@ -128,7 +136,7 @@
value,
callingPid,
callingUid,
- this as PreferenceMetadata,
+ this,
)
/** The sensitivity level of the preference. */
@@ -143,15 +151,6 @@
fun isValidValue(context: Context, index: Int): Boolean
}
-/**
- * A boolean type value.
- *
- * A zero value means `False`, otherwise it is `True`.
- */
-interface BooleanValue : ValueDescriptor {
- override fun isValidValue(context: Context, index: Int) = true
-}
-
/** Value falls into a given array. */
interface DiscreteValue<T> : ValueDescriptor {
@get:ArrayRes val values: Int
@@ -220,6 +219,3 @@
override fun isValidValue(context: Context, index: Int) =
index in getMinValue(context)..getMaxValue(context)
}
-
-/** 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 9fc2134..c74b315 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 @@
*/
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 87bd261..fecf3e5 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,17 @@
import androidx.annotation.StringRes
-/** Common base class for preferences that have two selectable states and save a boolean value. */
-interface TwoStatePreference : PreferenceMetadata, PersistentPreference<Boolean>, BooleanValue
+/** 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
+}
/** A preference that provides a two-state toggleable option. */
open class SwitchPreference
@@ -28,9 +37,10 @@
override val key: String,
@StringRes override val title: Int = 0,
@StringRes override val summary: Int = 0,
-) : TwoStatePreference
+) : BooleanValuePreference
/** A preference that provides a two-state toggleable option that can be used as a main switch. */
open class MainSwitchPreference
@JvmOverloads
-constructor(override val key: String, @StringRes override val title: Int = 0) : TwoStatePreference
+constructor(override val key: String, @StringRes override val title: Int = 0) :
+ BooleanValuePreference
diff --git a/packages/SettingsLib/OWNERS b/packages/SettingsLib/OWNERS
index e4bc7b4..04df308 100644
--- a/packages/SettingsLib/OWNERS
+++ b/packages/SettingsLib/OWNERS
@@ -3,6 +3,7 @@
chiujason@google.com
cipson@google.com
dsandler@android.com
+dswliu@google.com
edgarwang@google.com
evanlaird@google.com
jiannan@google.com
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 6b7be91..c61c6a5 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 @@
* @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 bd5d17c..65fbe2b 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 @@
context.getString(screenTitle)
} else {
screenMetadata.getScreenTitle(context)
- ?: (this as? PreferenceTitleProvider)?.getTitle(context)
+ ?: (screenMetadata as? PreferenceTitleProvider)?.getTitle(context)
}
}
}
@@ -66,7 +66,7 @@
}
/** A boolean value type preference associated with the abstract [TwoStatePreference]. */
-interface TwoStatePreferenceBinding : PreferenceBinding {
+interface BooleanValuePreferenceBinding : PreferenceBinding {
override fun bind(preference: Preference, metadata: PreferenceMetadata) {
super.bind(preference, metadata)
@@ -78,7 +78,7 @@
}
/** A boolean value type preference associated with [SwitchPreferenceCompat]. */
-interface SwitchPreferenceBinding : TwoStatePreferenceBinding {
+interface SwitchPreferenceBinding : BooleanValuePreferenceBinding {
override fun createWidget(context: Context): Preference = SwitchPreferenceCompat(context)
@@ -88,7 +88,7 @@
}
/** A boolean value type preference associated with [MainSwitchPreference]. */
-interface MainSwitchPreferenceBinding : TwoStatePreferenceBinding {
+interface MainSwitchPreferenceBinding : BooleanValuePreferenceBinding {
override fun createWidget(context: Context): Preference = MainSwitchPreference(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 0000000..482eaf9
--- /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 e237a6a..ffe181d 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.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 @@
preferenceScreen = createPreferenceScreen()
}
+ override fun setPreferenceScreen(preferenceScreen: PreferenceScreen?) {
+ super.setPreferenceScreen(preferenceScreen)
+ updateActivityTitle()
+ }
+
fun createPreferenceScreen(): PreferenceScreen? =
createPreferenceScreen(PreferenceScreenFactory(this))
@@ -102,9 +108,19 @@
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 657f69a..f3f854c 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 @@
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 @@
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 8358ab9..4a6a589 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.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 @@
*/
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 @@
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 @@
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 @@
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 2e7221b..f5ab4b2 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 @@
}
}
}
+
+/**
+ * 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 5287d6c..68b7242 100644
--- a/packages/SettingsLib/Preference/testutils/Android.bp
+++ b/packages/SettingsLib/Preference/testutils/Android.bp
@@ -27,6 +27,7 @@
"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 220614b..172c68a 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.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 @@
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/Android.bp b/packages/SettingsLib/SettingsTheme/Android.bp
index 1661dfb..e5c009d 100644
--- a/packages/SettingsLib/SettingsTheme/Android.bp
+++ b/packages/SettingsLib/SettingsTheme/Android.bp
@@ -16,6 +16,7 @@
],
resource_dirs: ["res"],
static_libs: [
+ "aconfig_settingstheme_exported_flags_java_lib",
"androidx.preference_preference",
"com.google.android.material_material",
],
@@ -23,12 +24,12 @@
min_sdk_version: "21",
apex_available: [
"//apex_available:platform",
+ "com.android.adservices",
"com.android.cellbroadcast",
"com.android.devicelock",
"com.android.extservices",
- "com.android.permission",
- "com.android.adservices",
"com.android.healthfitness",
"com.android.mediaprovider",
+ "com.android.permission",
],
}
diff --git a/packages/SettingsLib/SettingsTheme/aconfig/settingstheme.aconfig b/packages/SettingsLib/SettingsTheme/aconfig/settingstheme.aconfig
new file mode 100644
index 0000000..83e732b
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/aconfig/settingstheme.aconfig
@@ -0,0 +1,10 @@
+package: "com.android.settingslib.widget.theme.flags"
+container: "system"
+
+flag {
+ name: "is_expressive_design_enabled"
+ namespace: "android_settings"
+ description: "enable expressive design in Settings"
+ bug: "386013400"
+ is_exported: true
+}
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_expressive_icon_chevron.xml b/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_chevron.xml
similarity index 100%
rename from packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_expressive_icon_chevron.xml
rename to packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_chevron.xml
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_expressive_icon_collapse.xml b/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_collapse.xml
similarity index 100%
rename from packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_expressive_icon_collapse.xml
rename to packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_collapse.xml
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_cross.xml b/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_cross.xml
new file mode 100644
index 0000000..3ba85a2
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_cross.xml
@@ -0,0 +1,36 @@
+<!--
+ 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.
+ -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item>
+ <shape
+ android:shape="oval">
+ <size android:width="28dp" android:height="28dp"/>
+ <solid android:color="@color/settingslib_materialColorSurfaceContainerHigh"/>
+ </shape>
+ </item>
+ <item android:gravity="center">
+ <vector
+ android:width="16dp"
+ android:height="16dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:fillColor="@color/settingslib_materialColorOnSurface"
+ android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12 19,6.41z"/>
+ </vector>
+ </item>
+</layer-list>
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_expressive_icon_expand.xml b/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_expand.xml
similarity index 100%
rename from packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_expressive_icon_expand.xml
rename to packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_expand.xml
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_level_high.xml b/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_level_high.xml
new file mode 100644
index 0000000..aa4155b
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_level_high.xml
@@ -0,0 +1,41 @@
+<?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.
+ -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item>
+ <vector
+ android:width="20dp"
+ android:height="20dp"
+ android:viewportWidth="20"
+ android:viewportHeight="20">
+ <path
+ android:pathData="M8.804,0.296C9.554,-0.099 10.446,-0.099 11.196,0.296L12.45,0.955C12.703,1.088 12.976,1.178 13.257,1.221L14.654,1.436C15.489,1.564 16.211,2.096 16.589,2.862L17.223,4.144C17.349,4.402 17.518,4.637 17.721,4.84L18.726,5.847C19.328,6.449 19.603,7.31 19.465,8.155L19.236,9.57C19.189,9.855 19.189,10.145 19.236,10.43L19.465,11.845C19.603,12.69 19.328,13.55 18.726,14.153L17.721,15.16C17.518,15.363 17.349,15.597 17.223,15.856L16.589,17.137C16.211,17.903 15.489,18.435 14.654,18.564L13.257,18.78C12.976,18.822 12.703,18.913 12.45,19.045L11.196,19.704C10.446,20.099 9.554,20.099 8.804,19.704L7.549,19.045C7.297,18.913 7.024,18.822 6.743,18.78L5.346,18.564C4.511,18.435 3.789,17.903 3.411,17.137L2.777,15.856C2.651,15.597 2.482,15.363 2.279,15.16L1.274,14.153C0.672,13.55 0.397,12.69 0.535,11.845L0.764,10.43C0.811,10.145 0.811,9.855 0.764,9.57L0.535,8.155C0.397,7.31 0.672,6.449 1.274,5.847L2.279,4.84C2.482,4.637 2.651,4.402 2.777,4.144L3.411,2.862C3.789,2.096 4.511,1.564 5.346,1.436L6.743,1.221C7.024,1.178 7.297,1.088 7.549,0.955L8.804,0.296Z"
+ android:fillColor="@color/settingslib_colorBackgroundLevel_high"/>
+ </vector>
+ </item>
+ <item android:gravity="center">
+ <vector
+ android:width="4dp"
+ android:height="12dp"
+ android:viewportWidth="4"
+ android:viewportHeight="12">
+ <path
+ android:pathData="M0.894,8.081V0.919H3.106V8.081H0.894ZM0.894,11.081V8.869H3.106V11.081H0.894Z"
+ android:fillColor="@color/settingslib_colorContentLevel_high"/>
+ </vector>
+ </item>
+</layer-list>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_level_low.xml b/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_level_low.xml
new file mode 100644
index 0000000..9caa095
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_level_low.xml
@@ -0,0 +1,41 @@
+<?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.
+ -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item>
+ <vector
+ android:width="20dp"
+ android:height="20dp"
+ android:viewportWidth="20"
+ android:viewportHeight="20">
+ <path
+ android:pathData="M8.804,0.296C9.554,-0.099 10.446,-0.099 11.196,0.296L12.45,0.955C12.703,1.088 12.976,1.178 13.257,1.221L14.654,1.436C15.489,1.564 16.211,2.096 16.589,2.862L17.223,4.144C17.349,4.402 17.518,4.637 17.721,4.84L18.726,5.847C19.328,6.449 19.603,7.31 19.465,8.155L19.236,9.57C19.189,9.855 19.189,10.145 19.236,10.43L19.465,11.845C19.603,12.69 19.328,13.55 18.726,14.153L17.721,15.16C17.518,15.363 17.349,15.597 17.223,15.856L16.589,17.137C16.211,17.903 15.489,18.435 14.654,18.564L13.257,18.78C12.976,18.822 12.703,18.913 12.45,19.045L11.196,19.704C10.446,20.099 9.554,20.099 8.804,19.704L7.549,19.045C7.297,18.913 7.024,18.822 6.743,18.78L5.346,18.564C4.511,18.435 3.789,17.903 3.411,17.137L2.777,15.856C2.651,15.597 2.482,15.363 2.279,15.16L1.274,14.153C0.672,13.55 0.397,12.69 0.535,11.845L0.764,10.43C0.811,10.145 0.811,9.855 0.764,9.57L0.535,8.155C0.397,7.31 0.672,6.449 1.274,5.847L2.279,4.84C2.482,4.637 2.651,4.402 2.777,4.144L3.411,2.862C3.789,2.096 4.511,1.564 5.346,1.436L6.743,1.221C7.024,1.178 7.297,1.088 7.549,0.955L8.804,0.296Z"
+ android:fillColor="@color/settingslib_colorBackgroundLevel_low"/>
+ </vector>
+ </item>
+ <item android:gravity="center">
+ <vector
+ android:width="10dp"
+ android:height="9dp"
+ android:viewportWidth="10"
+ android:viewportHeight="9">
+ <path
+ android:pathData="M3.5,8.975L0.069,5.544L1.644,3.969L3.5,5.825L8.356,0.969L9.931,2.544L3.5,8.975Z"
+ android:fillColor="@color/settingslib_colorContentLevel_low"/>
+ </vector>
+ </item>
+</layer-list>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_level_medium.xml b/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_level_medium.xml
new file mode 100644
index 0000000..cdcb982
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_level_medium.xml
@@ -0,0 +1,41 @@
+<?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.
+ -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item>
+ <vector
+ android:width="20dp"
+ android:height="20dp"
+ android:viewportWidth="20"
+ android:viewportHeight="20">
+ <path
+ android:pathData="M8.804,0.296C9.554,-0.099 10.446,-0.099 11.196,0.296L12.45,0.955C12.703,1.088 12.976,1.178 13.257,1.221L14.654,1.436C15.489,1.564 16.211,2.096 16.589,2.862L17.223,4.144C17.349,4.402 17.518,4.637 17.721,4.84L18.726,5.847C19.328,6.449 19.603,7.31 19.465,8.155L19.236,9.57C19.189,9.855 19.189,10.145 19.236,10.43L19.465,11.845C19.603,12.69 19.328,13.55 18.726,14.153L17.721,15.16C17.518,15.363 17.349,15.597 17.223,15.856L16.589,17.137C16.211,17.903 15.489,18.435 14.654,18.564L13.257,18.78C12.976,18.822 12.703,18.913 12.45,19.045L11.196,19.704C10.446,20.099 9.554,20.099 8.804,19.704L7.549,19.045C7.297,18.913 7.024,18.822 6.743,18.78L5.346,18.564C4.511,18.435 3.789,17.903 3.411,17.137L2.777,15.856C2.651,15.597 2.482,15.363 2.279,15.16L1.274,14.153C0.672,13.55 0.397,12.69 0.535,11.845L0.764,10.43C0.811,10.145 0.811,9.855 0.764,9.57L0.535,8.155C0.397,7.31 0.672,6.449 1.274,5.847L2.279,4.84C2.482,4.637 2.651,4.402 2.777,4.144L3.411,2.862C3.789,2.096 4.511,1.564 5.346,1.436L6.743,1.221C7.024,1.178 7.297,1.088 7.549,0.955L8.804,0.296Z"
+ android:fillColor="@color/settingslib_colorBackgroundLevel_medium"/>
+ </vector>
+ </item>
+ <item android:gravity="center">
+ <vector
+ android:width="4dp"
+ android:height="12dp"
+ android:viewportWidth="4"
+ android:viewportHeight="12">
+ <path
+ android:pathData="M0.894,8.081V0.919H3.106V8.081H0.894ZM0.894,11.081V8.869H3.106V11.081H0.894Z"
+ android:fillColor="@color/settingslib_colorContentLevel_medium"/>
+ </vector>
+ </item>
+</layer-list>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_level_normal.xml b/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_level_normal.xml
new file mode 100644
index 0000000..448d596
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_level_normal.xml
@@ -0,0 +1,41 @@
+<?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.
+ -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item>
+ <vector
+ android:width="20dp"
+ android:height="20dp"
+ android:viewportWidth="20"
+ android:viewportHeight="20">
+ <path
+ android:pathData="M8.804,0.296C9.554,-0.099 10.446,-0.099 11.196,0.296L12.45,0.955C12.703,1.088 12.976,1.178 13.257,1.221L14.654,1.436C15.489,1.564 16.211,2.096 16.589,2.862L17.223,4.144C17.349,4.402 17.518,4.637 17.721,4.84L18.726,5.847C19.328,6.449 19.603,7.31 19.465,8.155L19.236,9.57C19.189,9.855 19.189,10.145 19.236,10.43L19.465,11.845C19.603,12.69 19.328,13.55 18.726,14.153L17.721,15.16C17.518,15.363 17.349,15.597 17.223,15.856L16.589,17.137C16.211,17.903 15.489,18.435 14.654,18.564L13.257,18.78C12.976,18.822 12.703,18.913 12.45,19.045L11.196,19.704C10.446,20.099 9.554,20.099 8.804,19.704L7.549,19.045C7.297,18.913 7.024,18.822 6.743,18.78L5.346,18.564C4.511,18.435 3.789,17.903 3.411,17.137L2.777,15.856C2.651,15.597 2.482,15.363 2.279,15.16L1.274,14.153C0.672,13.55 0.397,12.69 0.535,11.845L0.764,10.43C0.811,10.145 0.811,9.855 0.764,9.57L0.535,8.155C0.397,7.31 0.672,6.449 1.274,5.847L2.279,4.84C2.482,4.637 2.651,4.402 2.777,4.144L3.411,2.862C3.789,2.096 4.511,1.564 5.346,1.436L6.743,1.221C7.024,1.178 7.297,1.088 7.549,0.955L8.804,0.296Z"
+ android:fillColor="@color/settingslib_materialColorOnSurface"/>
+ </vector>
+ </item>
+ <item android:gravity="center">
+ <vector
+ android:width="14dp"
+ android:height="4dp"
+ android:viewportWidth="14"
+ android:viewportHeight="4">
+ <path
+ android:pathData="M0.962,3.106V0.894H13.038V3.106H0.962Z"
+ android:fillColor="@color/settingslib_materialColorSurface"/>
+ </vector>
+ </item>
+</layer-list>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_up.xml b/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_up.xml
new file mode 100644
index 0000000..c387305
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_up.xml
@@ -0,0 +1,25 @@
+<?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="24dp"
+ android:height="24dp"
+ android:viewportWidth="960"
+ android:viewportHeight="960">
+ <path
+ android:fillColor="@color/settingslib_materialColorOnSurface"
+ android:pathData="M480,432L296,616L240,560L480,320L720,560L664,616L480,432Z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference.xml b/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference.xml
index 511e2bb..4ef747a 100644
--- a/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference.xml
+++ b/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference.xml
@@ -19,7 +19,7 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:minHeight="?android:attr/listPreferredItemHeightSmall"
+ android:minHeight="72dp"
android:gravity="center_vertical"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
diff --git a/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_collapsable_textview.xml b/packages/SettingsLib/SettingsTheme/res/layout/settingslib_expressive_collapsable_textview.xml
similarity index 97%
rename from packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_collapsable_textview.xml
rename to packages/SettingsLib/SettingsTheme/res/layout/settingslib_expressive_collapsable_textview.xml
index 2776544..7d7bec14 100644
--- a/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_collapsable_textview.xml
+++ b/packages/SettingsLib/SettingsTheme/res/layout/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/SettingsTheme/res/values-night/colors.xml b/packages/SettingsLib/SettingsTheme/res/values-night/colors.xml
index e57fe4f..d677bba 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-night/colors.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-night/colors.xml
@@ -50,4 +50,9 @@
<color name="settingslib_materialColorSurfaceContainerLow">#0E0E0E</color>
<color name="settingslib_materialColorSurfaceContainerHigh">#2A2A2A</color>
<color name="settingslib_materialColorSurfaceContainerHighest">#343434</color>
+
+ <color name="settingslib_colorBackgroundLevel_high">@color/m3_ref_palette_red60</color>
+ <color name="settingslib_colorContentLevel_high">@color/m3_ref_palette_red10</color>
+ <color name="settingslib_colorBackgroundLevel_low">@color/m3_ref_palette_green70</color>
+ <color name="settingslib_colorContentLevel_low">@color/m3_ref_palette_green10</color>
</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v31/styles_expressive.xml b/packages/SettingsLib/SettingsTheme/res/values-v31/styles_expressive.xml
index b5f22b7..ec67d06 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v31/styles_expressive.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v31/styles_expressive.xml
@@ -104,6 +104,11 @@
<item name="android:layout_width">match_parent</item>
</style>
+ <style name="SettingslibTextAppearance.LinkableTextStyle.Expressive"
+ parent="@style/TextAppearance.SettingsLib.LabelLarge">
+ <item name="android:textColor">?android:attr/colorAccent</item>
+ </style>
+
<style name="SettingslibTextButtonStyle.Expressive"
parent="@style/Widget.Material3Expressive.Button.TextButton.Icon">
<item name="android:theme">@style/Theme.Material3.DynamicColors.DayNight</item>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v35/styles_expressive.xml b/packages/SettingsLib/SettingsTheme/res/values-v35/styles_expressive.xml
index 1a08568..3af88c4 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v35/styles_expressive.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v35/styles_expressive.xml
@@ -70,11 +70,6 @@
<item name="android:textAppearance">@style/TextAppearance.SettingsLib.TitleLarge.Emphasized</item>
</style>
- <style name="SettingslibTextAppearance.LinkableTextStyle.Expressive"
- parent="@style/TextAppearance.SettingsLib.LabelLarge">
- <item name="android:textColor">?android:attr/colorAccent</item>
- </style>
-
<style name="SettingsLibStatusBannerCardStyle">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">wrap_content</item>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v35/themes.xml b/packages/SettingsLib/SettingsTheme/res/values-v35/themes.xml
index b6e80c7..dc5c9b2 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v35/themes.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v35/themes.xml
@@ -18,7 +18,7 @@
<resources>
<style name="Theme.SettingsBase_v35" parent="Theme.SettingsBase_v33" >
<item name="android:colorAccent">@color/settingslib_materialColorPrimary</item>
- <item name="android:colorBackground">@color/settingslib_materialColorSurfaceContainer</item>
+ <item name="android:colorBackground">@color/settingslib_materialColorSurfaceContainerLowest</item>
<item name="android:textColorPrimary">@color/settingslib_materialColorOnSurface</item>
<item name="android:textColorSecondary">@color/settingslib_text_color_secondary</item>
<item name="android:textColorTertiary">@color/settingslib_materialColorOutline</item>
diff --git a/packages/SettingsLib/SettingsTheme/res/values/colors.xml b/packages/SettingsLib/SettingsTheme/res/values/colors.xml
index c5c613b..e8ab99e 100644
--- a/packages/SettingsLib/SettingsTheme/res/values/colors.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values/colors.xml
@@ -76,4 +76,11 @@
<color name="settingslib_materialColorSurfaceContainerLowest">#FFFFFF</color>
<color name="settingslib_materialColorSurfaceContainerHigh">#E8E8E8</color>
<color name="settingslib_materialColorSurfaceContainerHighest">#E3E3E3</color>
+
+ <color name="settingslib_colorBackgroundLevel_high">@color/m3_ref_palette_red50</color>
+ <color name="settingslib_colorContentLevel_high">@color/m3_ref_palette_red100</color>
+ <color name="settingslib_colorBackgroundLevel_medium">@color/m3_ref_palette_yellow80</color>
+ <color name="settingslib_colorContentLevel_medium">@color/m3_ref_palette_yellow10</color>
+ <color name="settingslib_colorBackgroundLevel_low">@color/m3_ref_palette_green50</color>
+ <color name="settingslib_colorContentLevel_low">@color/m3_ref_palette_green100</color>
</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/SettingsThemeHelper.kt b/packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/SettingsThemeHelper.kt
index 6794cd0..1776d25 100644
--- a/packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/SettingsThemeHelper.kt
+++ b/packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/SettingsThemeHelper.kt
@@ -18,6 +18,7 @@
import android.content.Context
import android.os.Build
+import com.android.settingslib.widget.theme.flags.Flags
object SettingsThemeHelper {
private const val IS_EXPRESSIVE_DESIGN_ENABLED = "is_expressive_design_enabled"
@@ -56,7 +57,8 @@
expressiveThemeState =
if (
(Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) &&
- getPropBoolean(context, IS_EXPRESSIVE_DESIGN_ENABLED, false)
+ (getPropBoolean(context, IS_EXPRESSIVE_DESIGN_ENABLED, false) ||
+ Flags.isExpressiveDesignEnabled())
) {
ExpressiveThemeState.ENABLED
} else {
diff --git a/packages/SettingsLib/Spa/.gitignore b/packages/SettingsLib/Spa/.gitignore
index b2ed268..5790fde 100644
--- a/packages/SettingsLib/Spa/.gitignore
+++ b/packages/SettingsLib/Spa/.gitignore
@@ -7,6 +7,7 @@
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
+/.kotlin
.DS_Store
build
/captures
diff --git a/packages/SettingsLib/Spa/build.gradle.kts b/packages/SettingsLib/Spa/build.gradle.kts
index cf695d0..5e72c43 100644
--- a/packages/SettingsLib/Spa/build.gradle.kts
+++ b/packages/SettingsLib/Spa/build.gradle.kts
@@ -28,7 +28,7 @@
allprojects {
extra["androidTop"] = androidTop
- extra["jetpackComposeVersion"] = "1.8.0-alpha06"
+ extra["jetpackComposeVersion"] = "1.8.0-alpha08"
}
subprojects {
diff --git a/packages/SettingsLib/Spa/gradle/libs.versions.toml b/packages/SettingsLib/Spa/gradle/libs.versions.toml
index 04ef96a..4113ad8 100644
--- a/packages/SettingsLib/Spa/gradle/libs.versions.toml
+++ b/packages/SettingsLib/Spa/gradle/libs.versions.toml
@@ -15,9 +15,9 @@
#
[versions]
-agp = "8.7.3"
+agp = "8.8.0"
dexmaker-mockito = "2.28.3"
-jvm = "17"
+jvm = "21"
kotlin = "2.0.21"
truth = "1.4.4"
diff --git a/packages/SettingsLib/Spa/spa/build.gradle.kts b/packages/SettingsLib/Spa/spa/build.gradle.kts
index a0bbb0c..1396629 100644
--- a/packages/SettingsLib/Spa/spa/build.gradle.kts
+++ b/packages/SettingsLib/Spa/spa/build.gradle.kts
@@ -52,14 +52,14 @@
dependencies {
api(project(":SettingsLibColor"))
api("androidx.appcompat:appcompat:1.7.0")
- api("androidx.compose.material3:material3:1.4.0-alpha04")
+ api("androidx.compose.material3:material3:1.4.0-alpha05")
api("androidx.compose.material:material-icons-extended")
api("androidx.compose.runtime:runtime-livedata:$jetpackComposeVersion")
api("androidx.compose.ui:ui-tooling-preview:$jetpackComposeVersion")
api("androidx.graphics:graphics-shapes-android:1.0.1")
api("androidx.lifecycle:lifecycle-livedata-ktx")
api("androidx.lifecycle:lifecycle-runtime-compose")
- api("androidx.navigation:navigation-compose:2.9.0-alpha03")
+ api("androidx.navigation:navigation-compose:2.9.0-alpha04")
api("com.github.PhilJay:MPAndroidChart:v3.1.0-alpha")
api("com.google.android.material:material:1.13.0-alpha08")
debugApi("androidx.compose.ui:ui-tooling:$jetpackComposeVersion")
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/DropdownTextBox.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/DropdownTextBox.kt
index bdbe62c..8e59fd7 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/DropdownTextBox.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/DropdownTextBox.kt
@@ -20,9 +20,9 @@
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.ExposedDropdownMenuAnchorType
import androidx.compose.material3.ExposedDropdownMenuBox
import androidx.compose.material3.ExposedDropdownMenuDefaults
-import androidx.compose.material3.MenuAnchorType
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
@@ -66,7 +66,7 @@
OutlinedTextField(
// The `menuAnchor` modifier must be passed to the text field for correctness.
modifier = Modifier
- .menuAnchor(MenuAnchorType.PrimaryNotEditable)
+ .menuAnchor(ExposedDropdownMenuAnchorType.PrimaryNotEditable)
.fillMaxWidth(),
value = text,
onValueChange = { },
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SearchScaffold.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SearchScaffold.kt
index 4a7937a..e5868d0 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SearchScaffold.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SearchScaffold.kt
@@ -51,12 +51,14 @@
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.semantics.clearAndSetSemantics
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewmodel.compose.viewModel
+import com.android.settingslib.spa.framework.compose.contentDescription
import com.android.settingslib.spa.framework.compose.hideKeyboardAction
import com.android.settingslib.spa.framework.compose.horizontalValues
import com.android.settingslib.spa.framework.theme.SettingsOpacity
@@ -175,12 +177,15 @@
onValueChange = onQueryChange,
modifier = Modifier
.fillMaxWidth()
- .focusRequester(focusRequester),
+ .focusRequester(focusRequester)
+ .contentDescription(stringResource(R.string.abc_search_hint)),
textStyle = textStyle,
placeholder = {
Text(
text = stringResource(R.string.abc_search_hint),
- modifier = Modifier.alpha(SettingsOpacity.Hint),
+ modifier = Modifier
+ .alpha(SettingsOpacity.Hint)
+ .clearAndSetSemantics {},
style = textStyle,
)
},
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SearchScaffoldTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SearchScaffoldTest.kt
index 826a0d4..3d73b06 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SearchScaffoldTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SearchScaffoldTest.kt
@@ -130,7 +130,7 @@
).performClick()
}
- private fun onSearchHint() = composeTestRule.onNodeWithText(
+ private fun onSearchHint() = composeTestRule.onNodeWithContentDescription(
context.getString(R.string.abc_search_hint)
)
diff --git a/packages/SettingsLib/Spa/testutils/build.gradle.kts b/packages/SettingsLib/Spa/testutils/build.gradle.kts
index 7dbd320..03cd243 100644
--- a/packages/SettingsLib/Spa/testutils/build.gradle.kts
+++ b/packages/SettingsLib/Spa/testutils/build.gradle.kts
@@ -36,7 +36,7 @@
dependencies {
api(project(":spa"))
- api("androidx.arch.core:core-testing:2.2.0-alpha01")
+ api("androidx.arch.core:core-testing:2.2.0")
api("androidx.compose.ui:ui-test-junit4:$jetpackComposeVersion")
api("androidx.lifecycle:lifecycle-runtime-testing")
api("org.mockito.kotlin:mockito-kotlin:2.2.11")
diff --git a/packages/SettingsLib/TopIntroPreference/res/layout-v33/top_intro_preference.xml b/packages/SettingsLib/TopIntroPreference/res/layout-v33/top_intro_preference.xml
deleted file mode 100644
index 195d45f..0000000
--- a/packages/SettingsLib/TopIntroPreference/res/layout-v33/top_intro_preference.xml
+++ /dev/null
@@ -1,41 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2022 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
- -->
-
-<LinearLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:paddingStart="?android:attr/listPreferredItemPaddingStart"
- android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
- android:paddingBottom="16dp"
- android:paddingTop="8dp"
- android:background="?android:attr/selectableItemBackground"
- android:clipToPadding="false">
-
- <TextView
- android:id="@android:id/title"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="start"
- android:textAlignment="viewStart"
- android:clickable="false"
- android:longClickable="false"
- android:maxLines="10"
- android:hyphenationFrequency="normalFast"
- android:lineBreakWordStyle="phrase"
- android:textAppearance="@style/TextAppearance.TopIntroText"/>
-</LinearLayout>
\ No newline at end of file
diff --git a/packages/SettingsLib/TopIntroPreference/res/layout-v35/settingslib_expressive_top_intro.xml b/packages/SettingsLib/TopIntroPreference/res/layout/settingslib_expressive_top_intro.xml
similarity index 95%
rename from packages/SettingsLib/TopIntroPreference/res/layout-v35/settingslib_expressive_top_intro.xml
rename to packages/SettingsLib/TopIntroPreference/res/layout/settingslib_expressive_top_intro.xml
index fb13ef7..834814c 100644
--- a/packages/SettingsLib/TopIntroPreference/res/layout-v35/settingslib_expressive_top_intro.xml
+++ b/packages/SettingsLib/TopIntroPreference/res/layout/settingslib_expressive_top_intro.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- Copyright (C) 2024 The Android Open Source Project
+ 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.
diff --git a/packages/SettingsLib/TopIntroPreference/res/layout/top_intro_preference.xml b/packages/SettingsLib/TopIntroPreference/res/layout/top_intro_preference.xml
deleted file mode 100644
index bee6bc7..0000000
--- a/packages/SettingsLib/TopIntroPreference/res/layout/top_intro_preference.xml
+++ /dev/null
@@ -1,39 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2020 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
- -->
-
-<LinearLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:paddingStart="?android:attr/listPreferredItemPaddingStart"
- android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
- android:paddingBottom="16dp"
- android:paddingTop="8dp"
- android:background="?android:attr/selectableItemBackground"
- android:clipToPadding="false">
-
- <TextView
- android:id="@android:id/title"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="start"
- android:textAlignment="viewStart"
- android:clickable="false"
- android:longClickable="false"
- android:maxLines="10"
- android:textAppearance="@style/TextAppearance.TopIntroText"/>
-</LinearLayout>
\ No newline at end of file
diff --git a/packages/SettingsLib/TopIntroPreference/src/com/android/settingslib/widget/TopIntroPreference.kt b/packages/SettingsLib/TopIntroPreference/src/com/android/settingslib/widget/TopIntroPreference.kt
index 4428480..08ba836 100644
--- a/packages/SettingsLib/TopIntroPreference/src/com/android/settingslib/widget/TopIntroPreference.kt
+++ b/packages/SettingsLib/TopIntroPreference/src/com/android/settingslib/widget/TopIntroPreference.kt
@@ -17,20 +17,20 @@
package com.android.settingslib.widget
import android.content.Context
-import android.os.Build
import android.text.TextUtils
import android.util.AttributeSet
import android.view.View
-import androidx.annotation.RequiresApi
import androidx.preference.Preference
import androidx.preference.PreferenceViewHolder
import com.android.settingslib.widget.preference.topintro.R
-open class TopIntroPreference @JvmOverloads constructor(
+open class TopIntroPreference
+@JvmOverloads
+constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0,
- defStyleRes: Int = 0
+ defStyleRes: Int = 0,
) : Preference(context, attrs, defStyleAttr, defStyleRes), GroupSectionDividerMixin {
private var isCollapsable: Boolean = false
@@ -40,25 +40,18 @@
private var learnMoreText: CharSequence? = null
init {
- if (SettingsThemeHelper.isExpressiveTheme(context)) {
- layoutResource = R.layout.settingslib_expressive_top_intro
- initAttributes(context, attrs, defStyleAttr)
- } else {
- layoutResource = R.layout.top_intro_preference
- }
+ layoutResource = R.layout.settingslib_expressive_top_intro
+ initAttributes(context, attrs, defStyleAttr)
+
isSelectable = false
}
private fun initAttributes(context: Context, attrs: AttributeSet?, defStyleAttr: Int) {
- context.obtainStyledAttributes(
- attrs,
- COLLAPSABLE_TEXT_VIEW_ATTRS, defStyleAttr, 0
- ).apply {
+ context.obtainStyledAttributes(attrs, COLLAPSABLE_TEXT_VIEW_ATTRS, defStyleAttr, 0).apply {
isCollapsable = getBoolean(IS_COLLAPSABLE, false)
- minLines = getInt(
- MIN_LINES,
- if (isCollapsable) DEFAULT_MIN_LINES else DEFAULT_MAX_LINES
- ).coerceIn(1, DEFAULT_MAX_LINES)
+ minLines =
+ getInt(MIN_LINES, if (isCollapsable) DEFAULT_MIN_LINES else DEFAULT_MAX_LINES)
+ .coerceIn(1, DEFAULT_MAX_LINES)
recycle()
}
}
@@ -68,10 +61,6 @@
holder.isDividerAllowedAbove = false
holder.isDividerAllowedBelow = false
- if (!SettingsThemeHelper.isExpressiveTheme(context)) {
- return
- }
-
(holder.findViewById(R.id.collapsable_text_view) as? CollapsableTextView)?.apply {
setCollapsable(isCollapsable)
setMinLines(minLines)
@@ -89,9 +78,9 @@
/**
* Sets whether the text view is collapsable.
+ *
* @param collapsable True if the text view should be collapsable, false otherwise.
*/
- @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
fun setCollapsable(collapsable: Boolean) {
isCollapsable = collapsable
notifyChanged()
@@ -99,9 +88,9 @@
/**
* Sets the minimum number of lines to display when collapsed.
+ *
* @param lines The minimum number of lines.
*/
- @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
fun setMinLines(lines: Int) {
minLines = lines.coerceIn(1, DEFAULT_MAX_LINES)
notifyChanged()
@@ -109,9 +98,9 @@
/**
* Sets the action when clicking on the hyperlink in the text.
+ *
* @param listener The click listener for hyperlink.
*/
- @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
fun setHyperlinkListener(listener: View.OnClickListener) {
if (hyperlinkListener != listener) {
hyperlinkListener = listener
@@ -121,9 +110,9 @@
/**
* Sets the action when clicking on the learn more view.
+ *
* @param listener The click listener for learn more.
*/
- @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
fun setLearnMoreAction(listener: View.OnClickListener) {
if (learnMoreListener != listener) {
learnMoreListener = listener
@@ -133,9 +122,9 @@
/**
* Sets the text of learn more view.
+ *
* @param text The text of learn more.
*/
- @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
fun setLearnMoreText(text: CharSequence) {
if (!TextUtils.equals(learnMoreText, text)) {
learnMoreText = text
diff --git a/packages/SettingsLib/ZeroStatePreference/Android.bp b/packages/SettingsLib/ZeroStatePreference/Android.bp
index 4fc00bd..0949e2c 100644
--- a/packages/SettingsLib/ZeroStatePreference/Android.bp
+++ b/packages/SettingsLib/ZeroStatePreference/Android.bp
@@ -29,5 +29,6 @@
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 c0b195c..ae3f1dd 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/res/values/config.xml b/packages/SettingsLib/res/values/config.xml
index 3c3de04..502eb6c 100644
--- a/packages/SettingsLib/res/values/config.xml
+++ b/packages/SettingsLib/res/values/config.xml
@@ -41,4 +41,15 @@
<string name="config_avatar_picker_class" translatable="false">
com.android.avatarpicker.ui.AvatarPickerActivity
</string>
+
+ <array name="config_override_carrier_5g_plus">
+ <item>@array/carrier_2032_5g_plus</item>
+ </array>
+
+ <integer-array name="carrier_2032_5g_plus">
+ <!-- carrier id: 2032 -->
+ <item>2032</item>
+ <!-- network type: "5G+" -->
+ <item>@string/data_connection_5g_plus_carrier_2032</item>
+ </integer-array>
</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 6cf9e83..3da2271 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1723,6 +1723,8 @@
<!-- Content description of the data connection type 5G+. [CHAR LIMIT=NONE] -->
<string name="data_connection_5g_plus" translatable="false">5G+</string>
+ <!-- Content description of the data connection type 5G+ for carrier 2032. [CHAR LIMIT=NONE] -->
+ <string name="data_connection_5g_plus_carrier_2032" translatable="false">5G+</string>
<!-- Content description of the data connection type Carrier WiFi. [CHAR LIMIT=NONE] -->
<string name="data_connection_carrier_wifi">W+</string>
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 0000000..a64e8cc
--- /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 5bcdcc0..d9e79fa 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 @@
* 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 @@
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 @@
}
}
}
+
+ /** 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/SettingsLib/src/com/android/settingslib/mobile/MobileMappings.java b/packages/SettingsLib/src/com/android/settingslib/mobile/MobileMappings.java
index b7108c9..cf52eb3 100644
--- a/packages/SettingsLib/src/com/android/settingslib/mobile/MobileMappings.java
+++ b/packages/SettingsLib/src/com/android/settingslib/mobile/MobileMappings.java
@@ -15,11 +15,14 @@
*/
package com.android.settingslib.mobile;
+import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.Resources;
+import android.content.res.TypedArray;
import android.os.PersistableBundle;
import android.telephony.Annotation;
import android.telephony.CarrierConfigManager;
+import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyDisplayInfo;
import android.telephony.TelephonyManager;
@@ -196,9 +199,9 @@
networkToIconLookup.put(toDisplayIconKey(
TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA),
TelephonyIcons.NR_5G);
- networkToIconLookup.put(toDisplayIconKey(
- TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED),
- TelephonyIcons.NR_5G_PLUS);
+ networkToIconLookup.put(
+ toDisplayIconKey(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED),
+ config.mobileIconGroup5gPlus);
networkToIconLookup.put(toIconKey(
TelephonyManager.NETWORK_TYPE_NR),
TelephonyIcons.NR_5G);
@@ -217,6 +220,7 @@
public boolean hideLtePlus = false;
public boolean hspaDataDistinguishable;
public boolean alwaysShowDataRatIcon = false;
+ public MobileIconGroup mobileIconGroup5gPlus = TelephonyIcons.NR_5G_PLUS;
/**
* Reads the latest configs.
@@ -250,9 +254,54 @@
config.hideLtePlus = b.getBoolean(
CarrierConfigManager.KEY_HIDE_LTE_PLUS_DATA_ICON_BOOL);
}
+
+ SubscriptionManager subscriptionManager =
+ context.getSystemService(SubscriptionManager.class);
+ if (subscriptionManager != null) {
+ SubscriptionInfo subInfo = subscriptionManager.getDefaultDataSubscriptionInfo();
+ if (subInfo != null) {
+ readMobileIconGroup5gPlus(subInfo.getCarrierId(), res, config);
+ }
+ }
return config;
}
+ @SuppressLint("ResourceType")
+ private static void readMobileIconGroup5gPlus(int carrierId, Resources res, Config config) {
+ int networkTypeResId = 0;
+ TypedArray groupArray;
+ try {
+ groupArray = res.obtainTypedArray(R.array.config_override_carrier_5g_plus);
+ } catch (Resources.NotFoundException e) {
+ return;
+ }
+ for (int i = 0; i < groupArray.length() && networkTypeResId == 0; i++) {
+ int groupId = groupArray.getResourceId(i, 0);
+ if (groupId == 0) {
+ continue;
+ }
+ TypedArray carrierArray;
+ try {
+ carrierArray = res.obtainTypedArray(groupId);
+ } catch (Resources.NotFoundException e) {
+ continue;
+ }
+ int groupCarrierId = carrierArray.getInt(0, 0);
+ if (groupCarrierId == carrierId) {
+ networkTypeResId = carrierArray.getResourceId(1, 0);
+ }
+ carrierArray.recycle();
+ }
+ groupArray.recycle();
+
+ if (networkTypeResId != 0) {
+ config.mobileIconGroup5gPlus = new MobileIconGroup(
+ TelephonyIcons.NR_5G_PLUS.name,
+ networkTypeResId,
+ TelephonyIcons.NR_5G_PLUS.dataType);
+ }
+ }
+
/**
* Returns true if this config and the other config are semantically equal.
*
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenDurationDialog.java b/packages/SettingsLib/src/com/android/settingslib/notification/modes/DndDurationDialogFactory.java
similarity index 96%
rename from packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenDurationDialog.java
rename to packages/SettingsLib/src/com/android/settingslib/notification/modes/DndDurationDialogFactory.java
index 98c3edb..c5fa0aa 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenDurationDialog.java
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/modes/DndDurationDialogFactory.java
@@ -44,7 +44,12 @@
import java.util.Arrays;
-public class ZenDurationDialog {
+/**
+ * This dialog configures the default behavior that the user prefers when enabling DND.
+ * Not to be confused with {@link EnableDndDialogFactory}, which is the dialog that will be shown
+ * when the user enables DND if the "Ask every time" option was selected in this dialog.
+ */
+public class DndDurationDialogFactory {
private static final int[] MINUTE_BUCKETS = ZenModeConfig.MINUTE_BUCKETS;
@VisibleForTesting
protected static final int MIN_BUCKET_MINUTES = MINUTE_BUCKETS[0];
@@ -72,7 +77,7 @@
@VisibleForTesting
protected LayoutInflater mLayoutInflater;
- public ZenDurationDialog(Context context) {
+ public DndDurationDialogFactory(Context context) {
mContext = context;
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/modes/EnableZenModeDialog.java b/packages/SettingsLib/src/com/android/settingslib/notification/modes/EnableDndDialogFactory.java
similarity index 95%
rename from packages/SettingsLib/src/com/android/settingslib/notification/modes/EnableZenModeDialog.java
rename to packages/SettingsLib/src/com/android/settingslib/notification/modes/EnableDndDialogFactory.java
index c48694c..f0e7fb8 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/modes/EnableZenModeDialog.java
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/modes/EnableDndDialogFactory.java
@@ -54,8 +54,15 @@
import java.util.Locale;
import java.util.Objects;
-public class EnableZenModeDialog {
- private static final String TAG = "EnableZenModeDialog";
+/**
+ * When enabling DND, if the user has the setting to "Ask every time" for the duration, we show
+ * this dialog to allow the user to select for how long they want DND to be enabled this time.
+ * Not to be confused with {@link DndDurationDialogFactory}, which is the dialog that allows the
+ * user to configure the default behavior for enabling DND (and in turn may lead to this dialog
+ * being shown, since it contains the said "Ask every time" option).
+ */
+public class EnableDndDialogFactory {
+ private static final String TAG = "EnableDndDialogFactory";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private static final int[] MINUTE_BUCKETS = ZenModeConfig.MINUTE_BUCKETS;
@@ -74,7 +81,7 @@
private static final int MINUTES_MS = 60 * SECONDS_MS;
@Nullable
- private final ZenModeDialogMetricsLogger mMetricsLogger;
+ private final EnableDndDialogMetricsLogger mMetricsLogger;
@VisibleForTesting
protected Uri mForeverId;
@@ -101,17 +108,17 @@
@VisibleForTesting
protected LayoutInflater mLayoutInflater;
- public EnableZenModeDialog(Context context) {
+ public EnableDndDialogFactory(Context context) {
this(context, 0);
}
- public EnableZenModeDialog(Context context, int themeResId) {
+ public EnableDndDialogFactory(Context context, int themeResId) {
this(context, themeResId, false /* cancelIsNeutral */,
- new ZenModeDialogMetricsLogger(context));
+ new EnableDndDialogMetricsLogger(context));
}
- public EnableZenModeDialog(Context context, int themeResId, boolean cancelIsNeutral,
- ZenModeDialogMetricsLogger metricsLogger) {
+ public EnableDndDialogFactory(Context context, int themeResId, boolean cancelIsNeutral,
+ EnableDndDialogMetricsLogger metricsLogger) {
mContext = context;
mThemeResId = themeResId;
mCancelIsNeutral = cancelIsNeutral;
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenModeDialogMetricsLogger.java b/packages/SettingsLib/src/com/android/settingslib/notification/modes/EnableDndDialogMetricsLogger.java
similarity index 93%
rename from packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenModeDialogMetricsLogger.java
rename to packages/SettingsLib/src/com/android/settingslib/notification/modes/EnableDndDialogMetricsLogger.java
index 17695e3..552bf8d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenModeDialogMetricsLogger.java
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/modes/EnableDndDialogMetricsLogger.java
@@ -22,12 +22,12 @@
import com.android.internal.logging.nano.MetricsProto;
/**
- * Logs ui events for {@link EnableZenModeDialog}.
+ * Logs ui events for {@link EnableDndDialogFactory}.
*/
-public class ZenModeDialogMetricsLogger {
+public class EnableDndDialogMetricsLogger {
private final Context mContext;
- public ZenModeDialogMetricsLogger(Context context) {
+ public EnableDndDialogMetricsLogger(Context context) {
mContext = context;
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenDurationDialogTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/DndDurationDialogFactoryTest.java
similarity index 65%
rename from packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenDurationDialogTest.java
rename to packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/DndDurationDialogFactoryTest.java
index 19845a0..0b05a4f 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenDurationDialogTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/DndDurationDialogFactoryTest.java
@@ -16,6 +16,12 @@
package com.android.settingslib.notification.modes;
+import static com.android.settingslib.notification.modes.DndDurationDialogFactory.ALWAYS_ASK_CONDITION_INDEX;
+import static com.android.settingslib.notification.modes.DndDurationDialogFactory.COUNTDOWN_CONDITION_INDEX;
+import static com.android.settingslib.notification.modes.DndDurationDialogFactory.FOREVER_CONDITION_INDEX;
+import static com.android.settingslib.notification.modes.DndDurationDialogFactory.MAX_BUCKET_MINUTES;
+import static com.android.settingslib.notification.modes.DndDurationDialogFactory.MIN_BUCKET_MINUTES;
+
import static com.google.common.truth.Truth.assertThat;
import static junit.framework.Assert.assertEquals;
@@ -32,6 +38,8 @@
import androidx.appcompat.app.AlertDialog;
+import com.android.settingslib.notification.modes.DndDurationDialogFactory.ConditionTag;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -39,8 +47,8 @@
import org.robolectric.RuntimeEnvironment;
@RunWith(RobolectricTestRunner.class)
-public class ZenDurationDialogTest {
- private ZenDurationDialog mController;
+public class DndDurationDialogFactoryTest {
+ private DndDurationDialogFactory mController;
private Context mContext;
private LayoutInflater mLayoutInflater;
@@ -53,7 +61,7 @@
mContentResolver = RuntimeEnvironment.application.getContentResolver();
mLayoutInflater = LayoutInflater.from(mContext);
- mController = spy(new ZenDurationDialog(mContext));
+ mController = spy(new DndDurationDialogFactory(mContext));
mController.mLayoutInflater = mLayoutInflater;
mController.getContentView();
mBuilder = new AlertDialog.Builder(mContext);
@@ -65,12 +73,9 @@
Settings.Global.ZEN_DURATION_PROMPT);
mController.setupDialog(mBuilder);
- assertFalse(mController.getConditionTagAt(ZenDurationDialog.FOREVER_CONDITION_INDEX).rb
- .isChecked());
- assertFalse(mController.getConditionTagAt(ZenDurationDialog.COUNTDOWN_CONDITION_INDEX).rb
- .isChecked());
- assertTrue(mController.getConditionTagAt(
- ZenDurationDialog.ALWAYS_ASK_CONDITION_INDEX).rb.isChecked());
+ assertFalse(mController.getConditionTagAt(FOREVER_CONDITION_INDEX).rb.isChecked());
+ assertFalse(mController.getConditionTagAt(COUNTDOWN_CONDITION_INDEX).rb.isChecked());
+ assertTrue(mController.getConditionTagAt(ALWAYS_ASK_CONDITION_INDEX).rb.isChecked());
}
@Test
@@ -79,12 +84,9 @@
Settings.Secure.ZEN_DURATION_FOREVER);
mController.setupDialog(mBuilder);
- assertTrue(mController.getConditionTagAt(ZenDurationDialog.FOREVER_CONDITION_INDEX).rb
- .isChecked());
- assertFalse(mController.getConditionTagAt(ZenDurationDialog.COUNTDOWN_CONDITION_INDEX).rb
- .isChecked());
- assertFalse(mController.getConditionTagAt(
- ZenDurationDialog.ALWAYS_ASK_CONDITION_INDEX).rb.isChecked());
+ assertTrue(mController.getConditionTagAt(FOREVER_CONDITION_INDEX).rb.isChecked());
+ assertFalse(mController.getConditionTagAt(COUNTDOWN_CONDITION_INDEX).rb.isChecked());
+ assertFalse(mController.getConditionTagAt(ALWAYS_ASK_CONDITION_INDEX).rb.isChecked());
}
@Test
@@ -92,12 +94,9 @@
Settings.Secure.putInt(mContentResolver, Settings.Secure.ZEN_DURATION, 45);
mController.setupDialog(mBuilder);
- assertFalse(mController.getConditionTagAt(ZenDurationDialog.FOREVER_CONDITION_INDEX).rb
- .isChecked());
- assertTrue(mController.getConditionTagAt(ZenDurationDialog.COUNTDOWN_CONDITION_INDEX).rb
- .isChecked());
- assertFalse(mController.getConditionTagAt(
- ZenDurationDialog.ALWAYS_ASK_CONDITION_INDEX).rb.isChecked());
+ assertFalse(mController.getConditionTagAt(FOREVER_CONDITION_INDEX).rb.isChecked());
+ assertTrue(mController.getConditionTagAt(COUNTDOWN_CONDITION_INDEX).rb.isChecked());
+ assertFalse(mController.getConditionTagAt(ALWAYS_ASK_CONDITION_INDEX).rb.isChecked());
}
@Test
@@ -106,8 +105,7 @@
Settings.Secure.ZEN_DURATION_FOREVER);
mController.setupDialog(mBuilder);
- mController.getConditionTagAt(ZenDurationDialog.ALWAYS_ASK_CONDITION_INDEX).rb.setChecked(
- true);
+ mController.getConditionTagAt(ALWAYS_ASK_CONDITION_INDEX).rb.setChecked(true);
mController.updateZenDuration(Settings.Secure.ZEN_DURATION_FOREVER);
assertEquals(Settings.Secure.ZEN_DURATION_PROMPT, Settings.Secure.getInt(mContentResolver,
@@ -120,8 +118,7 @@
Settings.Secure.ZEN_DURATION_PROMPT);
mController.setupDialog(mBuilder);
- mController.getConditionTagAt(ZenDurationDialog.FOREVER_CONDITION_INDEX).rb.setChecked(
- true);
+ mController.getConditionTagAt(FOREVER_CONDITION_INDEX).rb.setChecked(true);
mController.updateZenDuration(Settings.Secure.ZEN_DURATION_PROMPT);
assertEquals(Settings.Secure.ZEN_DURATION_FOREVER, Settings.Secure.getInt(mContentResolver,
@@ -134,8 +131,7 @@
Settings.Secure.ZEN_DURATION_PROMPT);
mController.setupDialog(mBuilder);
- mController.getConditionTagAt(ZenDurationDialog.COUNTDOWN_CONDITION_INDEX).rb.setChecked(
- true);
+ mController.getConditionTagAt(COUNTDOWN_CONDITION_INDEX).rb.setChecked(true);
mController.updateZenDuration(Settings.Secure.ZEN_DURATION_PROMPT);
// countdown defaults to 60 minutes:
@@ -152,59 +148,50 @@
// click time button starts at 60 minutes
// - 1 hour to MAX_BUCKET_MINUTES (12 hours), increments by 1 hour
// - 0-60 minutes increments by 15 minutes
- View view = mController.mZenRadioGroupContent.getChildAt(
- ZenDurationDialog.COUNTDOWN_CONDITION_INDEX);
- ZenDurationDialog.ConditionTag tag = mController.getConditionTagAt(
- ZenDurationDialog.COUNTDOWN_CONDITION_INDEX);
+ View view = mController.mZenRadioGroupContent.getChildAt(COUNTDOWN_CONDITION_INDEX);
+ ConditionTag tag = mController.getConditionTagAt(COUNTDOWN_CONDITION_INDEX);
// test incrementing up:
- mController.onClickTimeButton(view, tag, true, ZenDurationDialog.COUNTDOWN_CONDITION_INDEX);
+ mController.onClickTimeButton(view, tag, true, COUNTDOWN_CONDITION_INDEX);
assertEquals(120, tag.countdownZenDuration); // goes from 1 hour to 2 hours
// try clicking up 50 times - should max out at ZenDurationDialog.MAX_BUCKET_MINUTES
for (int i = 0; i < 50; i++) {
- mController.onClickTimeButton(view, tag, true,
- ZenDurationDialog.COUNTDOWN_CONDITION_INDEX);
+ mController.onClickTimeButton(view, tag, true, COUNTDOWN_CONDITION_INDEX);
}
- assertEquals(ZenDurationDialog.MAX_BUCKET_MINUTES, tag.countdownZenDuration);
+ assertEquals(MAX_BUCKET_MINUTES, tag.countdownZenDuration);
// reset, test incrementing down:
mController.mBucketIndex = -1; // reset current bucket index to reset countdownZenDuration
tag.countdownZenDuration = 60; // back to default
- mController.onClickTimeButton(view, tag, false,
- ZenDurationDialog.COUNTDOWN_CONDITION_INDEX);
+ mController.onClickTimeButton(view, tag, false, COUNTDOWN_CONDITION_INDEX);
assertEquals(45, tag.countdownZenDuration); // goes from 60 minutes to 45 minutes
// try clicking down 50 times - should stop at MIN_BUCKET_MINUTES
for (int i = 0; i < 50; i++) {
- mController.onClickTimeButton(view, tag, false,
- ZenDurationDialog.COUNTDOWN_CONDITION_INDEX);
+ mController.onClickTimeButton(view, tag, false, COUNTDOWN_CONDITION_INDEX);
}
- assertEquals(ZenDurationDialog.MIN_BUCKET_MINUTES, tag.countdownZenDuration);
+ assertEquals(MIN_BUCKET_MINUTES, tag.countdownZenDuration);
// reset countdownZenDuration to unbucketed number, should round change to nearest bucket
mController.mBucketIndex = -1;
tag.countdownZenDuration = 50;
- mController.onClickTimeButton(view, tag, false,
- ZenDurationDialog.COUNTDOWN_CONDITION_INDEX);
+ mController.onClickTimeButton(view, tag, false, COUNTDOWN_CONDITION_INDEX);
assertEquals(45, tag.countdownZenDuration);
mController.mBucketIndex = -1;
tag.countdownZenDuration = 50;
- mController.onClickTimeButton(view, tag, true,
- ZenDurationDialog.COUNTDOWN_CONDITION_INDEX);
+ mController.onClickTimeButton(view, tag, true, COUNTDOWN_CONDITION_INDEX);
assertEquals(60, tag.countdownZenDuration);
mController.mBucketIndex = -1;
tag.countdownZenDuration = 75;
- mController.onClickTimeButton(view, tag, false,
- ZenDurationDialog.COUNTDOWN_CONDITION_INDEX);
+ mController.onClickTimeButton(view, tag, false, COUNTDOWN_CONDITION_INDEX);
assertEquals(60, tag.countdownZenDuration);
mController.mBucketIndex = -1;
tag.countdownZenDuration = 75;
- mController.onClickTimeButton(view, tag, true,
- ZenDurationDialog.COUNTDOWN_CONDITION_INDEX);
+ mController.onClickTimeButton(view, tag, true, COUNTDOWN_CONDITION_INDEX);
assertEquals(120, tag.countdownZenDuration);
}
@@ -213,12 +200,9 @@
Settings.Secure.putInt(mContentResolver, Settings.Secure.ZEN_DURATION,
Settings.Secure.ZEN_DURATION_FOREVER);
mController.setupDialog(mBuilder);
- ZenDurationDialog.ConditionTag forever = mController.getConditionTagAt(
- ZenDurationDialog.FOREVER_CONDITION_INDEX);
- ZenDurationDialog.ConditionTag countdown = mController.getConditionTagAt(
- ZenDurationDialog.COUNTDOWN_CONDITION_INDEX);
- ZenDurationDialog.ConditionTag alwaysAsk = mController.getConditionTagAt(
- ZenDurationDialog.ALWAYS_ASK_CONDITION_INDEX);
+ ConditionTag forever = mController.getConditionTagAt(FOREVER_CONDITION_INDEX);
+ ConditionTag countdown = mController.getConditionTagAt(COUNTDOWN_CONDITION_INDEX);
+ ConditionTag alwaysAsk = mController.getConditionTagAt(ALWAYS_ASK_CONDITION_INDEX);
forever.rb.setChecked(true);
assertThat(forever.line1.getStateDescription().toString()).isEqualTo("selected");
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/EnableZenModeDialogTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/EnableDndDialogFactoryTest.java
similarity index 73%
rename from packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/EnableZenModeDialogTest.java
rename to packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/EnableDndDialogFactoryTest.java
index e397f97..fc9fd80 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/EnableZenModeDialogTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/EnableDndDialogFactoryTest.java
@@ -16,6 +16,10 @@
package com.android.settingslib.notification.modes;
+import static com.android.settingslib.notification.modes.EnableDndDialogFactory.COUNTDOWN_ALARM_CONDITION_INDEX;
+import static com.android.settingslib.notification.modes.EnableDndDialogFactory.COUNTDOWN_CONDITION_INDEX;
+import static com.android.settingslib.notification.modes.EnableDndDialogFactory.FOREVER_CONDITION_INDEX;
+
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertFalse;
@@ -39,6 +43,8 @@
import android.service.notification.Condition;
import android.view.LayoutInflater;
+import com.android.settingslib.notification.modes.EnableDndDialogFactory.ConditionTag;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -48,8 +54,8 @@
import org.robolectric.RuntimeEnvironment;
@RunWith(RobolectricTestRunner.class)
-public class EnableZenModeDialogTest {
- private EnableZenModeDialog mController;
+public class EnableDndDialogFactoryTest {
+ private EnableDndDialogFactory mController;
@Mock
private Context mContext;
@@ -74,7 +80,7 @@
when(mFragment.getContext()).thenReturn(mShadowContext);
mLayoutInflater = LayoutInflater.from(mShadowContext);
- mController = spy(new EnableZenModeDialog(mContext));
+ mController = spy(new EnableDndDialogFactory(mContext));
mController.mContext = mContext;
mController.mLayoutInflater = mLayoutInflater;
mController.mForeverId = Condition.newId(mContext).appendPath("forever").build();
@@ -101,36 +107,29 @@
Uri countdown = Condition.newId(mContext).appendPath("countdown").build();
mCountdownCondition = new Condition(countdown, "countdown", "", "", 0, 0, 0);
mController.bind(mCountdownCondition,
- mController.mZenRadioGroupContent.getChildAt(
- EnableZenModeDialog.COUNTDOWN_CONDITION_INDEX),
- EnableZenModeDialog.COUNTDOWN_CONDITION_INDEX);
+ mController.mZenRadioGroupContent.getChildAt(COUNTDOWN_CONDITION_INDEX),
+ COUNTDOWN_CONDITION_INDEX);
mController.bind(mAlarmCondition,
mController.mZenRadioGroupContent.getChildAt(
- EnableZenModeDialog.COUNTDOWN_ALARM_CONDITION_INDEX),
- EnableZenModeDialog.COUNTDOWN_ALARM_CONDITION_INDEX);
+ COUNTDOWN_ALARM_CONDITION_INDEX),
+ COUNTDOWN_ALARM_CONDITION_INDEX);
}
@Test
public void testForeverChecked() {
mController.bindConditions(mController.forever());
- assertTrue(mController.getConditionTagAt(EnableZenModeDialog.FOREVER_CONDITION_INDEX).rb
- .isChecked());
- assertFalse(mController.getConditionTagAt(EnableZenModeDialog.COUNTDOWN_CONDITION_INDEX).rb
- .isChecked());
- assertFalse(mController.getConditionTagAt(
- EnableZenModeDialog.COUNTDOWN_ALARM_CONDITION_INDEX).rb.isChecked());
+ assertTrue(mController.getConditionTagAt(FOREVER_CONDITION_INDEX).rb.isChecked());
+ assertFalse(mController.getConditionTagAt(COUNTDOWN_CONDITION_INDEX).rb.isChecked());
+ assertFalse(mController.getConditionTagAt(COUNTDOWN_ALARM_CONDITION_INDEX).rb.isChecked());
}
@Test
public void testNoneChecked() {
mController.bindConditions(null);
- assertFalse(mController.getConditionTagAt(EnableZenModeDialog.FOREVER_CONDITION_INDEX).rb
- .isChecked());
- assertFalse(mController.getConditionTagAt(EnableZenModeDialog.COUNTDOWN_CONDITION_INDEX).rb
- .isChecked());
- assertFalse(mController.getConditionTagAt(
- EnableZenModeDialog.COUNTDOWN_ALARM_CONDITION_INDEX).rb.isChecked());
+ assertFalse(mController.getConditionTagAt(FOREVER_CONDITION_INDEX).rb.isChecked());
+ assertFalse(mController.getConditionTagAt(COUNTDOWN_CONDITION_INDEX).rb.isChecked());
+ assertFalse(mController.getConditionTagAt(COUNTDOWN_ALARM_CONDITION_INDEX).rb.isChecked());
}
@Test
@@ -139,12 +138,9 @@
doReturn(true).when(mController).isAlarm(mAlarmCondition);
mController.bindConditions(mAlarmCondition);
- assertFalse(mController.getConditionTagAt(EnableZenModeDialog.FOREVER_CONDITION_INDEX).rb
- .isChecked());
- assertFalse(mController.getConditionTagAt(EnableZenModeDialog.COUNTDOWN_CONDITION_INDEX).rb
- .isChecked());
- assertTrue(mController.getConditionTagAt(
- EnableZenModeDialog.COUNTDOWN_ALARM_CONDITION_INDEX).rb.isChecked());
+ assertFalse(mController.getConditionTagAt(FOREVER_CONDITION_INDEX).rb.isChecked());
+ assertFalse(mController.getConditionTagAt(COUNTDOWN_CONDITION_INDEX).rb.isChecked());
+ assertTrue(mController.getConditionTagAt(COUNTDOWN_ALARM_CONDITION_INDEX).rb.isChecked());
}
@Test
@@ -153,12 +149,9 @@
doReturn(true).when(mController).isCountdown(mCountdownCondition);
mController.bindConditions(mCountdownCondition);
- assertFalse(mController.getConditionTagAt(EnableZenModeDialog.FOREVER_CONDITION_INDEX).rb
- .isChecked());
- assertTrue(mController.getConditionTagAt(EnableZenModeDialog.COUNTDOWN_CONDITION_INDEX).rb
- .isChecked());
- assertFalse(mController.getConditionTagAt(
- EnableZenModeDialog.COUNTDOWN_ALARM_CONDITION_INDEX).rb.isChecked());
+ assertFalse(mController.getConditionTagAt(FOREVER_CONDITION_INDEX).rb.isChecked());
+ assertTrue(mController.getConditionTagAt(COUNTDOWN_CONDITION_INDEX).rb.isChecked());
+ assertFalse(mController.getConditionTagAt(COUNTDOWN_ALARM_CONDITION_INDEX).rb.isChecked());
}
@Test
@@ -198,12 +191,12 @@
@Test
public void testAccessibility() {
mController.bindConditions(null);
- EnableZenModeDialog.ConditionTag forever = mController.getConditionTagAt(
- ZenDurationDialog.FOREVER_CONDITION_INDEX);
- EnableZenModeDialog.ConditionTag countdown = mController.getConditionTagAt(
- ZenDurationDialog.COUNTDOWN_CONDITION_INDEX);
- EnableZenModeDialog.ConditionTag alwaysAsk = mController.getConditionTagAt(
- ZenDurationDialog.ALWAYS_ASK_CONDITION_INDEX);
+ ConditionTag forever = mController.getConditionTagAt(
+ DndDurationDialogFactory.FOREVER_CONDITION_INDEX);
+ ConditionTag countdown = mController.getConditionTagAt(
+ DndDurationDialogFactory.COUNTDOWN_CONDITION_INDEX);
+ ConditionTag alwaysAsk = mController.getConditionTagAt(
+ DndDurationDialogFactory.ALWAYS_ASK_CONDITION_INDEX);
forever.rb.setChecked(true);
assertThat(forever.line1.getStateDescription().toString()).isEqualTo("selected");
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index 7b4a2ca..640829e 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -92,6 +92,7 @@
Settings.Secure.KEY_REPEAT_DELAY_MS,
Settings.Secure.CAMERA_GESTURE_DISABLED,
Settings.Secure.ACCESSIBILITY_AUTOCLICK_CURSOR_AREA_SIZE,
+ Settings.Secure.ACCESSIBILITY_AUTOCLICK_IGNORE_MINOR_CURSOR_MOVEMENT,
Settings.Secure.ACCESSIBILITY_AUTOCLICK_ENABLED,
Settings.Secure.ACCESSIBILITY_AUTOCLICK_DELAY,
Settings.Secure.ACCESSIBILITY_LARGE_POINTER_ICON,
@@ -203,7 +204,6 @@
Settings.Secure.AWARE_TAP_PAUSE_TOUCH_COUNT,
Settings.Secure.PEOPLE_STRIP,
Settings.Secure.MEDIA_CONTROLS_RESUME,
- Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION,
Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN,
Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE,
Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS,
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
index 1f56f10..cf0447f 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
@@ -117,6 +117,7 @@
Settings.System.TOUCHPAD_TAP_TO_CLICK,
Settings.System.TOUCHPAD_TAP_DRAGGING,
Settings.System.TOUCHPAD_RIGHT_CLICK_ZONE,
+ Settings.System.TOUCHPAD_ACCELERATION_ENABLED,
Settings.System.CAMERA_FLASH_NOTIFICATION,
Settings.System.SCREEN_FLASH_NOTIFICATION,
Settings.System.SCREEN_FLASH_NOTIFICATION_COLOR,
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index b0309a8f..9fd0cc8 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -142,6 +142,8 @@
VALIDATORS.put(Secure.CAMERA_GESTURE_DISABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(
Secure.ACCESSIBILITY_AUTOCLICK_CURSOR_AREA_SIZE, NON_NEGATIVE_INTEGER_VALIDATOR);
+ VALIDATORS.put(
+ Secure.ACCESSIBILITY_AUTOCLICK_IGNORE_MINOR_CURSOR_MOVEMENT, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.ACCESSIBILITY_AUTOCLICK_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.ACCESSIBILITY_AUTOCLICK_DELAY, NON_NEGATIVE_INTEGER_VALIDATOR);
VALIDATORS.put(Secure.ACCESSIBILITY_LARGE_POINTER_ICON, BOOLEAN_VALIDATOR);
@@ -310,7 +312,6 @@
VALIDATORS.put(Secure.TAP_GESTURE, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.PEOPLE_STRIP, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.MEDIA_CONTROLS_RESUME, BOOLEAN_VALIDATOR);
- VALIDATORS.put(Secure.MEDIA_CONTROLS_RECOMMENDATION, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.MEDIA_CONTROLS_LOCK_SCREEN, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.ACCESSIBILITY_MAGNIFICATION_MODE,
new InclusiveIntegerRangeValidator(
@@ -454,5 +455,6 @@
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/src/android/provider/settings/validators/SystemSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
index 4d98a11..4f649ed 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
@@ -234,6 +234,7 @@
VALIDATORS.put(System.TOUCHPAD_TAP_DRAGGING, BOOLEAN_VALIDATOR);
VALIDATORS.put(System.TOUCHPAD_RIGHT_CLICK_ZONE, BOOLEAN_VALIDATOR);
VALIDATORS.put(System.TOUCHPAD_SYSTEM_GESTURES, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(System.TOUCHPAD_ACCELERATION_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(System.LOCK_TO_APP_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(
System.EGG_MODE,
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index 1c6d681..9505977 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -1718,6 +1718,9 @@
Settings.Secure.ACCESSIBILITY_AUTOCLICK_CURSOR_AREA_SIZE,
SecureSettingsProto.Accessibility.AUTOCLICK_CURSOR_AREA_SIZE);
dumpSetting(s, p,
+ Settings.Secure.ACCESSIBILITY_AUTOCLICK_IGNORE_MINOR_CURSOR_MOVEMENT,
+ SecureSettingsProto.Accessibility.AUTOCLICK_IGNORE_MINOR_CURSOR_MOVEMENT);
+ dumpSetting(s, p,
Settings.Secure.ACCESSIBILITY_AUTOCLICK_ENABLED,
SecureSettingsProto.Accessibility.AUTOCLICK_ENABLED);
dumpSetting(s, p,
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index cbdb36f..9aad5d5 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -689,6 +689,7 @@
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/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 4448000..a044738 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -998,6 +998,11 @@
<!-- Permission required for CTS test - CtsContentProviderMultiUserTest -->
<uses-permission android:name="android.permission.RESOLVE_COMPONENT_FOR_UID" />
+ <!-- Permissions required for CTS test - MediaQualityTest -->
+ <uses-permission android:name="android.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE" />
+ <uses-permission android:name="android.permission.MANAGE_GLOBAL_SOUND_QUALITY_SERVICE" />
+ <uses-permission android:name="android.permission.READ_COLOR_ZONES" />
+
<application
android:label="@string/app_label"
android:theme="@android:style/Theme.DeviceDefault.DayNight"
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index a935aac..30dcd09 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -531,9 +531,13 @@
"-Adagger.fastInit=enabled",
"-Adagger.explicitBindingConflictsWithInject=ERROR",
"-Adagger.strictMultibindingValidation=enabled",
+ "-Adagger.useBindingGraphFix=ENABLED",
"-Aroom.schemaLocation=frameworks/base/packages/SystemUI/schemas",
],
- kotlincflags: ["-Xjvm-default=all"],
+ kotlincflags: [
+ "-Xjvm-default=all",
+ "-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi",
+ ],
plugins: [
"androidx.room_room-compiler-plugin",
@@ -710,6 +714,9 @@
use_resource_processor: true,
manifest: "tests/AndroidManifest-base.xml",
resource_dirs: [],
+
+ kotlin_lang_version: "1.9",
+
additional_manifests: ["tests/AndroidManifest.xml"],
srcs: [
"tests/src/**/*.kt",
@@ -756,7 +763,12 @@
"-Xjvm-default=all",
// TODO(b/352363800): Why do we need this?
"-J-Xmx8192M",
+ "-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi",
],
+ javacflags: [
+ "-Adagger.useBindingGraphFix=ENABLED",
+ ],
+
aaptflags: [
"--extra-packages",
"com.android.systemui",
@@ -847,7 +859,6 @@
"androidx.test.ext.truth",
],
-
instrumentation_for: "SystemUIRobo-stub",
java_resource_dirs: ["tests/robolectric/config"],
plugins: [
@@ -884,7 +895,6 @@
"androidx.test.ext.truth",
],
-
instrumentation_for: "SystemUIRobo-stub",
java_resource_dirs: ["tests/robolectric/config"],
plugins: [
diff --git a/packages/SystemUI/OWNERS b/packages/SystemUI/OWNERS
index c6cc9a9..33e9919 100644
--- a/packages/SystemUI/OWNERS
+++ b/packages/SystemUI/OWNERS
@@ -24,6 +24,7 @@
bhnm@google.com
brycelee@google.com
brzezinski@google.com
+burakov@google.com
caitlinshk@google.com
cameronyee@google.com
chandruis@google.com
@@ -39,6 +40,7 @@
gallmann@google.com
graciecheng@google.com
gwasserman@google.com
+helencheuk@google.com
hwwang@google.com
hyunyoungs@google.com
ikateryna@google.com
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index b33421d..f753316 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -176,13 +176,6 @@
}
flag {
- name: "notifications_dismiss_pruned_summaries"
- namespace: "systemui"
- description: "NotifCollection.dismissNotifications will now dismiss summaries that are pruned from the shade."
- bug: "355967751"
-}
-
-flag {
name: "notification_transparent_header_fix"
namespace: "systemui"
description: "fix the transparent group header issue for async header inflation."
@@ -493,6 +486,14 @@
}
flag {
+ name: "status_bar_no_hun_behavior"
+ namespace: "systemui"
+ description: "When there's a HUN, don't show the HUN text or icon in the status bar. Instead, "
+ "continue showing the usual status bar."
+ bug: "385740230"
+}
+
+flag {
name: "promote_notifications_automatically"
namespace: "systemui"
description: "Flag to automatically turn certain notifications into promoted notifications so "
@@ -617,13 +618,6 @@
}
flag {
- name: "status_bar_connected_displays"
- namespace: "lse_desktop_experience"
- description: "Shows the status bar on connected displays"
- bug: "379264862"
-}
-
-flag {
name: "status_bar_switch_to_spn_from_data_spn"
namespace: "systemui"
description: "Fix usage of the SPN broadcast extras"
@@ -1919,3 +1913,37 @@
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "stabilize_heads_up_group"
+ namespace: "systemui"
+ description: "Stabilize heads up groups in VisualStabilityCoordinator"
+ bug: "381864715"
+ metadata {
+ 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
+ }
+}
+
+flag {
+ name: "ui_rich_ongoing_force_expanded"
+ namespace: "systemui"
+ description: "Force promoted notifications to always be expanded"
+ bug: "380901479"
+}
+
+flag {
+ name: "aod_ui_rich_ongoing"
+ namespace: "systemui"
+ description: "Show a rich ongoing notification on the always-on display (depends on ui_rich_ongoing)"
+ bug: "369151941"
+}
\ No newline at end of file
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 8a57e8c..f36f030 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.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 @@
// 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/core/src/com/android/compose/gesture/effect/OffsetOverscrollEffect.kt b/packages/SystemUI/compose/core/src/com/android/compose/gesture/effect/OffsetOverscrollEffect.kt
index d992403..fc01c78 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/gesture/effect/OffsetOverscrollEffect.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/gesture/effect/OffsetOverscrollEffect.kt
@@ -18,10 +18,10 @@
import androidx.annotation.VisibleForTesting
import androidx.compose.animation.core.AnimationSpec
-import androidx.compose.animation.core.Spring
-import androidx.compose.animation.core.spring
import androidx.compose.foundation.OverscrollEffect
import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
+import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
@@ -41,7 +41,7 @@
class OffsetOverscrollEffect(
orientation: Orientation,
animationScope: CoroutineScope,
- animationSpec: AnimationSpec<Float> = DefaultAnimationSpec,
+ animationSpec: AnimationSpec<Float>,
) : BaseContentOverscrollEffect(orientation, animationScope, animationSpec) {
private var _node: DelegatableNode = newNode()
override val node: DelegatableNode
@@ -71,13 +71,6 @@
companion object {
private val MaxDistance = 400.dp
- internal val DefaultAnimationSpec =
- spring(
- stiffness = Spring.StiffnessLow,
- dampingRatio = Spring.DampingRatioLowBouncy,
- visibilityThreshold = 0.5f,
- )
-
@VisibleForTesting
fun computeOffset(density: Density, overscrollDistance: Float): Int {
val maxDistancePx = with(density) { MaxDistance.toPx() }
@@ -87,10 +80,11 @@
}
}
+@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun rememberOffsetOverscrollEffect(
orientation: Orientation,
- animationSpec: AnimationSpec<Float> = OffsetOverscrollEffect.DefaultAnimationSpec,
+ animationSpec: AnimationSpec<Float> = MaterialTheme.motionScheme.defaultSpatialSpec(),
): OffsetOverscrollEffect {
val animationScope = rememberCoroutineScope()
return remember(orientation, animationScope, animationSpec) {
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 4399685..456edaf 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,11 +87,11 @@
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.rememberMutableSceneTransitionLayoutState
import com.android.compose.animation.scene.transitions
import com.android.compose.windowsizeclass.LocalWindowSizeClass
import com.android.systemui.bouncer.shared.model.BouncerActionButtonModel
@@ -494,7 +494,7 @@
val currentSceneKey =
if (isSplitAroundTheFold) SceneKeys.SplitSceneKey else SceneKeys.ContiguousSceneKey
- val state = remember { MutableSceneTransitionLayoutState(currentSceneKey, SceneTransitions) }
+ val state = rememberMutableSceneTransitionLayoutState(currentSceneKey, SceneTransitions)
// Update state whenever currentSceneKey has changed.
LaunchedEffect(state, currentSceneKey) {
@@ -515,7 +515,7 @@
}
@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 55b4293..fad8ae7 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.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 @@
}
@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 @@
}
@Composable
-private fun SceneScope.BouncerScene(
+private fun ContentScope.BouncerScene(
viewModel: BouncerSceneContentViewModel,
dialogFactory: BouncerDialogFactory,
modifier: Modifier = Modifier,
@@ -96,8 +96,8 @@
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 a2a91fc..824f5a2 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,16 +33,17 @@
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
+import com.android.compose.animation.scene.rememberMutableSceneTransitionLayoutState
import com.android.compose.animation.scene.transitions
import com.android.compose.modifiers.thenIf
import com.android.systemui.communal.shared.model.CommunalBackgroundType
@@ -165,13 +166,13 @@
viewModel.communalBackground.collectAsStateWithLifecycle(
initialValue = CommunalBackgroundType.ANIMATED
)
- val state: MutableSceneTransitionLayoutState = remember {
- MutableSceneTransitionLayoutState(
+ val state: MutableSceneTransitionLayoutState =
+ rememberMutableSceneTransitionLayoutState(
initialScene = currentSceneKey,
canChangeScene = { _ -> viewModel.canChangeScene() },
transitions = if (viewModel.v2FlagEnabled()) sceneTransitionsV2 else sceneTransitions,
)
- }
+
val isUiBlurred by viewModel.isUiBlurred.collectAsStateWithLifecycle()
val detector = remember { CommunalSwipeDetector() }
@@ -229,7 +230,7 @@
/** 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 0a0003e..fea3492 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.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 @@
) {
@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 @@
dialogFactory = dialogFactory,
widgetSection = widgetSection,
modifier = Modifier.element(Communal.Elements.Grid),
- sceneScope = this@Content,
+ contentScope = this@Content,
)
}
if (communalSettingsInteractor.isV2FlagEnabled()) {
@@ -193,6 +193,7 @@
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 068df8e..3c0480d 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.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 @@
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 @@
widgetConfigurator = widgetConfigurator,
interactionHandler = interactionHandler,
widgetSection = widgetSection,
- sceneScope = sceneScope,
+ contentScope = contentScope,
)
}
}
@@ -827,7 +827,7 @@
widgetConfigurator: WidgetConfigurator?,
interactionHandler: RemoteViews.InteractionHandler?,
widgetSection: CommunalAppWidgetSection,
- sceneScope: SceneScope?,
+ contentScope: ContentScope?,
) {
var gridModifier =
Modifier.align(Alignment.TopStart).onGloballyPositioned { setGridCoordinates(it) }
@@ -1009,7 +1009,7 @@
interactionHandler = interactionHandler,
widgetSection = widgetSection,
resizeableItemFrameViewModel = resizeableItemFrameViewModel,
- sceneScope = sceneScope,
+ contentScope = contentScope,
)
}
}
@@ -1065,8 +1065,7 @@
) {
Icon(
imageVector = Icons.Default.Add,
- contentDescription =
- stringResource(R.string.label_for_button_in_empty_state_cta),
+ contentDescription = null,
modifier = Modifier.size(24.dp),
)
Spacer(Modifier.width(ButtonDefaults.IconSpacing))
@@ -1261,7 +1260,7 @@
interactionHandler: RemoteViews.InteractionHandler?,
widgetSection: CommunalAppWidgetSection,
resizeableItemFrameViewModel: ResizeableItemFrameViewModel,
- sceneScope: SceneScope? = null,
+ contentScope: ContentScope? = null,
) {
when (model) {
is CommunalContentModel.WidgetContent.Widget ->
@@ -1285,7 +1284,7 @@
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 +1450,6 @@
} else {
Modifier
}
-
Box(
modifier =
modifier
@@ -1539,7 +1537,10 @@
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 +1702,11 @@
@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 +1789,7 @@
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 88b6510..143fbe4 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.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 @@
}
@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 3b335fa..1b0ddcb 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 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 @@
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 @@
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 f4374c6..6cd0c5d 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.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 @@
}
@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 5c5514a..7b2f9dc 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.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 @@
}
@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 c7c29f9..5e61af6 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 @@
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 @@
}
@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 adad446..c365ec5 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.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 @@
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 df36d07..cfafb62 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 @@
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 9643f19..c55a3fd 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.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 @@
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 af9a195..99a7633 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 @@
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 5e9ade1..52ccab3 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.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 @@
* 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 @@
}
@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 fb01e70..34c0bca 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.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 @@
private val aodBurnInViewModel: AodBurnInViewModel,
) {
@Composable
- fun SceneScope.SmallClock(
+ fun ContentScope.SmallClock(
burnInParams: BurnInParameters,
onTopChanged: (top: Float?) -> Unit,
modifier: Modifier = Modifier,
@@ -87,7 +87,7 @@
}
@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 597cbf2..4795e7c 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.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 @@
@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 4a9f44b..0ff567b 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.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 @@
) {
@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 0344ab8..2bc392d 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.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 @@
}
@Composable
- fun SceneScope.HeadsUpNotifications() {
+ fun ContentScope.HeadsUpNotifications() {
SnoozeableHeadsUpNotificationSpace(
stackScrollView = stackScrollView.get(),
viewModel = rememberViewModel("HeadsUpNotifications") { viewModelFactory.create() },
@@ -160,7 +160,7 @@
* 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 1cee4d6..c3ba7ab 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.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 @@
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 0d8a470..172c3f5 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.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 @@
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/TopAreaSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt
index 79cf24b..410499a 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt
@@ -27,7 +27,6 @@
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
-import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
@@ -36,7 +35,7 @@
import androidx.compose.ui.unit.IntOffset
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.compose.animation.scene.ContentScope
-import com.android.compose.animation.scene.MutableSceneTransitionLayoutState
+import com.android.compose.animation.scene.rememberMutableSceneTransitionLayoutState
import com.android.compose.modifiers.thenIf
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
import com.android.systemui.keyguard.ui.composable.blueprint.ClockScenes.largeClockScene
@@ -82,9 +81,11 @@
WeatherClockScenes.splitShadeLargeClockScene
}
- val state = remember {
- MutableSceneTransitionLayoutState(currentScene, ClockTransition.defaultClockTransitions)
- }
+ val state =
+ rememberMutableSceneTransitionLayoutState(
+ currentScene,
+ ClockTransition.defaultClockTransitions,
+ )
// Update state whenever currentSceneKey has changed.
LaunchedEffect(state, currentScene) {
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 73c4fab..6250da3 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.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 @@
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 @@
}
@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 @@
}
@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 @@
}
@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 @@
}
@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 @@
}
@Composable
- private fun SceneScope.WeatherElement(
+ private fun ContentScope.WeatherElement(
weatherClockElementViewId: Int,
clock: ClockController,
elementKey: ElementKey,
@@ -144,32 +126,28 @@
}
},
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 b5d7839..f5de7dc 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.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 @@
}
@Composable
-fun SceneScope.MediaCarousel(
+fun ContentScope.MediaCarousel(
isVisible: Boolean,
mediaHost: MediaHost,
modifier: Modifier = Modifier,
@@ -136,6 +136,6 @@
}
@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 bad7405..5252842 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 @@
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 @@
/** 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 @@
}
/** 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 @@
State.Gone
}
}
+
is TransitionState.Transition.ChangeScene ->
with(transitionState) {
if (
@@ -130,6 +133,7 @@
State.Gone
}
}
+
is TransitionState.Transition.OverlayTransition ->
with(transitionState) {
if (
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaContentPicker.kt b/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaContentPicker.kt
index d523232..215a433 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaContentPicker.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaContentPicker.kt
@@ -24,7 +24,6 @@
import com.android.compose.animation.scene.content.state.TransitionState
import com.android.systemui.scene.shared.model.Overlays
import com.android.systemui.scene.shared.model.Scenes
-import com.android.systemui.shade.shared.flag.DualShade
/** [ElementContentPicker] implementation for the media carousel object. */
object MediaContentPicker : StaticElementContentPicker {
@@ -46,8 +45,11 @@
toContentZIndex: Float,
): ContentKey {
return when {
- shouldElevateMedia(transition) -> {
- if (DualShade.isEnabled) Overlays.NotificationsShade else Scenes.Shade
+ transition.isTransitioningBetween(Scenes.Lockscreen, Scenes.Shade) -> {
+ Scenes.Shade
+ }
+ transition.isTransitioningBetween(Scenes.Lockscreen, Overlays.NotificationsShade) -> {
+ Overlays.NotificationsShade
}
transition.isTransitioningBetween(Scenes.Lockscreen, Scenes.Communal) -> {
Scenes.Lockscreen
@@ -71,14 +73,12 @@
}
}
}
-
- /** Returns true when the media should be laid on top of the rest for the given [transition]. */
- fun shouldElevateMedia(transition: TransitionState.Transition): Boolean {
- return transition.isTransitioningBetween(Scenes.Lockscreen, Scenes.Shade) ||
- transition.isTransitioningBetween(Scenes.Lockscreen, Overlays.NotificationsShade)
- }
}
+/** Whether media should be laid on top of the rest for the given [transition]. */
fun MediaContentPicker.shouldElevateMedia(layoutState: SceneTransitionLayoutState): Boolean {
- return layoutState.currentTransition?.let { shouldElevateMedia(it) } ?: false
+ return layoutState.currentTransition?.let { transition ->
+ transition.isTransitioningBetween(Scenes.Lockscreen, Scenes.Shade) ||
+ transition.isTransitioningBetween(Scenes.Lockscreen, Overlays.NotificationsShade)
+ } ?: false
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
index 183929c..a6918a7 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
@@ -96,9 +96,7 @@
import com.android.systemui.res.R
import com.android.systemui.scene.session.ui.composable.SaveableSession
import com.android.systemui.scene.session.ui.composable.rememberSession
-import com.android.systemui.scene.shared.model.Overlays
import com.android.systemui.scene.shared.model.Scenes
-import com.android.systemui.shade.shared.flag.DualShade
import com.android.systemui.shade.ui.composable.ShadeHeader
import com.android.systemui.statusbar.notification.stack.shared.model.AccessibilityScrollEvent
import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimBounds
@@ -124,12 +122,6 @@
}
}
-private val notificationsShadeContentKey: ContentKey
- get() = if (DualShade.isEnabled) Overlays.NotificationsShade else Scenes.Shade
-
-private val quickSettingsShadeContentKey: ContentKey
- get() = if (DualShade.isEnabled) Overlays.QuickSettingsShade else Scenes.QuickSettings
-
/**
* Adds the space where heads up notifications can appear in the scene. This should generally be the
* entire size of the scene.
@@ -270,7 +262,12 @@
HeadsUpNotificationSpace(
stackScrollView = stackScrollView,
viewModel = viewModel,
- useHunBounds = { shouldUseLockscreenHunBounds(layoutState.transitionState) },
+ useHunBounds = {
+ shouldUseLockscreenHunBounds(
+ layoutState.transitionState,
+ viewModel.quickSettingsShadeContentKey,
+ )
+ },
modifier = Modifier.align(Alignment.TopCenter),
)
NotificationStackCutoffGuideline(
@@ -494,11 +491,11 @@
if (
scrimOffset.value < 0 &&
(layoutState.isTransitioning(
- from = notificationsShadeContentKey,
+ from = viewModel.notificationsShadeContentKey,
to = Scenes.Gone,
) ||
layoutState.isTransitioning(
- from = notificationsShadeContentKey,
+ from = viewModel.notificationsShadeContentKey,
to = Scenes.Lockscreen,
))
) {
@@ -527,6 +524,7 @@
shouldAnimateScrimCornerRadius(
layoutState,
shouldPunchHoleBehindScrim,
+ viewModel.notificationsShadeContentKey,
),
)
.let { scrimRounding.value.toRoundedCornerShape(it) }
@@ -613,7 +611,12 @@
HeadsUpNotificationSpace(
stackScrollView = stackScrollView,
viewModel = viewModel,
- useHunBounds = { !shouldUseLockscreenHunBounds(layoutState.transitionState) },
+ useHunBounds = {
+ !shouldUseLockscreenHunBounds(
+ layoutState.transitionState,
+ viewModel.quickSettingsShadeContentKey,
+ )
+ },
modifier = Modifier.padding(top = stackTopPadding),
)
}
@@ -720,20 +723,24 @@
return state is TransitionState.Idle && state.isOnLockscreen()
}
-private fun shouldUseLockscreenHunBounds(state: TransitionState): Boolean {
+private fun shouldUseLockscreenHunBounds(
+ state: TransitionState,
+ quickSettingsShade: ContentKey,
+): Boolean {
return when (state) {
is TransitionState.Idle -> state.isOnLockscreen()
is TransitionState.Transition ->
- state.isTransitioning(from = quickSettingsShadeContentKey, to = Scenes.Lockscreen)
+ state.isTransitioning(from = quickSettingsShade, to = Scenes.Lockscreen)
}
}
private fun shouldAnimateScrimCornerRadius(
state: SceneTransitionLayoutState,
shouldPunchHoleBehindScrim: Boolean,
+ notificationsShade: ContentKey,
): Boolean {
return shouldPunchHoleBehindScrim ||
- state.isTransitioning(from = notificationsShadeContentKey, to = Scenes.Lockscreen)
+ state.isTransitioning(from = notificationsShade, to = Scenes.Lockscreen)
}
private fun calculateCornerRadius(
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 7f7273d..25b673b 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.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 kotlinx.coroutines.launch
@Composable
-fun SceneScope.FooterActionsWithAnimatedVisibility(
+fun ContentScope.FooterActionsWithAnimatedVisibility(
viewModel: FooterActionsViewModel,
isCustomizing: Boolean,
customizingAnimationDuration: Int,
@@ -256,6 +256,7 @@
} else {
NumberButton(
model.foregroundServicesCount,
+ contentDescription = model.text,
showNewDot = model.hasNewChanges,
onClick = model.onClick,
)
@@ -284,6 +285,7 @@
@Composable
private fun NumberButton(
number: Int,
+ contentDescription: String,
showNewDot: Boolean,
onClick: (Expandable) -> Unit,
modifier: Modifier = Modifier,
@@ -308,14 +310,16 @@
) {
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 @@
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 @@
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 @@
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 58336c2..b826187 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.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 @@
}
}
-private fun SceneScope.stateForQuickSettingsContent(
+private fun ContentScope.stateForQuickSettingsContent(
isSplitShade: Boolean,
squishiness: () -> Float = { QuickSettings.SharedValues.SquishinessValues.Default },
): QSSceneAdapter.State {
@@ -141,7 +141,7 @@
/**
* 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 @@
* * 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 26cf706..4bfbb3a 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.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 @@
}
@Composable
- override fun SceneScope.Content(modifier: Modifier) {
+ override fun ContentScope.Content(modifier: Modifier) {
QuickSettingsScene(
notificationStackScrollView = notificationStackScrollView.get(),
viewModelFactory = contentViewModelFactory,
@@ -164,7 +164,7 @@
}
@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 9ee25c3..2175a59 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.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 @@
}
@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 8d8ab8e..6c80c69 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 @@
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 @@
/** 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/scene/ui/composable/SceneContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
index a6a6362..0ca7a6a 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
@@ -36,7 +36,6 @@
import androidx.compose.ui.platform.LocalView
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.compose.animation.scene.ContentKey
-import com.android.compose.animation.scene.MutableSceneTransitionLayoutState
import com.android.compose.animation.scene.OverlayKey
import com.android.compose.animation.scene.SceneKey
import com.android.compose.animation.scene.SceneTransitionLayout
@@ -44,6 +43,7 @@
import com.android.compose.animation.scene.UserAction
import com.android.compose.animation.scene.UserActionResult
import com.android.compose.animation.scene.observableTransitionState
+import com.android.compose.animation.scene.rememberMutableSceneTransitionLayoutState
import com.android.systemui.lifecycle.rememberActivated
import com.android.systemui.qs.ui.adapter.QSSceneAdapter
import com.android.systemui.qs.ui.composable.QuickSettingsTheme
@@ -94,29 +94,27 @@
val sceneJankMonitor =
rememberActivated(traceName = "sceneJankMonitor") { sceneJankMonitorFactory.create() }
- val state: MutableSceneTransitionLayoutState =
- remember(view, sceneJankMonitor) {
- MutableSceneTransitionLayoutState(
- initialScene = initialSceneKey,
- canChangeScene = { toScene -> viewModel.canChangeScene(toScene) },
- transitions = sceneTransitions,
- onTransitionStart = { transition ->
- sceneJankMonitor.onTransitionStart(
- view = view,
- from = transition.fromContent,
- to = transition.toContent,
- cuj = transition.cuj,
- )
- },
- onTransitionEnd = { transition ->
- sceneJankMonitor.onTransitionEnd(
- from = transition.fromContent,
- to = transition.toContent,
- cuj = transition.cuj,
- )
- },
- )
- }
+ val state =
+ rememberMutableSceneTransitionLayoutState(
+ initialScene = initialSceneKey,
+ canChangeScene = { toScene -> viewModel.canChangeScene(toScene) },
+ transitions = sceneTransitions,
+ onTransitionStart = { transition ->
+ sceneJankMonitor.onTransitionStart(
+ view = view,
+ from = transition.fromContent,
+ to = transition.toContent,
+ cuj = transition.cuj,
+ )
+ },
+ onTransitionEnd = { transition ->
+ sceneJankMonitor.onTransitionEnd(
+ from = transition.fromContent,
+ to = transition.toContent,
+ cuj = transition.cuj,
+ )
+ },
+ )
DisposableEffect(state) {
val dataSource = SceneTransitionLayoutDataSource(state, coroutineScope)
@@ -179,6 +177,12 @@
}
}
) {
+ SceneRevealScrim(
+ viewModel = viewModel.lightRevealScrim,
+ wallpaperViewModel = viewModel.wallpaperViewModel,
+ modifier = Modifier.fillMaxSize(),
+ )
+
SceneTransitionLayout(
state = state,
modifier = modifier.fillMaxSize(),
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt
index aa8b4ae..d7545cb 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt
@@ -1,6 +1,5 @@
package com.android.systemui.scene.ui.composable
-import androidx.compose.animation.core.spring
import com.android.compose.animation.scene.TransitionKey
import com.android.compose.animation.scene.transitions
import com.android.internal.jank.Cuj
@@ -48,9 +47,6 @@
val SceneContainerTransitions = transitions {
interruptionHandler = SceneContainerInterruptionHandler
- defaultMotionSpatialSpec =
- spring(stiffness = 300f, dampingRatio = 0.8f, visibilityThreshold = 0.5f)
-
// Scene transitions
from(Scenes.Bouncer, to = Scenes.Gone) { bouncerToGoneTransition() }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneRevealScrim.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneRevealScrim.kt
new file mode 100644
index 0000000..a1f89fcb
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneRevealScrim.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.scene.ui.composable
+
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.viewinterop.AndroidView
+import com.android.systemui.keyguard.ui.binder.LightRevealScrimViewBinder
+import com.android.systemui.keyguard.ui.viewmodel.LightRevealScrimViewModel
+import com.android.systemui.statusbar.LightRevealScrim
+import com.android.systemui.wallpapers.ui.viewmodel.WallpaperViewModel
+
+@Composable
+fun SceneRevealScrim(
+ viewModel: LightRevealScrimViewModel,
+ wallpaperViewModel: WallpaperViewModel,
+ modifier: Modifier = Modifier,
+) {
+ AndroidView(
+ factory = { context ->
+ LightRevealScrim(context).apply {
+ LightRevealScrimViewBinder.bind(
+ revealScrim = this,
+ viewModel = viewModel,
+ wallpaperViewModel = wallpaperViewModel,
+ )
+ }
+ },
+ modifier = modifier,
+ )
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToSplitShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToSplitShadeTransition.kt
index e30e7d3..b53c4e238 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToSplitShadeTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToSplitShadeTransition.kt
@@ -16,8 +16,6 @@
package com.android.systemui.scene.ui.composable.transitions
-import androidx.compose.animation.core.Spring
-import androidx.compose.animation.core.spring
import androidx.compose.animation.core.tween
import com.android.compose.animation.scene.TransitionBuilder
import com.android.compose.animation.scene.UserActionDistance
@@ -30,11 +28,6 @@
fun TransitionBuilder.goneToSplitShadeTransition(durationScale: Double = 1.0) {
spec = tween(durationMillis = (DefaultDuration * durationScale).inWholeMilliseconds.toInt())
- motionSpatialSpec =
- spring(
- stiffness = Spring.StiffnessMediumLow,
- visibilityThreshold = Shade.Dimensions.ScrimVisibilityThreshold,
- )
distance = UserActionDistance { fromContent, _, _ ->
val fromContentSize = checkNotNull(fromContent.targetSize())
fromContentSize.height.toFloat() * 2 / 3f
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToSplitShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToSplitShadeTransition.kt
index 1a243ca..6c8885e 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToSplitShadeTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToSplitShadeTransition.kt
@@ -16,8 +16,6 @@
package com.android.systemui.scene.ui.composable.transitions
-import androidx.compose.animation.core.Spring
-import androidx.compose.animation.core.spring
import androidx.compose.animation.core.tween
import com.android.compose.animation.scene.TransitionBuilder
import com.android.compose.animation.scene.UserActionDistance
@@ -29,11 +27,6 @@
fun TransitionBuilder.lockscreenToSplitShadeTransition(durationScale: Double = 1.0) {
spec = tween(durationMillis = (DefaultDuration * durationScale).inWholeMilliseconds.toInt())
- motionSpatialSpec =
- spring(
- stiffness = Spring.StiffnessMediumLow,
- visibilityThreshold = Shade.Dimensions.ScrimVisibilityThreshold,
- )
distance = UserActionDistance { fromContent, _, _ ->
val fromContentSize = checkNotNull(fromContent.targetSize())
fromContentSize.height.toFloat() * 2 / 3f
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromNotificationsShadeToQuickSettingsShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromNotificationsShadeToQuickSettingsShadeTransition.kt
index a9af95b..9a7f99f 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromNotificationsShadeToQuickSettingsShadeTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromNotificationsShadeToQuickSettingsShadeTransition.kt
@@ -16,22 +16,14 @@
package com.android.systemui.scene.ui.composable.transitions
-import androidx.compose.animation.core.Spring
-import androidx.compose.animation.core.spring
import androidx.compose.animation.core.tween
import com.android.compose.animation.scene.TransitionBuilder
-import com.android.systemui.shade.ui.composable.Shade
import kotlin.time.Duration.Companion.milliseconds
fun TransitionBuilder.notificationsShadeToQuickSettingsShadeTransition(
durationScale: Double = 1.0
) {
spec = tween(durationMillis = (DefaultDuration * durationScale).inWholeMilliseconds.toInt())
- motionSpatialSpec =
- spring(
- stiffness = Spring.StiffnessMediumLow,
- visibilityThreshold = Shade.Dimensions.ScrimVisibilityThreshold,
- )
}
private val DefaultDuration = 300.milliseconds
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToNotificationsShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToNotificationsShadeTransition.kt
index ddea585..019639d 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToNotificationsShadeTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToNotificationsShadeTransition.kt
@@ -16,8 +16,6 @@
package com.android.systemui.scene.ui.composable.transitions
-import androidx.compose.animation.core.Spring
-import androidx.compose.animation.core.spring
import androidx.compose.animation.core.tween
import com.android.compose.animation.scene.Edge
import com.android.compose.animation.scene.TransitionBuilder
@@ -26,16 +24,10 @@
import com.android.systemui.notifications.ui.composable.NotificationsShade
import com.android.systemui.scene.shared.model.Overlays
import com.android.systemui.shade.ui.composable.OverlayShade
-import com.android.systemui.shade.ui.composable.Shade
import kotlin.time.Duration.Companion.milliseconds
fun TransitionBuilder.toNotificationsShadeTransition(durationScale: Double = 1.0) {
spec = tween(durationMillis = (DefaultDuration * durationScale).inWholeMilliseconds.toInt())
- motionSpatialSpec =
- spring(
- stiffness = Spring.StiffnessMediumLow,
- visibilityThreshold = Shade.Dimensions.ScrimVisibilityThreshold,
- )
// Ensure the clock isn't clipped by the shade outline during the transition from lockscreen.
sharedElement(
ClockElementKeys.smallClockElementKey,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToQuickSettingsShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToQuickSettingsShadeTransition.kt
index e477a41..faccf14 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToQuickSettingsShadeTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToQuickSettingsShadeTransition.kt
@@ -16,23 +16,15 @@
package com.android.systemui.scene.ui.composable.transitions
-import androidx.compose.animation.core.Spring
-import androidx.compose.animation.core.spring
import androidx.compose.animation.core.tween
import com.android.compose.animation.scene.Edge
import com.android.compose.animation.scene.TransitionBuilder
import com.android.compose.animation.scene.UserActionDistance
import com.android.systemui.shade.ui.composable.OverlayShade
-import com.android.systemui.shade.ui.composable.Shade
import kotlin.time.Duration.Companion.milliseconds
fun TransitionBuilder.toQuickSettingsShadeTransition(durationScale: Double = 1.0) {
spec = tween(durationMillis = (DefaultDuration * durationScale).inWholeMilliseconds.toInt())
- motionSpatialSpec =
- spring(
- stiffness = Spring.StiffnessMediumLow,
- visibilityThreshold = Shade.Dimensions.ScrimVisibilityThreshold,
- )
distance = UserActionDistance { fromContent, _, _ ->
val fromContentSize = checkNotNull(fromContent.targetSize())
fromContentSize.height.toFloat() * 2 / 3f
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToShadeTransition.kt
index 4db4934..2ed296b 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToShadeTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToShadeTransition.kt
@@ -16,8 +16,6 @@
package com.android.systemui.scene.ui.composable.transitions
-import androidx.compose.animation.core.Spring
-import androidx.compose.animation.core.spring
import androidx.compose.animation.core.tween
import com.android.compose.animation.scene.Edge
import com.android.compose.animation.scene.TransitionBuilder
@@ -32,11 +30,6 @@
fun TransitionBuilder.toShadeTransition(durationScale: Double = 1.0) {
spec = tween(durationMillis = (DefaultDuration * durationScale).inWholeMilliseconds.toInt())
- motionSpatialSpec =
- spring(
- stiffness = Spring.StiffnessMediumLow,
- visibilityThreshold = Shade.Dimensions.ScrimVisibilityThreshold,
- )
distance = UserActionDistance { _, _, _ ->
Notifications.Elements.NotificationScrim.targetOffset(Scenes.Shade)?.y ?: 0f
}
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 bfcde7d..3131b53 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.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 @@
}
@Composable
-fun SceneScope.CollapsedShadeHeader(
+fun ContentScope.CollapsedShadeHeader(
viewModelFactory: ShadeHeaderViewModel.Factory,
createTintedIconManager: (ViewGroup, StatusBarLocation) -> TintedIconManager,
createBatteryMeterViewController: (ViewGroup, StatusBarLocation) -> BatteryMeterViewController,
@@ -264,7 +264,7 @@
}
@Composable
-fun SceneScope.ExpandedShadeHeader(
+fun ContentScope.ExpandedShadeHeader(
viewModelFactory: ShadeHeaderViewModel.Factory,
createTintedIconManager: (ViewGroup, StatusBarLocation) -> TintedIconManager,
createBatteryMeterViewController: (ViewGroup, StatusBarLocation) -> BatteryMeterViewController,
@@ -339,7 +339,7 @@
}
@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 @@
}
@Composable
-private fun SceneScope.StatusIcons(
+private fun ContentScope.StatusIcons(
viewModel: ShadeHeaderViewModel,
createTintedIconManager: (ViewGroup, StatusBarLocation) -> TintedIconManager,
statusBarIconController: StatusBarIconController,
@@ -548,7 +548,10 @@
}
@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 0d3bab2..f829a0d 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.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 @@
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 @@
}
@Composable
-private fun SceneScope.ShadeScene(
+private fun ContentScope.ShadeScene(
notificationStackScrollView: NotificationScrollView,
viewModel: ShadeSceneContentViewModel,
notificationsPlaceholderViewModel: NotificationsPlaceholderViewModel,
@@ -242,7 +242,7 @@
}
@Composable
-private fun SceneScope.SingleShade(
+private fun ContentScope.SingleShade(
notificationStackScrollView: NotificationScrollView,
viewModel: ShadeSceneContentViewModel,
notificationsPlaceholderViewModel: NotificationsPlaceholderViewModel,
@@ -410,7 +410,7 @@
}
@Composable
-private fun SceneScope.SplitShade(
+private fun ContentScope.SplitShade(
notificationStackScrollView: NotificationScrollView,
viewModel: ShadeSceneContentViewModel,
notificationsPlaceholderViewModel: NotificationsPlaceholderViewModel,
@@ -632,7 +632,7 @@
}
@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/spatialaudio/ui/composable/SpatialAudioPopup.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioPopup.kt
index 3295dde..bcd4d92 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioPopup.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioPopup.kt
@@ -28,6 +28,7 @@
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.text.style.TextOverflow
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.internal.logging.UiEventLogger
import com.android.systemui.animation.Expandable
@@ -54,7 +55,7 @@
VolumePanelUiEvent.VOLUME_PANEL_SPATIAL_AUDIO_POP_UP_SHOWN,
0,
null,
- viewModel.spatialAudioButtons.value.indexOfFirst { it.button.isActive }
+ viewModel.spatialAudioButtons.value.indexOfFirst { it.button.isActive },
)
val gravity = horizontalGravity or Gravity.BOTTOM
volumePanelPopup.show(expandable, gravity, { Title() }, { Content(it) })
@@ -95,14 +96,14 @@
icon = { Icon(icon = buttonViewModel.button.icon) },
label = {
Text(
- modifier = Modifier.basicMarquee(),
text = label,
style = MaterialTheme.typography.labelMedium,
color = LocalContentColor.current,
textAlign = TextAlign.Center,
- maxLines = 2
+ maxLines = 1,
+ overflow = TextOverflow.Ellipsis,
)
- }
+ },
)
}
}
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 25892c5..d9e8f02 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 @@
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 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 @@
VolumeSlider(
modifier =
- Modifier.padding(top = 16.dp)
- .fillMaxWidth()
+ Modifier.fillMaxWidth()
.animateEnterExit(
enter =
enterTransition(
@@ -157,7 +153,10 @@
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 5f991fb..bdd0da9 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.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.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.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 @@
}
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 @@
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 @@
}
},
)
+ 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 6349c14..bc30132 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 @@
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/AnimateContent.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateContent.kt
index d876606..b04d89d 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateContent.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateContent.kt
@@ -19,6 +19,7 @@
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.AnimationVector1D
import androidx.compose.animation.core.SpringSpec
+import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import com.android.compose.animation.scene.content.state.TransitionState
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
@@ -32,7 +33,10 @@
): Job {
oneOffAnimation.onRun = {
// Animate the progress to its target value.
- val animationSpec = transition.transformationSpec.progressSpec
+ @OptIn(ExperimentalMaterial3ExpressiveApi::class)
+ val animationSpec =
+ transition.transformationSpec.progressSpec
+ ?: layoutState.motionScheme.defaultSpatialSpec()
val visibilityThreshold =
(animationSpec as? SpringSpec)?.visibilityThreshold ?: ProgressVisibilityThreshold
val replacedTransition = transition.replacedTransition
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 ef11932..431a376 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
@@ -159,6 +159,9 @@
/** The state of the [SceneTransitionLayout] in which this content is contained. */
val layoutState: SceneTransitionLayoutState
+ /** The [LookaheadScope] used by the [SceneTransitionLayout]. */
+ val lookaheadScope: LookaheadScope
+
/**
* Tag an element identified by [key].
*
@@ -264,9 +267,6 @@
)
}
-@Deprecated("Use ContentScope instead", ReplaceWith("ContentScope"))
-typealias SceneScope = ContentScope
-
@Stable
@ElementDsl
interface ContentScope : BaseContentScope {
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
index 8153586..5b275a5 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
@@ -14,14 +14,22 @@
* limitations under the License.
*/
+@file:OptIn(ExperimentalMaterial3ExpressiveApi::class)
+
package com.android.compose.animation.scene
import android.util.Log
import androidx.annotation.VisibleForTesting
+import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.MotionScheme
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.Stable
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.util.fastAll
import androidx.compose.ui.util.fastAny
@@ -226,6 +234,7 @@
*/
fun MutableSceneTransitionLayoutState(
initialScene: SceneKey,
+ motionScheme: MotionScheme,
transitions: SceneTransitions = SceneTransitions.Empty,
initialOverlays: Set<OverlayKey> = emptySet(),
canChangeScene: (SceneKey) -> Boolean = { true },
@@ -237,6 +246,7 @@
): MutableSceneTransitionLayoutState {
return MutableSceneTransitionLayoutStateImpl(
initialScene,
+ motionScheme,
transitions,
initialOverlays,
canChangeScene,
@@ -248,19 +258,62 @@
)
}
+@Composable
+fun rememberMutableSceneTransitionLayoutState(
+ initialScene: SceneKey,
+ transitions: SceneTransitions = SceneTransitions.Empty,
+ initialOverlays: Set<OverlayKey> = emptySet(),
+ canChangeScene: (SceneKey) -> Boolean = { true },
+ canShowOverlay: (OverlayKey) -> Boolean = { true },
+ canHideOverlay: (OverlayKey) -> Boolean = { true },
+ canReplaceOverlay: (from: OverlayKey, to: OverlayKey) -> Boolean = { _, _ -> true },
+ onTransitionStart: (TransitionState.Transition) -> Unit = {},
+ onTransitionEnd: (TransitionState.Transition) -> Unit = {},
+): MutableSceneTransitionLayoutState {
+ val motionScheme = MaterialTheme.motionScheme
+ val layoutState = remember {
+ MutableSceneTransitionLayoutStateImpl(
+ initialScene = initialScene,
+ motionScheme = motionScheme,
+ transitions = transitions,
+ initialOverlays = initialOverlays,
+ canChangeScene = canChangeScene,
+ canShowOverlay = canShowOverlay,
+ canHideOverlay = canHideOverlay,
+ canReplaceOverlay = canReplaceOverlay,
+ onTransitionStart = onTransitionStart,
+ onTransitionEnd = onTransitionEnd,
+ )
+ }
+
+ SideEffect {
+ layoutState.transitions = transitions
+ layoutState.motionScheme = motionScheme
+ layoutState.canChangeScene = canChangeScene
+ layoutState.canShowOverlay = canShowOverlay
+ layoutState.canHideOverlay = canHideOverlay
+ layoutState.canReplaceOverlay = canReplaceOverlay
+ layoutState.onTransitionStart = onTransitionStart
+ layoutState.onTransitionEnd = onTransitionEnd
+ }
+ return layoutState
+}
+
/** A [MutableSceneTransitionLayoutState] that holds the value for the current scene. */
+@OptIn(ExperimentalMaterial3ExpressiveApi::class)
internal class MutableSceneTransitionLayoutStateImpl(
initialScene: SceneKey,
+ internal var motionScheme: MotionScheme,
override var transitions: SceneTransitions = transitions {},
initialOverlays: Set<OverlayKey> = emptySet(),
- internal val canChangeScene: (SceneKey) -> Boolean = { true },
- internal val canShowOverlay: (OverlayKey) -> Boolean = { true },
- internal val canHideOverlay: (OverlayKey) -> Boolean = { true },
- internal val canReplaceOverlay: (from: OverlayKey, to: OverlayKey) -> Boolean = { _, _ ->
+ internal var canChangeScene: (SceneKey) -> Boolean = { true },
+ internal var canShowOverlay: (OverlayKey) -> Boolean = { true },
+ internal var canHideOverlay: (OverlayKey) -> Boolean = { true },
+ internal var canReplaceOverlay: (from: OverlayKey, to: OverlayKey) -> Boolean = { _, _ ->
true
},
- private val onTransitionStart: (TransitionState.Transition) -> Unit = {},
- private val onTransitionEnd: (TransitionState.Transition) -> Unit = {},
+ internal var onTransitionStart: (TransitionState.Transition) -> Unit = {},
+ internal var onTransitionEnd: (TransitionState.Transition) -> Unit = {},
) : MutableSceneTransitionLayoutState {
private val creationThread: Thread = Thread.currentThread()
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
index d50304d4..52c9ddc 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
@@ -17,10 +17,7 @@
package com.android.compose.animation.scene
import androidx.compose.animation.core.AnimationSpec
-import androidx.compose.animation.core.Spring
-import androidx.compose.animation.core.SpringSpec
import androidx.compose.animation.core.snap
-import androidx.compose.animation.core.spring
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.util.fastForEach
@@ -34,7 +31,6 @@
/** The transitions configuration of a [SceneTransitionLayout]. */
class SceneTransitions
internal constructor(
- internal val defaultMotionSpatialSpec: SpringSpec<Float>,
internal val transitionSpecs: List<TransitionSpecImpl>,
internal val interruptionHandler: InterruptionHandler,
) {
@@ -123,16 +119,8 @@
)
companion object {
- internal val DefaultSwipeSpec =
- spring(
- stiffness = Spring.StiffnessMediumLow,
- dampingRatio = Spring.DampingRatioLowBouncy,
- visibilityThreshold = OffsetVisibilityThreshold,
- )
-
val Empty =
SceneTransitions(
- defaultMotionSpatialSpec = DefaultSwipeSpec,
transitionSpecs = emptyList(),
interruptionHandler = DefaultInterruptionHandler,
)
@@ -188,15 +176,7 @@
* The [AnimationSpec] used to animate the associated transition progress from `0` to `1` when
* the transition is triggered (i.e. it is not gesture-based).
*/
- val progressSpec: AnimationSpec<Float>
-
- /**
- * The [SpringSpec] used to animate the associated transition progress when the transition was
- * started by a swipe and is now animating back to a scene because the user lifted their finger.
- *
- * If `null`, then the [SceneTransitions.defaultMotionSpatialSpec] will be used.
- */
- val motionSpatialSpec: AnimationSpec<Float>?
+ val progressSpec: AnimationSpec<Float>?
/**
* The distance it takes for this transition to animate from 0% to 100% when it is driven by a
@@ -213,7 +193,6 @@
internal val Empty =
TransformationSpecImpl(
progressSpec = snap(),
- motionSpatialSpec = null,
distance = null,
transformationMatchers = emptyList(),
)
@@ -246,7 +225,6 @@
val reverse = transformationSpec.invoke(transition)
TransformationSpecImpl(
progressSpec = reverse.progressSpec,
- motionSpatialSpec = reverse.motionSpatialSpec,
distance = reverse.distance,
transformationMatchers =
reverse.transformationMatchers.map {
@@ -275,8 +253,7 @@
* [ElementTransformations].
*/
internal class TransformationSpecImpl(
- override val progressSpec: AnimationSpec<Float>,
- override val motionSpatialSpec: SpringSpec<Float>?,
+ override val progressSpec: AnimationSpec<Float>?,
override val distance: UserActionDistance?,
override val transformationMatchers: List<TransformationMatcher>,
) : TransformationSpec {
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeAnimation.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeAnimation.kt
index 4137f5f..3bd37ad 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeAnimation.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeAnimation.kt
@@ -14,12 +14,15 @@
* limitations under the License.
*/
+@file:OptIn(ExperimentalMaterial3ExpressiveApi::class)
+
package com.android.compose.animation.scene
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.AnimationSpec
import androidx.compose.animation.core.AnimationVector1D
import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
@@ -364,10 +367,7 @@
check(isAnimatingOffset())
- val motionSpatialSpec =
- spec
- ?: contentTransition.transformationSpec.motionSpatialSpec
- ?: layoutState.transitions.defaultMotionSpatialSpec
+ val motionSpatialSpec = spec ?: layoutState.motionScheme.defaultSpatialSpec()
val velocityConsumed = CompletableDeferred<Float>()
offsetAnimationRunnable.complete {
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
index 776d553..a29c1bb 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
@@ -19,7 +19,6 @@
import androidx.compose.animation.core.AnimationSpec
import androidx.compose.animation.core.Easing
import androidx.compose.animation.core.LinearEasing
-import androidx.compose.animation.core.SpringSpec
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
@@ -37,12 +36,6 @@
@TransitionDsl
interface SceneTransitionsBuilder {
/**
- * The default [AnimationSpec] used when after the user lifts their finger after starting a
- * swipe to transition, to animate back into one of the 2 scenes we are transitioning to.
- */
- var defaultMotionSpatialSpec: SpringSpec<Float>
-
- /**
* The [InterruptionHandler] used when transitions are interrupted. Defaults to
* [DefaultInterruptionHandler].
*/
@@ -139,15 +132,7 @@
* The [AnimationSpec] used to animate the associated transition progress from `0` to `1` when
* the transition is triggered (i.e. it is not gesture-based).
*/
- var spec: AnimationSpec<Float>
-
- /**
- * The [SpringSpec] used to animate the associated transition progress when the transition was
- * started by a swipe and is now animating back to a scene because the user lifted their finger.
- *
- * If `null`, then the [SceneTransitionsBuilder.defaultMotionSpatialSpec] will be used.
- */
- var motionSpatialSpec: SpringSpec<Float>?
+ var spec: AnimationSpec<Float>?
/** The CUJ associated to this transitions. */
@CujType var cuj: Int?
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
index 9a9b05e..ccb7024 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
@@ -19,10 +19,7 @@
import androidx.compose.animation.core.AnimationSpec
import androidx.compose.animation.core.DurationBasedAnimationSpec
import androidx.compose.animation.core.Easing
-import androidx.compose.animation.core.Spring
-import androidx.compose.animation.core.SpringSpec
import androidx.compose.animation.core.VectorConverter
-import androidx.compose.animation.core.spring
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.unit.Dp
import com.android.compose.animation.scene.content.state.TransitionState
@@ -42,14 +39,12 @@
internal fun transitionsImpl(builder: SceneTransitionsBuilder.() -> Unit): SceneTransitions {
val impl = SceneTransitionsBuilderImpl().apply(builder)
return SceneTransitions(
- defaultMotionSpatialSpec = impl.defaultMotionSpatialSpec,
transitionSpecs = impl.transitionSpecs,
interruptionHandler = impl.interruptionHandler,
)
}
private class SceneTransitionsBuilderImpl : SceneTransitionsBuilder {
- override var defaultMotionSpatialSpec: SpringSpec<Float> = SceneTransitions.DefaultSwipeSpec
override var interruptionHandler: InterruptionHandler = DefaultInterruptionHandler
val transitionSpecs = mutableListOf<TransitionSpecImpl>()
@@ -109,7 +104,6 @@
val impl = TransitionBuilderImpl(transition).apply(builder)
return TransformationSpecImpl(
progressSpec = impl.spec,
- motionSpatialSpec = impl.motionSpatialSpec,
distance = impl.distance,
transformationMatchers = impl.transformationMatchers,
)
@@ -212,8 +206,7 @@
internal class TransitionBuilderImpl(override val transition: TransitionState.Transition) :
BaseTransitionBuilderImpl(), TransitionBuilder {
- override var spec: AnimationSpec<Float> = spring(stiffness = Spring.StiffnessLow)
- override var motionSpatialSpec: SpringSpec<Float>? = null
+ override var spec: AnimationSpec<Float>? = null
override var distance: UserActionDistance? = null
override var cuj: Int? = null
private val durationMillis: Int by lazy {
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt
index 59b4a09..6ccd498 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt
@@ -17,8 +17,12 @@
package com.android.compose.animation.scene.content
import android.annotation.SuppressLint
+import androidx.compose.animation.core.AnimationSpec
+import androidx.compose.animation.core.AnimationVector
+import androidx.compose.animation.core.TwoWayConverter
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.layout.Box
+import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.getValue
@@ -27,6 +31,7 @@
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.LookaheadScope
import androidx.compose.ui.layout.approachLayout
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.unit.IntSize
@@ -119,16 +124,28 @@
override val layoutState: SceneTransitionLayoutState = layoutImpl.state
+ override val lookaheadScope: LookaheadScope
+ get() = layoutImpl.lookaheadScope
+
+ @OptIn(ExperimentalMaterial3ExpressiveApi::class)
+ private val animationSpatialSpec =
+ object : AnimationSpec<Float> {
+ override fun <V : AnimationVector> vectorize(converter: TwoWayConverter<Float, V>) =
+ layoutImpl.state.motionScheme.defaultSpatialSpec<Float>().vectorize(converter)
+ }
+
private val _verticalOverscrollEffect =
OffsetOverscrollEffect(
orientation = Orientation.Vertical,
animationScope = layoutImpl.animationScope,
+ animationSpec = animationSpatialSpec,
)
private val _horizontalOverscrollEffect =
OffsetOverscrollEffect(
orientation = Orientation.Horizontal,
animationScope = layoutImpl.animationScope,
+ animationSpec = animationSpatialSpec,
)
val verticalOverscrollGestureEffect = GestureEffect(_verticalOverscrollEffect)
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/TransitionState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/TransitionState.kt
index e9542c8..e23e234 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/TransitionState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/TransitionState.kt
@@ -18,8 +18,7 @@
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.AnimationVector1D
-import androidx.compose.animation.core.Spring
-import androidx.compose.animation.core.spring
+import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.runtime.Stable
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
@@ -385,14 +384,13 @@
fun create(): Animatable<Float, AnimationVector1D> {
val animatable = Animatable(1f, visibilityThreshold = ProgressVisibilityThreshold)
layoutImpl.animationScope.launch {
- val motionSpatialSpec = layoutImpl.state.transitions.defaultMotionSpatialSpec
- val progressSpec =
- spring(
- stiffness = motionSpatialSpec.stiffness,
- dampingRatio = Spring.DampingRatioNoBouncy,
- visibilityThreshold = ProgressVisibilityThreshold,
- )
- animatable.animateTo(0f, progressSpec)
+ @OptIn(ExperimentalMaterial3ExpressiveApi::class)
+ animatable.animateTo(
+ targetValue = 0f,
+ // Quickly animate (use fast) the current transition and without bounces
+ // (use effects). A new transition will start soon.
+ animationSpec = layoutImpl.state.motionScheme.fastEffectsSpec(),
+ )
}
return animatable
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/reveal/ContainerReveal.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/reveal/ContainerReveal.kt
index 00cd0ca..2134510 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/reveal/ContainerReveal.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/reveal/ContainerReveal.kt
@@ -20,7 +20,6 @@
import androidx.compose.animation.core.DeferredTargetAnimation
import androidx.compose.animation.core.ExperimentalAnimatableApi
import androidx.compose.animation.core.FiniteAnimationSpec
-import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.VectorConverter
import androidx.compose.animation.core.spring
import androidx.compose.ui.unit.Dp
@@ -103,14 +102,6 @@
// The spring animating the alpha of the container.
val alphaSpec = spring<Float>(stiffness = 1200f, dampingRatio = 0.99f)
- // The spring animating the progress when releasing the finger.
- motionSpatialSpec =
- spring(
- stiffness = Spring.StiffnessMediumLow,
- dampingRatio = Spring.DampingRatioNoBouncy,
- visibilityThreshold = 0.5f,
- )
-
// Size transformation.
transformation(container) {
VerticalContainerRevealSizeTransformation(
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt
index 62ec221..d11e6da1 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt
@@ -386,7 +386,7 @@
@Test
fun animatedValueIsUsingLastTransition() = runTest {
val state =
- rule.runOnUiThread { MutableSceneTransitionLayoutStateImpl(SceneA, transitions {}) }
+ rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(SceneA, transitions {}) }
val foo = ValueKey("foo")
val bar = ValueKey("bar")
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ContentTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ContentTest.kt
index 06a9735..dc69426 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ContentTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ContentTest.kt
@@ -42,7 +42,7 @@
lateinit var layoutImpl: SceneTransitionLayoutImpl
rule.setContent {
SceneTransitionLayoutForTesting(
- remember { MutableSceneTransitionLayoutState(SceneA) },
+ remember { MutableSceneTransitionLayoutStateForTests(SceneA) },
onLayoutImpl = { layoutImpl = it },
) {
scene(SceneA) {
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
index ef36077..35ff004 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
@@ -60,7 +60,7 @@
private class TestGestureScope(val testScope: MonotonicClockTestScope) {
var canChangeScene: (SceneKey) -> Boolean = { true }
val layoutState =
- MutableSceneTransitionLayoutStateImpl(
+ MutableSceneTransitionLayoutStateForTests(
SceneA,
EmptyTestTransitions,
canChangeScene = { canChangeScene(it) },
@@ -640,10 +640,7 @@
@Test
fun overscroll_releaseBetween0And100Percent_up() = runGestureTest {
// Make scene B overscrollable.
- layoutState.transitions = transitions {
- defaultMotionSpatialSpec = spring(dampingRatio = Spring.DampingRatioNoBouncy)
- from(SceneA, to = SceneB) {}
- }
+ layoutState.transitions = transitions { from(SceneA, to = SceneB) {} }
val dragController =
onDragStarted(
@@ -671,10 +668,7 @@
@Test
fun overscroll_releaseBetween0And100Percent_down() = runGestureTest {
// Make scene C overscrollable.
- layoutState.transitions = transitions {
- defaultMotionSpatialSpec = spring(dampingRatio = Spring.DampingRatioNoBouncy)
- from(SceneA, to = SceneC) {}
- }
+ layoutState.transitions = transitions { from(SceneA, to = SceneC) {} }
val dragController =
onDragStarted(
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 0051469..8d0af9b 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
@@ -209,7 +209,7 @@
val state =
rule.runOnUiThread {
- MutableSceneTransitionLayoutState(
+ MutableSceneTransitionLayoutStateForTests(
SceneA,
transitions {
from(SceneA, to = SceneB) { spec = tween }
@@ -343,7 +343,7 @@
@Test
fun elementIsReusedBetweenScenes() {
- val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
+ val state = rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(SceneA) }
var sceneCState by mutableStateOf(0)
val key = TestElements.Foo
var nullableLayoutImpl: SceneTransitionLayoutImpl? = null
@@ -473,7 +473,7 @@
@Test
fun elementModifierSupportsUpdates() {
- val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
+ val state = rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(SceneA) }
var key by mutableStateOf(TestElements.Foo)
var nullableLayoutImpl: SceneTransitionLayoutImpl? = null
@@ -523,7 +523,7 @@
rule.waitUntil(timeoutMillis = 10_000) { animationFinished }
}
- val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
+ val state = rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(SceneA) }
rule.setContent {
scrollScope = rememberCoroutineScope()
@@ -618,7 +618,7 @@
fun layoutGetsCurrentTransitionStateFromComposition() {
val state =
rule.runOnUiThread {
- MutableSceneTransitionLayoutState(
+ MutableSceneTransitionLayoutStateForTests(
SceneA,
transitions {
from(SceneA, to = SceneB) {
@@ -663,7 +663,8 @@
// The draggable touch slop, i.e. the min px distance a touch pointer must move before it is
// detected as a drag event.
var touchSlop = 0f
- val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(initialScene = SceneA) }
+ val state =
+ rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(initialScene = SceneA) }
rule.setContent {
density = LocalDensity.current
touchSlop = LocalViewConfiguration.current.touchSlop
@@ -718,7 +719,8 @@
// The draggable touch slop, i.e. the min px distance a touch pointer must move before it is
// detected as a drag event.
var touchSlop = 0f
- val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(initialScene = SceneA) }
+ val state =
+ rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(initialScene = SceneA) }
rule.setContent {
density = LocalDensity.current
touchSlop = LocalViewConfiguration.current.touchSlop
@@ -813,7 +815,8 @@
// The draggable touch slop, i.e. the min px distance a touch pointer must move before it is
// detected as a drag event.
var touchSlop = 0f
- val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(initialScene = SceneA) }
+ val state =
+ rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(initialScene = SceneA) }
rule.setContent {
density = LocalDensity.current
touchSlop = LocalViewConfiguration.current.touchSlop
@@ -866,7 +869,8 @@
val layoutWidth = 200.dp
val layoutHeight = 400.dp
- val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(initialScene = SceneA) }
+ val state =
+ rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(initialScene = SceneA) }
rule.setContent {
density = LocalDensity.current
@@ -934,7 +938,7 @@
val state =
rule.runOnUiThread {
- MutableSceneTransitionLayoutState(
+ MutableSceneTransitionLayoutStateForTests(
initialScene = SceneA,
transitions =
transitions {
@@ -943,7 +947,6 @@
}
},
)
- as MutableSceneTransitionLayoutStateImpl
}
rule.setContent {
@@ -999,7 +1002,7 @@
val state =
rule.runOnUiThread {
- MutableSceneTransitionLayoutState(
+ MutableSceneTransitionLayoutStateForTests(
SceneA,
transitions {
// Foo is at the top left corner of scene A. We make it disappear during A
@@ -1126,7 +1129,7 @@
val state =
rule.runOnUiThread {
- MutableSceneTransitionLayoutState(
+ MutableSceneTransitionLayoutStateForTests(
SceneA,
transitions {
from(SceneA, to = SceneB) { spec = tween(duration, easing = LinearEasing) }
@@ -1331,7 +1334,7 @@
val fooSize = 100.dp
val state =
rule.runOnUiThread {
- MutableSceneTransitionLayoutState(
+ MutableSceneTransitionLayoutStateForTests(
SceneA,
transitions {
from(SceneA, to = SceneB) { spec = tween(duration, easing = LinearEasing) }
@@ -1439,7 +1442,7 @@
@Test
fun targetStateIsSetEvenWhenNotPlaced() {
// Start directly at A => B but with progress < 0f to overscroll on A.
- val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
+ val state = rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(SceneA) }
lateinit var layoutImpl: SceneTransitionLayoutImpl
val scope =
@@ -1473,7 +1476,7 @@
fun lastAlphaIsNotSetByOutdatedLayer() {
val state =
rule.runOnUiThread {
- MutableSceneTransitionLayoutState(
+ MutableSceneTransitionLayoutStateForTests(
SceneA,
transitions { from(SceneA, to = SceneB) { fade(TestElements.Foo) } },
)
@@ -1537,7 +1540,7 @@
fun fadingElementsDontAppearInstantly() {
val state =
rule.runOnUiThread {
- MutableSceneTransitionLayoutState(
+ MutableSceneTransitionLayoutStateForTests(
SceneA,
transitions { from(SceneA, to = SceneB) { fade(TestElements.Foo) } },
)
@@ -1583,7 +1586,7 @@
@Test
fun lastPlacementValuesAreClearedOnNestedElements() {
- val state = rule.runOnIdle { MutableSceneTransitionLayoutState(SceneA) }
+ val state = rule.runOnIdle { MutableSceneTransitionLayoutStateForTests(SceneA) }
@Composable
fun ContentScope.NestedFooBar() {
@@ -1658,7 +1661,7 @@
fun currentTransitionSceneIsUsedToComputeElementValues() {
val state =
rule.runOnIdle {
- MutableSceneTransitionLayoutState(
+ MutableSceneTransitionLayoutStateForTests(
SceneA,
transitions {
from(SceneB, to = SceneC) {
@@ -1709,7 +1712,7 @@
@Test
fun interruptionDeltasAreProperlyCleaned() {
- val state = rule.runOnIdle { MutableSceneTransitionLayoutState(SceneA) }
+ val state = rule.runOnIdle { MutableSceneTransitionLayoutStateForTests(SceneA) }
@Composable
fun ContentScope.Foo(offset: Dp) {
@@ -1780,7 +1783,7 @@
fun transparentElementIsNotImpactingInterruption() {
val state =
rule.runOnIdle {
- MutableSceneTransitionLayoutState(
+ MutableSceneTransitionLayoutStateForTests(
SceneA,
transitions {
from(SceneA, to = SceneB) {
@@ -1856,7 +1859,7 @@
@Test
fun replacedTransitionDoesNotTriggerInterruption() {
- val state = rule.runOnIdle { MutableSceneTransitionLayoutState(SceneA) }
+ val state = rule.runOnIdle { MutableSceneTransitionLayoutStateForTests(SceneA) }
@Composable
fun ContentScope.Foo(modifier: Modifier = Modifier) {
@@ -2027,7 +2030,7 @@
): SceneTransitionLayoutImpl {
val state =
rule.runOnIdle {
- MutableSceneTransitionLayoutState(
+ MutableSceneTransitionLayoutStateForTests(
from,
transitions { from(from, to = to, preview = preview, builder = transition) },
)
@@ -2095,7 +2098,7 @@
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 +2162,7 @@
// 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)
@@ -2174,7 +2177,7 @@
)
}
- val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
+ val state = rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(SceneA) }
val scope =
rule.setContentAndCreateMainScope {
SceneTransitionLayout(state) {
@@ -2211,11 +2214,11 @@
@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))
}
- val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
+ val state = rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(SceneA) }
val scope =
rule.setContentAndCreateMainScope {
SceneTransitionLayout(state) {
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/InterruptionHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/InterruptionHandlerTest.kt
index 3622369..b44f552 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/InterruptionHandlerTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/InterruptionHandlerTest.kt
@@ -39,7 +39,7 @@
@Test
fun default() = runMonotonicClockTest {
val state =
- MutableSceneTransitionLayoutState(
+ MutableSceneTransitionLayoutStateForTests(
SceneA,
transitions { /* default interruption handler */ },
)
@@ -62,7 +62,7 @@
@Test
fun chainingDisabled() = runMonotonicClockTest {
val state =
- MutableSceneTransitionLayoutState(
+ MutableSceneTransitionLayoutStateForTests(
SceneA,
transitions {
// Handler that animates from currentScene (default) but disables chaining.
@@ -97,7 +97,7 @@
fun animateFromOtherScene() = runMonotonicClockTest {
val duration = 500
val state =
- MutableSceneTransitionLayoutState(
+ MutableSceneTransitionLayoutStateForTests(
SceneA,
transitions {
// Handler that animates from the scene that is not currentScene.
@@ -146,7 +146,7 @@
@Test
fun animateToFromScene() = runMonotonicClockTest {
- val state = MutableSceneTransitionLayoutStateImpl(SceneA, transitions {})
+ val state = MutableSceneTransitionLayoutStateForTests(SceneA, transitions {})
// Fake a transition from A to B that has a non 0 velocity.
val progressVelocity = 1f
@@ -182,7 +182,7 @@
@Test
fun animateToToScene() = runMonotonicClockTest {
- val state = MutableSceneTransitionLayoutStateImpl(SceneA, transitions {})
+ val state = MutableSceneTransitionLayoutStateForTests(SceneA, transitions {})
// Fake a transition from A to B with current scene = A that has a non 0 velocity.
val progressVelocity = -1f
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 b4c8ad7..8d718c1 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,14 +373,14 @@
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)) } }
}
val state =
rule.runOnUiThread {
- MutableSceneTransitionLayoutState(
+ MutableSceneTransitionLayoutStateForTests(
initialScene = SceneA,
initialOverlays = setOf(OverlayA),
)
@@ -420,7 +420,7 @@
val state =
rule.runOnUiThread {
- MutableSceneTransitionLayoutState(
+ MutableSceneTransitionLayoutStateForTests(
initialScene = SceneA,
initialOverlays = setOf(OverlayA),
)
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MutableSceneTransitionLayoutStateForTests.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MutableSceneTransitionLayoutStateForTests.kt
new file mode 100644
index 0000000..4326ec9
--- /dev/null
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MutableSceneTransitionLayoutStateForTests.kt
@@ -0,0 +1,47 @@
+/*
+ * 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.compose.animation.scene
+
+import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
+import androidx.compose.material3.MotionScheme
+import com.android.compose.animation.scene.content.state.TransitionState
+
+internal fun MutableSceneTransitionLayoutStateForTests(
+ initialScene: SceneKey,
+ transitions: SceneTransitions = SceneTransitions.Empty,
+ initialOverlays: Set<OverlayKey> = emptySet(),
+ canChangeScene: (SceneKey) -> Boolean = { true },
+ canShowOverlay: (OverlayKey) -> Boolean = { true },
+ canHideOverlay: (OverlayKey) -> Boolean = { true },
+ canReplaceOverlay: (from: OverlayKey, to: OverlayKey) -> Boolean = { _, _ -> true },
+ onTransitionStart: (TransitionState.Transition) -> Unit = {},
+ onTransitionEnd: (TransitionState.Transition) -> Unit = {},
+): MutableSceneTransitionLayoutStateImpl {
+ @OptIn(ExperimentalMaterial3ExpressiveApi::class)
+ return MutableSceneTransitionLayoutStateImpl(
+ initialScene,
+ motionScheme = MotionScheme.standard(),
+ transitions,
+ initialOverlays,
+ canChangeScene,
+ canShowOverlay,
+ canHideOverlay,
+ canReplaceOverlay,
+ onTransitionStart,
+ onTransitionEnd,
+ )
+}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ObservableTransitionStateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ObservableTransitionStateTest.kt
index 596e2cd..f2c0ca5 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ObservableTransitionStateTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ObservableTransitionStateTest.kt
@@ -47,7 +47,9 @@
@Test
fun testObservableTransitionState() = runTest {
val state =
- rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA, EmptyTestTransitions) }
+ rule.runOnUiThread {
+ MutableSceneTransitionLayoutStateForTests(SceneA, EmptyTestTransitions)
+ }
// Collect the current observable state into [observableState].
// TODO(b/290184746): Use collectValues {} once it is extracted into a library that can be
@@ -106,7 +108,7 @@
@Test
fun observableCurrentScene() = runTestWithSnapshots {
val state =
- MutableSceneTransitionLayoutStateImpl(
+ MutableSceneTransitionLayoutStateForTests(
initialScene = SceneA,
transitions = transitions {},
)
@@ -150,7 +152,7 @@
fun testObservablePreviewTransitionState() = runTest {
val layoutState =
rule.runOnUiThread {
- MutableSceneTransitionLayoutState(
+ MutableSceneTransitionLayoutStateForTests(
SceneA,
transitions = transitions { from(SceneA, to = SceneB, preview = {}) },
)
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 bad4c62..50bfbfe 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
@@ -73,7 +73,7 @@
@Test
fun showThenHideOverlay() {
- val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
+ val state = rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(SceneA) }
lateinit var coroutineScope: CoroutineScope
rule.setContent {
coroutineScope = rememberCoroutineScope()
@@ -115,7 +115,7 @@
@Test
fun multipleOverlays() {
- val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
+ val state = rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(SceneA) }
lateinit var coroutineScope: CoroutineScope
rule.setContent {
coroutineScope = rememberCoroutineScope()
@@ -213,7 +213,7 @@
}
}
- val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
+ val state = rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(SceneA) }
lateinit var coroutineScope: CoroutineScope
rule.setContent {
coroutineScope = rememberCoroutineScope()
@@ -285,7 +285,7 @@
fun overlayAlignment() {
val state =
rule.runOnUiThread {
- MutableSceneTransitionLayoutState(SceneA, initialOverlays = setOf(OverlayA))
+ MutableSceneTransitionLayoutStateForTests(SceneA, initialOverlays = setOf(OverlayA))
}
var alignment by mutableStateOf(Alignment.Center)
rule.setContent {
@@ -320,7 +320,7 @@
fun overlayMaxSizeIsCurrentSceneSize() {
val state =
rule.runOnUiThread {
- MutableSceneTransitionLayoutState(SceneA, initialOverlays = setOf(OverlayA))
+ MutableSceneTransitionLayoutStateForTests(SceneA, initialOverlays = setOf(OverlayA))
}
val contentTag = "overlayContent"
@@ -547,7 +547,7 @@
val sharedIntValueByContent = mutableMapOf<ContentKey, Int>()
@Composable
- fun SceneScope.animateContentInt(targetValue: Int) {
+ fun ContentScope.animateContentInt(targetValue: Int) {
val animatedValue = animateContentIntAsState(targetValue, sharedIntKey)
LaunchedEffect(animatedValue) {
try {
@@ -742,7 +742,7 @@
@Test
fun overscrollingOverlay_movableElementNotInOverlay() {
- val state = rule.runOnUiThread { MutableSceneTransitionLayoutStateImpl(SceneA) }
+ val state = rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(SceneA) }
val key = MovableElementKey("Foo", contents = setOf(SceneA))
val movableElementChildTag = "movableElementChildTag"
@@ -769,7 +769,7 @@
@Test
fun overlaysAreModalByDefault() {
- val state = rule.runOnUiThread { MutableSceneTransitionLayoutStateImpl(SceneA) }
+ val state = rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(SceneA) }
val scrollState = ScrollState(initial = 0)
val scope =
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/PredictiveBackHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/PredictiveBackHandlerTest.kt
index 4224a0c..9f15ebd 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/PredictiveBackHandlerTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/PredictiveBackHandlerTest.kt
@@ -50,7 +50,7 @@
@Test
fun testBack() {
- val layoutState = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
+ val layoutState = rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(SceneA) }
rule.setContent {
SceneTransitionLayout(layoutState) {
scene(SceneA, mapOf(Back to SceneB)) { Box(Modifier.fillMaxSize()) }
@@ -70,7 +70,7 @@
val transitionFrames = 2
val layoutState =
rule.runOnUiThread {
- MutableSceneTransitionLayoutState(
+ MutableSceneTransitionLayoutStateForTests(
SceneA,
transitions =
transitions {
@@ -142,7 +142,7 @@
fun testPredictiveBackWithPreview() {
val layoutState =
rule.runOnUiThread {
- MutableSceneTransitionLayoutState(
+ MutableSceneTransitionLayoutStateForTests(
SceneA,
transitions = transitions { from(SceneA, to = SceneB, preview = {}) },
)
@@ -192,7 +192,7 @@
var canChangeSceneCalled = false
val layoutState =
rule.runOnUiThread {
- MutableSceneTransitionLayoutState(
+ MutableSceneTransitionLayoutStateForTests(
SceneA,
canChangeScene = {
canChangeSceneCalled = true
@@ -241,7 +241,7 @@
fun backDismissesOverlayWithHighestZIndexByDefault() {
val state =
rule.runOnUiThread {
- MutableSceneTransitionLayoutState(
+ MutableSceneTransitionLayoutStateForTests(
SceneA,
initialOverlays = setOf(OverlayA, OverlayB),
)
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
index f3be5e4..c5e4061 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
@@ -51,7 +51,7 @@
@Test
fun isTransitioningTo_idle() {
- val state = MutableSceneTransitionLayoutStateImpl(SceneA, SceneTransitions.Empty)
+ val state = MutableSceneTransitionLayoutStateForTests(SceneA, SceneTransitions.Empty)
assertThat(state.isTransitioning()).isFalse()
assertThat(state.isTransitioning(from = SceneA)).isFalse()
@@ -61,7 +61,7 @@
@Test
fun isTransitioningTo_transition() = runTest {
- val state = MutableSceneTransitionLayoutStateImpl(SceneA, SceneTransitions.Empty)
+ val state = MutableSceneTransitionLayoutStateForTests(SceneA, SceneTransitions.Empty)
state.startTransitionImmediately(
animationScope = backgroundScope,
transition(from = SceneA, to = SceneB),
@@ -77,13 +77,13 @@
@Test
fun setTargetScene_idleToSameScene() = runMonotonicClockTest {
- val state = MutableSceneTransitionLayoutState(SceneA)
+ val state = MutableSceneTransitionLayoutStateForTests(SceneA)
assertThat(state.setTargetScene(SceneA, animationScope = this)).isNull()
}
@Test
fun setTargetScene_idleToDifferentScene() = runMonotonicClockTest {
- val state = MutableSceneTransitionLayoutState(SceneA)
+ val state = MutableSceneTransitionLayoutStateForTests(SceneA)
val (transition, job) = checkNotNull(state.setTargetScene(SceneB, animationScope = this))
assertThat(state.transitionState).isEqualTo(transition)
@@ -93,7 +93,7 @@
@Test
fun setTargetScene_transitionToSameScene() = runMonotonicClockTest {
- val state = MutableSceneTransitionLayoutState(SceneA)
+ val state = MutableSceneTransitionLayoutStateForTests(SceneA)
val (_, job) = checkNotNull(state.setTargetScene(SceneB, animationScope = this))
assertThat(state.setTargetScene(SceneB, animationScope = this)).isNull()
@@ -104,7 +104,7 @@
@Test
fun setTargetScene_transitionToDifferentScene() = runMonotonicClockTest {
- val state = MutableSceneTransitionLayoutState(SceneA)
+ val state = MutableSceneTransitionLayoutStateForTests(SceneA)
assertThat(state.setTargetScene(SceneB, animationScope = this)).isNotNull()
val (_, job) = checkNotNull(state.setTargetScene(SceneC, animationScope = this))
@@ -115,7 +115,7 @@
@Test
fun setTargetScene_coroutineScopeCancelled() = runMonotonicClockTest {
- val state = MutableSceneTransitionLayoutState(SceneA)
+ val state = MutableSceneTransitionLayoutStateForTests(SceneA)
lateinit var transition: TransitionState.Transition
val job =
@@ -133,7 +133,7 @@
fun setTargetScene_withTransitionKey() = runMonotonicClockTest {
val transitionkey = TransitionKey(debugName = "foo")
val state =
- MutableSceneTransitionLayoutState(
+ MutableSceneTransitionLayoutStateForTests(
SceneA,
transitions =
transitions {
@@ -176,7 +176,7 @@
return { /* do nothing */ }
}
- val state = MutableSceneTransitionLayoutStateImpl(SceneA, EmptyTestTransitions)
+ val state = MutableSceneTransitionLayoutStateForTests(SceneA, EmptyTestTransitions)
val aToB = transition(SceneA, SceneB, onFreezeAndAnimate = ::onFreezeAndAnimate)
val bToC = transition(SceneB, SceneC, onFreezeAndAnimate = ::onFreezeAndAnimate)
val cToA = transition(SceneC, SceneA, onFreezeAndAnimate = ::onFreezeAndAnimate)
@@ -226,7 +226,7 @@
@Test
fun tooManyTransitionsLogsWtfAndClearsTransitions() = runTest {
- val state = MutableSceneTransitionLayoutStateImpl(SceneA, EmptyTestTransitions)
+ val state = MutableSceneTransitionLayoutStateForTests(SceneA, EmptyTestTransitions)
fun startTransition() {
val transition =
@@ -251,7 +251,7 @@
@Test
fun snapToScene() = runMonotonicClockTest {
- val state = MutableSceneTransitionLayoutState(SceneA)
+ val state = MutableSceneTransitionLayoutStateForTests(SceneA)
// Transition to B.
state.setTargetScene(SceneB, animationScope = this)
@@ -266,7 +266,7 @@
@Test
fun snapToScene_freezesCurrentTransition() = runMonotonicClockTest {
- val state = MutableSceneTransitionLayoutStateImpl(SceneA)
+ val state = MutableSceneTransitionLayoutStateForTests(SceneA)
// Start a transition that is never finished. We don't use backgroundScope on purpose so
// that this test would fail if the transition was not frozen when snapping.
@@ -283,7 +283,7 @@
@Test
fun seekToScene() = runMonotonicClockTest {
- val state = MutableSceneTransitionLayoutState(SceneA)
+ val state = MutableSceneTransitionLayoutStateForTests(SceneA)
val progress = Channel<Float>()
val job =
@@ -309,7 +309,7 @@
@Test
fun seekToScene_cancelled() = runMonotonicClockTest {
- val state = MutableSceneTransitionLayoutState(SceneA)
+ val state = MutableSceneTransitionLayoutStateForTests(SceneA)
val progress = Channel<Float>()
val job =
@@ -335,7 +335,7 @@
@Test
fun seekToScene_interrupted() = runMonotonicClockTest {
- val state = MutableSceneTransitionLayoutState(SceneA)
+ val state = MutableSceneTransitionLayoutStateForTests(SceneA)
val progress = Channel<Float>()
val job =
@@ -354,7 +354,7 @@
@Test
fun replacedTransitionIsRemovedFromFinishedTransitions() = runTest {
- val state = MutableSceneTransitionLayoutState(SceneA)
+ val state = MutableSceneTransitionLayoutStateForTests(SceneA)
val aToB =
transition(
@@ -403,7 +403,7 @@
@Test
fun transitionCanBeStartedOnlyOnce() = runTest {
- val state = MutableSceneTransitionLayoutState(SceneA)
+ val state = MutableSceneTransitionLayoutStateForTests(SceneA)
val transition = transition(from = SceneA, to = SceneB)
state.startTransitionImmediately(backgroundScope, transition)
@@ -414,7 +414,7 @@
@Test
fun transitionFinishedWhenScopeIsEmpty() = runTest {
- val state = MutableSceneTransitionLayoutState(SceneA)
+ val state = MutableSceneTransitionLayoutStateForTests(SceneA)
// Start a transition.
val transition = transition(from = SceneA, to = SceneB)
@@ -439,7 +439,7 @@
@Test
fun transitionScopeIsCancelledWhenTransitionIsForceFinished() = runTest {
- val state = MutableSceneTransitionLayoutState(SceneA)
+ val state = MutableSceneTransitionLayoutStateForTests(SceneA)
// Start a transition.
val transition = transition(from = SceneA, to = SceneB)
@@ -458,7 +458,7 @@
@Test
fun badTransitionSpecThrowsMeaningfulMessageWhenStartingTransition() {
val state =
- MutableSceneTransitionLayoutState(
+ MutableSceneTransitionLayoutStateForTests(
SceneA,
transitions {
// This transition definition is bad because they both match when transitioning
@@ -483,7 +483,7 @@
@Test
fun snapToScene_multipleTransitions() = runMonotonicClockTest {
- val state = MutableSceneTransitionLayoutState(SceneA)
+ val state = MutableSceneTransitionLayoutStateForTests(SceneA)
state.startTransitionImmediately(this, transition(SceneA, SceneB))
state.startTransitionImmediately(this, transition(SceneB, SceneC))
state.snapToScene(SceneC)
@@ -498,7 +498,7 @@
val finished = mutableSetOf<TransitionState.Transition>()
val cujWhenStarting = mutableMapOf<TransitionState.Transition, Int?>()
val state =
- MutableSceneTransitionLayoutState(
+ MutableSceneTransitionLayoutStateForTests(
SceneA,
transitions {
// A <=> B.
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
index e580e3c..3c490ae 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
@@ -17,7 +17,9 @@
package com.android.compose.animation.scene
import androidx.compose.animation.core.FastOutSlowInEasing
+import androidx.compose.animation.core.FiniteAnimationSpec
import androidx.compose.animation.core.LinearEasing
+import androidx.compose.animation.core.spring
import androidx.compose.animation.core.tween
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
@@ -25,9 +27,13 @@
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.size
+import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.MotionScheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
@@ -60,7 +66,6 @@
import com.android.compose.test.assertSizeIsEqualTo
import com.android.compose.test.subjects.DpOffsetSubject
import com.android.compose.test.subjects.assertThat
-import com.android.compose.test.transition
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineScope
import org.junit.Assert.assertThrows
@@ -88,7 +93,9 @@
@Composable
private fun TestContent() {
coroutineScope = rememberCoroutineScope()
- layoutState = remember { MutableSceneTransitionLayoutState(SceneA, EmptyTestTransitions) }
+ layoutState = remember {
+ MutableSceneTransitionLayoutStateForTests(SceneA, EmptyTestTransitions)
+ }
SceneTransitionLayout(state = layoutState, modifier = Modifier.size(LayoutSize)) {
scene(SceneA, userActions = mapOf(Back to SceneB)) {
@@ -317,7 +324,7 @@
val state =
rule.runOnUiThread {
- MutableSceneTransitionLayoutState(
+ MutableSceneTransitionLayoutStateForTests(
SceneA,
transitions {
from(SceneA, to = SceneB) {
@@ -421,7 +428,7 @@
assertThrows(IllegalStateException::class.java) {
rule.setContent {
SceneTransitionLayout(
- state = remember { MutableSceneTransitionLayoutState(SceneA) },
+ state = remember { MutableSceneTransitionLayoutStateForTests(SceneA) },
modifier = Modifier.size(LayoutSize),
) {
// from SceneA to SceneA
@@ -436,7 +443,7 @@
@Test
fun sceneKeyInScope() {
- val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
+ val state = rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(SceneA) }
var keyInA: ContentKey? = null
var keyInB: ContentKey? = null
@@ -465,7 +472,7 @@
lateinit var layoutImpl: SceneTransitionLayoutImpl
rule.setContent {
SceneTransitionLayoutForTesting(
- remember { MutableSceneTransitionLayoutState(SceneA) },
+ remember { MutableSceneTransitionLayoutStateForTests(SceneA) },
onLayoutImpl = { layoutImpl = it },
) {
scene(SceneA) { Box(Modifier.fillMaxSize()) }
@@ -483,7 +490,8 @@
// The draggable touch slop, i.e. the min px distance a touch pointer must move before it is
// detected as a drag event.
var touchSlop = 0f
- val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(initialScene = SceneA) }
+ val state =
+ rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(initialScene = SceneA) }
rule.setContent {
touchSlop = LocalViewConfiguration.current.touchSlop
SceneTransitionLayout(state, Modifier.size(layoutWidth, layoutHeight)) {
@@ -511,4 +519,67 @@
// Fling animation, we are overscrolling now. Progress should always be between [0, 1].
assertThat(transition).hasProgress(1f)
}
+
+ @OptIn(ExperimentalMaterial3ExpressiveApi::class)
+ @Test
+ fun motionSchemeArePassedToSTLState() {
+ // Implementation inspired by MotionScheme.standard()
+ @Suppress("UNCHECKED_CAST")
+ fun motionScheme(animationSpec: FiniteAnimationSpec<Any>) =
+ object : MotionScheme {
+ override fun <T> defaultEffectsSpec() = animationSpec as FiniteAnimationSpec<T>
+
+ override fun <T> defaultSpatialSpec() = animationSpec as FiniteAnimationSpec<T>
+
+ override fun <T> fastEffectsSpec() = animationSpec as FiniteAnimationSpec<T>
+
+ override fun <T> fastSpatialSpec() = animationSpec as FiniteAnimationSpec<T>
+
+ override fun <T> slowEffectsSpec() = animationSpec as FiniteAnimationSpec<T>
+
+ override fun <T> slowSpatialSpec() = animationSpec as FiniteAnimationSpec<T>
+ }
+
+ lateinit var state1: MutableSceneTransitionLayoutState
+ lateinit var state2: MutableSceneTransitionLayoutState
+
+ lateinit var motionScheme1: MotionScheme
+ var motionScheme2 by mutableStateOf(motionScheme(animationSpec = tween(500)))
+ rule.setContent {
+ motionScheme1 = MaterialTheme.motionScheme
+ state1 = rememberMutableSceneTransitionLayoutState(initialScene = SceneA)
+ SceneTransitionLayout(state1) {
+ scene(SceneA, userActions = mapOf(Swipe.Down to SceneB)) {
+ Spacer(Modifier.fillMaxSize())
+ }
+ }
+
+ MaterialTheme(motionScheme = motionScheme2) {
+ // Important: we should read this state inside the MaterialTheme composable.
+ state2 = rememberMutableSceneTransitionLayoutState(initialScene = SceneA)
+ SceneTransitionLayout(state2) {
+ scene(SceneA, userActions = mapOf(Swipe.Down to SceneB)) {
+ Spacer(Modifier.fillMaxSize())
+ }
+ }
+ }
+ }
+
+ assertThat(motionScheme1).isNotNull()
+ assertThat(motionScheme1).isNotEqualTo(motionScheme2)
+
+ assertThat((state1 as MutableSceneTransitionLayoutStateImpl).motionScheme)
+ .isEqualTo(motionScheme1)
+
+ assertThat((state2 as MutableSceneTransitionLayoutStateImpl).motionScheme)
+ .isEqualTo(motionScheme2)
+
+ // Update the MaterialTheme's MotionScheme configuration.
+ motionScheme2 = motionScheme(animationSpec = spring())
+
+ // We just updated the motionScheme2 state, wait for a recomposition.
+ rule.waitForIdle()
+ assertThat((state2 as MutableSceneTransitionLayoutStateImpl).motionScheme)
+ .isEqualTo(motionScheme2)
+ }
}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
index e036084..751b314 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
@@ -93,7 +93,9 @@
initialScene: SceneKey = SceneA,
transitions: SceneTransitions = EmptyTestTransitions,
): MutableSceneTransitionLayoutState {
- return rule.runOnUiThread { MutableSceneTransitionLayoutState(initialScene, transitions) }
+ return rule.runOnUiThread {
+ MutableSceneTransitionLayoutStateForTests(initialScene, transitions)
+ }
}
/** The content under test. */
@@ -738,7 +740,7 @@
fun startEnd_ltrLayout() {
val state =
rule.runOnUiThread {
- MutableSceneTransitionLayoutState(
+ MutableSceneTransitionLayoutStateForTests(
initialScene = SceneA,
transitions =
transitions {
@@ -811,7 +813,7 @@
fun startEnd_rtlLayout() {
val state =
rule.runOnUiThread {
- MutableSceneTransitionLayoutState(
+ MutableSceneTransitionLayoutStateForTests(
initialScene = SceneA,
transitions =
transitions {
@@ -893,7 +895,9 @@
Text("Count: $count")
}
- SceneTransitionLayout(remember { MutableSceneTransitionLayoutState(SceneA) }) {
+ SceneTransitionLayout(
+ remember { MutableSceneTransitionLayoutStateForTests(SceneA) }
+ ) {
scene(SceneA) { Box(Modifier.fillMaxSize()) }
}
}
@@ -909,7 +913,7 @@
@Test
fun swipeToSceneSupportsUpdates() {
- val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
+ val state = rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(SceneA) }
rule.setContent {
SceneTransitionLayout(state) {
@@ -943,7 +947,7 @@
@Test
fun swipeToSceneNodeIsKeptWhenDisabled() {
var hasHorizontalActions by mutableStateOf(false)
- val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
+ val state = rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(SceneA) }
var touchSlop = 0f
rule.setContent {
touchSlop = LocalViewConfiguration.current.touchSlop
@@ -983,7 +987,7 @@
@Test
fun nestedScroll_useFromSourceInfo() {
- val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
+ val state = rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(SceneA) }
var touchSlop = 0f
rule.setContent {
touchSlop = LocalViewConfiguration.current.touchSlop
@@ -1033,7 +1037,7 @@
@Test
fun nestedScroll_ignoreMouseWheel() {
- val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
+ val state = rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(SceneA) }
var touchSlop = 0f
rule.setContent {
touchSlop = LocalViewConfiguration.current.touchSlop
@@ -1057,7 +1061,7 @@
@Test
fun nestedScroll_keepPriorityEvenIfWeCanNoLongerScrollOnThatDirection() {
- val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
+ val state = rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(SceneA) }
var touchSlop = 0f
rule.setContent {
touchSlop = LocalViewConfiguration.current.touchSlop
@@ -1097,7 +1101,7 @@
fun nestedScroll_replaceOverlay() {
val state =
rule.runOnUiThread {
- MutableSceneTransitionLayoutState(SceneA, initialOverlays = setOf(OverlayA))
+ MutableSceneTransitionLayoutStateForTests(SceneA, initialOverlays = setOf(OverlayA))
}
var touchSlop = 0f
rule.setContent {
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt
index aada4a50..e098aac 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt
@@ -17,9 +17,7 @@
package com.android.compose.animation.scene
import androidx.compose.animation.core.CubicBezierEasing
-import androidx.compose.animation.core.SpringSpec
import androidx.compose.animation.core.TweenSpec
-import androidx.compose.animation.core.spring
import androidx.compose.animation.core.tween
import androidx.compose.ui.unit.IntSize
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -84,7 +82,7 @@
fun defaultTransitionSpec() {
val transitions = transitions { from(SceneA, to = SceneB) }
val transformationSpec = transitions.transitionSpecs.single().transformationSpec(aToB())
- assertThat(transformationSpec.progressSpec).isInstanceOf(SpringSpec::class.java)
+ assertThat(transformationSpec.progressSpec).isNull()
}
@Test
@@ -272,44 +270,10 @@
}
@Test
- fun springSpec() {
- val defaultSpec = spring<Float>(stiffness = 1f)
- val specFromAToC = spring<Float>(stiffness = 2f)
- val transitions = transitions {
- defaultMotionSpatialSpec = defaultSpec
-
- from(SceneA, to = SceneB) {
- // Default swipe spec.
- }
- from(SceneA, to = SceneC) { motionSpatialSpec = specFromAToC }
- }
-
- assertThat(transitions.defaultMotionSpatialSpec).isSameInstanceAs(defaultSpec)
-
- // A => B does not have a custom spec.
- assertThat(
- transitions
- .transitionSpec(from = SceneA, to = SceneB, key = null)
- .transformationSpec(aToB())
- .motionSpatialSpec
- )
- .isNull()
-
- // A => C has a custom swipe spec.
- assertThat(
- transitions
- .transitionSpec(from = SceneA, to = SceneC, key = null)
- .transformationSpec(transition(from = SceneA, to = SceneC))
- .motionSpatialSpec
- )
- .isSameInstanceAs(specFromAToC)
- }
-
- @Test
fun transitionIsPassedToBuilder() = runTest {
var transitionPassedToBuilder: TransitionState.Transition? = null
val state =
- MutableSceneTransitionLayoutState(
+ MutableSceneTransitionLayoutStateForTests(
SceneA,
transitions { from(SceneA, to = SceneB) { transitionPassedToBuilder = transition } },
)
@@ -340,7 +304,7 @@
}
}
- val state = MutableSceneTransitionLayoutState(SceneA, transitions)
+ val state = MutableSceneTransitionLayoutStateForTests(SceneA, transitions)
assertThrows(IllegalStateException::class.java) {
runBlocking { state.startTransition(transition(from = SceneA, to = SceneB)) }
}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/NestedElementTransformationTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/NestedElementTransformationTest.kt
index 0da422b..bb511bc 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/NestedElementTransformationTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/NestedElementTransformationTest.kt
@@ -37,6 +37,7 @@
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.MutableSceneTransitionLayoutStateForTests
import com.android.compose.animation.scene.Scale
import com.android.compose.animation.scene.SceneKey
import com.android.compose.animation.scene.SceneTransitionLayout
@@ -104,7 +105,9 @@
startScene: SceneKey,
transitions: SceneTransitions = SceneTransitions.Empty,
): MutableSceneTransitionLayoutState {
- return rule.runOnUiThread { MutableSceneTransitionLayoutState(startScene, transitions) }
+ return rule.runOnUiThread {
+ MutableSceneTransitionLayoutStateForTests(startScene, transitions)
+ }
}
private val threeNestedStls:
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/NestedSharedElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/NestedSharedElementTest.kt
index d8b7136..83dd6d3 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/NestedSharedElementTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/NestedSharedElementTest.kt
@@ -39,6 +39,7 @@
import com.android.compose.animation.scene.Default4FrameLinearTransition
import com.android.compose.animation.scene.Edge
import com.android.compose.animation.scene.MutableSceneTransitionLayoutState
+import com.android.compose.animation.scene.MutableSceneTransitionLayoutStateForTests
import com.android.compose.animation.scene.SceneKey
import com.android.compose.animation.scene.TestElements
import com.android.compose.animation.scene.TestScenes
@@ -94,7 +95,7 @@
private val nestedState: MutableSceneTransitionLayoutState =
rule.runOnUiThread {
- MutableSceneTransitionLayoutState(
+ MutableSceneTransitionLayoutStateForTests(
Scenes.NestedSceneA,
transitions {
from(
@@ -108,7 +109,7 @@
private val nestedNestedState: MutableSceneTransitionLayoutState =
rule.runOnUiThread {
- MutableSceneTransitionLayoutState(
+ MutableSceneTransitionLayoutStateForTests(
Scenes.NestedNestedSceneA,
transitions {
from(
diff --git a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestContentScope.kt b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestContentScope.kt
index 5cccfb1..6d47bab 100644
--- a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestContentScope.kt
+++ b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestContentScope.kt
@@ -29,6 +29,6 @@
currentScene: SceneKey = remember { SceneKey("current") },
content: @Composable ContentScope.() -> Unit,
) {
- val state = remember { MutableSceneTransitionLayoutState(currentScene) }
+ val state = rememberMutableSceneTransitionLayoutState(currentScene)
SceneTransitionLayout(state, modifier) { scene(currentScene, content = content) }
}
diff --git a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt
index bc160fc..f94a7ed 100644
--- a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt
+++ b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt
@@ -129,13 +129,12 @@
builder: TransitionTestBuilder.() -> Unit,
) {
testTransition(
- state =
- runOnUiThread {
- MutableSceneTransitionLayoutState(
- fromScene,
- transitions { from(fromScene, to = toScene, builder = transition) },
- )
- },
+ state = {
+ rememberMutableSceneTransitionLayoutState(
+ fromScene,
+ transitions { from(fromScene, to = toScene, builder = transition) },
+ )
+ },
changeState = changeState,
transitionLayout = { state ->
SceneTransitionLayout(state, layoutModifier) {
@@ -157,13 +156,12 @@
builder: TransitionTestBuilder.() -> Unit,
) {
testTransition(
- state =
- runOnUiThread {
- MutableSceneTransitionLayoutState(
- fromScene,
- transitions = transitions { from(fromScene, overlay, builder = transition) },
- )
- },
+ state = {
+ rememberMutableSceneTransitionLayoutState(
+ fromScene,
+ transitions = transitions { from(fromScene, overlay, builder = transition) },
+ )
+ },
transitionLayout = { state ->
SceneTransitionLayout(state) {
scene(fromScene) { fromSceneContent() }
@@ -185,14 +183,13 @@
builder: TransitionTestBuilder.() -> Unit,
) {
testTransition(
- state =
- runOnUiThread {
- MutableSceneTransitionLayoutState(
- toScene,
- initialOverlays = setOf(overlay),
- transitions = transitions { from(overlay, toScene, builder = transition) },
- )
- },
+ state = {
+ rememberMutableSceneTransitionLayoutState(
+ toScene,
+ initialOverlays = setOf(overlay),
+ transitions = transitions { from(overlay, toScene, builder = transition) },
+ )
+ },
transitionLayout = { state ->
SceneTransitionLayout(state) {
scene(toScene) { toSceneContent() }
@@ -218,14 +215,13 @@
builder: TransitionTestBuilder.() -> Unit,
) {
testTransition(
- state =
- runOnUiThread {
- MutableSceneTransitionLayoutState(
- currentScene,
- initialOverlays = setOf(from),
- transitions = transitions { from(from, to, builder = transition) },
- )
- },
+ state = {
+ rememberMutableSceneTransitionLayoutState(
+ currentScene,
+ initialOverlays = setOf(from),
+ transitions = transitions { from(from, to, builder = transition) },
+ )
+ },
transitionLayout = { state ->
SceneTransitionLayout(state) {
scene(currentScene) { currentSceneContent() }
@@ -263,16 +259,14 @@
fromScene: SceneKey = TestScenes.SceneA,
toScene: SceneKey = TestScenes.SceneB,
): RecordedMotion {
- val state =
- toolkit.composeContentTestRule.runOnUiThread {
- MutableSceneTransitionLayoutState(
- fromScene,
- transitions { from(fromScene, to = toScene, builder = transition) },
- )
- }
-
+ lateinit var state: MutableSceneTransitionLayoutState
return recordMotion(
content = { play ->
+ state =
+ rememberMutableSceneTransitionLayoutState(
+ fromScene,
+ transitions { from(fromScene, to = toScene, builder = transition) },
+ )
LaunchedEffect(play) {
if (play) {
state.setTargetScene(toScene, animationScope = this)
@@ -309,7 +303,7 @@
}
testTransition(
- state = state,
+ state = { state },
changeState = { state -> state.setTargetScene(to, animationScope = this) },
transitionLayout = transitionLayout,
builder = builder,
@@ -323,7 +317,7 @@
builder: TransitionTestBuilder.() -> Unit,
) {
testTransition(
- state = states[0],
+ state = { states[0] },
changeState = { changeState(states) },
transitionLayout = { transitionLayout(states) },
builder = builder,
@@ -331,16 +325,18 @@
}
/** Test the transition from [state] to [to]. */
-fun ComposeContentTestRule.testTransition(
- state: MutableSceneTransitionLayoutState,
+private fun ComposeContentTestRule.testTransition(
+ state: @Composable () -> MutableSceneTransitionLayoutState,
changeState: CoroutineScope.(MutableSceneTransitionLayoutState) -> Unit,
transitionLayout: @Composable (state: MutableSceneTransitionLayoutState) -> Unit,
builder: TransitionTestBuilder.() -> Unit,
) {
lateinit var coroutineScope: CoroutineScope
+ lateinit var layoutState: MutableSceneTransitionLayoutState
setContent {
+ layoutState = state()
coroutineScope = rememberCoroutineScope()
- transitionLayout(state)
+ transitionLayout(layoutState)
}
val assertionScope =
@@ -390,7 +386,7 @@
mainClock.autoAdvance = false
// Change the current scene.
- runOnUiThread { coroutineScope.changeState(state) }
+ runOnUiThread { coroutineScope.changeState(layoutState) }
waitForIdle()
mainClock.advanceTimeByFrame()
waitForIdle()
diff --git a/packages/SystemUI/log/src/com/android/systemui/log/LogBuffer.kt b/packages/SystemUI/log/src/com/android/systemui/log/LogBuffer.kt
index 4b5e9de..72304a1 100644
--- a/packages/SystemUI/log/src/com/android/systemui/log/LogBuffer.kt
+++ b/packages/SystemUI/log/src/com/android/systemui/log/LogBuffer.kt
@@ -75,6 +75,7 @@
private val maxSize: Int,
private val logcatEchoTracker: LogcatEchoTracker,
private val systrace: Boolean = true,
+ private val systraceTrackName: String = DEFAULT_LOGBUFFER_TRACK_NAME,
) : MessageBuffer {
private val buffer = RingBuffer(maxSize) { LogMessageImpl.create() }
@@ -244,10 +245,11 @@
}
private fun echoToSystrace(level: LogLevel, tag: String, strMessage: String) {
+ if (!Trace.isEnabled()) return
Trace.instantForTrack(
Trace.TRACE_TAG_APP,
- "UI Events",
- "$name - ${level.shortString} $tag: $strMessage"
+ systraceTrackName,
+ "$name - ${level.shortString} $tag: $strMessage",
)
}
@@ -261,6 +263,10 @@
LogLevel.WTF -> Log.wtf(message.tag, strMessage, message.exception)
}
}
+
+ companion object {
+ const val DEFAULT_LOGBUFFER_TRACK_NAME = "UI Events"
+ }
}
private const val TAG = "LogBuffer"
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogDelegateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogDelegateTest.kt
index 73efea7..2713bb0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogDelegateTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogDelegateTest.kt
@@ -316,5 +316,7 @@
dialog.onConfigurationChanged(config)
testableLooper.processAllMessages()
assertThat(doneButton.isEnabled).isTrue()
+
+ dialog.dismiss()
}
}
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 a654155..a6ed37e 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.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 @@
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/communal/CommunalDreamStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalDreamStartableTest.kt
index e531e65..00d5afe 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalDreamStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalDreamStartableTest.kt
@@ -16,14 +16,17 @@
package com.android.systemui.communal
+import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.FlagsParameterization
import android.service.dream.dreamManager
-import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.Flags
+import com.android.systemui.Flags.FLAG_GLANCEABLE_HUB_V2
import com.android.systemui.SysuiTestCase
import com.android.systemui.communal.domain.interactor.communalSceneInteractor
import com.android.systemui.communal.domain.interactor.communalSettingsInteractor
+import com.android.systemui.communal.domain.interactor.setCommunalV2Enabled
import com.android.systemui.flags.Flags.COMMUNAL_SERVICE_ENABLED
import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
@@ -48,12 +51,14 @@
import org.junit.runner.RunWith
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@EnableFlags(Flags.FLAG_COMMUNAL_HUB)
-@RunWith(AndroidJUnit4::class)
-class CommunalDreamStartableTest : SysuiTestCase() {
+@RunWith(ParameterizedAndroidJunit4::class)
+class CommunalDreamStartableTest(flags: FlagsParameterization) : SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
@@ -63,26 +68,50 @@
private val keyguardRepository by lazy { kosmos.fakeKeyguardRepository }
private val powerRepository by lazy { kosmos.fakePowerRepository }
+ init {
+ mSetFlagsRule.setFlagsParameterization(flags)
+ }
+
@Before
fun setUp() {
kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, true)
underTest =
CommunalDreamStartable(
- powerInteractor = kosmos.powerInteractor,
- communalSettingsInteractor = kosmos.communalSettingsInteractor,
- keyguardInteractor = kosmos.keyguardInteractor,
- keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor,
- dreamManager = dreamManager,
- communalSceneInteractor = kosmos.communalSceneInteractor,
- bgScope = kosmos.applicationCoroutineScope,
- )
- .apply { start() }
+ powerInteractor = kosmos.powerInteractor,
+ communalSettingsInteractor = kosmos.communalSettingsInteractor,
+ keyguardInteractor = kosmos.keyguardInteractor,
+ keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor,
+ dreamManager = dreamManager,
+ communalSceneInteractor = kosmos.communalSceneInteractor,
+ bgScope = kosmos.applicationCoroutineScope,
+ )
}
+ @EnableFlags(FLAG_GLANCEABLE_HUB_V2)
+ @Test
+ fun dreamNotStartedWhenTransitioningToHub() =
+ testScope.runTest {
+ // Enable v2 flag and recreate + rerun start method.
+ kosmos.setCommunalV2Enabled(true)
+ underTest.start()
+
+ keyguardRepository.setKeyguardShowing(true)
+ keyguardRepository.setDreaming(false)
+ powerRepository.setScreenPowerState(ScreenPowerState.SCREEN_ON)
+ whenever(dreamManager.canStartDreaming(/* isScreenOn= */ true)).thenReturn(true)
+ runCurrent()
+
+ transition(from = KeyguardState.DREAMING, to = KeyguardState.GLANCEABLE_HUB)
+
+ verify(dreamManager, never()).startDream()
+ }
+
+ @DisableFlags(FLAG_GLANCEABLE_HUB_V2)
@Test
fun startDreamWhenTransitioningToHub() =
testScope.runTest {
+ underTest.start()
keyguardRepository.setKeyguardShowing(true)
keyguardRepository.setDreaming(false)
powerRepository.setScreenPowerState(ScreenPowerState.SCREEN_ON)
@@ -100,6 +129,7 @@
@EnableFlags(Flags.FLAG_RESTART_DREAM_ON_UNOCCLUDE)
fun restartDreamingWhenTransitioningFromDreamingToOccludedToDreaming() =
testScope.runTest {
+ underTest.start()
keyguardRepository.setDreaming(false)
powerRepository.setScreenPowerState(ScreenPowerState.SCREEN_ON)
whenever(dreamManager.canStartDreaming(/* isScreenOn= */ true)).thenReturn(true)
@@ -122,9 +152,11 @@
verify(dreamManager).startDream()
}
+ @DisableFlags(FLAG_GLANCEABLE_HUB_V2)
@Test
fun shouldNotStartDreamWhenIneligibleToDream() =
testScope.runTest {
+ underTest.start()
keyguardRepository.setDreaming(false)
powerRepository.setScreenPowerState(ScreenPowerState.SCREEN_ON)
// Not eligible to dream
@@ -134,9 +166,11 @@
verify(dreamManager, never()).startDream()
}
+ @DisableFlags(FLAG_GLANCEABLE_HUB_V2)
@Test
fun shouldNotStartDreamIfAlreadyDreaming() =
testScope.runTest {
+ underTest.start()
keyguardRepository.setDreaming(true)
powerRepository.setScreenPowerState(ScreenPowerState.SCREEN_ON)
whenever(dreamManager.canStartDreaming(/* isScreenOn= */ true)).thenReturn(true)
@@ -145,9 +179,11 @@
verify(dreamManager, never()).startDream()
}
+ @DisableFlags(FLAG_GLANCEABLE_HUB_V2)
@Test
fun shouldNotStartDreamForInvalidTransition() =
testScope.runTest {
+ underTest.start()
keyguardRepository.setDreaming(true)
powerRepository.setScreenPowerState(ScreenPowerState.SCREEN_ON)
whenever(dreamManager.canStartDreaming(/* isScreenOn= */ true)).thenReturn(true)
@@ -160,9 +196,11 @@
}
}
+ @DisableFlags(FLAG_GLANCEABLE_HUB_V2)
@Test
fun shouldNotStartDreamWhenLaunchingWidget() =
testScope.runTest {
+ underTest.start()
keyguardRepository.setKeyguardShowing(true)
keyguardRepository.setDreaming(false)
powerRepository.setScreenPowerState(ScreenPowerState.SCREEN_ON)
@@ -175,9 +213,11 @@
verify(dreamManager, never()).startDream()
}
+ @DisableFlags(FLAG_GLANCEABLE_HUB_V2)
@Test
fun shouldNotStartDreamWhenOccluded() =
testScope.runTest {
+ underTest.start()
keyguardRepository.setKeyguardShowing(true)
keyguardRepository.setDreaming(false)
powerRepository.setScreenPowerState(ScreenPowerState.SCREEN_ON)
@@ -194,8 +234,16 @@
kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps(
from = from,
to = to,
- testScope = this
+ testScope = this,
)
runCurrent()
}
+
+ companion object {
+ @JvmStatic
+ @Parameters(name = "{0}")
+ fun getParams(): List<FlagsParameterization> {
+ return FlagsParameterization.allCombinationsOf(FLAG_GLANCEABLE_HUB_V2)
+ }
+ }
}
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 47cba07..0302336 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 @@
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 @@
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 @@
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/dreams/DreamOverlayServiceTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
index 5921e94..0df584f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
@@ -58,6 +58,7 @@
import com.android.systemui.communal.domain.interactor.communalSettingsInteractor
import com.android.systemui.communal.domain.interactor.setCommunalAvailable
import com.android.systemui.communal.domain.interactor.setCommunalV2ConfigEnabled
+import com.android.systemui.communal.domain.interactor.setCommunalV2Enabled
import com.android.systemui.communal.shared.log.CommunalUiEvent
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.complication.ComplicationHostViewController
@@ -747,7 +748,7 @@
@Test
@EnableFlags(Flags.FLAG_DREAM_WAKE_REDIRECT, FLAG_COMMUNAL_HUB)
- @DisableFlags(FLAG_SCENE_CONTAINER)
+ @DisableFlags(FLAG_SCENE_CONTAINER, FLAG_GLANCEABLE_HUB_V2)
@kotlin.Throws(RemoteException::class)
fun testTransitionToGlanceableHub() =
testScope.runTest {
@@ -774,6 +775,7 @@
@Test
@EnableFlags(Flags.FLAG_DREAM_WAKE_REDIRECT, FLAG_SCENE_CONTAINER, FLAG_COMMUNAL_HUB)
+ @DisableFlags(FLAG_GLANCEABLE_HUB_V2)
@kotlin.Throws(RemoteException::class)
fun testTransitionToGlanceableHub_sceneContainer() =
testScope.runTest {
@@ -802,7 +804,29 @@
}
@Test
+ @EnableFlags(Flags.FLAG_DREAM_WAKE_REDIRECT, FLAG_COMMUNAL_HUB, FLAG_GLANCEABLE_HUB_V2)
+ @Throws(RemoteException::class)
+ fun testRedirect_v2Enabled_notTriggered() =
+ testScope.runTest {
+ kosmos.setCommunalV2Enabled(true)
+ // Inform the overlay service of dream starting. Do not show dream complications.
+ client.startDream(
+ mWindowParams,
+ mDreamOverlayCallback,
+ DREAM_COMPONENT,
+ false /*isPreview*/,
+ false, /*shouldShowComplication*/
+ )
+ // Set communal available, verify that onRedirectWake is never called.
+ kosmos.setCommunalAvailable(true)
+ mMainExecutor.runAllReady()
+ runCurrent()
+ verify(mDreamOverlayCallback, never()).onRedirectWake(any())
+ }
+
+ @Test
@EnableFlags(Flags.FLAG_DREAM_WAKE_REDIRECT, FLAG_COMMUNAL_HUB)
+ @DisableFlags(FLAG_GLANCEABLE_HUB_V2)
@Throws(RemoteException::class)
fun testRedirectExit() =
testScope.runTest {
@@ -1347,7 +1371,11 @@
@JvmStatic
@Parameters(name = "{0}")
fun getParams(): List<FlagsParameterization> {
- return FlagsParameterization.allCombinationsOf(FLAG_COMMUNAL_HUB).andSceneContainer()
+ return FlagsParameterization.allCombinationsOf(
+ FLAG_COMMUNAL_HUB,
+ FLAG_GLANCEABLE_HUB_V2,
+ )
+ .andSceneContainer()
}
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt
index bf49186..451ebf3 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt
@@ -27,7 +27,7 @@
import android.provider.Settings.Secure.ZEN_DURATION_PROMPT
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import com.android.settingslib.notification.modes.EnableZenModeDialog
+import com.android.settingslib.notification.modes.EnableDndDialogFactory
import com.android.settingslib.notification.modes.TestModeBuilder.MANUAL_DND
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.Expandable
@@ -85,7 +85,7 @@
@Mock private lateinit var zenModeController: ZenModeController
@Mock private lateinit var userTracker: UserTracker
@Mock private lateinit var conditionUri: Uri
- @Mock private lateinit var enableZenModeDialog: EnableZenModeDialog
+ @Mock private lateinit var mEnableDndDialogFactory: EnableDndDialogFactory
@Captor private lateinit var spyZenMode: ArgumentCaptor<Int>
@Captor private lateinit var spyConditionId: ArgumentCaptor<Uri?>
@@ -105,7 +105,7 @@
testDispatcher,
testScope.backgroundScope,
conditionUri,
- enableZenModeDialog,
+ mEnableDndDialogFactory,
)
}
@@ -322,7 +322,7 @@
testScope.runTest {
val expandable: Expandable = mock()
secureSettingsRepository.setInt(Settings.Secure.ZEN_DURATION, ZEN_DURATION_PROMPT)
- whenever(enableZenModeDialog.createDialog()).thenReturn(mock())
+ whenever(mEnableDndDialogFactory.createDialog()).thenReturn(mock())
collectLastValue(underTest.lockScreenState)
runCurrent()
@@ -344,7 +344,7 @@
whenever(zenModeController.isZenAvailable).thenReturn(true)
whenever(zenModeController.zen).thenReturn(ZEN_MODE_OFF)
settings.putInt(Settings.Secure.ZEN_DURATION, ZEN_DURATION_PROMPT)
- whenever(enableZenModeDialog.createDialog()).thenReturn(mock())
+ whenever(mEnableDndDialogFactory.createDialog()).thenReturn(mock())
collectLastValue(underTest.lockScreenState)
runCurrent()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
index fe9da0d..88c8b1f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
@@ -19,6 +19,7 @@
import android.app.admin.DevicePolicyManager
import android.os.UserHandle
+import android.view.accessibility.AccessibilityManager
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.widget.LockPatternUtils
@@ -96,6 +97,7 @@
@Mock private lateinit var shadeInteractor: ShadeInteractor
@Mock private lateinit var logger: KeyguardQuickAffordancesLogger
@Mock private lateinit var metricsLogger: KeyguardQuickAffordancesMetricsLogger
+ @Mock private lateinit var accessibilityManager: AccessibilityManager
private lateinit var underTest: KeyguardQuickAffordanceInteractor
@@ -199,11 +201,13 @@
backgroundDispatcher = kosmos.testDispatcher,
appContext = context,
communalSettingsInteractor = kosmos.communalSettingsInteractor,
+ accessibilityManager = accessibilityManager,
sceneInteractor = { kosmos.sceneInteractor },
)
kosmos.keyguardQuickAffordanceInteractor = underTest
whenever(shadeInteractor.anyExpansion).thenReturn(MutableStateFlow(0f))
+ whenever(accessibilityManager.isEnabled()).thenReturn(false)
}
@Test
@@ -672,6 +676,22 @@
}
@Test
+ fun useLongPress_withA11yEnabled_isFalse() =
+ testScope.runTest {
+ whenever(accessibilityManager.isEnabled()).thenReturn(true)
+ val useLongPress by collectLastValue(underTest.useLongPress())
+ assertThat(useLongPress).isFalse()
+ }
+
+ @Test
+ fun useLongPress_withA11yDisabled_isFalse() =
+ testScope.runTest {
+ whenever(accessibilityManager.isEnabled()).thenReturn(false)
+ val useLongPress by collectLastValue(underTest.useLongPress())
+ assertThat(useLongPress).isTrue()
+ }
+
+ @Test
fun useLongPress_whenDocked_isFalse() =
testScope.runTest {
dockManager.setIsDocked(true)
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 605a5d2..bafabe0 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.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.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 @@
@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/keyguard/ui/viewmodel/AodToPrimaryBouncerTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodToPrimaryBouncerTransitionViewModelTest.kt
index 4936f85..d782d1e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodToPrimaryBouncerTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodToPrimaryBouncerTransitionViewModelTest.kt
@@ -15,16 +15,25 @@
*/
package com.android.systemui.keyguard.ui.viewmodel
+import android.platform.test.annotations.EnableFlags
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectValues
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionState.FINISHED
+import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING
+import com.android.systemui.keyguard.shared.model.TransitionState.STARTED
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.keyguard.ui.transitions.blurConfig
import com.android.systemui.kosmos.testScope
import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
@@ -46,17 +55,33 @@
transitionProgress = listOf(0.0f, 0.0f, 0.3f, 0.4f, 0.5f, 1.0f),
startValue = kosmos.blurConfig.maxBlurRadiusPx,
endValue = kosmos.blurConfig.maxBlurRadiusPx,
- transitionFactory = { value, state ->
- TransitionStep(
- from = KeyguardState.AOD,
- to = KeyguardState.PRIMARY_BOUNCER,
- value = value,
- transitionState = state,
- ownerName = "AodToPrimaryBouncerTransitionViewModelTest",
- )
- },
+ transitionFactory = ::step,
actualValuesProvider = { values },
checkInterpolatedValues = false,
)
}
+
+ @Test
+ @EnableFlags(Flags.FLAG_BOUNCER_UI_REVAMP)
+ fun aodToPrimaryBouncerHidesLockscreen() =
+ testScope.runTest {
+ val lockscreenAlpha by collectValues(underTest.lockscreenAlpha)
+ val notificationAlpha by collectValues(underTest.notificationAlpha)
+
+ val transitionSteps = listOf(step(0.0f, STARTED), step(0.5f), step(1.0f, FINISHED))
+ kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps(transitionSteps, testScope)
+ runCurrent()
+
+ lockscreenAlpha.forEach { assertThat(it).isEqualTo(0.0f) }
+ notificationAlpha.forEach { assertThat(it).isEqualTo(0.0f) }
+ }
+
+ private fun step(value: Float, transitionState: TransitionState = RUNNING) =
+ TransitionStep(
+ from = KeyguardState.AOD,
+ to = KeyguardState.PRIMARY_BOUNCER,
+ value = value,
+ transitionState = transitionState,
+ ownerName = "AodToPrimaryBouncerTransitionViewModelTest",
+ )
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DozingToPrimaryBouncerTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DozingToPrimaryBouncerTransitionViewModelTest.kt
index 0d48750..4d58f7a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DozingToPrimaryBouncerTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DozingToPrimaryBouncerTransitionViewModelTest.kt
@@ -16,21 +16,26 @@
package com.android.systemui.keyguard.ui.viewmodel
+import android.platform.test.annotations.EnableFlags
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectValues
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionState.FINISHED
import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING
+import com.android.systemui.keyguard.shared.model.TransitionState.STARTED
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.keyguard.ui.transitions.blurConfig
import com.android.systemui.kosmos.testScope
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
@@ -85,6 +90,21 @@
)
}
+ @Test
+ @EnableFlags(Flags.FLAG_BOUNCER_UI_REVAMP)
+ fun dozingToPrimaryBouncerHidesLockscreen() =
+ testScope.runTest {
+ val lockscreenAlpha by collectValues(underTest.lockscreenAlpha)
+ val notificationAlpha by collectValues(underTest.notificationAlpha)
+
+ val transitionSteps = listOf(step(0.0f, STARTED), step(0.5f), step(1.0f, FINISHED))
+ kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps(transitionSteps, testScope)
+ runCurrent()
+
+ lockscreenAlpha.forEach { assertThat(it).isEqualTo(0.0f) }
+ notificationAlpha.forEach { assertThat(it).isEqualTo(0.0f) }
+ }
+
private fun step(value: Float, state: TransitionState = RUNNING): TransitionStep {
return TransitionStep(
from = KeyguardState.DOZING,
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 ec0773f..5a35043 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.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 @@
@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 @@
testScope.runTest {
val latest by collectLastValue(underTest.dualShadeColumns)
- assertThat(latest).isEqualTo(4)
+ assertThat(latest).isEqualTo(2)
}
}
@@ -72,9 +81,8 @@
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 9173ac9..f005375 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.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 @@
)
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/base/logging/QSTileLoggerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/logging/QSTileLoggerTest.kt
index a063531..6a33b5f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/logging/QSTileLoggerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/logging/QSTileLoggerTest.kt
@@ -54,7 +54,7 @@
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
- whenever(logBufferFactory.create(any(), any(), any(), any())).thenReturn(logBuffer)
+ whenever(logBufferFactory.create(any(), any(), any(), any(), any())).thenReturn(logBuffer)
val tileSpec: TileSpec = TileSpec.create("chatty_tile")
underTest =
QSTileLogger(mapOf(tileSpec to chattyLogBuffer), logBufferFactory, statusBarController)
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 c8b3aba..89b8e91 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.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 @@
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 ac3089d..75262a4 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
@@ -23,6 +23,7 @@
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
@@ -30,10 +31,19 @@
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.core.StatusBarRootModernization
+import com.android.systemui.statusbar.notification.data.model.activeNotificationModel
+import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
+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.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
@@ -42,6 +52,7 @@
import kotlin.test.Test
import kotlinx.coroutines.test.runTest
import org.junit.runner.RunWith
+import org.mockito.kotlin.any
import org.mockito.kotlin.mock
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
@@ -50,6 +61,7 @@
@RunWith(AndroidJUnit4::class)
class CallChipViewModelTest : SysuiTestCase() {
private val kosmos = Kosmos()
+ private val notificationListRepository = kosmos.activeNotificationListRepository
private val testScope = kosmos.testScope
private val repo = kosmos.ongoingCallRepository
@@ -63,8 +75,10 @@
)
.thenReturn(chipBackgroundView)
}
+ private val mockExpandable: Expandable =
+ mock<Expandable>().apply { whenever(dialogTransitionController(any())).thenReturn(mock()) }
- private val underTest = kosmos.callChipViewModel
+ private val underTest by lazy { kosmos.callChipViewModel }
@Test
fun chip_noCall_isHidden() =
@@ -219,28 +233,94 @@
}
@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)
@@ -269,23 +349,25 @@
}
@Test
+ @DisableFlags(StatusBarChipsModernization.FLAG_NAME)
fun chip_inCall_nullIntent_nullClickListener() =
testScope.runTest {
val latest by collectLastValue(underTest.chip)
repo.setOngoingCallState(inCallModel(startTimeMs = 1000, intent = null))
- assertThat((latest as OngoingActivityChipModel.Shown).onClickListener).isNull()
+ assertThat((latest as OngoingActivityChipModel.Shown).onClickListenerLegacy).isNull()
}
@Test
+ @DisableFlags(StatusBarChipsModernization.FLAG_NAME)
fun chip_inCall_positiveStartTime_validIntent_clickListenerLaunchesIntent() =
testScope.runTest {
val latest by collectLastValue(underTest.chip)
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)
@@ -296,13 +378,15 @@
}
@Test
+ @DisableFlags(StatusBarChipsModernization.FLAG_NAME)
fun chip_inCall_zeroStartTime_validIntent_clickListenerLaunchesIntent() =
testScope.runTest {
val latest by collectLastValue(underTest.chip)
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)
@@ -312,6 +396,72 @@
verify(kosmos.activityStarter).postStartActivityDismissingKeyguard(pendingIntent, null)
}
+ @Test
+ @EnableFlags(StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME)
+ fun chip_inCall_nullIntent_noneClickBehavior() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.chip)
+
+ postOngoingCallNotification(
+ repository = notificationListRepository,
+ startTimeMs = 1000L,
+ intent = null,
+ )
+
+ assertThat((latest as OngoingActivityChipModel.Shown).clickBehavior)
+ .isInstanceOf(OngoingActivityChipModel.ClickBehavior.None::class.java)
+ }
+
+ @Test
+ @EnableFlags(StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME)
+ fun chip_inCall_positiveStartTime_validIntent_clickBehaviorLaunchesIntent() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.chip)
+
+ val pendingIntent = mock<PendingIntent>()
+ postOngoingCallNotification(
+ repository = notificationListRepository,
+ startTimeMs = 1000L,
+ intent = pendingIntent,
+ )
+
+ val clickBehavior = (latest as OngoingActivityChipModel.Shown).clickBehavior
+ assertThat(clickBehavior)
+ .isInstanceOf(OngoingActivityChipModel.ClickBehavior.ExpandAction::class.java)
+ (clickBehavior as OngoingActivityChipModel.ClickBehavior.ExpandAction).onClick(
+ mockExpandable
+ )
+
+ // Ensure that the SysUI didn't modify the notification's intent by verifying it
+ // directly matches the `PendingIntent` set -- see b/212467440.
+ verify(kosmos.activityStarter).postStartActivityDismissingKeyguard(pendingIntent, null)
+ }
+
+ @Test
+ @EnableFlags(StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME)
+ fun chip_inCall_zeroStartTime_validIntent_clickBehaviorLaunchesIntent() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.chip)
+
+ val pendingIntent = mock<PendingIntent>()
+ postOngoingCallNotification(
+ repository = notificationListRepository,
+ startTimeMs = 0L,
+ intent = pendingIntent,
+ )
+
+ val clickBehavior = (latest as OngoingActivityChipModel.Shown).clickBehavior
+ assertThat(clickBehavior)
+ .isInstanceOf(OngoingActivityChipModel.ClickBehavior.ExpandAction::class.java)
+ (clickBehavior as OngoingActivityChipModel.ClickBehavior.ExpandAction).onClick(
+ mockExpandable
+ )
+
+ // Ensure that the SysUI didn't modify the notification's intent by verifying it
+ // directly matches the `PendingIntent` set -- see b/212467440.
+ verify(kosmos.activityStarter).postStartActivityDismissingKeyguard(pendingIntent, null)
+ }
+
companion object {
fun createStatusBarIconViewOrNull(): StatusBarIconView? =
if (StatusBarConnectedDisplays.isEnabled) {
@@ -319,5 +469,40 @@
} else {
mock<StatusBarIconView>()
}
+
+ fun postOngoingCallNotification(
+ repository: ActiveNotificationListRepository,
+ startTimeMs: Long,
+ intent: PendingIntent?,
+ ) {
+ repository.activeNotifications.value =
+ ActiveNotificationsStore.Builder()
+ .apply {
+ addIndividualNotif(
+ activeNotificationModel(
+ key = "notif1",
+ whenTime = startTimeMs,
+ callType = CallType.Ongoing,
+ statusBarChipIcon = null,
+ contentIntent = intent,
+ )
+ )
+ }
+ .build()
+ }
+
+ 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 c511c43..fcf8c83 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.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.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 @@
)
.thenReturn(chipBackgroundView)
}
+ private val mockExpandable: Expandable =
+ mock<Expandable>().apply { whenever(dialogTransitionController(any())).thenReturn(mock()) }
private val underTest = kosmos.castToOtherDeviceChipViewModel
@@ -263,7 +269,13 @@
// 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 @@
// 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 @@
}
@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 @@
}
@Test
+ @DisableFlags(StatusBarChipsModernization.FLAG_NAME)
fun chip_projectionStateSingleTask_clickListenerShowsScreenCastDialog() =
testScope.runTest {
val latest by collectLastValue(underTest.chip)
@@ -442,7 +462,7 @@
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 @@
}
@Test
+ @DisableFlags(StatusBarChipsModernization.FLAG_NAME)
fun chip_routerStateCasting_clickListenerShowsGenericCastDialog() =
testScope.runTest {
val latest by collectLastValue(underTest.chip)
@@ -466,7 +487,7 @@
)
)
- val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener)
+ val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListenerLegacy)
assertThat(clickListener).isNotNull()
clickListener!!.onClick(chipView)
@@ -480,13 +501,14 @@
}
@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 @@
}
@Test
+ @DisableFlags(StatusBarChipsModernization.FLAG_NAME)
fun chip_routerStateCasting_clickListenerHasCuj() =
testScope.runTest {
val latest by collectLastValue(underTest.chip)
@@ -514,7 +537,7 @@
)
)
- 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 @@
.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 ee4a52d..e89c929 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.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 @@
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 902db5e..eec23d3 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 @@
)
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 48d8add6..1f82dcd 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.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 @@
)
.thenReturn(chipBackgroundView)
}
+ private val mockExpandable: Expandable =
+ mock<Expandable>().apply { whenever(dialogTransitionController(any())).thenReturn(mock()) }
private val underTest = kosmos.screenRecordChipViewModel
@@ -106,7 +113,7 @@
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 @@
// 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 @@
}
@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 @@
}
@Test
+ @DisableFlags(StatusBarChipsModernization.FLAG_NAME)
fun chip_projectingEntireScreen_clickListenerShowsDialog() =
testScope.runTest {
val latest by collectLastValue(underTest.chip)
@@ -286,7 +301,7 @@
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 @@
}
@Test
+ @DisableFlags(StatusBarChipsModernization.FLAG_NAME)
fun chip_projectingSingleTask_clickListenerShowsDialog() =
testScope.runTest {
val latest by collectLastValue(underTest.chip)
@@ -307,7 +323,7 @@
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 @@
}
@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 b3dec2e..36fc5aa 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.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.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 @@
)
.thenReturn(chipBackgroundView)
}
+ private val mockExpandable: Expandable =
+ mock<Expandable>().apply { whenever(dialogTransitionController(any())).thenReturn(mock()) }
private val underTest = kosmos.shareToAppChipViewModel
@@ -215,7 +221,13 @@
// 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 @@
@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 @@
}
@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 @@
}
@Test
+ @DisableFlags(StatusBarChipsModernization.FLAG_NAME)
fun chip_singleTask_clickListenerShowsScreenShareDialog() =
testScope.runTest {
val latest by collectLastValue(underTest.chip)
@@ -318,7 +333,7 @@
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 @@
}
@Test
+ @DisableFlags(StatusBarChipsModernization.FLAG_NAME)
fun chip_clickListenerHasCuj() =
testScope.runTest {
val latest by collectLastValue(underTest.chip)
@@ -342,7 +358,7 @@
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 @@
.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 8d4c68d..d099e70 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 @@
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 @@
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 @@
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 @@
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 e3510f5..fc3af11 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.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 @@
)
.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 @@
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 42358cc..a4b6a84 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 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.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 @@
)
.thenReturn(chipBackgroundView)
}
+ private val mockExpandable: Expandable =
+ mock<Expandable>().apply { whenever(dialogTransitionController(any())).thenReturn(mock()) }
private val underTest = kosmos.ongoingActivityChipsViewModel
@@ -294,7 +298,13 @@
// 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 @@
// 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 @@
fun getStopActionFromDialog(
latest: OngoingActivityChipModel?,
chipView: View,
+ expandable: Expandable,
dialog: SystemUIDialog,
kosmos: Kosmos,
): DialogInterface.OnClickListener {
@@ -349,9 +366,17 @@
.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 0f42f29..28f3601 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 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 @@
)
.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 @@
// 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 @@
// 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/collection/NotifCollectionTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
index 4ef9792..0df1073 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
@@ -1383,7 +1383,6 @@
}
@Test
- @EnableFlags(Flags.FLAG_NOTIFICATIONS_DISMISS_PRUNED_SUMMARIES)
public void testDismissNotificationsIncludesPrunedParents() {
// GIVEN a collection with 2 groups; one has a single child, one has two.
mCollection.addNotificationDismissInterceptor(mInterceptor1);
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
index 657fffb..7b12094 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
@@ -16,17 +16,21 @@
package com.android.systemui.statusbar.notification.collection.coordinator;
-import static com.android.systemui.flags.SceneContainerFlagParameterizationKt.parameterizeSceneContainerFlag;
+import static android.platform.test.flag.junit.FlagsParameterization.allCombinationsOf;
import static com.google.common.truth.Truth.assertThat;
import static junit.framework.Assert.assertFalse;
+
+import static org.junit.Assume.assumeFalse;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -48,6 +52,7 @@
import com.android.systemui.flags.BrokenWithSceneContainer;
import com.android.systemui.flags.DisableSceneContainer;
import com.android.systemui.flags.EnableSceneContainer;
+import com.android.systemui.flags.SceneContainerFlagParameterizationKt;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.keyguard.shared.model.KeyguardState;
import com.android.systemui.keyguard.shared.model.TransitionState;
@@ -86,9 +91,11 @@
import org.mockito.verification.VerificationMode;
import java.util.List;
+import java.util.Set;
import kotlinx.coroutines.flow.MutableStateFlow;
import kotlinx.coroutines.test.TestScope;
+
import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
import platform.test.runner.parameterized.Parameters;
@@ -99,7 +106,8 @@
@Parameters(name = "{0}")
public static List<FlagsParameterization> getParams() {
- return parameterizeSceneContainerFlag();
+ return SceneContainerFlagParameterizationKt
+ .andSceneContainer(allCombinationsOf(Flags.FLAG_STABILIZE_HEADS_UP_GROUP));
}
private VisualStabilityCoordinator mCoordinator;
@@ -122,7 +130,8 @@
private final KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
private FakeSystemClock mFakeSystemClock = new FakeSystemClock();
- private FakeExecutor mFakeExecutor = new FakeExecutor(mFakeSystemClock);
+ private FakeExecutor mFakeBackgroundExecutor = new FakeExecutor(mFakeSystemClock);
+ private FakeExecutor mFakeMainExecutor = new FakeExecutor(mFakeSystemClock);
private final TestScope mTestScope = mKosmos.getTestScope();
private final JavaAdapter mJavaAdapter = new JavaAdapter(mTestScope.getBackgroundScope());
@@ -147,7 +156,8 @@
mShadeAnimationInteractor = new ShadeAnimationInteractorLegacyImpl(
new ShadeAnimationRepository(), mShadeRepository);
mCoordinator = new VisualStabilityCoordinator(
- mFakeExecutor,
+ mFakeBackgroundExecutor,
+ mFakeMainExecutor,
mDumpManager,
mHeadsUpManager,
mShadeAnimationInteractor,
@@ -417,8 +427,8 @@
assertTrue(mNotifStabilityManager.isSectionChangeAllowed(mEntry));
// WHEN the timeout for the temporarily allow section reordering runnable is finsihed
- mFakeExecutor.advanceClockToNext();
- mFakeExecutor.runNextReady();
+ mFakeBackgroundExecutor.advanceClockToNext();
+ mFakeBackgroundExecutor.runNextReady();
// THEN section changes aren't allowed anymore
assertFalse(mNotifStabilityManager.isSectionChangeAllowed(mEntry));
@@ -701,6 +711,212 @@
assertFalse(mNotifStabilityManager.isGroupPruneAllowed(mGroupEntry));
}
+ @Test
+ public void everyChangeAllowed_onReorderingEnabled_legacy() {
+ assumeFalse(StabilizeHeadsUpGroup.isEnabled());
+ // GIVEN - reordering is allowed.
+ setPulsing(false);
+ setPanelExpanded(false);
+
+ // THEN
+ assertThat(mNotifStabilityManager.isEveryChangeAllowed()).isTrue();
+ assertThat(mNotifStabilityManager.isGroupChangeAllowed(any())).isTrue();
+ assertThat(mNotifStabilityManager.isGroupPruneAllowed(any())).isTrue();
+ assertThat(mNotifStabilityManager.isSectionChangeAllowed(any())).isTrue();
+ assertThat(mNotifStabilityManager.isEntryReorderingAllowed(any())).isTrue();
+ }
+
+ @Test
+ public void everyChangeAllowed_noActiveHeadsUpGroup_onReorderingEnabled() {
+ assumeTrue(StabilizeHeadsUpGroup.isEnabled());
+ // GIVEN - reordering is allowed.
+ setPulsing(false);
+ setPanelExpanded(false);
+
+ // GIVEN - empty heads-up-group keys
+ mCoordinator.setHeadsUpGroupKeys(Set.of());
+
+ // THEN
+ assertThat(mNotifStabilityManager.isEveryChangeAllowed()).isTrue();
+ assertThat(mNotifStabilityManager.isGroupChangeAllowed(any())).isTrue();
+ assertThat(mNotifStabilityManager.isGroupPruneAllowed(any())).isTrue();
+ assertThat(mNotifStabilityManager.isSectionChangeAllowed(any())).isTrue();
+ assertThat(mNotifStabilityManager.isEntryReorderingAllowed(any())).isTrue();
+ }
+
+ @Test
+ public void everyChangeDisallowed_activeHeadsUpGroup_onReorderingEnabled() {
+ assumeTrue(StabilizeHeadsUpGroup.isEnabled());
+ // GIVEN - reordering is allowed.
+ setPulsing(false);
+ setPanelExpanded(false);
+
+ // GIVEN - there is a group heads-up.
+ mCoordinator.setHeadsUpGroupKeys(Set.of("heads_up_group_key"));
+
+ // THEN
+ assertThat(mNotifStabilityManager.isEveryChangeAllowed()).isFalse();
+ }
+
+ @Test
+ public void nonHeadsUpGroup_changesAllowed_onReorderingEnabled() {
+ assumeTrue(StabilizeHeadsUpGroup.isEnabled());
+ // GIVEN - reordering is allowed.
+ setPulsing(false);
+ setPanelExpanded(false);
+
+ // GIVEN - there is a group heads-up.
+ String headsUpGroupKey = "heads_up_group_key";
+ mCoordinator.setHeadsUpGroupKeys(Set.of(headsUpGroupKey));
+ when(mHeadsUpManager.isHeadsUpEntry(headsUpGroupKey)).thenReturn(true);
+
+ // GIVEN - HUN Group Summary
+ final NotificationEntry nonHeadsUpGroupSummary = mock(NotificationEntry.class);
+ when(nonHeadsUpGroupSummary.getKey()).thenReturn("non_heads_up_group_key");
+ when(nonHeadsUpGroupSummary.isSummaryWithChildren()).thenReturn(true);
+ final GroupEntry nonHeadsUpGroupEntry = mock(GroupEntry.class);
+ when(nonHeadsUpGroupEntry.getSummary()).thenReturn(nonHeadsUpGroupSummary);
+ when(nonHeadsUpGroupEntry.getRepresentativeEntry()).thenReturn(nonHeadsUpGroupSummary);
+
+ // THEN
+ assertThat(mNotifStabilityManager.isGroupPruneAllowed(nonHeadsUpGroupEntry)).isTrue();
+ assertThat(mNotifStabilityManager.isEntryReorderingAllowed(nonHeadsUpGroupEntry)).isTrue();
+ }
+
+ @Test
+ public void headsUpGroup_changesDisallowed_onReorderingEnabled() {
+ assumeTrue(StabilizeHeadsUpGroup.isEnabled());
+ // GIVEN - reordering is allowed.
+ setPulsing(false);
+ setPanelExpanded(false);
+
+ // GIVEN - there is a group heads-up.
+ final String headsUpGroupKey = "heads_up_group_key";
+ mCoordinator.setHeadsUpGroupKeys(Set.of(headsUpGroupKey));
+ when(mHeadsUpManager.isHeadsUpEntry(headsUpGroupKey)).thenReturn(true);
+
+ // GIVEN - HUN Group
+ final NotificationEntry headsUpGroupSummary = mock(NotificationEntry.class);
+ when(headsUpGroupSummary.rowIsChildInGroup()).thenReturn(false);
+ when(headsUpGroupSummary.getKey()).thenReturn(headsUpGroupKey);
+ when(headsUpGroupSummary.isSummaryWithChildren()).thenReturn(true);
+
+ final GroupEntry headsUpGroupEntry = mock(GroupEntry.class);
+ when(headsUpGroupEntry.getSummary()).thenReturn(headsUpGroupSummary);
+ when(headsUpGroupEntry.getRepresentativeEntry()).thenReturn(headsUpGroupSummary);
+
+ when(headsUpGroupSummary.getParent()).thenReturn(headsUpGroupEntry);
+
+ // GIVEN - HUN is in visible location
+ when(mVisibilityLocationProvider.isInVisibleLocation(headsUpGroupSummary)).thenReturn(true);
+
+ // THEN
+ assertThat(mNotifStabilityManager.isGroupPruneAllowed(headsUpGroupEntry)).isFalse();
+ assertThat(mNotifStabilityManager.isEntryReorderingAllowed(headsUpGroupEntry)).isFalse();
+ }
+
+ @Test
+ public void headsUpGroupSummaries_changesDisallowed_onReorderingEnabled() {
+ assumeTrue(StabilizeHeadsUpGroup.isEnabled());
+ // GIVEN - reordering is allowed.
+ setPulsing(false);
+ setPanelExpanded(false);
+
+ // GIVEN - there is a group heads-up.
+ final String headsUpGroupKey = "heads_up_group_key";
+ mCoordinator.setHeadsUpGroupKeys(Set.of(headsUpGroupKey));
+ when(mHeadsUpManager.isHeadsUpEntry(headsUpGroupKey)).thenReturn(true);
+
+ // GIVEN - HUN Group
+ final NotificationEntry headsUpGroupSummary = mock(NotificationEntry.class);
+ when(headsUpGroupSummary.rowIsChildInGroup()).thenReturn(false);
+ when(headsUpGroupSummary.getKey()).thenReturn(headsUpGroupKey);
+ when(headsUpGroupSummary.isSummaryWithChildren()).thenReturn(true);
+
+ final GroupEntry headsUpGroupEntry = mock(GroupEntry.class);
+ when(headsUpGroupEntry.getSummary()).thenReturn(headsUpGroupSummary);
+ when(headsUpGroupEntry.getRepresentativeEntry()).thenReturn(headsUpGroupSummary);
+
+ when(headsUpGroupSummary.getParent()).thenReturn(headsUpGroupEntry);
+
+ // GIVEN - HUN is in visible location
+ when(mVisibilityLocationProvider.isInVisibleLocation(headsUpGroupSummary)).thenReturn(true);
+
+ // THEN
+ assertThat(mNotifStabilityManager.isGroupChangeAllowed(headsUpGroupSummary)).isFalse();
+ assertThat(mNotifStabilityManager.isEntryReorderingAllowed(headsUpGroupSummary)).isFalse();
+ assertThat(mNotifStabilityManager.isSectionChangeAllowed(headsUpGroupSummary)).isFalse();
+ }
+
+ @Test
+ public void notificationInNonHUNGroup_changesAllowed_onReorderingEnabled() {
+ assumeTrue(StabilizeHeadsUpGroup.isEnabled());
+ // GIVEN - reordering is allowed.
+ setPulsing(false);
+ setPanelExpanded(false);
+
+ // GIVEN - there is a group heads-up.
+ String headsUpGroupKey = "heads_up_group_key";
+ mCoordinator.setHeadsUpGroupKeys(Set.of(headsUpGroupKey));
+ when(mHeadsUpManager.isHeadsUpEntry(headsUpGroupKey)).thenReturn(true);
+
+ // GIVEN - non HUN parent Group Summary
+ final NotificationEntry groupSummary = mock(NotificationEntry.class);
+ when(groupSummary.getKey()).thenReturn("non_heads_up_group_key");
+ when(groupSummary.isSummaryWithChildren()).thenReturn(true);
+
+ final GroupEntry nonHeadsUpGroupEntry = mock(GroupEntry.class);
+ when(nonHeadsUpGroupEntry.getSummary()).thenReturn(groupSummary);
+ when(nonHeadsUpGroupEntry.getRepresentativeEntry()).thenReturn(groupSummary);
+
+ // GIVEN - child entry in a non heads-up group.
+ final NotificationEntry childEntry = mock(NotificationEntry.class);
+ when(childEntry.rowIsChildInGroup()).thenReturn(true);
+ when(childEntry.getParent()).thenReturn(nonHeadsUpGroupEntry);
+ when(childEntry.getParent()).thenReturn(nonHeadsUpGroupEntry);
+
+ // THEN
+ assertThat(mNotifStabilityManager.isGroupChangeAllowed(childEntry)).isTrue();
+ assertThat(mNotifStabilityManager.isSectionChangeAllowed(childEntry)).isTrue();
+ assertThat(mNotifStabilityManager.isEntryReorderingAllowed(nonHeadsUpGroupEntry)).isTrue();
+ }
+
+ @Test
+ public void notificationInHUNGroup_changesDisallowed_reorderingEnabled() {
+ assumeTrue(StabilizeHeadsUpGroup.isEnabled());
+ // GIVEN - reordering is allowed.
+ setPulsing(false);
+ setPanelExpanded(false);
+
+ // GIVEN - there is a group heads-up.
+ final String headsUpGroupKey = "heads_up_group_key";
+ mCoordinator.setHeadsUpGroupKeys(Set.of(headsUpGroupKey));
+ when(mHeadsUpManager.isHeadsUpEntry(headsUpGroupKey)).thenReturn(true);
+
+ // GIVEN - HUN Group Summary
+ final NotificationEntry headsUpGroupSummary = mock(NotificationEntry.class);
+ when(headsUpGroupSummary.rowIsChildInGroup()).thenReturn(false);
+ when(headsUpGroupSummary.getKey()).thenReturn(headsUpGroupKey);
+ when(headsUpGroupSummary.isSummaryWithChildren()).thenReturn(true);
+
+ final GroupEntry nonHeadsUpGroupEntry = mock(GroupEntry.class);
+ when(nonHeadsUpGroupEntry.getSummary()).thenReturn(headsUpGroupSummary);
+ when(nonHeadsUpGroupEntry.getRepresentativeEntry()).thenReturn(headsUpGroupSummary);
+
+ // GIVEN - child entry in a non heads-up group.
+ final NotificationEntry childEntry = mock(NotificationEntry.class);
+ when(childEntry.rowIsChildInGroup()).thenReturn(true);
+ when(childEntry.getParent()).thenReturn(nonHeadsUpGroupEntry);
+
+ // GIVEN - HUN is in visible location
+ when(mVisibilityLocationProvider.isInVisibleLocation(headsUpGroupSummary)).thenReturn(true);
+
+ // THEN
+ assertThat(mNotifStabilityManager.isGroupChangeAllowed(childEntry)).isFalse();
+ assertThat(mNotifStabilityManager.isSectionChangeAllowed(childEntry)).isFalse();
+ assertThat(mNotifStabilityManager.isEntryReorderingAllowed(childEntry)).isFalse();
+ }
+
private void verifyStabilityManagerWasInvalidated(VerificationMode mode) {
verify(mInvalidateListener, mode).onPluggableInvalidated(eq(mNotifStabilityManager), any());
}
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 739a9c9..9dfc922 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 @@
}
@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 @@
}
@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/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt
index d86c6ef..ba73504 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt
@@ -18,6 +18,8 @@
import android.graphics.Rect
import android.graphics.drawable.Icon
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.FlagsParameterization
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -36,6 +38,7 @@
import com.android.systemui.power.shared.model.WakeSleepReason
import com.android.systemui.power.shared.model.WakefulnessState
import com.android.systemui.shade.shadeTestUtil
+import com.android.systemui.statusbar.headsup.shared.StatusBarNoHunBehavior
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
@@ -258,6 +261,7 @@
}
@Test
+ @DisableFlags(StatusBarNoHunBehavior.FLAG_NAME)
fun isolatedIcon_animateOnAppear_shadeCollapsed() =
testScope.runTest {
val icon: Icon = mock()
@@ -285,6 +289,7 @@
}
@Test
+ @DisableFlags(StatusBarNoHunBehavior.FLAG_NAME)
fun isolatedIcon_dontAnimateOnAppear_shadeExpanded() =
testScope.runTest {
val icon: Icon = mock()
@@ -312,6 +317,7 @@
}
@Test
+ @DisableFlags(StatusBarNoHunBehavior.FLAG_NAME)
fun isolatedIcon_updateWhenIconDataChanges() =
testScope.runTest {
val icon: Icon = mock()
@@ -339,6 +345,7 @@
}
@Test
+ @DisableFlags(StatusBarNoHunBehavior.FLAG_NAME)
fun isolatedIcon_lastMessageIsFromReply_notNull() =
testScope.runTest {
val icon: Icon = mock()
@@ -362,4 +369,32 @@
assertThat(isolatedIcon?.value?.notifKey).isEqualTo("notif1")
}
+
+ @Test
+ @EnableFlags(StatusBarNoHunBehavior.FLAG_NAME)
+ fun isolatedIcon_noHunBehaviorFlagEnabled_doesNothing() =
+ testScope.runTest {
+ val icon: Icon = mock()
+ val isolatedIcon by collectLastValue(underTest.isolatedIcon)
+ runCurrent()
+
+ headsUpViewStateRepository.isolatedNotification.value = "notif1"
+ runCurrent()
+
+ activeNotificationsRepository.activeNotifications.value =
+ ActiveNotificationsStore.Builder()
+ .apply {
+ addIndividualNotif(
+ activeNotificationModel(
+ key = "notif1",
+ groupKey = "group",
+ statusBarIcon = icon,
+ )
+ )
+ }
+ .build()
+ runCurrent()
+
+ assertThat(isolatedIcon?.value).isNull()
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderTest.java
index f7673da..4c87e47 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderTest.java
@@ -26,12 +26,10 @@
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
-
import androidx.core.os.CancellationSignal;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import com.android.internal.util.NotificationMessagingUtil;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -51,7 +49,6 @@
@SmallTest
public class HeadsUpViewBinderTest extends SysuiTestCase {
private HeadsUpViewBinder mViewBinder;
- @Mock private NotificationMessagingUtil mNotificationMessagingUtil;
@Mock private RowContentBindStage mBindStage;
private final HeadsUpViewBinderLogger mLogger = spy(
new HeadsUpViewBinderLogger(logcatLogBuffer()));
@@ -61,7 +58,7 @@
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
- mViewBinder = new HeadsUpViewBinder(mNotificationMessagingUtil, mBindStage, mLogger);
+ mViewBinder = new HeadsUpViewBinder(mBindStage, mLogger);
when(mEntry.getKey()).thenReturn("key");
when(mEntry.getRow()).thenReturn(mRow);
when(mBindStage.getStageParams(eq(mEntry))).thenReturn(new RowContentBindParams());
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorImplTest.kt
index 9227119..8d90d38 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorImplTest.kt
@@ -212,7 +212,11 @@
}
@Test
- @EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME)
+ @EnableFlags(
+ PromotedNotificationUi.FLAG_NAME,
+ StatusBarNotifChips.FLAG_NAME,
+ android.app.Flags.FLAG_API_RICH_ONGOING,
+ )
fun extractContent_fromProgressStyle() {
val entry = createEntry {
setStyle(ProgressStyle().addProgressSegment(Segment(100)).setProgress(75))
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
index a49a66f..da31cd9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
@@ -163,42 +163,6 @@
}
@Test
- public void testIncreasedHeadsUpBeingUsed() {
- BindParams params = new BindParams(false, false, /* usesIncreasedHeadsUpHeight */ true,
- REDACTION_TYPE_NONE);
- Notification.Builder builder = spy(mBuilder);
- mNotificationInflater.inflateNotificationViews(
- mRow.getEntry(),
- mRow,
- params,
- true /* inflateSynchronously */,
- FLAG_CONTENT_VIEW_ALL,
- builder,
- mContext,
- mContext,
- mSmartReplyStateInflater);
- verify(builder).createHeadsUpContentView(true);
- }
-
- @Test
- public void testIncreasedHeightBeingUsed() {
- BindParams params = new BindParams(false, /* usesIncreasedHeight */ true, false,
- REDACTION_TYPE_NONE);
- Notification.Builder builder = spy(mBuilder);
- mNotificationInflater.inflateNotificationViews(
- mRow.getEntry(),
- mRow,
- params,
- true /* inflateSynchronously */,
- FLAG_CONTENT_VIEW_ALL,
- builder,
- mContext,
- mContext,
- mSmartReplyStateInflater);
- verify(builder).createContentView(true);
- }
-
- @Test
public void testInflationCallsUpdated() throws Exception {
inflateAndWait(mNotificationInflater, FLAG_CONTENT_VIEW_ALL, mRow);
verify(mRow).onNotificationUpdated();
@@ -238,7 +202,7 @@
mRow.getEntry(),
mRow,
FLAG_CONTENT_VIEW_ALL,
- new BindParams(false, false, false, REDACTION_TYPE_NONE),
+ new BindParams(false, REDACTION_TYPE_NONE),
false /* forceInflate */,
null /* callback */);
Assert.assertNull(mRow.getEntry().getRunningTask());
@@ -576,7 +540,7 @@
row.getEntry(),
row,
contentToInflate,
- new BindParams(false, false, false, redactionType),
+ new BindParams(false, redactionType),
false /* forceInflate */,
callback /* callback */);
assertTrue(countDownLatch.await(500, TimeUnit.MILLISECONDS));
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt
index f25ba2c..680b1be 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt
@@ -144,43 +144,6 @@
}
@Test
- fun testIncreasedHeadsUpBeingUsed() {
- val params =
- BindParams(false, false, /* usesIncreasedHeadsUpHeight */ true, REDACTION_TYPE_NONE)
- val builder = spy(builder)
- notificationInflater.inflateNotificationViews(
- row.entry,
- row,
- params,
- true, /* inflateSynchronously */
- FLAG_CONTENT_VIEW_ALL,
- builder,
- mContext,
- smartReplyStateInflater,
- mock(),
- )
- verify(builder).createHeadsUpContentView(true)
- }
-
- @Test
- fun testIncreasedHeightBeingUsed() {
- val params = BindParams(false, /* usesIncreasedHeight */ true, false, REDACTION_TYPE_NONE)
- val builder = spy(builder)
- notificationInflater.inflateNotificationViews(
- row.entry,
- row,
- params,
- true, /* inflateSynchronously */
- FLAG_CONTENT_VIEW_ALL,
- builder,
- mContext,
- smartReplyStateInflater,
- mock(),
- )
- verify(builder).createContentView(true)
- }
-
- @Test
fun testInflationCallsUpdated() {
inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_ALL, row)
verify(row).onNotificationUpdated()
@@ -226,7 +189,7 @@
row.entry,
row,
FLAG_CONTENT_VIEW_ALL,
- BindParams(false, false, false, REDACTION_TYPE_NONE),
+ BindParams(false, REDACTION_TYPE_NONE),
false, /* forceInflate */
null, /* callback */
)
@@ -703,7 +666,7 @@
row.entry,
row,
contentToInflate,
- BindParams(false, false, false, redactionType),
+ BindParams(false, redactionType),
false, /* forceInflate */
callback, /* callback */
)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/RowContentBindStageTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/RowContentBindStageTest.java
index 841cb4a..98e275a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/RowContentBindStageTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/RowContentBindStageTest.java
@@ -214,54 +214,6 @@
}
@Test
- public void testSetUseIncreasedHeight() {
- // GIVEN a view with all content bound.
- RowContentBindParams params = mRowContentBindStage.getStageParams(mEntry);
- params.requireContentViews(FLAG_CONTENT_VIEW_ALL);
- params.clearDirtyContentViews();
-
- // WHEN use increased height is set and stage executed.
- params.setUseIncreasedCollapsedHeight(true);
- mRowContentBindStage.executeStage(mEntry, mRow, (en) -> { });
-
- // THEN binder is called with group view and contracted is bound.
- ArgumentCaptor<BindParams> bindParamsCaptor = ArgumentCaptor.forClass(BindParams.class);
- verify(mBinder).bindContent(
- eq(mEntry),
- any(),
- eq(FLAG_CONTENT_VIEW_CONTRACTED),
- bindParamsCaptor.capture(),
- anyBoolean(),
- any());
- BindParams usedParams = bindParamsCaptor.getValue();
- assertTrue(usedParams.usesIncreasedHeight);
- }
-
- @Test
- public void testSetUseIncreasedHeadsUpHeight() {
- // GIVEN a view with all content bound.
- RowContentBindParams params = mRowContentBindStage.getStageParams(mEntry);
- params.requireContentViews(FLAG_CONTENT_VIEW_ALL);
- params.clearDirtyContentViews();
-
- // WHEN use increased heads up height is set and stage executed.
- params.setUseIncreasedHeadsUpHeight(true);
- mRowContentBindStage.executeStage(mEntry, mRow, (en) -> { });
-
- // THEN binder is called with use group view and heads up is bound.
- ArgumentCaptor<BindParams> bindParamsCaptor = ArgumentCaptor.forClass(BindParams.class);
- verify(mBinder).bindContent(
- eq(mEntry),
- any(),
- eq(FLAG_CONTENT_VIEW_HEADS_UP),
- bindParamsCaptor.capture(),
- anyBoolean(),
- any());
- BindParams usedParams = bindParamsCaptor.getValue();
- assertTrue(usedParams.usesIncreasedHeadsUpHeight);
- }
-
- @Test
public void testSetNeedsReinflation() {
// GIVEN a view with all content bound.
RowContentBindParams params = mRowContentBindStage.getStageParams(mEntry);
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.kt
index 216f51d..bd76268 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.kt
@@ -34,6 +34,7 @@
import com.android.systemui.statusbar.HeadsUpStatusBarView
import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
import com.android.systemui.statusbar.commandQueue
+import com.android.systemui.statusbar.headsup.shared.StatusBarNoHunBehavior
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationIconInteractor
import com.android.systemui.statusbar.notification.domain.interactor.headsUpNotificationIconInteractor
@@ -118,6 +119,7 @@
}
@Test
+ @DisableFlags(StatusBarNoHunBehavior.FLAG_NAME)
fun showingEntryUpdated_whenPinnedBySystem() {
row.setPinnedStatus(PinnedStatus.PinnedBySystem)
setHeadsUpNotifOnManager(entry)
@@ -133,8 +135,18 @@
}
@Test
- @DisableFlags(StatusBarNotifChips.FLAG_NAME)
- fun showingEntryUpdated_whenPinnedByUser_andFlagOff() {
+ @EnableFlags(StatusBarNoHunBehavior.FLAG_NAME)
+ fun showingEntryNotUpdated_whenPinnedBySystem_whenNoHunBehaviorEnabled() {
+ row.setPinnedStatus(PinnedStatus.PinnedBySystem)
+ setHeadsUpNotifOnManager(entry)
+ underTest.onHeadsUpPinned(entry)
+
+ assertThat(headsUpStatusBarView.showingEntry).isNull()
+ }
+
+ @Test
+ @DisableFlags(StatusBarNotifChips.FLAG_NAME, StatusBarNoHunBehavior.FLAG_NAME)
+ fun showingEntryUpdated_whenPinnedByUser_andNotifChipsFlagOff() {
row.setPinnedStatus(PinnedStatus.PinnedByUser)
setHeadsUpNotifOnManager(entry)
underTest.onHeadsUpPinned(entry)
@@ -144,7 +156,7 @@
@Test
@EnableFlags(StatusBarNotifChips.FLAG_NAME)
- fun showingEntryNotUpdated_whenPinnedByUser_andFlagOn() {
+ fun showingEntryNotUpdated_whenPinnedByUser_andNotifChipsFlagOn() {
// WHEN the HUN was pinned by the user
row.setPinnedStatus(PinnedStatus.PinnedByUser)
setHeadsUpNotifOnManager(entry)
@@ -155,6 +167,7 @@
}
@Test
+ @DisableFlags(StatusBarNoHunBehavior.FLAG_NAME)
fun pinnedStatusUpdatedToSystem_whenPinnedBySystem() {
row.setPinnedStatus(PinnedStatus.PinnedBySystem)
setHeadsUpNotifOnManager(entry)
@@ -168,8 +181,19 @@
}
@Test
+ @EnableFlags(StatusBarNoHunBehavior.FLAG_NAME)
+ fun pinnedStatusNotUpdatedToSystem_whenPinnedBySystem_whenNoHunBehaviorEnabled() {
+ row.setPinnedStatus(PinnedStatus.PinnedBySystem)
+ setHeadsUpNotifOnManager(entry)
+ underTest.onHeadsUpPinned(entry)
+
+ assertThat(underTest.pinnedStatus).isEqualTo(PinnedStatus.NotPinned)
+ }
+
+ @Test
@EnableFlags(StatusBarNotifChips.FLAG_NAME)
- fun pinnedStatusUpdatedToNotPinned_whenPinnedByUser_andFlagOn() {
+ @DisableFlags(StatusBarNoHunBehavior.FLAG_NAME)
+ fun pinnedStatusUpdatedToNotPinned_whenPinnedByUser_andNotifChipsFlagOn() {
row.setPinnedStatus(PinnedStatus.PinnedByUser)
setHeadsUpNotifOnManager(entry)
underTest.onHeadsUpPinned(entry)
@@ -182,6 +206,7 @@
}
@Test
+ @DisableFlags(StatusBarNoHunBehavior.FLAG_NAME)
fun isolatedIconSet_whenPinnedBySystem() =
kosmos.runTest {
val latestIsolatedIcon by
@@ -201,8 +226,22 @@
}
@Test
- @DisableFlags(StatusBarNotifChips.FLAG_NAME)
- fun isolatedIconSet_whenPinnedByUser_andFlagOff() =
+ @EnableFlags(StatusBarNoHunBehavior.FLAG_NAME)
+ fun isolatedIconNotSet_whenPinnedBySystem_whenNoHunBehaviorEnabled() =
+ kosmos.runTest {
+ val latestIsolatedIcon by
+ collectLastValue(kosmos.headsUpNotificationIconInteractor.isolatedNotification)
+
+ row.setPinnedStatus(PinnedStatus.PinnedBySystem)
+ setHeadsUpNotifOnManager(entry)
+ underTest.onHeadsUpPinned(entry)
+
+ assertThat(latestIsolatedIcon).isNull()
+ }
+
+ @Test
+ @DisableFlags(StatusBarNotifChips.FLAG_NAME, StatusBarNoHunBehavior.FLAG_NAME)
+ fun isolatedIconSet_whenPinnedByUser_andNotifChipsFlagOff() =
kosmos.runTest {
val latestIsolatedIcon by
collectLastValue(kosmos.headsUpNotificationIconInteractor.isolatedNotification)
@@ -216,7 +255,7 @@
@Test
@EnableFlags(StatusBarNotifChips.FLAG_NAME)
- fun isolatedIconNotSet_whenPinnedByUser_andFlagOn() =
+ fun isolatedIconNotSet_whenPinnedByUser_andNotifChipsFlagOn() =
kosmos.runTest {
val latestIsolatedIcon by
collectLastValue(kosmos.headsUpNotificationIconInteractor.isolatedNotification)
@@ -243,6 +282,7 @@
}
@Test
+ @DisableFlags(StatusBarNoHunBehavior.FLAG_NAME)
fun operatorNameViewUpdated_whenPinnedBySystem() {
underTest.setAnimationsEnabled(false)
@@ -258,8 +298,20 @@
}
@Test
- @DisableFlags(StatusBarNotifChips.FLAG_NAME)
- fun operatorNameViewUpdated_whenPinnedByUser_andFlagOff() {
+ @EnableFlags(StatusBarNoHunBehavior.FLAG_NAME)
+ fun operatorNameViewNotUpdated_whenPinnedBySystem_whenNoHunBehaviorEnabled() {
+ underTest.setAnimationsEnabled(false)
+
+ row.setPinnedStatus(PinnedStatus.PinnedBySystem)
+ setHeadsUpNotifOnManager(entry)
+ underTest.onHeadsUpPinned(entry)
+
+ assertThat(operatorNameView.visibility).isEqualTo(View.VISIBLE)
+ }
+
+ @Test
+ @DisableFlags(StatusBarNotifChips.FLAG_NAME, StatusBarNoHunBehavior.FLAG_NAME)
+ fun operatorNameViewUpdated_whenPinnedByUser_andNotifChipsFlagOff() {
underTest.setAnimationsEnabled(false)
row.setPinnedStatus(PinnedStatus.PinnedByUser)
@@ -271,7 +323,7 @@
@Test
@EnableFlags(StatusBarNotifChips.FLAG_NAME)
- fun operatorNameViewNotUpdated_whenPinnedByUser_andFlagOn() {
+ fun operatorNameViewNotUpdated_whenPinnedByUser_andNotifChipsFlagOn() {
underTest.setAnimationsEnabled(false)
// WHEN the row was pinned by the user
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 650fa7c..58856d9 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.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 @@
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 @@
@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 @@
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 @@
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 6da06a3..02135f6 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.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 @@
}
@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 8fb95e8..14263c4 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.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 @@
}
@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/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt
index 937f333..a1c910d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt
@@ -24,6 +24,8 @@
import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
import com.android.systemui.statusbar.events.shared.model.SystemEventAnimationState.Idle
import com.android.systemui.statusbar.featurepods.popups.shared.model.PopupChipModel
+import com.android.systemui.statusbar.pipeline.shared.ui.model.SystemInfoCombinedVisibilityModel
+import com.android.systemui.statusbar.pipeline.shared.ui.model.VisibilityModel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
@@ -51,28 +53,15 @@
override val shouldShowOperatorNameView = MutableStateFlow(false)
override val isClockVisible =
- MutableStateFlow(
- HomeStatusBarViewModel.VisibilityModel(
- visibility = View.GONE,
- shouldAnimateChange = false,
- )
- )
+ MutableStateFlow(VisibilityModel(visibility = View.GONE, shouldAnimateChange = false))
override val isNotificationIconContainerVisible =
- MutableStateFlow(
- HomeStatusBarViewModel.VisibilityModel(
- visibility = View.GONE,
- shouldAnimateChange = false,
- )
- )
+ MutableStateFlow(VisibilityModel(visibility = View.GONE, shouldAnimateChange = false))
override val systemInfoCombinedVis =
MutableStateFlow(
- HomeStatusBarViewModel.SystemInfoCombinedVisibilityModel(
- HomeStatusBarViewModel.VisibilityModel(
- visibility = View.GONE,
- shouldAnimateChange = false,
- ),
+ SystemInfoCombinedVisibilityModel(
+ VisibilityModel(visibility = View.GONE, shouldAnimateChange = false),
Idle,
)
)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt
index be4af86..e74d009 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt
@@ -70,6 +70,7 @@
import com.android.systemui.statusbar.events.shared.model.SystemEventAnimationState.AnimatingIn
import com.android.systemui.statusbar.events.shared.model.SystemEventAnimationState.AnimatingOut
import com.android.systemui.statusbar.events.shared.model.SystemEventAnimationState.Idle
+import com.android.systemui.statusbar.headsup.shared.StatusBarNoHunBehavior
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.UnconfinedFakeHeadsUpRowRepository
@@ -82,12 +83,11 @@
import com.android.systemui.statusbar.phone.data.repository.fakeDarkIconRepository
import com.android.systemui.statusbar.pipeline.shared.domain.interactor.setHomeStatusBarIconBlockList
import com.android.systemui.statusbar.pipeline.shared.domain.interactor.setHomeStatusBarInteractorShowOperatorName
-import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.HomeStatusBarViewModel.VisibilityModel
+import com.android.systemui.statusbar.pipeline.shared.ui.model.VisibilityModel
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import org.junit.Before
@@ -422,8 +422,9 @@
fun areNotificationsLightsOut_requiresFlagEnabled() =
kosmos.runTest {
assertLogsWtf {
- val flow = underTest.areNotificationsLightsOut
- assertThat(flow).isEqualTo(emptyFlow<Boolean>())
+ val latest by collectLastValue(underTest.areNotificationsLightsOut)
+ // Nothing is emitted
+ assertThat(latest).isNull()
}
}
@@ -552,9 +553,10 @@
}
@Test
- fun shouldShowOperatorNameView_allowedByInteractor_hunPinned_false() =
+ @DisableFlags(StatusBarNoHunBehavior.FLAG_NAME)
+ fun shouldShowOperatorNameView_allowedByInteractor_hunPinned_noHunBehaviorFlagOff_false() =
kosmos.runTest {
- kosmos.setHomeStatusBarInteractorShowOperatorName(false)
+ kosmos.setHomeStatusBarInteractorShowOperatorName(true)
transitionKeyguardToGone()
@@ -565,7 +567,7 @@
headsUpNotificationRepository.setNotifications(
UnconfinedFakeHeadsUpRowRepository(
key = "key",
- pinnedStatus = MutableStateFlow(PinnedStatus.PinnedByUser),
+ pinnedStatus = MutableStateFlow(PinnedStatus.PinnedBySystem),
)
)
@@ -575,6 +577,31 @@
}
@Test
+ @EnableFlags(StatusBarNoHunBehavior.FLAG_NAME)
+ fun shouldShowOperatorNameView_allowedByInteractor_hunPinned_noHunBehaviorFlagOn_true() =
+ kosmos.runTest {
+ kosmos.setHomeStatusBarInteractorShowOperatorName(true)
+
+ transitionKeyguardToGone()
+
+ fakeDisableFlagsRepository.disableFlags.value =
+ DisableFlagsModel(DISABLE_NONE, DISABLE2_NONE)
+
+ // WHEN there is an active HUN
+ headsUpNotificationRepository.setNotifications(
+ UnconfinedFakeHeadsUpRowRepository(
+ key = "key",
+ pinnedStatus = MutableStateFlow(PinnedStatus.PinnedBySystem),
+ )
+ )
+
+ val latest by collectLastValue(underTest.shouldShowOperatorNameView)
+
+ // THEN we still show the operator name view if NoHunBehavior flag is enabled
+ assertThat(latest).isTrue()
+ }
+
+ @Test
fun shouldHomeStatusBarBeVisible_keyguardNotGone_noHun_false() =
kosmos.runTest {
// Do not transition from keyguard. i.e., we don't call transitionKeyguardToGone()
@@ -692,7 +719,7 @@
@Test
@EnableFlags(StatusBarNotifChips.FLAG_NAME)
- fun isClockVisible_allowedByFlags_hunPinnedByUser_visible() =
+ fun isClockVisible_allowedByDisableFlags_hunPinnedByUser_visible() =
kosmos.runTest {
val latest by collectLastValue(underTest.isClockVisible)
transitionKeyguardToGone()
@@ -711,7 +738,8 @@
}
@Test
- fun isClockVisible_allowedByFlags_hunPinnedBySystem_notVisible() =
+ @DisableFlags(StatusBarNoHunBehavior.FLAG_NAME)
+ fun isClockVisible_allowedByDisableFlags_hunPinnedBySystem_noHunBehaviorFlagOff_notVisible() =
kosmos.runTest {
val latest by collectLastValue(underTest.isClockVisible)
transitionKeyguardToGone()
@@ -730,7 +758,29 @@
}
@Test
- fun isClockVisible_allowedByFlags_hunBecomesInactive_visibleAgain() =
+ @EnableFlags(StatusBarNoHunBehavior.FLAG_NAME)
+ fun isClockVisible_allowedByDisableFlags_hunPinnedBySystem_noHunBehaviorFlagOn_visible() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.isClockVisible)
+ transitionKeyguardToGone()
+
+ fakeDisableFlagsRepository.disableFlags.value =
+ DisableFlagsModel(DISABLE_NONE, DISABLE2_NONE)
+ // WHEN there is an active HUN
+ headsUpNotificationRepository.setNotifications(
+ UnconfinedFakeHeadsUpRowRepository(
+ key = "key",
+ pinnedStatus = MutableStateFlow(PinnedStatus.PinnedBySystem),
+ )
+ )
+
+ // THEN we still show the clock view if NoHunBehavior flag is enabled
+ assertThat(latest!!.visibility).isEqualTo(View.VISIBLE)
+ }
+
+ @Test
+ @DisableFlags(StatusBarNoHunBehavior.FLAG_NAME)
+ fun isClockVisible_allowedByDisableFlags_hunBecomesInactive_visibleAgain() =
kosmos.runTest {
val latest by collectLastValue(underTest.isClockVisible)
transitionKeyguardToGone()
@@ -753,7 +803,8 @@
}
@Test
- fun isClockVisible_disabledByFlags_hunBecomesInactive_neverVisible() =
+ @DisableFlags(StatusBarNoHunBehavior.FLAG_NAME)
+ fun isClockVisible_disableFlagsProhibitClock_hunBecomesInactive_neverVisible() =
kosmos.runTest {
val latest by collectLastValue(underTest.isClockVisible)
transitionKeyguardToGone()
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 b2378d2..2d63150 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 @@
whenever(
mockDialogTransitionAnimator.createActivityTransitionController(
any<SystemUIDialog>(),
- eq(null)
+ eq(null),
)
)
.thenReturn(mockAnimationController)
underTest =
ModesDialogDelegate(
+ context,
kosmos.systemUIDialogFactory,
mockDialogTransitionAnimator,
activityStarter,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt
index 0598b87..73e5004 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.ui.viewmodel
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.FlagsParameterization
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -32,6 +34,7 @@
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.statusbar.domain.interactor.keyguardStatusBarInteractor
+import com.android.systemui.statusbar.headsup.shared.StatusBarNoHunBehavior
import com.android.systemui.statusbar.notification.data.repository.FakeHeadsUpRowRepository
import com.android.systemui.statusbar.notification.stack.data.repository.headsUpNotificationRepository
import com.android.systemui.statusbar.notification.stack.domain.interactor.headsUpNotificationInteractor
@@ -127,7 +130,8 @@
@Test
@EnableSceneContainer
- fun isVisible_headsUpStatusBarShown_false() =
+ @DisableFlags(StatusBarNoHunBehavior.FLAG_NAME)
+ fun isVisible_headsUpShown_noHunBehaviorFlagOff_false() =
testScope.runTest {
val latest by collectLastValue(underTest.isVisible)
@@ -145,6 +149,26 @@
}
@Test
+ @EnableSceneContainer
+ @EnableFlags(StatusBarNoHunBehavior.FLAG_NAME)
+ fun isVisible_headsUpShown_noHunBehaviorFlagOn_true() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.isVisible)
+
+ // WHEN HUN displayed on the bypass lock screen
+ headsUpRepository.setNotifications(FakeHeadsUpRowRepository("key 0", isPinned = true))
+ keyguardTransitionRepository.emitInitialStepsFromOff(
+ KeyguardState.LOCKSCREEN,
+ testSetup = true,
+ )
+ kosmos.sceneContainerRepository.snapToScene(Scenes.Lockscreen)
+ faceAuthRepository.isBypassEnabled.value = true
+
+ // THEN KeyguardStatusBar is still visible because StatusBarNoHunBehavior is enabled
+ assertThat(latest).isTrue()
+ }
+
+ @Test
fun isVisible_sceneLockscreen_andNotDozing_andNotShowingHeadsUpStatusBar_true() =
testScope.runTest {
val latest by collectLastValue(underTest.isVisible)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/wmshell/TestableBubbleController.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/wmshell/TestableBubbleController.java
index aa71b84..75c1742 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/wmshell/TestableBubbleController.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/wmshell/TestableBubbleController.java
@@ -43,6 +43,7 @@
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.taskview.TaskViewRepository;
import com.android.wm.shell.taskview.TaskViewTransitions;
import com.android.wm.shell.transition.Transitions;
@@ -76,6 +77,7 @@
DragAndDropController dragAndDropController,
ShellExecutor shellMainExecutor,
Handler shellMainHandler,
+ TaskViewRepository taskViewRepository,
TaskViewTransitions taskViewTransitions,
Transitions transitions,
SyncTransactionQueue syncQueue,
@@ -86,7 +88,7 @@
displayInsetsController, displayImeController, userManager, launcherApps,
bubbleLogger, taskStackListener, shellTaskOrganizer, positioner, displayController,
oneHandedOptional, dragAndDropController, shellMainExecutor, shellMainHandler,
- new SyncExecutor(), taskViewTransitions, transitions,
+ new SyncExecutor(), taskViewRepository, taskViewTransitions, transitions,
syncQueue, wmService, bubbleProperties);
setInflateSynchronously(true);
onInit();
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java
index 55be9f7..ca98cbf 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java
@@ -140,6 +140,11 @@
void postStartActivityDismissingKeyguard(Intent intent, int delay,
@Nullable ActivityTransitionAnimator.Controller animationController,
@Nullable String customMessage);
+ /** Posts a start activity intent that dismisses keyguard. */
+ void postStartActivityDismissingKeyguard(Intent intent, int delay,
+ @Nullable ActivityTransitionAnimator.Controller animationController,
+ @Nullable String customMessage,
+ @Nullable UserHandle userHandle);
void postStartActivityDismissingKeyguard(PendingIntent intent);
/**
diff --git a/packages/SystemUI/proguard_common.flags b/packages/SystemUI/proguard_common.flags
index 02b2bcf..e152c98 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 0000000..32aacf6
--- /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 f8c0fa0..0000000
--- 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 0000000..5f9d421
--- /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/layout/contextual_edu_dialog.xml b/packages/SystemUI/res/layout/contextual_edu_dialog.xml
index 09aa8da..e83d490 100644
--- a/packages/SystemUI/res/layout/contextual_edu_dialog.xml
+++ b/packages/SystemUI/res/layout/contextual_edu_dialog.xml
@@ -29,7 +29,7 @@
android:layout_height="wrap_content"
android:contentDescription="@null"
android:importantForAccessibility="no"
- android:paddingRight="16dp" />
+ android:paddingHorizontal="16dp" />
<TextView
android:id="@+id/edu_message"
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 2ffa3d1..f9904e3 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -254,9 +254,6 @@
<!-- Reference width used when validating notification layouts -->
<dimen name="notification_validation_reference_width">320dp</dimen>
- <!-- Increased height of a small notification in the status bar -->
- <dimen name="notification_min_height_increased">146dp</dimen>
-
<!-- Height of a small notification in the status bar which was used before android N -->
<dimen name="notification_min_height_legacy">64dp</dimen>
@@ -281,9 +278,6 @@
<!-- Height of a heads up notification in the status bar -->
<dimen name="notification_max_heads_up_height">136dp</dimen>
- <!-- Height of a heads up notification in the status bar -->
- <dimen name="notification_max_heads_up_height_increased">188dp</dimen>
-
<!-- Side padding on the side of notifications -->
<dimen name="notification_side_paddings">16dp</dimen>
@@ -1128,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 e785af8..414d3f1 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2250,8 +2250,6 @@
<string name="keyboard_shortcut_group_system_notifications">Notifications</string>
<!-- User visible title for the keyboard shortcut that triggers the keyboard shortcuts helper. -->
<string name="keyboard_shortcut_group_system_shortcuts_helper">Keyboard Shortcuts</string>
- <!-- User visible title for the keyboard shortcut that switches to the next hardware keyboard layout. -->
- <string name="keyboard_shortcut_group_system_switch_input">Switch keyboard layout</string>
<!-- User visible string that joins different shortcuts in a list, e.g. shortcut1 "or" shortcut2 "or" ... -->
<string name="keyboard_shortcut_join">or</string>
@@ -3956,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] -->
@@ -3993,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 83ca496..d363e52 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 @@
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: 38
oneway interface IOverviewProxy {
void onActiveNavBarRegionChanges(in Region activeRegion) = 11;
@@ -137,4 +138,20 @@
* 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;
+
+ /**
+ * Sent when {@link TaskbarDelegate#onDisplayReady} is called.
+ */
+ void onDisplayReady(int displayId) = 36;
+
+ /**
+ * Sent when {@link TaskbarDelegate#onDisplayRemoved} is called.
+ */
+ void onDisplayRemoved(int displayId) = 37;
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationListener.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationListener.java
index 51892aa..ff6bcdb 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationListener.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationListener.java
@@ -19,6 +19,7 @@
import android.graphics.Rect;
import android.os.Bundle;
import android.view.RemoteAnimationTarget;
+import android.window.TransitionInfo;
import com.android.systemui.shared.recents.model.ThumbnailData;
@@ -30,7 +31,7 @@
*/
void onAnimationStart(RecentsAnimationControllerCompat controller,
RemoteAnimationTarget[] apps, RemoteAnimationTarget[] wallpapers,
- Rect homeContentInsets, Rect minimizedHomeBounds, Bundle extras);
+ Rect homeContentInsets, Rect minimizedHomeBounds, Bundle extras, TransitionInfo info);
/**
* Called when the animation into Recents was canceled. This call is made on the binder thread.
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index 71b622a..9b852df 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -61,6 +61,8 @@
import com.android.systemui.plugins.clocks.WeatherData
import com.android.systemui.plugins.clocks.ZenData
import com.android.systemui.plugins.clocks.ZenData.ZenMode
+import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.power.shared.model.ScreenPowerState
import com.android.systemui.res.R as SysuiR
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.settings.UserTracker
@@ -106,6 +108,7 @@
private val zenModeController: ZenModeController,
private val zenModeInteractor: ZenModeInteractor,
private val userTracker: UserTracker,
+ private val powerInteractor: PowerInteractor,
) {
var loggers =
listOf(
@@ -377,13 +380,13 @@
override fun onTimeChanged() {
refreshTime()
}
-
- private fun refreshTime() {
- clock?.smallClock?.events?.onTimeTick()
- clock?.largeClock?.events?.onTimeTick()
- }
}
+ private fun refreshTime() {
+ clock?.smallClock?.events?.onTimeTick()
+ clock?.largeClock?.events?.onTimeTick()
+ }
+
@VisibleForTesting
internal fun listenForDnd(scope: CoroutineScope): Job {
ModesUi.assertInNewMode()
@@ -474,6 +477,7 @@
listenForAnyStateToAodTransition(this)
listenForAnyStateToLockscreenTransition(this)
listenForAnyStateToDozingTransition(this)
+ listenForScreenPowerOn(this)
}
}
smallTimeListener?.update(shouldTimeListenerRun)
@@ -643,6 +647,17 @@
}
}
+ @VisibleForTesting
+ internal fun listenForScreenPowerOn(scope: CoroutineScope): Job {
+ return scope.launch {
+ powerInteractor.screenPowerState.collect { powerState ->
+ if (powerState != ScreenPowerState.SCREEN_OFF) {
+ refreshTime()
+ }
+ }
+ }
+ }
+
class TimeListener(val clockFace: ClockFaceController, val executor: DelayableExecutor) {
val predrawListener =
ViewTreeObserver.OnPreDrawListener {
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/FullscreenMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/FullscreenMagnificationController.java
index caf043a..b2f3df6 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 @@
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 @@
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 @@
// 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 @@
// 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 6635d8b..a061d38 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 @@
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 c05dcd5..c59c681 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 @@
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/common/ui/view/ViewExt.kt b/packages/SystemUI/src/com/android/systemui/common/ui/view/ViewExt.kt
index f7ea25c..b248043 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/view/ViewExt.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/view/ViewExt.kt
@@ -68,3 +68,11 @@
setOnTouchListener(listener)
return DisposableHandle { setOnTouchListener(null) }
}
+
+/** A null listener should also set the longClickable property to false */
+fun View.updateLongClickListener(listener: View.OnLongClickListener?) {
+ setOnLongClickListener(listener)
+ if (listener == null) {
+ setLongClickable(false)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalDreamStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/CommunalDreamStartable.kt
index 1bd541e..6dc7c97 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/CommunalDreamStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalDreamStartable.kt
@@ -91,13 +91,19 @@
.launchIn(bgScope)
}
- // Restart the dream underneath the hub in order to support the ability to swipe
- // away the hub to enter the dream.
- startDream
- .sampleFilter(powerInteractor.isAwake) { isAwake ->
- !glanceableHubAllowKeyguardWhenDreaming() && dreamManager.canStartDreaming(isAwake)
- }
- .onEach { dreamManager.startDream() }
- .launchIn(bgScope)
+ // With hub v2, we no longer need to keep the dream running underneath the hub as there is
+ // no more swipe between the hub and dream. We can just start the dream on-demand when the
+ // user presses the dream coin.
+ if (!communalSettingsInteractor.isV2FlagEnabled()) {
+ // Restart the dream underneath the hub in order to support the ability to swipe away
+ // the hub to enter the dream.
+ startDream
+ .sampleFilter(powerInteractor.isAwake) { isAwake ->
+ !glanceableHubAllowKeyguardWhenDreaming() &&
+ dreamManager.canStartDreaming(isAwake)
+ }
+ .onEach { dreamManager.startDream() }
+ .launchIn(bgScope)
+ }
}
}
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 0000000..c7b7050
--- /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 2d19b02..e344322 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.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 @@
@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 7d684ca..5e3b2ae 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 @@
DeviceEntryRestrictionReason.UnattendedUpdate
authFlags.isPrimaryAuthRequiredAfterTimeout ->
DeviceEntryRestrictionReason.SecurityTimeout
- authFlags.isPrimaryAuthRequiredAfterLockout ->
- DeviceEntryRestrictionReason.BouncerLockedOut
isFingerprintLockedOut ->
DeviceEntryRestrictionReason.StrongBiometricsLockedOut
isFaceLockedOut && faceAuthInteractor.isFaceAuthStrong() ->
@@ -376,8 +374,7 @@
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/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
index 0b2b368..a56a63c 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
@@ -562,6 +562,13 @@
return;
}
+ if (mCommunalSettingsInteractor.isV2FlagEnabled()) {
+ // Dream wake redirect is not needed in V2 as we do not need to keep the dream awake
+ // underneath the hub anymore as there is no more swipe between the dream and hub. SysUI
+ // will automatically transition to the hub when the dream wakes.
+ return;
+ }
+
redirectWake(mCommunalAvailable && !glanceableHubAllowKeyguardWhenDreaming());
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
index 2ed0671..e588077 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
@@ -30,6 +30,9 @@
import com.android.systemui.statusbar.notification.collection.SortBySectionTimeFlag
import com.android.systemui.statusbar.notification.emptyshade.shared.ModesEmptyShadeFix
import com.android.systemui.statusbar.notification.interruption.VisualInterruptionRefactor
+import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUi
+import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUiAod
+import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUiForceExpanded
import com.android.systemui.statusbar.notification.shared.NotificationAvalancheSuppression
import com.android.systemui.statusbar.notification.shared.NotificationMinimalism
import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun
@@ -52,6 +55,10 @@
NotificationMinimalism.token dependsOn NotificationThrottleHun.token
ModesEmptyShadeFix.token dependsOn modesUi
+ PromotedNotificationUiForceExpanded.token dependsOn PromotedNotificationUi.token
+
+ PromotedNotificationUiAod.token dependsOn PromotedNotificationUi.token
+
// SceneContainer dependencies
SceneContainerFlag.getFlagDependencies().forEach { (alpha, beta) -> alpha dependsOn beta }
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutCustomizer.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutCustomizer.kt
index d9e55f8..d8e2dab 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutCustomizer.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutCustomizer.kt
@@ -16,6 +16,7 @@
package com.android.systemui.keyboard.shortcut.ui.composable
+import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.interaction.MutableInteractionSource
@@ -27,6 +28,7 @@
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.sizeIn
@@ -211,19 +213,19 @@
shape = RoundedCornerShape(50.dp),
onClick = onCancel,
color = Color.Transparent,
- width = 80.dp,
+ modifier = Modifier.heightIn(40.dp),
contentColor = MaterialTheme.colorScheme.primary,
text = stringResource(R.string.shortcut_helper_customize_dialog_cancel_button_label),
+ border = BorderStroke(width = 1.dp, color = MaterialTheme.colorScheme.outlineVariant)
)
Spacer(modifier = Modifier.width(8.dp))
ShortcutHelperButton(
- modifier =
- Modifier.focusRequester(focusRequester).focusProperties {
- canFocus = true
- }, // enable focus on touch/click mode
+ modifier = Modifier
+ .heightIn(40.dp)
+ .focusRequester(focusRequester)
+ .focusProperties { canFocus = true }, // enable focus on touch/click mode
onClick = onConfirm,
color = MaterialTheme.colorScheme.primary,
- width = 116.dp,
contentColor = MaterialTheme.colorScheme.onPrimary,
text = confirmButtonText,
enabled = isConfirmButtonEnabled,
@@ -413,8 +415,7 @@
private fun ActionKeyContainer(defaultModifierKey: ShortcutKey.Icon.ResIdIcon) {
Row(
modifier =
- Modifier.height(48.dp)
- .width(105.dp)
+ Modifier.sizeIn(minWidth = 105.dp, minHeight = 48.dp)
.background(
color = MaterialTheme.colorScheme.surface,
shape = RoundedCornerShape(16.dp),
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
index bf60c9a5..0054dd7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
Binary files differ
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/Surfaces.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/Surfaces.kt
index 9a380f4..981a55c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/Surfaces.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/Surfaces.kt
@@ -32,7 +32,6 @@
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
@@ -45,7 +44,6 @@
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.LocalTonalElevationEnabled
import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.contentColorFor
import androidx.compose.material3.minimumInteractiveComponentSize
@@ -74,13 +72,14 @@
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.semantics.role
import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.zIndex
-import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.compose.modifiers.thenIf
import com.android.systemui.keyboard.shortcut.ui.model.IconSource
+import com.android.app.tracing.coroutines.launchTraced as launch
/**
* A selectable surface with no default focus/hover indications.
@@ -217,30 +216,37 @@
*/
@Composable
fun ShortcutHelperButton(
- modifier: Modifier = Modifier,
onClick: () -> Unit,
- shape: Shape = RoundedCornerShape(360.dp),
+ contentColor: Color,
color: Color,
- width: Dp,
- height: Dp = 40.dp,
+ modifier: Modifier = Modifier,
+ shape: Shape = RoundedCornerShape(360.dp),
iconSource: IconSource = IconSource(),
text: String? = null,
- contentColor: Color,
contentPaddingHorizontal: Dp = 16.dp,
contentPaddingVertical: Dp = 10.dp,
enabled: Boolean = true,
border: BorderStroke? = null,
contentDescription: String? = null,
) {
- ShortcutHelperButtonSurface(
+ ClickableShortcutSurface(
onClick = onClick,
shape = shape,
- color = color,
- modifier = modifier,
- enabled = enabled,
- width = width,
- height = height,
+ color = color.getDimmedColorIfDisabled(enabled),
border = border,
+ modifier = modifier.semantics { role = Role.Button },
+ interactionsConfig = InteractionsConfig(
+ hoverOverlayColor = MaterialTheme.colorScheme.onSurface,
+ hoverOverlayAlpha = 0.11f,
+ pressedOverlayColor = MaterialTheme.colorScheme.onSurface,
+ pressedOverlayAlpha = 0.15f,
+ focusOutlineColor = MaterialTheme.colorScheme.secondary,
+ focusOutlineStrokeWidth = 3.dp,
+ focusOutlinePadding = 2.dp,
+ surfaceCornerRadius = 28.dp,
+ focusOutlineCornerRadius = 33.dp,
+ ),
+ enabled = enabled
) {
Row(
modifier =
@@ -251,76 +257,45 @@
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center,
) {
- if (iconSource.imageVector != null) {
- Icon(
- tint = contentColor,
- imageVector = iconSource.imageVector,
- contentDescription = contentDescription,
- modifier = Modifier.size(20.dp).wrapContentSize(Alignment.Center),
- )
- }
-
- if (iconSource.imageVector != null && text != null)
- Spacer(modifier = Modifier.weight(1f))
-
- if (text != null) {
- Text(
- text,
- color = contentColor,
- fontSize = 14.sp,
- style = MaterialTheme.typography.labelLarge,
- modifier = Modifier.wrapContentSize(Alignment.Center),
- )
- }
+ ShortcutHelperButtonContent(iconSource, contentColor, text, contentDescription)
}
}
}
@Composable
-private fun ShortcutHelperButtonSurface(
- onClick: () -> Unit,
- shape: Shape,
- color: Color,
- modifier: Modifier = Modifier,
- enabled: Boolean,
- width: Dp,
- height: Dp,
- border: BorderStroke?,
- content: @Composable () -> Unit,
+private fun ShortcutHelperButtonContent(
+ iconSource: IconSource,
+ contentColor: Color,
+ text: String?,
+ contentDescription: String?
) {
- if (enabled) {
- ClickableShortcutSurface(
- onClick = onClick,
- shape = shape,
- color = color,
- border = border,
- modifier = modifier.semantics { role = Role.Button }.width(width).height(height),
- interactionsConfig =
- InteractionsConfig(
- hoverOverlayColor = MaterialTheme.colorScheme.onSurface,
- hoverOverlayAlpha = 0.11f,
- pressedOverlayColor = MaterialTheme.colorScheme.onSurface,
- pressedOverlayAlpha = 0.15f,
- focusOutlineColor = MaterialTheme.colorScheme.secondary,
- focusOutlineStrokeWidth = 3.dp,
- focusOutlinePadding = 2.dp,
- surfaceCornerRadius = 28.dp,
- focusOutlineCornerRadius = 33.dp,
- ),
- ) {
- content()
- }
- } else {
- Surface(
- shape = shape,
- color = color.copy(0.38f),
- modifier = modifier.semantics { role = Role.Button }.width(width).height(height),
- ) {
- content()
- }
+ if (iconSource.imageVector != null) {
+ Icon(
+ tint = contentColor,
+ imageVector = iconSource.imageVector,
+ contentDescription = contentDescription,
+ modifier = Modifier.size(20.dp).wrapContentSize(Alignment.Center),
+ )
+ }
+
+ if (iconSource.imageVector != null && text != null)
+ Spacer(modifier = Modifier.width(8.dp))
+
+ if (text != null) {
+ Text(
+ text,
+ color = contentColor,
+ fontSize = 14.sp,
+ style = MaterialTheme.typography.labelLarge,
+ modifier = Modifier.wrapContentSize(Alignment.Center),
+ overflow = TextOverflow.Ellipsis,
+ )
}
}
+private fun Color.getDimmedColorIfDisabled(enabled: Boolean): Color =
+ if (enabled) this else copy(alpha = 0.38f)
+
@Composable
private fun surfaceColorAtElevation(color: Color, elevation: Dp): Color {
return MaterialTheme.colorScheme.applyTonalElevation(color, elevation)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/OWNERS b/packages/SystemUI/src/com/android/systemui/keyguard/OWNERS
index 208a17c..ebe603b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/OWNERS
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/OWNERS
@@ -2,6 +2,8 @@
# 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 a74384f..5869274 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 @@
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 @@
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 cc070b6..d3e2560 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.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 @@
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/data/quickaffordance/DoNotDisturbQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfig.kt
index 1b8baf6..f11ebee 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfig.kt
@@ -26,8 +26,8 @@
import android.provider.Settings.Secure.ZEN_DURATION_PROMPT
import android.service.notification.ZenModeConfig
import android.util.Log
-import com.android.settingslib.notification.modes.EnableZenModeDialog
-import com.android.settingslib.notification.modes.ZenModeDialogMetricsLogger
+import com.android.settingslib.notification.modes.EnableDndDialogFactory
+import com.android.settingslib.notification.modes.EnableDndDialogMetricsLogger
import com.android.systemui.animation.Expandable
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
@@ -60,8 +60,7 @@
import kotlinx.coroutines.flow.stateIn
@SysUISingleton
-class DoNotDisturbQuickAffordanceConfig
-constructor(
+class DoNotDisturbQuickAffordanceConfig(
private val context: Context,
private val controller: ZenModeController,
private val interactor: ZenModeInteractor,
@@ -70,7 +69,7 @@
@Background private val backgroundDispatcher: CoroutineDispatcher,
@Background private val backgroundScope: CoroutineScope,
private val testConditionId: Uri?,
- testDialog: EnableZenModeDialog?,
+ testDialogFactory: EnableDndDialogFactory?,
) : KeyguardQuickAffordanceConfig {
@Inject
@@ -118,13 +117,13 @@
)
.id
- private val dialog: EnableZenModeDialog by lazy {
- testDialog
- ?: EnableZenModeDialog(
+ private val dialogFactory: EnableDndDialogFactory by lazy {
+ testDialogFactory
+ ?: EnableDndDialogFactory(
context,
R.style.Theme_SystemUI_Dialog,
true, /* cancelIsNeutral */
- ZenModeDialogMetricsLogger(context),
+ EnableDndDialogMetricsLogger(context),
)
}
@@ -224,7 +223,7 @@
if (interactor.shouldAskForZenDuration(dnd)) {
// NOTE: The dialog handles turning on the mode itself.
return KeyguardQuickAffordanceConfig.OnTriggeredResult.ShowDialog(
- dialog.createDialog(),
+ dialogFactory.createDialog(),
expandable,
)
} else {
@@ -243,7 +242,7 @@
settingsValue == ZEN_DURATION_PROMPT ->
KeyguardQuickAffordanceConfig.OnTriggeredResult.ShowDialog(
- dialog.createDialog(),
+ dialogFactory.createDialog(),
expandable,
)
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 089e5dc..c0a486c 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 @@
) {
/**
- * 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/domain/interactor/KeyguardQuickAffordanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
index 7d8badd..b866fca 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
@@ -22,6 +22,7 @@
import android.content.Context
import android.content.Intent
import android.util.Log
+import android.view.accessibility.AccessibilityManager
import com.android.app.tracing.coroutines.withContextTraced as withContext
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.internal.widget.LockPatternUtils
@@ -92,6 +93,7 @@
private val dockManager: DockManager,
private val biometricSettingsRepository: BiometricSettingsRepository,
private val communalSettingsInteractor: CommunalSettingsInteractor,
+ private val accessibilityManager: AccessibilityManager,
@Background private val backgroundDispatcher: CoroutineDispatcher,
@ShadeDisplayAware private val appContext: Context,
private val sceneInteractor: Lazy<SceneInteractor>,
@@ -115,7 +117,10 @@
*
* If `false`, the UI goes back to using single taps.
*/
- fun useLongPress(): Flow<Boolean> = dockManager.retrieveIsDocked().map { !it }
+ fun useLongPress(): Flow<Boolean> =
+ dockManager.retrieveIsDocked().map { isDocked ->
+ !isDocked && !accessibilityManager.isEnabled()
+ }
/** Returns an observable for the quick affordance at the given position. */
suspend fun quickAffordance(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt
index 8a2e3dd..f396cf9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt
@@ -39,6 +39,7 @@
import com.android.systemui.animation.view.LaunchableImageView
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.common.ui.binder.IconViewBinder
+import com.android.systemui.common.ui.view.updateLongClickListener
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordanceHapticViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordanceViewModel
@@ -275,6 +276,7 @@
)
} else {
view.setOnClickListener(OnClickListener(viewModel, checkNotNull(falsingManager)))
+ view.updateLongClickListener(null)
}
} else {
view.onLongClickListener = null
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 213083d..9c886b2 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.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 @@
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 cd038d7..9319bc8 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 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 @@
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 92bb5e6..733d7d7 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.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 @@
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/AodToPrimaryBouncerTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToPrimaryBouncerTransitionViewModel.kt
index e3b5587..26bf0bc 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToPrimaryBouncerTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToPrimaryBouncerTransitionViewModel.kt
@@ -16,6 +16,7 @@
package com.android.systemui.keyguard.ui.viewmodel
+import com.android.systemui.Flags
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.FromAodTransitionInteractor
import com.android.systemui.keyguard.shared.model.Edge
@@ -29,6 +30,7 @@
import javax.inject.Inject
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.emptyFlow
/**
* Breaks down AOD->PRIMARY BOUNCER transition into discrete steps for corresponding views to
@@ -54,6 +56,12 @@
override val windowBlurRadius: Flow<Float> =
transitionAnimation.immediatelyTransitionTo(blurConfig.maxBlurRadiusPx)
+ val lockscreenAlpha: Flow<Float> =
+ if (Flags.bouncerUiRevamp()) transitionAnimation.immediatelyTransitionTo(0.0f)
+ else emptyFlow()
+
+ val notificationAlpha = lockscreenAlpha
+
override val notificationBlurRadius: Flow<Float> =
transitionAnimation.immediatelyTransitionTo(0.0f)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToPrimaryBouncerTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToPrimaryBouncerTransitionViewModel.kt
index c937d5c..d9ca267 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToPrimaryBouncerTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToPrimaryBouncerTransitionViewModel.kt
@@ -16,6 +16,7 @@
package com.android.systemui.keyguard.ui.viewmodel
+import com.android.systemui.Flags
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.FromDozingTransitionInteractor.Companion.TO_PRIMARY_BOUNCER_DURATION
import com.android.systemui.keyguard.shared.model.Edge
@@ -29,6 +30,7 @@
import javax.inject.Inject
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.emptyFlow
/**
* Breaks down DOZING->PRIMARY BOUNCER transition into discrete steps for corresponding views to
@@ -64,6 +66,13 @@
},
onFinish = { blurConfig.maxBlurRadiusPx },
)
+
+ val lockscreenAlpha: Flow<Float> =
+ if (Flags.bouncerUiRevamp()) transitionAnimation.immediatelyTransitionTo(0.0f)
+ else emptyFlow()
+
+ val notificationAlpha: Flow<Float> = lockscreenAlpha
+
override val notificationBlurRadius: Flow<Float> =
transitionAnimation.immediatelyTransitionTo(0f)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
index eaba5d5..e51e05b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
@@ -96,9 +96,12 @@
private val aodToGoneTransitionViewModel: AodToGoneTransitionViewModel,
private val aodToLockscreenTransitionViewModel: AodToLockscreenTransitionViewModel,
private val aodToOccludedTransitionViewModel: AodToOccludedTransitionViewModel,
+ private val aodToPrimaryBouncerTransitionViewModel: AodToPrimaryBouncerTransitionViewModel,
private val dozingToGoneTransitionViewModel: DozingToGoneTransitionViewModel,
private val dozingToLockscreenTransitionViewModel: DozingToLockscreenTransitionViewModel,
private val dozingToOccludedTransitionViewModel: DozingToOccludedTransitionViewModel,
+ private val dozingToPrimaryBouncerTransitionViewModel:
+ DozingToPrimaryBouncerTransitionViewModel,
private val dreamingToAodTransitionViewModel: DreamingToAodTransitionViewModel,
private val dreamingToGoneTransitionViewModel: DreamingToGoneTransitionViewModel,
private val dreamingToLockscreenTransitionViewModel: DreamingToLockscreenTransitionViewModel,
@@ -243,9 +246,11 @@
aodToGoneTransitionViewModel.lockscreenAlpha(viewState),
aodToLockscreenTransitionViewModel.lockscreenAlpha(viewState),
aodToOccludedTransitionViewModel.lockscreenAlpha(viewState),
+ aodToPrimaryBouncerTransitionViewModel.lockscreenAlpha,
dozingToGoneTransitionViewModel.lockscreenAlpha(viewState),
dozingToLockscreenTransitionViewModel.lockscreenAlpha,
dozingToOccludedTransitionViewModel.lockscreenAlpha(viewState),
+ dozingToPrimaryBouncerTransitionViewModel.lockscreenAlpha,
dreamingToAodTransitionViewModel.lockscreenAlpha,
dreamingToGoneTransitionViewModel.lockscreenAlpha,
dreamingToLockscreenTransitionViewModel.lockscreenAlpha,
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 5ee80a7..f8425c1 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 @@
isWeatherVisible(
clockIncludesCustomWeatherDisplay =
keyguardClockViewModel.hasCustomWeatherDataDisplay.value,
- isWeatherEnabled = smartspaceInteractor.isWeatherEnabled.value,
+ isWeatherEnabled = isWeatherEnabled.value,
),
)
diff --git a/packages/SystemUI/src/com/android/systemui/log/LogBufferFactory.kt b/packages/SystemUI/src/com/android/systemui/log/LogBufferFactory.kt
index 6351d7d..c9d6f81 100644
--- a/packages/SystemUI/src/com/android/systemui/log/LogBufferFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/LogBufferFactory.kt
@@ -18,6 +18,7 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dump.DumpManager
+import com.android.systemui.log.LogBuffer.Companion.DEFAULT_LOGBUFFER_TRACK_NAME
import com.android.systemui.log.LogBufferHelper.Companion.adjustMaxSize
import com.android.systemui.log.echo.LogcatEchoTrackerAlways
import javax.inject.Inject
@@ -27,7 +28,7 @@
@Inject
constructor(
private val dumpManager: DumpManager,
- private val logcatEchoTracker: LogcatEchoTracker
+ private val logcatEchoTracker: LogcatEchoTracker,
) {
@JvmOverloads
fun create(
@@ -35,9 +36,11 @@
maxSize: Int,
systrace: Boolean = true,
alwaysLogToLogcat: Boolean = false,
+ systraceTrackName: String = DEFAULT_LOGBUFFER_TRACK_NAME,
): LogBuffer {
val echoTracker = if (alwaysLogToLogcat) LogcatEchoTrackerAlways else logcatEchoTracker
- val buffer = LogBuffer(name, adjustMaxSize(maxSize), echoTracker, systrace)
+ val buffer =
+ LogBuffer(name, adjustMaxSize(maxSize), echoTracker, systrace, systraceTrackName)
dumpManager.registerBuffer(name, buffer)
return buffer
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImpl.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImpl.kt
index 2191f37..f1f299a 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImpl.kt
@@ -45,16 +45,15 @@
import android.media.session.MediaSession
import android.media.session.PlaybackState
import android.net.Uri
-import android.os.Parcelable
import android.os.Process
import android.os.UserHandle
-import android.provider.Settings
import android.service.notification.StatusBarNotification
import android.support.v4.media.MediaMetadataCompat
import android.text.TextUtils
import android.util.Log
import android.util.Pair as APair
import androidx.media.utils.MediaConstants
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.app.tracing.traceSection
import com.android.internal.annotations.Keep
import com.android.internal.logging.InstanceId
@@ -86,11 +85,9 @@
import com.android.systemui.media.controls.util.MediaFlags
import com.android.systemui.media.controls.util.MediaUiEventLogger
import com.android.systemui.media.controls.util.SmallHash
-import com.android.systemui.plugins.BcSmartspaceDataPlugin
import com.android.systemui.res.R
import com.android.systemui.statusbar.NotificationMediaManager.isPlayingState
import com.android.systemui.statusbar.notification.row.HybridGroupManager
-import com.android.systemui.tuner.TunerService
import com.android.systemui.util.Assert
import com.android.systemui.util.Utils
import com.android.systemui.util.concurrency.DelayableExecutor
@@ -103,7 +100,6 @@
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
-import com.android.app.tracing.coroutines.launchTraced as launch
import kotlinx.coroutines.withContext
// URI fields to try loading album art from
@@ -152,22 +148,6 @@
expiryTimeMs = 0,
)
-const val MEDIA_TITLE_ERROR_MESSAGE = "Invalid media data: title is null or blank."
-
-/**
- * Allow recommendations from smartspace to show in media controls. Requires
- * [Utils.useQsMediaPlayer] to be enabled. On by default, but can be disabled by setting to 0
- */
-private fun allowMediaRecommendations(context: Context): Boolean {
- val flag =
- Settings.Secure.getInt(
- context.contentResolver,
- Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION,
- 1,
- )
- return Utils.useQsMediaPlayer(context) && flag > 0
-}
-
/** A class that facilitates management and loading of Media Data, ready for binding. */
@SysUISingleton
class LegacyMediaDataManagerImpl(
@@ -191,14 +171,13 @@
private var useMediaResumption: Boolean,
private val useQsMediaPlayer: Boolean,
private val systemClock: SystemClock,
- private val tunerService: TunerService,
private val mediaFlags: MediaFlags,
private val logger: MediaUiEventLogger,
private val smartspaceManager: SmartspaceManager?,
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
private val mediaDataLoader: dagger.Lazy<MediaDataLoader>,
private val mediaLogger: MediaLogger,
-) : Dumpable, BcSmartspaceDataPlugin.SmartspaceTargetListener, MediaDataManager {
+) : Dumpable, MediaDataManager {
companion object {
// UI surface label for subscribing Smartspace updates.
@@ -238,7 +217,6 @@
// There should ONLY be at most one Smartspace media recommendation.
var smartspaceMediaData: SmartspaceMediaData = EMPTY_SMARTSPACE_MEDIA_DATA
@Keep private var smartspaceSession: SmartspaceSession? = null
- private var allowMediaRecommendations = allowMediaRecommendations(context)
private val artworkWidth =
context.resources.getDimensionPixelSize(
@@ -276,7 +254,6 @@
mediaDataFilter: LegacyMediaDataFilterImpl,
smartspaceMediaDataProvider: SmartspaceMediaDataProvider,
clock: SystemClock,
- tunerService: TunerService,
mediaFlags: MediaFlags,
logger: MediaUiEventLogger,
smartspaceManager: SmartspaceManager?,
@@ -306,7 +283,6 @@
Utils.useMediaResumption(context),
Utils.useQsMediaPlayer(context),
clock,
- tunerService,
mediaFlags,
logger,
smartspaceManager,
@@ -372,7 +348,7 @@
context.registerReceiver(appChangeReceiver, uninstallFilter)
// Register for Smartspace data updates.
- smartspaceMediaDataProvider.registerListener(this)
+ // TODO(b/382680767): remove
smartspaceSession =
smartspaceManager?.createSmartspaceSession(
SmartspaceConfig.Builder(context, SMARTSPACE_UI_SURFACE_LABEL).build()
@@ -391,24 +367,9 @@
)
}
smartspaceSession?.let { it.requestSmartspaceUpdate() }
- tunerService.addTunable(
- object : TunerService.Tunable {
- override fun onTuningChanged(key: String?, newValue: String?) {
- allowMediaRecommendations = allowMediaRecommendations(context)
- if (!allowMediaRecommendations) {
- dismissSmartspaceRecommendation(
- key = smartspaceMediaData.targetId,
- delay = 0L,
- )
- }
- }
- },
- Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION,
- )
}
override fun destroy() {
- smartspaceMediaDataProvider.unregisterListener(this)
smartspaceSession?.close()
smartspaceSession = null
context.unregisterReceiver(appChangeReceiver)
@@ -1328,61 +1289,6 @@
}
}
- override fun onSmartspaceTargetsUpdated(targets: List<Parcelable>) {
- if (!allowMediaRecommendations) {
- if (DEBUG) Log.d(TAG, "Smartspace recommendation is disabled in Settings.")
- return
- }
-
- val mediaTargets = targets.filterIsInstance<SmartspaceTarget>()
- when (mediaTargets.size) {
- 0 -> {
- if (!smartspaceMediaData.isActive) {
- return
- }
- if (DEBUG) {
- Log.d(TAG, "Set Smartspace media to be inactive for the data update")
- }
- if (mediaFlags.isPersistentSsCardEnabled()) {
- // Smartspace uses this signal to hide the card (e.g. when it expires or user
- // disconnects headphones), so treat as setting inactive when flag is on
- smartspaceMediaData = smartspaceMediaData.copy(isActive = false)
- notifySmartspaceMediaDataLoaded(
- smartspaceMediaData.targetId,
- smartspaceMediaData,
- )
- } else {
- smartspaceMediaData =
- EMPTY_SMARTSPACE_MEDIA_DATA.copy(
- targetId = smartspaceMediaData.targetId,
- instanceId = smartspaceMediaData.instanceId,
- )
- notifySmartspaceMediaDataRemoved(
- smartspaceMediaData.targetId,
- immediately = false,
- )
- }
- }
- 1 -> {
- val newMediaTarget = mediaTargets.get(0)
- if (smartspaceMediaData.targetId == newMediaTarget.smartspaceTargetId) {
- // The same Smartspace updates can be received. Skip the duplicate updates.
- return
- }
- if (DEBUG) Log.d(TAG, "Forwarding Smartspace media update.")
- smartspaceMediaData = toSmartspaceMediaData(newMediaTarget)
- notifySmartspaceMediaDataLoaded(smartspaceMediaData.targetId, smartspaceMediaData)
- }
- else -> {
- // There should NOT be more than 1 Smartspace media update. When it happens, it
- // indicates a bad state or an error. Reset the status accordingly.
- Log.wtf(TAG, "More than 1 Smartspace Media Update. Resetting the status...")
- notifySmartspaceMediaDataRemoved(smartspaceMediaData.targetId, immediately = false)
- smartspaceMediaData = EMPTY_SMARTSPACE_MEDIA_DATA
- }
- }
- }
-
override fun onNotificationRemoved(key: String) {
Assert.isMainThread()
val removed = mediaEntries.remove(key) ?: return
@@ -1641,7 +1547,6 @@
println("externalListeners: ${mediaDataFilter.listeners}")
println("mediaEntries: $mediaEntries")
println("useMediaResumption: $useMediaResumption")
- println("allowMediaRecommendations: $allowMediaRecommendations")
}
mediaDeviceManager.dump(pw)
}
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 a6b9442..71c8d1f 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 @@
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 9bf556c..5fef81f 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 @@
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/MediaDataProcessor.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt
index 3821f3d..a524db4 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt
@@ -45,16 +45,15 @@
import android.media.session.MediaSession
import android.media.session.PlaybackState
import android.net.Uri
-import android.os.Parcelable
import android.os.Process
import android.os.UserHandle
-import android.provider.Settings
import android.service.notification.StatusBarNotification
import android.support.v4.media.MediaMetadataCompat
import android.text.TextUtils
import android.util.Log
import android.util.Pair as APair
import androidx.media.utils.MediaConstants
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.app.tracing.traceSection
import com.android.internal.annotations.Keep
import com.android.internal.logging.InstanceId
@@ -87,8 +86,6 @@
import com.android.systemui.media.controls.util.MediaFlags
import com.android.systemui.media.controls.util.MediaUiEventLogger
import com.android.systemui.media.controls.util.SmallHash
-import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.plugins.BcSmartspaceDataPlugin
import com.android.systemui.res.R
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.statusbar.NotificationMediaManager.isPlayingState
@@ -97,8 +94,6 @@
import com.android.systemui.util.Utils
import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.systemui.util.concurrency.ThreadFactory
-import com.android.systemui.util.settings.SecureSettings
-import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
import com.android.systemui.util.time.SystemClock
import java.io.IOException
import java.io.PrintWriter
@@ -106,12 +101,6 @@
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.collectLatest
-import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.flowOn
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.onStart
-import com.android.app.tracing.coroutines.launchTraced as launch
import kotlinx.coroutines.withContext
// URI fields to try loading album art from
@@ -139,12 +128,10 @@
private val mediaControllerFactory: MediaControllerFactory,
private val broadcastDispatcher: BroadcastDispatcher,
private val dumpManager: DumpManager,
- private val activityStarter: ActivityStarter,
private val smartspaceMediaDataProvider: SmartspaceMediaDataProvider,
private var useMediaResumption: Boolean,
private val useQsMediaPlayer: Boolean,
private val systemClock: SystemClock,
- private val secureSettings: SecureSettings,
private val mediaFlags: MediaFlags,
private val logger: MediaUiEventLogger,
private val smartspaceManager: SmartspaceManager?,
@@ -152,7 +139,7 @@
private val mediaDataRepository: MediaDataRepository,
private val mediaDataLoader: dagger.Lazy<MediaDataLoader>,
private val mediaLogger: MediaLogger,
-) : CoreStartable, BcSmartspaceDataPlugin.SmartspaceTargetListener {
+) : CoreStartable {
companion object {
/**
@@ -191,7 +178,6 @@
// There should ONLY be at most one Smartspace media recommendation.
@Keep private var smartspaceSession: SmartspaceSession? = null
- private var allowMediaRecommendations = false
private val artworkWidth =
context.resources.getDimensionPixelSize(
@@ -221,10 +207,8 @@
mediaControllerFactory: MediaControllerFactory,
dumpManager: DumpManager,
broadcastDispatcher: BroadcastDispatcher,
- activityStarter: ActivityStarter,
smartspaceMediaDataProvider: SmartspaceMediaDataProvider,
clock: SystemClock,
- secureSettings: SecureSettings,
mediaFlags: MediaFlags,
logger: MediaUiEventLogger,
smartspaceManager: SmartspaceManager?,
@@ -245,12 +229,10 @@
mediaControllerFactory,
broadcastDispatcher,
dumpManager,
- activityStarter,
smartspaceMediaDataProvider,
Utils.useMediaResumption(context),
Utils.useQsMediaPlayer(context),
clock,
- secureSettings,
mediaFlags,
logger,
smartspaceManager,
@@ -296,7 +278,7 @@
context.registerReceiver(appChangeReceiver, uninstallFilter)
// Register for Smartspace data updates.
- smartspaceMediaDataProvider.registerListener(this)
+ // TODO(b/382680767): remove
smartspaceSession =
smartspaceManager?.createSmartspaceSession(
SmartspaceConfig.Builder(context, SMARTSPACE_UI_SURFACE_LABEL).build()
@@ -314,13 +296,9 @@
}
}
smartspaceSession?.requestSmartspaceUpdate()
-
- // Track media controls recommendation setting.
- applicationScope.launch { trackMediaControlsRecommendationSetting() }
}
fun destroy() {
- smartspaceMediaDataProvider.unregisterListener(this)
smartspaceSession?.close()
smartspaceSession = null
context.unregisterReceiver(appChangeReceiver)
@@ -357,43 +335,6 @@
}
}
- /**
- * Allow recommendations from smartspace to show in media controls. Requires
- * [Utils.useQsMediaPlayer] to be enabled. On by default, but can be disabled by setting to 0
- */
- private suspend fun allowMediaRecommendations(): Boolean {
- return withContext(backgroundDispatcher) {
- val flag =
- secureSettings.getBoolForUser(
- Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION,
- true,
- UserHandle.USER_CURRENT,
- )
-
- useQsMediaPlayer && flag
- }
- }
-
- private suspend fun trackMediaControlsRecommendationSetting() {
- secureSettings
- .observerFlow(UserHandle.USER_ALL, Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION)
- // perform a query at the beginning.
- .onStart { emit(Unit) }
- .map { allowMediaRecommendations() }
- .distinctUntilChanged()
- .flowOn(backgroundDispatcher)
- // only track the most recent emission
- .collectLatest {
- allowMediaRecommendations = it
- if (!allowMediaRecommendations) {
- dismissSmartspaceRecommendation(
- key = mediaDataRepository.smartspaceMediaData.value.targetId,
- delay = 0L,
- )
- }
- }
- }
-
private fun removeAllForPackage(packageName: String) {
Assert.isMainThread()
val toRemove =
@@ -1277,62 +1218,6 @@
}
}
- override fun onSmartspaceTargetsUpdated(targets: List<Parcelable>) {
- if (!allowMediaRecommendations) {
- if (DEBUG) Log.d(TAG, "Smartspace recommendation is disabled in Settings.")
- return
- }
-
- val mediaTargets = targets.filterIsInstance<SmartspaceTarget>()
- val smartspaceMediaData = mediaDataRepository.smartspaceMediaData.value
- when (mediaTargets.size) {
- 0 -> {
- if (!smartspaceMediaData.isActive) {
- return
- }
- if (DEBUG) {
- Log.d(TAG, "Set Smartspace media to be inactive for the data update")
- }
- if (mediaFlags.isPersistentSsCardEnabled()) {
- // Smartspace uses this signal to hide the card (e.g. when it expires or user
- // disconnects headphones), so treat as setting inactive when flag is on
- val recommendation = smartspaceMediaData.copy(isActive = false)
- mediaDataRepository.setRecommendation(recommendation)
- notifySmartspaceMediaDataLoaded(recommendation.targetId, recommendation)
- } else {
- notifySmartspaceMediaDataRemoved(
- smartspaceMediaData.targetId,
- immediately = false,
- )
- mediaDataRepository.setRecommendation(
- SmartspaceMediaData(
- targetId = smartspaceMediaData.targetId,
- instanceId = smartspaceMediaData.instanceId,
- )
- )
- }
- }
- 1 -> {
- val newMediaTarget = mediaTargets.get(0)
- if (smartspaceMediaData.targetId == newMediaTarget.smartspaceTargetId) {
- // The same Smartspace updates can be received. Skip the duplicate updates.
- return
- }
- if (DEBUG) Log.d(TAG, "Forwarding Smartspace media update.")
- val recommendation = toSmartspaceMediaData(newMediaTarget)
- mediaDataRepository.setRecommendation(recommendation)
- notifySmartspaceMediaDataLoaded(recommendation.targetId, recommendation)
- }
- else -> {
- // There should NOT be more than 1 Smartspace media update. When it happens, it
- // indicates a bad state or an error. Reset the status accordingly.
- Log.wtf(TAG, "More than 1 Smartspace Media Update. Resetting the status...")
- notifySmartspaceMediaDataRemoved(smartspaceMediaData.targetId, immediately = false)
- mediaDataRepository.setRecommendation(SmartspaceMediaData())
- }
- }
- }
-
fun onNotificationRemoved(key: String) {
Assert.isMainThread()
val removed = mediaDataRepository.removeMediaEntry(key) ?: return
@@ -1621,7 +1506,6 @@
pw.apply {
println("internalListeners: $internalListeners")
println("useMediaResumption: $useMediaResumption")
- println("allowMediaRecommendations: $allowMediaRecommendations")
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt
index 86e9294..975f8f4 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt
@@ -1042,6 +1042,18 @@
return null
}
+ if (state.expansion == 1.0f) {
+ val height =
+ if (state.expandedMatchesParentHeight) {
+ heightInSceneContainerPx
+ } else {
+ context.resources.getDimensionPixelSize(
+ R.dimen.qs_media_session_height_expanded
+ )
+ }
+ setBackgroundHeights(height)
+ }
+
// Similar to obtainViewState: Let's create a new measurement
val result =
transitionLayout?.calculateViewState(
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt
index d94424c..e19d6e9 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt
@@ -57,6 +57,7 @@
// activity and a null second task, so the foreground task will be index 1, but when
// opening the app selector in split screen mode, the foreground task will be the second
// task in index 0.
+ // TODO(346588978): This needs to be updated for mixed groups
val foregroundGroup =
if (groupedTasks.firstOrNull()?.splitBounds != null) groupedTasks.first()
else groupedTasks.elementAtOrNull(1)
@@ -69,7 +70,7 @@
it.taskInfo1,
it.taskInfo1.taskId in foregroundTaskIds && it.taskInfo1.isVisible,
userManager.getUserInfo(it.taskInfo1.userId).toUserType(),
- it.splitBounds
+ it.splitBounds,
)
val task2 =
@@ -78,7 +79,7 @@
it.taskInfo2!!,
it.taskInfo2!!.taskId in foregroundTaskIds && it.taskInfo2!!.isVisible,
userManager.getUserInfo(it.taskInfo2!!.userId).toUserType(),
- it.splitBounds
+ it.splitBounds,
)
} else null
@@ -92,7 +93,7 @@
Integer.MAX_VALUE,
RECENT_IGNORE_UNAVAILABLE,
userTracker.userId,
- backgroundExecutor
+ backgroundExecutor,
) { tasks ->
continuation.resume(tasks)
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
index 3f14b55e..9270fff6 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
@@ -236,11 +236,29 @@
@Override
public void onDisplayReady(int displayId) {
CommandQueue.Callbacks.super.onDisplayReady(displayId);
+ if (mOverviewProxyService.getProxy() == null) {
+ return;
+ }
+
+ try {
+ mOverviewProxyService.getProxy().onDisplayReady(displayId);
+ } catch (RemoteException e) {
+ Log.e(TAG, "onDisplayReady() failed", e);
+ }
}
@Override
public void onDisplayRemoved(int displayId) {
CommandQueue.Callbacks.super.onDisplayRemoved(displayId);
+ if (mOverviewProxyService.getProxy() == null) {
+ return;
+ }
+
+ try {
+ mOverviewProxyService.getProxy().onDisplayRemoved(displayId);
+ } catch (RemoteException e) {
+ Log.e(TAG, "onDisplayRemoved() failed", e);
+ }
}
// Separated into a method to keep setDependencies() clean/readable.
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogV2.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogV2.kt
index f53b6cd..57d4063 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogV2.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogV2.kt
@@ -39,7 +39,6 @@
import androidx.annotation.WorkerThread
import androidx.core.view.ViewCompat
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat
-import com.android.settingslib.Utils
import com.android.systemui.animation.ViewHierarchyAnimator
import com.android.systemui.res.R
import com.android.systemui.statusbar.phone.SystemUIDialog
@@ -63,7 +62,7 @@
private val list: List<PrivacyElement>,
private val manageApp: (String, Int, Intent) -> Unit,
private val closeApp: (String, Int) -> Unit,
- private val openPrivacyDashboard: () -> Unit
+ private val openPrivacyDashboard: () -> Unit,
) : SystemUIDialog(context, R.style.Theme_PrivacyDialog) {
private val dismissListeners = mutableListOf<WeakReference<OnDialogDismissed>>()
@@ -192,11 +191,9 @@
return null
}
val closeAppButton =
- checkNotNull(window).layoutInflater.inflate(
- R.layout.privacy_dialog_card_button,
- expandedLayout,
- false
- ) as Button
+ checkNotNull(window)
+ .layoutInflater
+ .inflate(R.layout.privacy_dialog_card_button, expandedLayout, false) as Button
expandedLayout.addView(closeAppButton)
closeAppButton.id = R.id.privacy_dialog_close_app_button
closeAppButton.setText(R.string.privacy_dialog_close_app_button)
@@ -248,11 +245,9 @@
private fun configureManageButton(element: PrivacyElement, expandedLayout: ViewGroup): View {
val manageButton =
- checkNotNull(window).layoutInflater.inflate(
- R.layout.privacy_dialog_card_button,
- expandedLayout,
- false
- ) as Button
+ checkNotNull(window)
+ .layoutInflater
+ .inflate(R.layout.privacy_dialog_card_button, expandedLayout, false) as Button
expandedLayout.addView(manageButton)
manageButton.id = R.id.privacy_dialog_manage_app_button
manageButton.setText(
@@ -294,7 +289,7 @@
itemCard,
AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_CLICK,
context.getString(R.string.privacy_dialog_expand_action),
- null
+ null,
)
val expandedLayout =
@@ -311,7 +306,7 @@
it!!,
AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_CLICK,
context.getString(R.string.privacy_dialog_expand_action),
- null
+ null,
)
} else {
expandedLayout.visibility = View.VISIBLE
@@ -320,12 +315,12 @@
it!!,
AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_CLICK,
context.getString(R.string.privacy_dialog_collapse_action),
- null
+ null,
)
}
ViewHierarchyAnimator.animateNextUpdate(
rootView = window!!.decorView,
- excludedViews = setOf(expandedLayout)
+ excludedViews = setOf(expandedLayout),
)
}
}
@@ -345,19 +340,13 @@
@ColorInt
private fun getForegroundColor(active: Boolean) =
- Utils.getColorAttrDefaultColor(
- context,
- if (active) com.android.internal.R.color.materialColorOnPrimaryFixed
- else com.android.internal.R.color.materialColorOnSurface,
- )
+ if (active) context.getColor(com.android.internal.R.color.materialColorOnPrimaryFixed)
+ else context.getColor(com.android.internal.R.color.materialColorOnSurface)
@ColorInt
private fun getBackgroundColor(active: Boolean) =
- Utils.getColorAttrDefaultColor(
- context,
- if (active) com.android.internal.R.color.materialColorPrimaryFixed
- else com.android.internal.R.color.materialColorSurfaceContainerHigh,
- )
+ if (active) context.getColor(com.android.internal.R.color.materialColorPrimaryFixed)
+ else context.getColor(com.android.internal.R.color.materialColorSurfaceContainerHigh)
private fun getMutableDrawable(@DrawableRes resId: Int) = context.getDrawable(resId)!!.mutate()
@@ -379,7 +368,7 @@
context.getString(
singleUsageResId,
element.applicationName,
- element.attributionLabel ?: element.proxyLabel
+ element.attributionLabel ?: element.proxyLabel,
)
} else {
val doubleUsageResId: Int =
@@ -389,7 +378,7 @@
doubleUsageResId,
element.applicationName,
element.attributionLabel,
- element.proxyLabel
+ element.proxyLabel,
)
}
@@ -429,7 +418,7 @@
return groupInfo.loadSafeLabel(
this,
0f,
- TextUtils.SAFE_STRING_FLAG_FIRST_LINE or TextUtils.SAFE_STRING_FLAG_TRIM
+ TextUtils.SAFE_STRING_FLAG_FIRST_LINE or TextUtils.SAFE_STRING_FLAG_TRIM,
)
}
@@ -472,7 +461,7 @@
icon: Drawable,
iconSize: Int,
background: Drawable,
- backgroundSize: Int
+ backgroundSize: Int,
): Drawable {
val layered = LayerDrawable(arrayOf(background, icon))
layered.setLayerSize(0, backgroundSize, backgroundSize)
@@ -497,7 +486,7 @@
val isPhoneCall: Boolean,
val isService: Boolean,
val permGroupName: String,
- val navigationIntent: Intent
+ val navigationIntent: Intent,
) {
private val builder = StringBuilder("PrivacyElement(")
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 65fba28..6d90784 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
@@ -57,7 +57,6 @@
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Alignment
@@ -88,13 +87,14 @@
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.rememberMutableSceneTransitionLayoutState
import com.android.compose.animation.scene.transitions
import com.android.compose.modifiers.height
import com.android.compose.modifiers.padding
@@ -313,8 +313,8 @@
*/
@Composable
private fun CollapsableQuickSettingsSTL() {
- val sceneState = remember {
- MutableSceneTransitionLayoutState(
+ val sceneState =
+ rememberMutableSceneTransitionLayoutState(
viewModel.expansionState.toIdleSceneKey(),
transitions =
transitions {
@@ -323,7 +323,6 @@
}
},
)
- }
LaunchedEffect(Unit) {
synchronizeQsState(
@@ -580,7 +579,7 @@
}
@Composable
- private fun SceneScope.QuickQuickSettingsElement() {
+ private fun ContentScope.QuickQuickSettingsElement() {
val qqsPadding = viewModel.qqsHeaderHeight
val bottomPadding = viewModel.qqsBottomPadding
DisposableEffect(Unit) {
@@ -664,7 +663,7 @@
}
@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 a22eb3a..185ea93 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 @@
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 @@
/** 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 39408d3..c72381f 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.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 @@
@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 8fda23d..5cb30b9 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.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.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 6c1906b..bcc44d3 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.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 66961b6..4432d33 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.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 @@
) : 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/DndTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
index 04f0b87..1e8ef35 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
@@ -42,7 +42,7 @@
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.settingslib.notification.modes.EnableZenModeDialog;
+import com.android.settingslib.notification.modes.EnableDndDialogFactory;
import com.android.systemui.Prefs;
import com.android.systemui.animation.DialogCuj;
import com.android.systemui.animation.DialogTransitionAnimator;
@@ -59,7 +59,7 @@
import com.android.systemui.qs.UserSettingObserver;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSTileImpl;
-import com.android.systemui.qs.tiles.dialog.QSZenModeDialogMetricsLogger;
+import com.android.systemui.qs.tiles.dialog.QSEnableDndDialogMetricsLogger;
import com.android.systemui.res.R;
import com.android.systemui.statusbar.phone.SystemUIDialog;
import com.android.systemui.statusbar.policy.ZenModeController;
@@ -84,7 +84,7 @@
private final SharedPreferences mSharedPreferences;
private final UserSettingObserver mSettingZenDuration;
private final DialogTransitionAnimator mDialogTransitionAnimator;
- private final QSZenModeDialogMetricsLogger mQSZenDialogMetricsLogger;
+ private final QSEnableDndDialogMetricsLogger mQSDndDurationDialogLogger;
private boolean mListening;
@@ -121,7 +121,7 @@
refreshState();
}
};
- mQSZenDialogMetricsLogger = new QSZenModeDialogMetricsLogger(mContext);
+ mQSDndDurationDialogLogger = new QSEnableDndDialogMetricsLogger(mContext);
}
public static void setVisible(Context context, boolean visible) {
@@ -201,9 +201,9 @@
}
private Dialog makeZenModeDialog() {
- AlertDialog dialog = new EnableZenModeDialog(mContext, R.style.Theme_SystemUI_Dialog,
+ AlertDialog dialog = new EnableDndDialogFactory(mContext, R.style.Theme_SystemUI_Dialog,
true /* cancelIsNeutral */,
- mQSZenDialogMetricsLogger).createDialog();
+ mQSDndDurationDialogLogger).createDialog();
SystemUIDialog.applyFlags(dialog);
SystemUIDialog.setShowForAllUsers(dialog, true);
SystemUIDialog.registerDismissListener(dialog);
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 e93cec8..42b35c7 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 @@
@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 340cb68..6b5a22a 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 @@
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/QSZenModeDialogMetricsLogger.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/QSEnableDndDialogMetricsLogger.java
similarity index 90%
rename from packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/QSZenModeDialogMetricsLogger.java
rename to packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/QSEnableDndDialogMetricsLogger.java
index b3f66a6..5196a22 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/QSZenModeDialogMetricsLogger.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/QSEnableDndDialogMetricsLogger.java
@@ -19,7 +19,7 @@
import android.content.Context;
import com.android.internal.logging.UiEventLogger;
-import com.android.settingslib.notification.modes.ZenModeDialogMetricsLogger;
+import com.android.settingslib.notification.modes.EnableDndDialogMetricsLogger;
import com.android.systemui.qs.QSDndEvent;
import com.android.systemui.qs.QSEvents;
@@ -30,10 +30,10 @@
*
* Other names for DND (Do Not Disturb) include "Zen" and "Priority only".
*/
-public class QSZenModeDialogMetricsLogger extends ZenModeDialogMetricsLogger {
+public class QSEnableDndDialogMetricsLogger extends EnableDndDialogMetricsLogger {
private final UiEventLogger mUiEventLogger = QSEvents.INSTANCE.getQsUiEventsLogger();
- public QSZenModeDialogMetricsLogger(Context context) {
+ public QSEnableDndDialogMetricsLogger(Context context) {
super(context);
}
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 42cb124..54e4a52 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 @@
// 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 594394f..5ce7f0d 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.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 @@
// 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 @@
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 e3cf411..adf9eb4 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.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 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 @@
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 @@
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 @@
mShadeViewControllerLazy.get().cancelInputFocusTransfer();
});
}
+ mIsPrevServiceCleanedUp = true;
startConnectionToCurrentUser();
}
@@ -889,6 +900,19 @@
}
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 @@
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 @@
// 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 @@
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/scene/ui/viewmodel/SceneContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
index c1e8032..40e6d28 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
@@ -30,6 +30,7 @@
import com.android.compose.animation.scene.UserActionResult
import com.android.systemui.classifier.Classifier
import com.android.systemui.classifier.domain.interactor.FalsingInteractor
+import com.android.systemui.keyguard.ui.viewmodel.LightRevealScrimViewModel
import com.android.systemui.lifecycle.ExclusiveActivatable
import com.android.systemui.lifecycle.Hydrator
import com.android.systemui.power.domain.interactor.PowerInteractor
@@ -41,6 +42,7 @@
import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.statusbar.domain.interactor.RemoteInputInteractor
import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer
+import com.android.systemui.wallpapers.ui.viewmodel.WallpaperViewModel
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
@@ -62,6 +64,8 @@
private val splitEdgeDetector: SplitEdgeDetector,
private val logger: SceneLogger,
hapticsViewModelFactory: SceneContainerHapticsViewModel.Factory,
+ val lightRevealScrim: LightRevealScrimViewModel,
+ val wallpaperViewModel: WallpaperViewModel,
@Assisted view: View,
@Assisted private val motionEventHandlerReceiver: (MotionEventHandler?) -> Unit,
) : ExclusiveActivatable() {
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
index 9ee99e4..140fbf3 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 @@
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 @@
MediaProjectionMetricsLogger mediaProjectionMetricsLogger,
ScreenCaptureDisabledDialogDelegate screenCaptureDisabledDialogDelegate,
ScreenRecordPermissionDialogDelegate.Factory
- screenRecordPermissionDialogDelegateFactory) {
+ screenRecordPermissionDialogDelegateFactory,
+ ScreenRecordPermissionViewBinder.Factory screenRecordPermissionViewBinderFactory) {
mMainExecutor = mainExecutor;
mDevicePolicyResolver = devicePolicyResolver;
mBroadcastDispatcher = broadcastDispatcher;
@@ -127,6 +130,7 @@
mMediaProjectionMetricsLogger = mediaProjectionMetricsLogger;
mScreenCaptureDisabledDialogDelegate = screenCaptureDisabledDialogDelegate;
mScreenRecordPermissionDialogDelegateFactory = screenRecordPermissionDialogDelegateFactory;
+ mScreenRecordPermissionViewBinderFactory = screenRecordPermissionViewBinderFactory;
BroadcastOptions options = BroadcastOptions.makeBasic();
options.setInteractive(true);
@@ -151,8 +155,7 @@
* 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 @@
}
/**
+ * 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 9fcb3df..23df1c54 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.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 @@
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 0650f86..9a1ffcb 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 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 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 03a8d17..49f3cfc 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 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 @@
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/shade/ShadeStateTraceLogger.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeStateTraceLogger.kt
index 2705cda..39703ab 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeStateTraceLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeStateTraceLogger.kt
@@ -25,6 +25,8 @@
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.shade.data.repository.ShadeDisplaysRepository
import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround
+import dagger.Lazy
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
@@ -34,7 +36,7 @@
@Inject
constructor(
private val shadeInteractor: ShadeInteractor,
- private val shadeDisplaysRepository: ShadeDisplaysRepository,
+ private val shadeDisplaysRepository: Lazy<ShadeDisplaysRepository>,
@Application private val scope: CoroutineScope,
) : CoreStartable {
override fun start() {
@@ -52,9 +54,11 @@
instantForGroup(TRACK_GROUP_NAME, "shadeExpansion", it)
}
}
- launch {
- shadeDisplaysRepository.displayId.collect {
- instantForGroup(TRACK_GROUP_NAME, "displayId", it)
+ if (ShadeWindowGoesAround.isEnabled) {
+ launch {
+ shadeDisplaysRepository.get().displayId.collect {
+ instantForGroup(TRACK_GROUP_NAME, "displayId", it)
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java
index 2ed168a..2157d75 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java
@@ -486,11 +486,6 @@
R.string.keyboard_shortcut_group_system_shortcuts_helper),
KeyEvent.KEYCODE_SLASH,
KeyEvent.META_META_ON));
- systemGroup.addItem(new KeyboardShortcutInfo(
- mContext.getString(
- R.string.keyboard_shortcut_group_system_switch_input),
- KeyEvent.KEYCODE_SPACE,
- KeyEvent.META_META_ON));
return systemGroup;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
index 2885ce8..a56624c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
@@ -84,7 +84,7 @@
scrim.width * initialWidthMultiplier + -scrim.width * ovalWidthIncreaseAmount,
scrim.height * OVAL_INITIAL_TOP_PERCENT - scrim.height * interpolatedAmount,
scrim.width * (1f - initialWidthMultiplier) + scrim.width * ovalWidthIncreaseAmount,
- scrim.height * OVAL_INITIAL_BOTTOM_PERCENT + scrim.height * interpolatedAmount
+ scrim.height * OVAL_INITIAL_BOTTOM_PERCENT + scrim.height * interpolatedAmount,
)
}
}
@@ -98,7 +98,7 @@
/* controlX1= */ 0.4f,
/* controlY1= */ 0f,
/* controlX2= */ 0.2f,
- /* controlY2= */ 1f
+ /* controlY2= */ 1f,
)
override fun setRevealAmountOnScrim(amount: Float, scrim: LightRevealScrim) {
@@ -109,14 +109,14 @@
scrim.startColorAlpha =
getPercentPastThreshold(
1 - interpolatedAmount,
- threshold = 1 - START_COLOR_REVEAL_PERCENTAGE
+ threshold = 1 - START_COLOR_REVEAL_PERCENTAGE,
)
scrim.revealGradientEndColorAlpha =
1f -
getPercentPastThreshold(
interpolatedAmount,
- threshold = REVEAL_GRADIENT_END_COLOR_ALPHA_START_PERCENTAGE
+ threshold = REVEAL_GRADIENT_END_COLOR_ALPHA_START_PERCENTAGE,
)
// Start changing gradient bounds later to avoid harsh gradient in the beginning
@@ -127,14 +127,14 @@
left = scrim.viewWidth / 2 - (scrim.viewWidth / 2) * gradientBoundsAmount,
top = 0f,
right = scrim.viewWidth / 2 + (scrim.viewWidth / 2) * gradientBoundsAmount,
- bottom = scrim.viewHeight.toFloat()
+ bottom = scrim.viewHeight.toFloat(),
)
} else {
scrim.setRevealGradientBounds(
left = 0f,
top = scrim.viewHeight / 2 - (scrim.viewHeight / 2) * gradientBoundsAmount,
right = scrim.viewWidth.toFloat(),
- bottom = scrim.viewHeight / 2 + (scrim.viewHeight / 2) * gradientBoundsAmount
+ bottom = scrim.viewHeight / 2 + (scrim.viewHeight / 2) * gradientBoundsAmount,
)
}
}
@@ -166,7 +166,7 @@
1f -
getPercentPastThreshold(
amount,
- threshold = REVEAL_GRADIENT_END_COLOR_ALPHA_START_PERCENTAGE
+ threshold = REVEAL_GRADIENT_END_COLOR_ALPHA_START_PERCENTAGE,
)
val gradientBoundsAmount = lerp(GRADIENT_START_BOUNDS_PERCENTAGE, 1f, amount)
@@ -175,14 +175,14 @@
left = -(scrim.viewWidth) * gradientBoundsAmount,
top = -(scrim.viewHeight) * gradientBoundsAmount,
right = (scrim.viewWidth) * gradientBoundsAmount,
- bottom = (scrim.viewHeight) + (scrim.viewHeight) * gradientBoundsAmount
+ bottom = (scrim.viewHeight) + (scrim.viewHeight) * gradientBoundsAmount,
)
} else {
scrim.setRevealGradientBounds(
left = -(scrim.viewWidth) * gradientBoundsAmount,
top = -(scrim.viewHeight) * gradientBoundsAmount,
right = (scrim.viewWidth) + (scrim.viewWidth) * gradientBoundsAmount,
- bottom = (scrim.viewHeight) * gradientBoundsAmount
+ bottom = (scrim.viewHeight) * gradientBoundsAmount,
)
}
}
@@ -212,7 +212,7 @@
/** Radius of initial state of circle reveal */
val startRadius: Int,
/** Radius of end state of circle reveal */
- val endRadius: Int
+ val endRadius: Int,
) : LightRevealEffect {
override fun setRevealAmountOnScrim(amount: Float, scrim: LightRevealScrim) {
// reveal amount updates already have an interpolator, so we intentionally use the
@@ -225,7 +225,7 @@
centerX - radius /* left */,
centerY - radius /* top */,
centerX + radius /* right */,
- centerY + radius /* bottom */
+ centerY + radius, /* bottom */
)
}
}
@@ -259,7 +259,7 @@
powerButtonY - height * interpolatedAmount,
width * (1f + OFF_SCREEN_START_AMOUNT) +
width * INCREASE_MULTIPLIER * interpolatedAmount,
- powerButtonY + height * interpolatedAmount
+ powerButtonY + height * interpolatedAmount,
)
} else if (rotation == RotationUtils.ROTATION_LANDSCAPE) {
setRevealGradientBounds(
@@ -268,7 +268,7 @@
height * INCREASE_MULTIPLIER * interpolatedAmount,
powerButtonY + width * interpolatedAmount,
(-height * OFF_SCREEN_START_AMOUNT) +
- height * INCREASE_MULTIPLIER * interpolatedAmount
+ height * INCREASE_MULTIPLIER * interpolatedAmount,
)
} else {
// RotationUtils.ROTATION_SEASCAPE
@@ -278,7 +278,7 @@
height * INCREASE_MULTIPLIER * interpolatedAmount,
(width - powerButtonY) + width * interpolatedAmount,
height * (1f + OFF_SCREEN_START_AMOUNT) +
- height * INCREASE_MULTIPLIER * interpolatedAmount
+ height * INCREASE_MULTIPLIER * interpolatedAmount,
)
}
}
@@ -296,9 +296,9 @@
@JvmOverloads
constructor(
context: Context?,
- attrs: AttributeSet?,
+ attrs: AttributeSet? = null,
initialWidth: Int? = null,
- initialHeight: Int? = null
+ initialHeight: Int? = null,
) : View(context, attrs) {
private val logString = this::class.simpleName!! + "@" + hashCode()
@@ -322,8 +322,9 @@
revealEffect.setRevealAmountOnScrim(value, this)
updateScrimOpaque()
TrackTracer.instantForGroup(
- "scrim", { "light_reveal_amount $logString" },
- (field * 100).toInt()
+ "scrim",
+ { "light_reveal_amount $logString" },
+ (field * 100).toInt(),
)
invalidate()
}
@@ -440,7 +441,7 @@
1f,
intArrayOf(Color.TRANSPARENT, Color.WHITE),
floatArrayOf(0f, 1f),
- Shader.TileMode.CLAMP
+ Shader.TileMode.CLAMP,
)
// SRC_OVER ensures that we draw the semitransparent pixels over other views in the same
@@ -466,6 +467,7 @@
viewWidth = measuredWidth
viewHeight = measuredHeight
}
+
/**
* Sets bounds for the transparent oval gradient that reveals the views below the scrim. This is
* simply a helper method that sets [revealGradientCenter], [revealGradientWidth], and
@@ -513,7 +515,7 @@
gradientPaint.colorFilter =
PorterDuffColorFilter(
getColorWithAlpha(revealGradientEndColor, revealGradientEndColorAlpha),
- PorterDuff.Mode.MULTIPLY
+ PorterDuff.Mode.MULTIPLY,
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
index 38f7c39..ca2fbdd 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 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.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 @@
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 @@
}
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 @@
}
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 @@
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 72b03bf..b2764e1 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* = 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 86954d5..541a07c 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
@@ -17,7 +17,7 @@
package com.android.systemui.statusbar.chips.call.ui.viewmodel
import android.view.View
-import com.android.internal.jank.InteractionJankMonitor
+import com.android.internal.jank.Cuj
import com.android.systemui.animation.ActivityTransitionAnimator
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
@@ -30,11 +30,14 @@
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
import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
+import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization
import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel
import com.android.systemui.util.time.SystemClock
import javax.inject.Inject
@@ -76,14 +79,22 @@
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),
+ clickBehavior = getClickBehavior(state),
)
} else {
val startTimeInElapsedRealtime =
@@ -91,9 +102,10 @@
systemClock.elapsedRealtime()
OngoingActivityChipModel.Shown.Timer(
icon = icon,
- colors = ColorsModel.Themed,
+ colors = colors,
startTimeMs = startTimeInElapsedRealtime,
- getOnClickListener(state),
+ onClickListenerLegacy = getOnClickListener(state),
+ clickBehavior = getClickBehavior(state),
)
}
}
@@ -107,6 +119,7 @@
}
return View.OnClickListener { view ->
+ StatusBarChipsModernization.assertInLegacyMode()
logger.log(TAG, LogLevel.INFO, {}, { "Chip clicked" })
val backgroundView =
view.requireViewById<ChipBackgroundContainer>(R.id.ongoing_activity_chip_background)
@@ -115,12 +128,33 @@
state.intent,
ActivityTransitionAnimator.Controller.fromView(
backgroundView,
- InteractionJankMonitor.CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP,
+ Cuj.CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP,
),
)
}
}
+ private fun getClickBehavior(
+ state: OngoingCallModel.InCall
+ ): OngoingActivityChipModel.ClickBehavior =
+ if (state.intent == null) {
+ OngoingActivityChipModel.ClickBehavior.None
+ } else {
+ OngoingActivityChipModel.ClickBehavior.ExpandAction(
+ onClick = { expandable ->
+ StatusBarChipsModernization.assertInNewMode()
+ val animationController =
+ expandable.activityTransitionController(
+ Cuj.CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP
+ )
+ activityStarter.postStartActivityDismissingKeyguard(
+ state.intent,
+ animationController,
+ )
+ }
+ )
+ }
+
companion object {
private val phoneIcon =
Icon.Resource(
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 3422337..baa8eec 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.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 @@
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 @@
)
),
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,
),
- logger,
- TAG,
- ),
+ clickBehavior =
+ OngoingActivityChipModel.ClickBehavior.ExpandAction(
+ createDialogLaunchOnClickCallback(
+ createGenericCastToOtherDeviceDialogDelegate(deviceName),
+ dialogTransitionAnimator,
+ DIALOG_CUJ_AUDIO_ONLY,
+ logger,
+ TAG,
+ )
+ ),
)
}
@@ -256,6 +277,13 @@
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 2121f94..4fad01d 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.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.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 @@
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 ec3a5b2..b7cad62 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.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 @@
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 @@
)
}
}
+ val clickBehavior = OngoingActivityChipModel.ClickBehavior.None
val isShowingHeadsUpFromChipTap =
headsUpState is TopPinnedState.Pinned &&
@@ -95,7 +92,12 @@
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 @@
colors,
this.promotedContent.shortCriticalText,
onClickListener,
+ clickBehavior,
)
}
@@ -115,11 +118,21 @@
// 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 @@
colors,
time = this.promotedContent.time.time,
onClickListener,
+ clickBehavior,
)
}
PromotedNotificationContentModel.When.Mode.CountUp -> {
@@ -136,6 +150,7 @@
colors,
startTimeMs = this.promotedContent.time.time,
onClickListener,
+ clickBehavior,
)
}
PromotedNotificationContentModel.When.Mode.CountDown -> {
@@ -145,6 +160,7 @@
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 0065593..7f2327a7 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.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 @@
),
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,
),
- logger,
- TAG,
- ),
+ clickBehavior =
+ OngoingActivityChipModel.ClickBehavior.ExpandAction(
+ createDialogLaunchOnClickCallback(
+ dialogDelegate = createDelegate(state.recordedTask),
+ dialogTransitionAnimator = dialogTransitionAnimator,
+ DIALOG_CUJ,
+ logger,
+ TAG,
+ )
+ ),
)
}
}
@@ -154,6 +163,8 @@
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 2af86a5..6654d4a 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.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 @@
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 @@
)
),
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,
),
- logger,
- TAG,
- ),
+ clickBehavior =
+ OngoingActivityChipModel.ClickBehavior.ExpandAction(
+ createDialogLaunchOnClickCallback(
+ createGenericShareToAppDialogDelegate(),
+ dialogTransitionAnimator,
+ DIALOG_CUJ_AUDIO_ONLY,
+ logger,
+ TAG,
+ )
+ ),
)
}
@@ -180,6 +201,10 @@
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 b0fa9d8..d46638f 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 @@
// 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 @@
// 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 1be5842..6ce3228 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.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 @@
@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 @@
}
}
}
+
+@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 cac25d0..25f90f9 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 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 @@
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 956d99e..68c8f8c 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 @@
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 @@
* [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 @@
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 @@
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 @@
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 @@
*/
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 2fc366b..a978c04 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 @@
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.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 @@
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 @@
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/headsup/shared/StatusBarNoHunBehavior.kt b/packages/SystemUI/src/com/android/systemui/statusbar/headsup/shared/StatusBarNoHunBehavior.kt
new file mode 100644
index 0000000..2ae54d7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/headsup/shared/StatusBarNoHunBehavior.kt
@@ -0,0 +1,61 @@
+/*
+ * 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.statusbar.headsup.shared
+
+import com.android.systemui.Flags
+import com.android.systemui.flags.FlagToken
+import com.android.systemui.flags.RefactorFlagUtils
+
+/** Helper for reading or using the status bar no hun behavior flag state. */
+@Suppress("NOTHING_TO_INLINE")
+object StatusBarNoHunBehavior {
+ /** The aconfig flag name */
+ const val FLAG_NAME = Flags.FLAG_STATUS_BAR_NO_HUN_BEHAVIOR
+
+ /** A token used for dependency declaration */
+ val token: FlagToken
+ get() = FlagToken(FLAG_NAME, isEnabled)
+
+ /** Is the refactor enabled */
+ @JvmStatic
+ inline val isEnabled
+ get() = Flags.statusBarNoHunBehavior() && android.app.Flags.notificationsRedesignAppIcons()
+
+ /**
+ * Called to ensure code is only run when the flag is enabled. This protects users from the
+ * unintended behaviors caused by accidentally running new logic, while also crashing on an eng
+ * build to ensure that the refactor author catches issues in testing.
+ */
+ @JvmStatic
+ inline fun isUnexpectedlyInLegacyMode() =
+ 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.
+ * Caution!! Using this check incorrectly will cause crashes in nextfood builds!
+ */
+ @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)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
index cf9ee61..826329d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
@@ -39,7 +39,6 @@
import static android.service.notification.NotificationListenerService.REASON_UNAUTOBUNDLED;
import static android.service.notification.NotificationListenerService.REASON_USER_STOPPED;
-import static com.android.systemui.Flags.notificationsDismissPrunedSummaries;
import static com.android.systemui.statusbar.notification.NotificationUtils.logKey;
import static com.android.systemui.statusbar.notification.collection.NotificationEntry.DismissState.DISMISSED;
import static com.android.systemui.statusbar.notification.collection.NotificationEntry.DismissState.NOT_DISMISSED;
@@ -278,9 +277,7 @@
Assert.isMainThread();
checkForReentrantCall();
- if (notificationsDismissPrunedSummaries()) {
- entriesToDismiss = includeSummariesToDismiss(entriesToDismiss);
- }
+ entriesToDismiss = includeSummariesToDismiss(entriesToDismiss);
final int entryCount = entriesToDismiss.size();
final List<NotificationEntry> entriesToLocallyDismiss = new ArrayList<>();
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 c38b84b..417e57d 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 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 @@
}
/**
+ * 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/collection/coordinator/StabilizeHeadsUpGroup.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StabilizeHeadsUpGroup.kt
new file mode 100644
index 0000000..629cb83
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StabilizeHeadsUpGroup.kt
@@ -0,0 +1,61 @@
+/*
+ * 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.notification.collection.coordinator
+
+import com.android.systemui.Flags
+import com.android.systemui.flags.FlagToken
+import com.android.systemui.flags.RefactorFlagUtils
+
+/** Helper for reading or using the every change not allowed in heads up Group mode flag state. */
+@Suppress("NOTHING_TO_INLINE")
+object StabilizeHeadsUpGroup {
+ /** The aconfig flag name */
+ const val FLAG_NAME: String = Flags.FLAG_STABILIZE_HEADS_UP_GROUP
+
+ /** A token used for dependency declaration */
+ val token: FlagToken
+ get() = FlagToken(FLAG_NAME, isEnabled)
+
+ /** Is the refactor enabled */
+ @JvmStatic
+ inline val isEnabled
+ get() = Flags.stabilizeHeadsUpGroup()
+
+ /**
+ * Called to ensure code is only run when the flag is enabled. This protects users from the
+ * unintended behaviors caused by accidentally running new logic, while also crashing on an eng
+ * build to ensure that the refactor author catches issues in testing.
+ */
+ @JvmStatic
+ inline fun isUnexpectedlyInLegacyMode() =
+ 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.
+ * Caution!! Using this check incorrectly will cause crashes in nextfood builds!
+ */
+ @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)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
index 3c31d89..49d5029 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
@@ -26,6 +26,7 @@
import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
@@ -41,11 +42,12 @@
import com.android.systemui.statusbar.notification.collection.ListEntry;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifStabilityManager;
import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider;
import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor;
-import com.android.systemui.statusbar.notification.shared.NotificationMinimalism;
import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
+import com.android.systemui.statusbar.notification.shared.NotificationMinimalism;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.util.kotlin.BooleanFlowOperators;
@@ -54,6 +56,7 @@
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -68,6 +71,7 @@
@SysUISingleton
public class VisualStabilityCoordinator implements Coordinator, Dumpable {
private final DelayableExecutor mDelayableExecutor;
+ private final DelayableExecutor mMainExecutor;
private final HeadsUpManager mHeadsUpManager;
private final SeenNotificationsInteractor mSeenNotificationsInteractor;
private final ShadeAnimationInteractor mShadeAnimationInteractor;
@@ -91,6 +95,7 @@
private boolean mCommunalShowing = false;
private boolean mLockscreenShowing = false;
private boolean mLockscreenInGoneTransition = false;
+ private Set<String> mHeadsUpGroupKeys = new HashSet<>();
private boolean mPipelineRunAllowed;
private boolean mReorderingAllowed;
@@ -110,6 +115,7 @@
@Inject
public VisualStabilityCoordinator(
@Background DelayableExecutor delayableExecutor,
+ @Main DelayableExecutor mainExecutor,
DumpManager dumpManager,
HeadsUpManager headsUpManager,
ShadeAnimationInteractor shadeAnimationInteractor,
@@ -133,6 +139,7 @@
mWakefulnessLifecycle = wakefulnessLifecycle;
mStatusBarStateController = statusBarStateController;
mDelayableExecutor = delayableExecutor;
+ mMainExecutor = mainExecutor;
mCommunalSceneInteractor = communalSceneInteractor;
mShadeInteractor = shadeInteractor;
mKeyguardTransitionInteractor = keyguardTransitionInteractor;
@@ -161,6 +168,11 @@
),
this::onCommunalShowingChanged);
+
+ if (StabilizeHeadsUpGroup.isEnabled()) {
+ pipeline.addOnBeforeRenderListListener(mOnBeforeRenderListListener);
+ }
+
if (SceneContainerFlag.isEnabled()) {
mJavaAdapter.alwaysCollectFlow(mKeyguardTransitionInteractor.transitionValue(
KeyguardState.LOCKSCREEN),
@@ -176,10 +188,37 @@
mKeyguardStateController.addCallback(mKeyguardFadeAwayAnimationCallback);
}
}
-
pipeline.setVisualStabilityManager(mNotifStabilityManager);
}
+ /**
+ * Setter of heads up group keys.
+ */
+ @VisibleForTesting
+ public void setHeadsUpGroupKeys(Set<String> currentHeadsUpGroupKeys) {
+ if (StabilizeHeadsUpGroup.isUnexpectedlyInLegacyMode()) {
+ return;
+ }
+
+ if (currentHeadsUpGroupKeys == null) {
+ currentHeadsUpGroupKeys = new HashSet<>();
+ }
+
+ boolean isAnyHeadsUpGroupRemoved = false;
+ for (String headsUpKey: mHeadsUpGroupKeys) {
+ if (!currentHeadsUpGroupKeys.contains(headsUpKey)) {
+ isAnyHeadsUpGroupRemoved = true;
+ break;
+ }
+ }
+ mHeadsUpGroupKeys = currentHeadsUpGroupKeys;
+
+ if (isAnyHeadsUpGroupRemoved) {
+ updateAllowedStates("headsUpGroupEntryChange",
+ mHeadsUpGroupKeys.isEmpty(), /* async= */ true);
+ }
+ }
+
final KeyguardStateController.Callback mKeyguardFadeAwayAnimationCallback =
new KeyguardStateController.Callback() {
@Override
@@ -206,6 +245,64 @@
return false;
}
+ private boolean isParentHeadsUpGroup(NotificationEntry entry) {
+ if (StabilizeHeadsUpGroup.isUnexpectedlyInLegacyMode()) {
+ return false;
+ }
+ if (entry == null) {
+ return false;
+ }
+
+ final GroupEntry parent = entry.getParent();
+
+ if (parent == null) {
+ return false;
+ }
+
+ return isHeadsUpGroup(parent);
+ }
+
+ private boolean isHeadsUpGroup(GroupEntry groupEntry) {
+ if (StabilizeHeadsUpGroup.isUnexpectedlyInLegacyMode()) {
+ return false;
+ }
+
+ if (groupEntry == null) {
+ return false;
+ }
+
+ final NotificationEntry summary = groupEntry.getSummary();
+ if (summary == null) {
+ return false;
+ }
+
+ return mHeadsUpManager.isHeadsUpEntry(summary.getKey());
+ }
+ /**
+ * When reordering is enabled, non-heads-up groups can be pruned.
+ * @return true if the given group entry can be pruned.
+ */
+ private boolean canReorderGroupEntry(GroupEntry entry) {
+ if (StabilizeHeadsUpGroup.isUnexpectedlyInLegacyMode()) {
+ return false;
+ }
+
+ return entry != null && mReorderingAllowed && !isHeadsUpGroup(entry);
+ }
+
+ /**
+ * When reordering is enabled, notifications in non-heads-up groups notifications
+ * are allowed to change.
+ * @return true if the given notification entry can changed.
+ */
+ private boolean canReorderNotificationEntry(NotificationEntry entry) {
+ if (StabilizeHeadsUpGroup.isUnexpectedlyInLegacyMode()) {
+ return false;
+ }
+
+ return entry != null && mReorderingAllowed && !isParentHeadsUpGroup(entry);
+ }
+
@Override
public void onBeginRun() {
mIsSuppressingPipelineRun = false;
@@ -222,25 +319,50 @@
@Override
public boolean isGroupChangeAllowed(@NonNull NotificationEntry entry) {
- final boolean isGroupChangeAllowedForEntry =
- mReorderingAllowed || canMoveForHeadsUp(entry);
+ final boolean isGroupChangeAllowedForEntry;
+ if (StabilizeHeadsUpGroup.isEnabled()) {
+ isGroupChangeAllowedForEntry =
+ isEveryChangeAllowed()
+ || canReorderNotificationEntry(entry)
+ || canMoveForHeadsUp(entry);
+ } else {
+ isGroupChangeAllowedForEntry = mReorderingAllowed
+ || canMoveForHeadsUp(entry);
+ }
mIsSuppressingGroupChange |= !isGroupChangeAllowedForEntry;
return isGroupChangeAllowedForEntry;
}
@Override
public boolean isGroupPruneAllowed(@NonNull GroupEntry entry) {
- final boolean isGroupPruneAllowedForEntry = mReorderingAllowed;
+ boolean isGroupPruneAllowedForEntry;
+ if (StabilizeHeadsUpGroup.isEnabled()) {
+ isGroupPruneAllowedForEntry = isEveryChangeAllowed()
+ || canReorderGroupEntry(entry);
+ } else {
+ isGroupPruneAllowedForEntry = mReorderingAllowed;
+ }
+
mIsSuppressingGroupChange |= !isGroupPruneAllowedForEntry;
return isGroupPruneAllowedForEntry;
}
@Override
public boolean isSectionChangeAllowed(@NonNull NotificationEntry entry) {
- final boolean isSectionChangeAllowedForEntry =
- mReorderingAllowed
- || canMoveForHeadsUp(entry)
- || mEntriesThatCanChangeSection.containsKey(entry.getKey());
+ final boolean isSectionChangeAllowedForEntry;
+ if (StabilizeHeadsUpGroup.isEnabled()) {
+ isSectionChangeAllowedForEntry =
+ isEveryChangeAllowed()
+ || canReorderNotificationEntry(entry)
+ || canMoveForHeadsUp(entry)
+ || mEntriesThatCanChangeSection.containsKey(entry.getKey());
+ } else {
+ isSectionChangeAllowedForEntry =
+ mReorderingAllowed
+ || canMoveForHeadsUp(entry)
+ || mEntriesThatCanChangeSection.containsKey(entry.getKey());
+ }
+
if (!isSectionChangeAllowedForEntry) {
mEntriesWithSuppressedSectionChange.add(entry.getKey());
}
@@ -249,12 +371,27 @@
@Override
public boolean isEntryReorderingAllowed(@NonNull ListEntry entry) {
- return mReorderingAllowed || canMoveForHeadsUp(entry.getRepresentativeEntry());
+ if (StabilizeHeadsUpGroup.isEnabled()) {
+ if (isEveryChangeAllowed()) {
+ return true;
+ }
+
+ final NotificationEntry notificationEntry = entry.getRepresentativeEntry();
+ return canReorderNotificationEntry(notificationEntry)
+ || canMoveForHeadsUp(notificationEntry);
+ } else {
+ return mReorderingAllowed || canMoveForHeadsUp(
+ entry.getRepresentativeEntry());
+ }
}
@Override
public boolean isEveryChangeAllowed() {
- return mReorderingAllowed;
+ if (StabilizeHeadsUpGroup.isEnabled()) {
+ return mReorderingAllowed && mHeadsUpGroupKeys.isEmpty();
+ } else {
+ return mReorderingAllowed;
+ }
}
@Override
@@ -263,7 +400,37 @@
}
};
+ private final OnBeforeRenderListListener mOnBeforeRenderListListener =
+ new OnBeforeRenderListListener() {
+ @Override
+ public void onBeforeRenderList(List<ListEntry> entries) {
+ if (StabilizeHeadsUpGroup.isUnexpectedlyInLegacyMode()) {
+ return;
+ }
+
+ final Set<String> currentHeadsUpKeys = new HashSet<>();
+
+ for (int i = 0; i < entries.size(); i++) {
+ if (entries.get(i) instanceof GroupEntry groupEntry) {
+ final NotificationEntry summary = groupEntry.getSummary();
+ if (summary == null) continue;
+
+ final String summaryKey = summary.getKey();
+ if (mHeadsUpManager.isHeadsUpEntry(summaryKey)) {
+ currentHeadsUpKeys.add(summaryKey);
+ }
+ }
+ }
+
+ setHeadsUpGroupKeys(currentHeadsUpKeys);
+ }
+ };
+
private void updateAllowedStates(String field, boolean value) {
+ updateAllowedStates(field, value, /* async = */ false);
+ }
+
+ private void updateAllowedStates(String field, boolean value, boolean async) {
boolean wasPipelineRunAllowed = mPipelineRunAllowed;
boolean wasReorderingAllowed = mReorderingAllowed;
// No need to run notification pipeline when the lockscreen is in fading animation.
@@ -275,20 +442,28 @@
mLogger.logAllowancesChanged(
wasPipelineRunAllowed, mPipelineRunAllowed,
wasReorderingAllowed, mReorderingAllowed,
- field, value);
+ field, value, async);
}
+ if (async) {
+ mMainExecutor.execute(this::maybeInvalidateList);
+ } else {
+ maybeInvalidateList();
+ }
+ mVisualStabilityProvider.setReorderingAllowed(mReorderingAllowed);
+ }
+
+ private void maybeInvalidateList() {
if (mPipelineRunAllowed && mIsSuppressingPipelineRun) {
mNotifStabilityManager.invalidateList("pipeline run suppression ended");
} else if (mReorderingAllowed && (mIsSuppressingGroupChange
|| isSuppressingSectionChange()
|| mIsSuppressingEntryReorder)) {
- String reason = "reorder suppression ended for"
+ final String reason = "reorder suppression ended for"
+ " group=" + mIsSuppressingGroupChange
+ " section=" + isSuppressingSectionChange()
+ " sort=" + mIsSuppressingEntryReorder;
mNotifStabilityManager.invalidateList(reason);
}
- mVisualStabilityProvider.setReorderingAllowed(mReorderingAllowed);
}
private boolean isSuppressingSectionChange() {
@@ -393,6 +568,9 @@
pw.println("isSuppressingPipelineRun: " + mIsSuppressingPipelineRun);
pw.println("isSuppressingGroupChange: " + mIsSuppressingGroupChange);
pw.println("isSuppressingEntryReorder: " + mIsSuppressingEntryReorder);
+ if (StabilizeHeadsUpGroup.isEnabled()) {
+ pw.println("headsUpGroupKeys: " + mHeadsUpGroupKeys.size());
+ }
pw.println("entriesWithSuppressedSectionChange: "
+ mEntriesWithSuppressedSectionChange.size());
for (String key : mEntriesWithSuppressedSectionChange) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorLogger.kt
index fe23e4e..5051afc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorLogger.kt
@@ -32,7 +32,8 @@
wasReorderingAllowed: Boolean,
isReorderingAllowed: Boolean,
field: String,
- value: Boolean
+ value: Boolean,
+ async: Boolean,
) {
buffer.log(
TAG,
@@ -44,13 +45,15 @@
bool4 = isReorderingAllowed
str1 = field
str2 = value.toString()
+ str3 = async.toString()
},
{
"stability allowances changed:" +
" pipelineRunAllowed $bool1->$bool2" +
" reorderingAllowed $bool3->$bool4" +
" when setting $str1=$str2"
- }
+ " async=$str3"
+ },
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
index d83acf3..9ed1632 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
@@ -248,8 +248,6 @@
@NonNull NotifInflater.Params inflaterParams,
ExpandableNotificationRow row,
@Nullable NotificationRowContentBinder.InflationCallback inflationCallback) {
- final boolean useIncreasedCollapsedHeight =
- mMessagingUtil.isImportantMessaging(entry.getSbn(), entry.getImportance());
final boolean isMinimized = inflaterParams.isMinimized();
// Set show snooze action
@@ -258,7 +256,6 @@
RowContentBindParams params = mRowContentBindStage.getStageParams(entry);
params.requireContentViews(FLAG_CONTENT_VIEW_CONTRACTED);
params.requireContentViews(FLAG_CONTENT_VIEW_EXPANDED);
- params.setUseIncreasedCollapsedHeight(useIncreasedCollapsedHeight);
params.setUseMinimized(isMinimized);
int redactionType = inflaterParams.getRedactionType();
@@ -303,7 +300,6 @@
mLogger.logRequestingRebind(entry, inflaterParams);
mRowContentBindStage.requestRebind(entry, en -> {
mLogger.logRebindComplete(entry);
- row.setUsesIncreasedCollapsedHeight(useIncreasedCollapsedHeight);
row.setIsMinimized(isMinimized);
if (inflationCallback != null) {
inflationCallback.onAsyncInflationFinished(en);
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 0c040c8..502fb51 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 @@
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 @@
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 @@
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/domain/interactor/HeadsUpNotificationIconInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationIconInteractor.kt
index 17b6e9f..e5cc3b9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationIconInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationIconInteractor.kt
@@ -16,16 +16,19 @@
package com.android.systemui.statusbar.notification.domain.interactor
import android.graphics.Rect
+import com.android.systemui.statusbar.headsup.shared.StatusBarNoHunBehavior
import com.android.systemui.statusbar.notification.data.repository.HeadsUpNotificationIconViewStateRepository
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
-/** Domain logic pertaining to heads up notification icons. */
+/**
+ * Domain logic pertaining to heads up notification icons.
+ *
+ * If [StatusBarNoHunBehavior] is enabled, this class should do nothing.
+ */
class HeadsUpNotificationIconInteractor
@Inject
-constructor(
- private val repository: HeadsUpNotificationIconViewStateRepository,
-) {
+constructor(private val repository: HeadsUpNotificationIconViewStateRepository) {
/** Notification key for a notification icon to show isolated, or `null` if none. */
val isolatedIconLocation: Flow<Rect?> = repository.isolatedIconLocation
@@ -34,11 +37,13 @@
/** Updates the location where isolated notification icons are shown. */
fun setIsolatedIconLocation(rect: Rect?) {
+ StatusBarNoHunBehavior.assertInLegacyMode()
repository.isolatedIconLocation.value = rect
}
/** Updates which notification will have its icon displayed isolated. */
fun setIsolatedIconNotificationKey(key: String?) {
+ StatusBarNoHunBehavior.assertInLegacyMode()
repository.isolatedNotification.value = key
}
}
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 c88dd7a..d401283 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 @@
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 d02e17c..0171fb7 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 @@
}
}
- 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/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt
index 643ee24..348552f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt
@@ -32,6 +32,7 @@
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.res.R
import com.android.systemui.statusbar.StatusBarIconView
+import com.android.systemui.statusbar.headsup.shared.StatusBarNoHunBehavior
import com.android.systemui.statusbar.notification.collection.NotifCollection
import com.android.systemui.statusbar.notification.icon.IconPack
import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder.IconViewStore
@@ -81,7 +82,9 @@
StatusBarIconViewBinder.bindIconColors(sbiv, iconColors, contrastColorUtil)
}
}
- launch { viewModel.bindIsolatedIcon(view, viewStore) }
+ if (!StatusBarNoHunBehavior.isEnabled) {
+ launch { viewModel.bindIsolatedIcon(view, viewStore) }
+ }
launch { viewModel.animationsEnabled.bindAnimationsEnabled(view) }
}
@@ -146,6 +149,7 @@
view: NotificationIconContainer,
viewStore: IconViewStore,
) {
+ StatusBarNoHunBehavior.assertInLegacyMode()
coroutineScope {
launch {
isolatedIconLocation.collectTracingEach("NIC#isolatedIconLocation") { location ->
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt
index e103282..8f43c32 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt
@@ -23,6 +23,7 @@
import com.android.systemui.plugins.DarkIconDispatcher
import com.android.systemui.res.R
import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.statusbar.headsup.shared.StatusBarNoHunBehavior
import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationIconInteractor
import com.android.systemui.statusbar.notification.icon.domain.interactor.StatusBarNotificationIconsInteractor
import com.android.systemui.statusbar.phone.domain.interactor.DarkIconInteractor
@@ -37,7 +38,9 @@
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.conflate
import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
@@ -92,31 +95,42 @@
/** An Icon to show "isolated" in the IconContainer. */
val isolatedIcon: Flow<AnimatedValue<NotificationIconInfo?>> =
- headsUpIconInteractor.isolatedNotification
- .combine(icons) { isolatedNotif, iconsViewData ->
- isolatedNotif?.let {
- iconsViewData.visibleIcons.firstOrNull { it.notifKey == isolatedNotif }
- }
- }
- .distinctUntilChanged()
- .flowOn(bgContext)
- .conflate()
- .distinctUntilChanged()
- .pairwise(initialValue = null)
- .sample(shadeInteractor.shadeExpansion) { (prev, iconInfo), shadeExpansion ->
- val animate =
- when {
- iconInfo?.notifKey == prev?.notifKey -> false
- iconInfo == null || prev == null -> shadeExpansion == 0f
- else -> false
+ if (StatusBarNoHunBehavior.isEnabled) {
+ flowOf(AnimatedValue.NotAnimating(null))
+ } else {
+ headsUpIconInteractor.isolatedNotification
+ .combine(icons) { isolatedNotif, iconsViewData ->
+ isolatedNotif?.let {
+ iconsViewData.visibleIcons.firstOrNull { it.notifKey == isolatedNotif }
}
- AnimatableEvent(iconInfo, animate)
- }
- .toAnimatedValueFlow()
+ }
+ .distinctUntilChanged()
+ .flowOn(bgContext)
+ .conflate()
+ .distinctUntilChanged()
+ .pairwise(initialValue = null)
+ .sample(shadeInteractor.shadeExpansion) { (prev, iconInfo), shadeExpansion ->
+ val animate =
+ when {
+ iconInfo?.notifKey == prev?.notifKey -> false
+ iconInfo == null || prev == null -> shadeExpansion == 0f
+ else -> false
+ }
+ AnimatableEvent(iconInfo, animate)
+ }
+ .toAnimatedValueFlow()
+ }
/** Location to show an isolated icon, if there is one. */
val isolatedIconLocation: Flow<Rect> =
- headsUpIconInteractor.isolatedIconLocation.filterNotNull().conflate().distinctUntilChanged()
+ if (StatusBarNoHunBehavior.isEnabled) {
+ emptyFlow()
+ } else {
+ headsUpIconInteractor.isolatedIconLocation
+ .filterNotNull()
+ .conflate()
+ .distinctUntilChanged()
+ }
private class IconColorsImpl(override val tint: Int, private val areas: Collection<Rect>) :
NotificationIconColors {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
index 0a9899e..c3266fc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
@@ -93,7 +93,6 @@
clickerBuilder.build(bubblesOptional, notificationActivityStarter)
)
notificationRowBinder.setUpWithPresenter(presenter, listContainer)
- headsUpViewBinder.setPresenter(presenter)
notifBindPipelineInitializer.initialize()
animatedImageNotificationManager.bind()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinder.java
index 32ec023..9988f72 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinder.java
@@ -23,9 +23,7 @@
import androidx.annotation.Nullable;
import androidx.core.os.CancellationSignal;
-import com.android.internal.util.NotificationMessagingUtil;
import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.statusbar.NotificationPresenter;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.coordinator.HeadsUpCoordinator;
import com.android.systemui.statusbar.notification.row.RowContentBindParams;
@@ -45,30 +43,18 @@
@SysUISingleton
public class HeadsUpViewBinder {
private final RowContentBindStage mStage;
- private final NotificationMessagingUtil mNotificationMessagingUtil;
private final Map<NotificationEntry, CancellationSignal> mOngoingBindCallbacks =
new ArrayMap<>();
private final HeadsUpViewBinderLogger mLogger;
- private NotificationPresenter mNotificationPresenter;
@Inject
- HeadsUpViewBinder(
- NotificationMessagingUtil notificationMessagingUtil,
- RowContentBindStage bindStage, HeadsUpViewBinderLogger logger) {
- mNotificationMessagingUtil = notificationMessagingUtil;
+ HeadsUpViewBinder(RowContentBindStage bindStage, HeadsUpViewBinderLogger logger) {
mStage = bindStage;
mLogger = logger;
}
/**
- * Set notification presenter to determine parameters for heads up view inflation.
- */
- public void setPresenter(NotificationPresenter presenter) {
- mNotificationPresenter = presenter;
- }
-
- /**
* Bind heads up view to the notification row.
* @param callback callback after heads up view is bound
*/
@@ -77,15 +63,9 @@
boolean isPinnedByUser,
@Nullable HeadsUpBindCallback callback) {
RowContentBindParams params = mStage.getStageParams(entry);
- final boolean isImportantMessage = mNotificationMessagingUtil.isImportantMessaging(
- entry.getSbn(), entry.getImportance());
- final boolean useIncreasedHeadsUp = isImportantMessage
- && !mNotificationPresenter.isPresenterFullyCollapsed();
- params.setUseIncreasedHeadsUpHeight(useIncreasedHeadsUp);
params.requireContentViews(FLAG_CONTENT_VIEW_HEADS_UP);
CancellationSignal signal = mStage.requestRebind(entry, en -> {
mLogger.entryBoundSuccessfully(entry);
- en.getRow().setUsesIncreasedHeadsUpHeight(params.useIncreasedHeadsUpHeight());
// requestRebind promises that if we called cancel before this callback would be
// invoked, then we will not enter this callback, and because we always cancel before
// adding to this map, we know this will remove the correct signal.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/dagger/NotificationsLogModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/dagger/NotificationsLogModule.kt
index d3359d3..6bcce3e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/dagger/NotificationsLogModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/dagger/NotificationsLogModule.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.notification.logging.dagger
+import com.android.app.tracing.TrackGroupUtils.trackGroup
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.LogBufferFactory
@@ -44,7 +45,11 @@
@SysUISingleton
@NotificationHeadsUpLog
fun provideNotificationHeadsUpLogBuffer(factory: LogBufferFactory): LogBuffer {
- return factory.create("NotifHeadsUpLog", 1000)
+ return factory.create(
+ "NotifHeadsUpLog",
+ 1000,
+ systraceTrackName = notifPipelineTrack("NotifHeadsUpLog"),
+ )
}
/** Provides a logging buffer for logs related to inflation of notifications. */
@@ -52,7 +57,11 @@
@SysUISingleton
@NotifInflationLog
fun provideNotifInflationLogBuffer(factory: LogBufferFactory): LogBuffer {
- return factory.create("NotifInflationLog", 250)
+ return factory.create(
+ "NotifInflationLog",
+ 250,
+ systraceTrackName = notifPipelineTrack("NotifInflationLog"),
+ )
}
/** Provides a logging buffer for all logs related to the data layer of notifications. */
@@ -60,7 +69,11 @@
@SysUISingleton
@NotifInteractionLog
fun provideNotifInteractionLogBuffer(factory: LogBufferFactory): LogBuffer {
- return factory.create("NotifInteractionLog", 50)
+ return factory.create(
+ "NotifInteractionLog",
+ 50,
+ systraceTrackName = notifPipelineTrack("NotifInteractionLog"),
+ )
}
/** Provides a logging buffer for notification interruption calculations. */
@@ -68,7 +81,11 @@
@SysUISingleton
@NotificationInterruptLog
fun provideNotificationInterruptLogBuffer(factory: LogBufferFactory): LogBuffer {
- return factory.create("NotifInterruptLog", 100)
+ return factory.create(
+ "NotifInterruptLog",
+ 100,
+ systraceTrackName = notifPipelineTrack("NotifInterruptLog"),
+ )
}
/** Provides a logging buffer for all logs related to notifications on the lockscreen. */
@@ -91,7 +108,12 @@
if (Compile.IS_DEBUG && notifPipelineFlags.isDevLoggingEnabled()) {
maxSize *= 10
}
- return factory.create("NotifLog", maxSize, Compile.IS_DEBUG /* systrace */)
+ return factory.create(
+ "NotifLog",
+ maxSize,
+ /* systrace= */ Compile.IS_DEBUG,
+ systraceTrackName = notifPipelineTrack("NotifLog"),
+ )
}
/** Provides a logging buffer for all logs related to remote input controller. */
@@ -107,7 +129,11 @@
@SysUISingleton
@NotificationRenderLog
fun provideNotificationRenderLogBuffer(factory: LogBufferFactory): LogBuffer {
- return factory.create("NotifRenderLog", 100)
+ return factory.create(
+ "NotifRenderLog",
+ 100,
+ systraceTrackName = notifPipelineTrack("NotifRenderLog"),
+ )
}
/** Provides a logging buffer for all logs related to managing notification sections. */
@@ -150,3 +176,13 @@
return factory.create("VisualStabilityLog", 50, /* maxSize */ false /* systrace */)
}
}
+
+private const val NOTIF_PIPELINE_TRACK_GROUP_NAME = "Notification pipeline"
+
+/**
+ * This generates a track name that is hierarcically collapsed inside
+ * [NOTIF_PIPELINE_TRACK_GROUP_NAME] in perfetto traces.
+ */
+private fun notifPipelineTrack(trackName: String): String {
+ return trackGroup(NOTIF_PIPELINE_TRACK_GROUP_NAME, trackName)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationUiAod.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationUiAod.kt
new file mode 100644
index 0000000..fa1f32c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationUiAod.kt
@@ -0,0 +1,60 @@
+/*
+ * 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.statusbar.notification.promoted
+
+import com.android.systemui.Flags
+import com.android.systemui.flags.FlagToken
+import com.android.systemui.flags.RefactorFlagUtils
+
+/** Helper for reading or using the promoted ongoing notifications AOD flag state. */
+object PromotedNotificationUiAod {
+ /** The aconfig flag name */
+ const val FLAG_NAME = Flags.FLAG_AOD_UI_RICH_ONGOING
+
+ /** A token used for dependency declaration */
+ val token: FlagToken
+ get() = FlagToken(FLAG_NAME, isEnabled)
+
+ /** Is the refactor enabled */
+ @JvmStatic
+ inline val isEnabled
+ get() = Flags.aodUiRichOngoing()
+
+ /**
+ * Called to ensure code is only run when the flag is enabled. This protects users from the
+ * unintended behaviors caused by accidentally running new logic, while also crashing on an eng
+ * build to ensure that the refactor author catches issues in testing.
+ */
+ @JvmStatic
+ inline fun isUnexpectedlyInLegacyMode() =
+ RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME)
+
+ /**
+ * Called to ensure code is only run when the flag is disabled. This will throw an exception if
+ * the flag is not enabled to ensure that the refactor author catches issues in testing.
+ * Caution!! Using this check incorrectly will cause crashes in nextfood builds!
+ */
+ @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)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationUiForceExpanded.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationUiForceExpanded.kt
new file mode 100644
index 0000000..cb0d674
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationUiForceExpanded.kt
@@ -0,0 +1,61 @@
+/*
+ * 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.notification.promoted
+
+import com.android.systemui.Flags
+import com.android.systemui.flags.FlagToken
+import com.android.systemui.flags.RefactorFlagUtils
+
+/** Helper for reading or using the expanded ui rich ongoing flag state. */
+@Suppress("NOTHING_TO_INLINE")
+object PromotedNotificationUiForceExpanded {
+ /** The aconfig flag name */
+ const val FLAG_NAME = Flags.FLAG_UI_RICH_ONGOING_FORCE_EXPANDED
+
+ /** A token used for dependency declaration */
+ val token: FlagToken
+ get() = FlagToken(FLAG_NAME, isEnabled)
+
+ /** Is the refactor enabled */
+ @JvmStatic
+ inline val isEnabled
+ get() = Flags.uiRichOngoingForceExpanded()
+
+ /**
+ * Called to ensure code is only run when the flag is enabled. This protects users from the
+ * unintended behaviors caused by accidentally running new logic, while also crashing on an eng
+ * build to ensure that the refactor author catches issues in testing.
+ */
+ @JvmStatic
+ inline fun isUnexpectedlyInLegacyMode() =
+ 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.
+ * Caution!! Using this check incorrectly will cause crashes in nextfood builds!
+ */
+ @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)
+}
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 258d80c..a175f90 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 @@
* 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/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 95604c1..598ff09 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -191,12 +191,10 @@
private int mMaxHeadsUpHeightBeforeP;
private int mMaxHeadsUpHeightBeforeS;
private int mMaxHeadsUpHeight;
- private int mMaxHeadsUpHeightIncreased;
private int mMaxSmallHeightBeforeN;
private int mMaxSmallHeightBeforeP;
private int mMaxSmallHeightBeforeS;
private int mMaxSmallHeight;
- private int mMaxSmallHeightLarge;
private int mMaxExpandedHeight;
private int mNotificationLaunchHeight;
private boolean mMustStayOnScreen;
@@ -414,8 +412,6 @@
private OnUserInteractionCallback mOnUserInteractionCallback;
private NotificationGutsManager mNotificationGutsManager;
private boolean mIsMinimized;
- private boolean mUseIncreasedCollapsedHeight;
- private boolean mUseIncreasedHeadsUpHeight;
private float mTranslationWhenRemoved;
private boolean mWasChildInGroupWhenRemoved;
private final NotificationInlineImageResolver mImageResolver;
@@ -828,8 +824,6 @@
}
} else if (isCallLayout) {
smallHeight = mMaxExpandedHeight;
- } else if (mUseIncreasedCollapsedHeight && layout == mPrivateLayout) {
- smallHeight = mMaxSmallHeightLarge;
} else {
smallHeight = mMaxSmallHeight;
}
@@ -845,8 +839,6 @@
} else {
headsUpHeight = mMaxHeadsUpHeightBeforeS;
}
- } else if (mUseIncreasedHeadsUpHeight && layout == mPrivateLayout) {
- headsUpHeight = mMaxHeadsUpHeightIncreased;
} else {
headsUpHeight = mMaxHeadsUpHeight;
}
@@ -1848,14 +1840,6 @@
return mIsMinimized;
}
- public void setUsesIncreasedCollapsedHeight(boolean use) {
- mUseIncreasedCollapsedHeight = use;
- }
-
- public void setUsesIncreasedHeadsUpHeight(boolean use) {
- mUseIncreasedHeadsUpHeight = use;
- }
-
/**
* Interface for logging {{@link ExpandableNotificationRow} events.}
*/
@@ -2086,8 +2070,6 @@
mMaxSmallHeight = NotificationUtils.getFontScaledHeight(mContext,
R.dimen.notification_min_height);
}
- mMaxSmallHeightLarge = NotificationUtils.getFontScaledHeight(mContext,
- R.dimen.notification_min_height_increased);
mMaxExpandedHeight = NotificationUtils.getFontScaledHeight(mContext,
R.dimen.notification_max_height);
mMaxHeadsUpHeightBeforeN = NotificationUtils.getFontScaledHeight(mContext,
@@ -2098,8 +2080,6 @@
R.dimen.notification_max_heads_up_height_before_s);
mMaxHeadsUpHeight = NotificationUtils.getFontScaledHeight(mContext,
R.dimen.notification_max_heads_up_height);
- mMaxHeadsUpHeightIncreased = NotificationUtils.getFontScaledHeight(mContext,
- R.dimen.notification_max_heads_up_height_increased);
Resources res = getResources();
mEnableNonGroupedNotificationExpand =
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 e27ff7d..793b3b8 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 var _alpha: Int = 255
- private var _colorFilter: ColorFilter? = null
- private val paint =
- Paint().apply {
- color = context.getColor(com.android.internal.R.color.materialColorPrimaryContainer)
+ 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)
+
+ /** 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/notification/row/NotificationContentInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
index 7b3a471..57fe24f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
@@ -436,8 +436,7 @@
if ((reInflateFlags & FLAG_CONTENT_VIEW_CONTRACTED) != 0) {
logger.logAsyncTaskProgress(entryForLogging, "creating contracted remote view");
- result.newContentView = createContentView(builder, bindParams.isMinimized,
- bindParams.usesIncreasedHeight);
+ result.newContentView = createContentView(builder, bindParams.isMinimized);
}
if ((reInflateFlags & FLAG_CONTENT_VIEW_EXPANDED) != 0) {
@@ -451,8 +450,7 @@
if (isHeadsUpCompact) {
result.newHeadsUpView = builder.createCompactHeadsUpContentView();
} else {
- result.newHeadsUpView = builder.createHeadsUpContentView(
- bindParams.usesIncreasedHeadsUpHeight);
+ result.newHeadsUpView = builder.createHeadsUpContentView();
}
}
@@ -462,7 +460,7 @@
&& bindParams.redactionType == REDACTION_TYPE_SENSITIVE_CONTENT) {
result.newPublicView = createSensitiveContentMessageNotification(
row.getEntry().getSbn().getNotification(), builder.getStyle(),
- systemUiContext, packageContext).createContentView(true);
+ systemUiContext, packageContext).createContentView();
} else {
result.newPublicView = builder.makePublicContentView(bindParams.isMinimized);
}
@@ -1135,11 +1133,11 @@
}
private static RemoteViews createContentView(Notification.Builder builder,
- boolean isMinimized, boolean useLarge) {
+ boolean isMinimized) {
if (isMinimized) {
return builder.makeLowPriorityContentView(false /* useRegularSubtext */);
}
- return builder.createContentView(useLarge);
+ return builder.createContentView();
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinder.java
index 1cef879..0be1d5d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinder.java
@@ -143,11 +143,8 @@
*/
class BindParams {
- public BindParams(boolean minimized, boolean increasedHeight,
- boolean increasedHeadsUpHeight, int redaction) {
+ public BindParams(boolean minimized, int redaction) {
isMinimized = minimized;
- usesIncreasedHeight = increasedHeight;
- usesIncreasedHeadsUpHeight = increasedHeadsUpHeight;
redactionType = redaction;
}
@@ -157,16 +154,6 @@
public final boolean isMinimized;
/**
- * Use increased height when binding contracted view.
- */
- public final boolean usesIncreasedHeight;
-
- /**
- * Use increased height when binding heads up views.
- */
- public final boolean usesIncreasedHeadsUpHeight;
-
- /**
* Controls the type of public view to show, if a public view is requested
*/
public final @RedactionType int redactionType;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt
index c619b17..49e38de 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt
@@ -822,11 +822,7 @@
entryForLogging,
"creating contracted remote view",
)
- createContentView(
- builder,
- bindParams.isMinimized,
- bindParams.usesIncreasedHeight,
- )
+ createContentView(builder, bindParams.isMinimized)
} else null
val expanded =
if (reInflateFlags and FLAG_CONTENT_VIEW_EXPANDED != 0) {
@@ -846,7 +842,7 @@
if (isHeadsUpCompact) {
builder.createCompactHeadsUpContentView()
} else {
- builder.createHeadsUpContentView(bindParams.usesIncreasedHeadsUpHeight)
+ @Suppress("DEPRECATION") builder.createHeadsUpContentView()
}
} else null
val public =
@@ -862,7 +858,7 @@
systemUiContext,
packageContext,
)
- .createContentView(bindParams.usesIncreasedHeight)
+ .createContentView()
} else {
builder.makePublicContentView(bindParams.isMinimized)
}
@@ -1654,11 +1650,12 @@
private fun createContentView(
builder: Notification.Builder,
isMinimized: Boolean,
- useLarge: Boolean,
): RemoteViews {
return if (isMinimized) {
builder.makeLowPriorityContentView(false /* useRegularSubtext */)
- } else builder.createContentView(useLarge)
+ } else {
+ @Suppress("DEPRECATION") builder.createContentView()
+ }
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindParams.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindParams.java
index bc44cb0..adbff10 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindParams.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindParams.java
@@ -20,7 +20,6 @@
import static com.android.systemui.statusbar.NotificationLockscreenUserManager.RedactionType;
import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED;
import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_EXPANDED;
-import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP;
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
@@ -29,8 +28,6 @@
*/
public final class RowContentBindParams {
private boolean mUseMinimized;
- private boolean mUseIncreasedHeight;
- private boolean mUseIncreasedHeadsUpHeight;
private boolean mViewsNeedReinflation;
private @InflationFlag int mContentViews = DEFAULT_INFLATION_FLAGS;
private @RedactionType int mRedactionType = REDACTION_TYPE_NONE;
@@ -75,34 +72,6 @@
}
/**
- * Set whether content should use an increased height version of its contracted view.
- */
- public void setUseIncreasedCollapsedHeight(boolean useIncreasedHeight) {
- if (mUseIncreasedHeight != useIncreasedHeight) {
- mDirtyContentViews |= FLAG_CONTENT_VIEW_CONTRACTED;
- }
- mUseIncreasedHeight = useIncreasedHeight;
- }
-
- public boolean useIncreasedHeight() {
- return mUseIncreasedHeight;
- }
-
- /**
- * Set whether content should use an increased height version of its heads up view.
- */
- public void setUseIncreasedHeadsUpHeight(boolean useIncreasedHeadsUpHeight) {
- if (mUseIncreasedHeadsUpHeight != useIncreasedHeadsUpHeight) {
- mDirtyContentViews |= FLAG_CONTENT_VIEW_HEADS_UP;
- }
- mUseIncreasedHeadsUpHeight = useIncreasedHeadsUpHeight;
- }
-
- public boolean useIncreasedHeadsUpHeight() {
- return mUseIncreasedHeadsUpHeight;
- }
-
- /**
* Require the specified content views to be bound after the rebind request.
*
* @see InflationFlag
@@ -169,10 +138,8 @@
@Override
public String toString() {
return String.format("RowContentBindParams[mContentViews=%x mDirtyContentViews=%x "
- + "mUseMinimized=%b mUseIncreasedHeight=%b "
- + "mUseIncreasedHeadsUpHeight=%b mViewsNeedReinflation=%b]",
- mContentViews, mDirtyContentViews, mUseMinimized, mUseIncreasedHeight,
- mUseIncreasedHeadsUpHeight, mViewsNeedReinflation);
+ + "mUseMinimized=%b mViewsNeedReinflation=%b]",
+ mContentViews, mDirtyContentViews, mUseMinimized, mViewsNeedReinflation);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStage.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStage.java
index 53f7416..6883ec5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStage.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStage.java
@@ -72,8 +72,7 @@
// Bind/unbind with parameters
mBinder.unbindContent(entry, row, contentToUnbind);
- BindParams bindParams = new BindParams(params.useMinimized(), params.useIncreasedHeight(),
- params.useIncreasedHeadsUpHeight(), params.getRedactionType());
+ BindParams bindParams = new BindParams(params.useMinimized(), params.getRedactionType());
boolean forceInflate = params.needsReinflation();
InflationCallback inflationCallback = new InflationCallback() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/AppIconProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/AppIconProvider.kt
index f3ee34f..52a0f6f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/AppIconProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/AppIconProvider.kt
@@ -84,7 +84,16 @@
init {
if (notificationsRedesignThemedAppIcons()) {
// Initialize the controller so that we can support themed icons.
- mThemeController = MonoIconThemeController()
+ mThemeController =
+ MonoIconThemeController(
+ colorProvider = { ctx ->
+ val res = ctx.resources
+ intArrayOf(
+ /* background */ res.getColor(R.color.materialColorPrimary),
+ /* icon */ res.getColor(R.color.materialColorSurfaceContainerHigh),
+ )
+ }
+ )
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index a8a1318..f571071 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -98,6 +98,7 @@
import com.android.systemui.shade.TouchLogger;
import com.android.systemui.statusbar.NotificationShelf;
import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.headsup.shared.StatusBarNoHunBehavior;
import com.android.systemui.statusbar.notification.ColorUpdateLogger;
import com.android.systemui.statusbar.notification.FakeShadowView;
import com.android.systemui.statusbar.notification.LaunchAnimationParameters;
@@ -126,7 +127,6 @@
import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrollState;
import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView;
import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
-import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
import com.android.systemui.statusbar.policy.ScrollAdapter;
import com.android.systemui.statusbar.policy.SplitShadeStateController;
import com.android.systemui.util.Assert;
@@ -281,11 +281,9 @@
private boolean mExpandedInThisMotion;
private boolean mShouldShowShelfOnly;
protected boolean mScrollingEnabled;
- private boolean mIsCurrentUserSetup;
protected FooterView mFooterView;
protected EmptyShadeView mEmptyShadeView;
private boolean mClearAllInProgress;
- private FooterClearAllListener mFooterClearAllListener;
private boolean mFlingAfterUpEvent;
/**
* Was the scroller scrolled to the top when the down motion was observed?
@@ -466,7 +464,6 @@
boolean mHeadsUpAnimatingAway;
private Consumer<Boolean> mHeadsUpAnimatingAwayListener;
private int mStatusBarState;
- private int mUpcomingStatusBarState;
private boolean mHeadsUpGoingAwayAnimationsAllowed = true;
private final Runnable mReflingAndAnimateScroll = this::animateScroll;
private int mCornerRadius;
@@ -497,7 +494,6 @@
private float mLastSentExpandedHeight;
private boolean mWillExpand;
private int mGapHeight;
- private boolean mIsRemoteInputActive;
/**
* The extra inset during the full shade transition
@@ -571,10 +567,8 @@
private boolean mDismissUsingRowTranslationX = true;
private ExpandableNotificationRow mTopHeadsUpRow;
private NotificationStackScrollLayoutController.TouchHandler mTouchHandler;
- private final ScreenOffAnimationController mScreenOffAnimationController;
private boolean mShouldUseSplitNotificationShade;
private boolean mShouldSkipTopPaddingAnimationAfterFold = false;
- private boolean mHasFilteredOutSeenNotifications;
@Nullable private SplitShadeStateController mSplitShadeStateController = null;
private boolean mIsSmallLandscapeLockscreenEnabled = false;
private boolean mSuppressHeightUpdates;
@@ -635,9 +629,6 @@
};
@Nullable
- private OnClickListener mManageButtonClickListener;
-
- @Nullable
private WallpaperInteractor mWallpaperInteractor;
public NotificationStackScrollLayout(Context context, AttributeSet attrs) {
@@ -649,8 +640,6 @@
mDebugLines = mFeatureFlags.isEnabled(Flags.NSSL_DEBUG_LINES);
mDebugRemoveAnimation = mFeatureFlags.isEnabled(Flags.NSSL_DEBUG_REMOVE_ANIMATION);
mSectionsManager = Dependency.get(NotificationSectionsManager.class);
- mScreenOffAnimationController =
- Dependency.get(ScreenOffAnimationController.class);
mSectionsManager.initialize(this);
mSections = mSectionsManager.createSectionsForBuckets();
@@ -5339,7 +5328,7 @@
void onStatePostChange(boolean fromShadeLocked) {
boolean onKeyguard = onKeyguard();
- if (mHeadsUpAppearanceController != null) {
+ if (mHeadsUpAppearanceController != null && !StatusBarNoHunBehavior.isEnabled()) {
mHeadsUpAppearanceController.onStateChanged();
}
@@ -5402,7 +5391,6 @@
println(pw, "suppressChildrenMeasureLayout", mSuppressChildrenMeasureAndLayout);
println(pw, "scrollY", mAmbientState.getScrollY());
println(pw, "showShelfOnly", mShouldShowShelfOnly);
- println(pw, "isCurrentUserSetup", mIsCurrentUserSetup);
println(pw, "hideAmount", mAmbientState.getHideAmount());
println(pw, "ambientStateSwipingUp", mAmbientState.isSwipingUp());
println(pw, "maxDisplayedNotifications", mMaxDisplayedNotifications);
@@ -6792,10 +6780,6 @@
void onClearAll(@SelectedRows int selectedRows);
}
- interface FooterClearAllListener {
- void onClearAll();
- }
-
interface ClearAllAnimationListener {
void onAnimationEnd(
List<ExpandableNotificationRow> viewsToRemove, @SelectedRows int selectedRows);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
index 5d55022..8e12e08 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
@@ -16,15 +16,21 @@
package com.android.systemui.statusbar.notification.stack.ui.viewmodel
+import androidx.compose.runtime.getValue
import com.android.app.tracing.coroutines.launchTraced as launch
+import com.android.compose.animation.scene.ContentKey
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.systemui.dump.DumpManager
import com.android.systemui.flags.FeatureFlagsClassic
import com.android.systemui.flags.Flags
import com.android.systemui.lifecycle.ExclusiveActivatable
+import com.android.systemui.lifecycle.Hydrator
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import com.android.systemui.scene.shared.model.Overlays
+import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.statusbar.domain.interactor.RemoteInputInteractor
import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor
@@ -63,6 +69,24 @@
tag = "NotificationsPlaceholderViewModel",
) {
+ private val hydrator = Hydrator("NotificationsPlaceholderViewModel")
+
+ /** The content key to use for the notification shade. */
+ val notificationsShadeContentKey: ContentKey by
+ hydrator.hydratedStateOf(
+ traceName = "notificationsShadeContentKey",
+ initialValue = getNotificationsShadeContentKey(shadeInteractor.shadeMode.value),
+ source = shadeInteractor.shadeMode.map { getNotificationsShadeContentKey(it) },
+ )
+
+ /** The content key to use for the quick settings shade. */
+ val quickSettingsShadeContentKey: ContentKey by
+ hydrator.hydratedStateOf(
+ traceName = "quickSettingsShadeContentKey",
+ initialValue = getQuickSettingsShadeContentKey(shadeInteractor.shadeMode.value),
+ source = shadeInteractor.shadeMode.map { getQuickSettingsShadeContentKey(it) },
+ )
+
/** DEBUG: whether the placeholder should be made slightly visible for positional debugging. */
val isVisualDebuggingEnabled: Boolean = featureFlags.isEnabled(Flags.NSSL_DEBUG_LINES)
@@ -71,6 +95,8 @@
override suspend fun onActivated(): Nothing {
coroutineScope {
+ launch { hydrator.activate() }
+
launch {
shadeInteractor.isAnyExpanded
.filter { it }
@@ -79,8 +105,7 @@
launch {
sceneInteractor.transitionState
- .map { state -> state is ObservableTransitionState.Idle }
- .filter { it }
+ .filter { it is ObservableTransitionState.Idle }
.collect { headsUpNotificationInteractor.onTransitionIdle() }
}
}
@@ -163,6 +188,18 @@
interactor.setAccessibilityScrollEventConsumer(consumer)
}
+ private fun getNotificationsShadeContentKey(shadeMode: ShadeMode): ContentKey {
+ return if (shadeMode is ShadeMode.Dual) Overlays.NotificationsShade else Scenes.Shade
+ }
+
+ private fun getQuickSettingsShadeContentKey(shadeMode: ShadeMode): ContentKey {
+ return when (shadeMode) {
+ is ShadeMode.Single -> Scenes.QuickSettings
+ is ShadeMode.Split -> Scenes.Shade
+ is ShadeMode.Dual -> Overlays.QuickSettingsShade
+ }
+ }
+
@AssistedFactory
interface Factory {
fun create(): NotificationsPlaceholderViewModel
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
index f0455fc..c1d0226 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
@@ -49,9 +49,11 @@
import com.android.systemui.keyguard.ui.viewmodel.AodToGoneTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.AodToLockscreenTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.AodToOccludedTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.AodToPrimaryBouncerTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.DozingToGlanceableHubTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.DozingToLockscreenTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.DozingToOccludedTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.DozingToPrimaryBouncerTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.GlanceableHubToLockscreenTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.GoneToAodTransitionViewModel
@@ -131,9 +133,12 @@
private val aodToGoneTransitionViewModel: AodToGoneTransitionViewModel,
private val aodToLockscreenTransitionViewModel: AodToLockscreenTransitionViewModel,
private val aodToOccludedTransitionViewModel: AodToOccludedTransitionViewModel,
+ private val aodToPrimaryBouncerTransitionViewModel: AodToPrimaryBouncerTransitionViewModel,
dozingToGlanceableHubTransitionViewModel: DozingToGlanceableHubTransitionViewModel,
private val dozingToLockscreenTransitionViewModel: DozingToLockscreenTransitionViewModel,
private val dozingToOccludedTransitionViewModel: DozingToOccludedTransitionViewModel,
+ private val dozingToPrimaryBouncerTransitionViewModel:
+ DozingToPrimaryBouncerTransitionViewModel,
private val dreamingToLockscreenTransitionViewModel: DreamingToLockscreenTransitionViewModel,
private val glanceableHubToLockscreenTransitionViewModel:
GlanceableHubToLockscreenTransitionViewModel,
@@ -554,8 +559,10 @@
aodToGoneTransitionViewModel.notificationAlpha(viewState),
aodToLockscreenTransitionViewModel.notificationAlpha,
aodToOccludedTransitionViewModel.lockscreenAlpha(viewState),
+ aodToPrimaryBouncerTransitionViewModel.notificationAlpha,
dozingToLockscreenTransitionViewModel.lockscreenAlpha,
dozingToOccludedTransitionViewModel.lockscreenAlpha(viewState),
+ dozingToPrimaryBouncerTransitionViewModel.notificationAlpha,
dreamingToLockscreenTransitionViewModel.lockscreenAlpha,
goneToAodTransitionViewModel.notificationAlpha,
goneToDreamingTransitionViewModel.lockscreenAlpha,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
index 4751293..5a63c0c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
@@ -312,6 +312,25 @@
}
}
+ override fun postStartActivityDismissingKeyguard(
+ intent: Intent,
+ delay: Int,
+ animationController: ActivityTransitionAnimator.Controller?,
+ customMessage: String?,
+ userHandle: UserHandle?,
+ ) {
+ postOnUiThread(delay) {
+ activityStarterInternal.startActivityDismissingKeyguard(
+ intent = intent,
+ onlyProvisioned = true,
+ dismissShade = true,
+ animationController = animationController,
+ customMessage = customMessage,
+ userHandle = userHandle,
+ )
+ }
+ }
+
override fun dismissKeyguardThenExecute(
action: OnDismissAction,
cancel: Runnable?,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
index 53a2950..548ab83 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
@@ -39,6 +39,7 @@
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips;
import com.android.systemui.statusbar.core.StatusBarRootModernization;
+import com.android.systemui.statusbar.headsup.shared.StatusBarNoHunBehavior;
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
import com.android.systemui.statusbar.notification.SourceType;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -151,19 +152,21 @@
mOperatorNameViewOptional = operatorNameViewOptional;
mDarkIconDispatcher = darkIconDispatcher;
- mView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
- @Override
- public void onLayoutChange(View v, int left, int top, int right, int bottom,
- int oldLeft, int oldTop, int oldRight, int oldBottom) {
- if (shouldHeadsUpStatusBarBeVisible()) {
- updateTopEntry();
+ if (!StatusBarNoHunBehavior.isEnabled()) {
+ mView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
+ @Override
+ public void onLayoutChange(View v, int left, int top, int right, int bottom,
+ int oldLeft, int oldTop, int oldRight, int oldBottom) {
+ if (shouldHeadsUpStatusBarBeVisible()) {
+ updatePinnedStatus();
- // trigger scroller to notify the latest panel translation
- mStackScrollerController.requestLayout();
+ // trigger scroller to notify the latest panel translation
+ mStackScrollerController.requestLayout();
+ }
+ mView.removeOnLayoutChangeListener(this);
}
- mView.removeOnLayoutChangeListener(this);
- }
- });
+ });
+ }
mBypassController = bypassController;
mStatusBarStateController = stateController;
mPhoneStatusBarTransitions = phoneStatusBarTransitions;
@@ -175,13 +178,15 @@
@Override
protected void onViewAttached() {
mHeadsUpManager.addListener(this);
- mView.setOnDrawingRectChangedListener(this::updateIsolatedIconLocation);
- updateIsolatedIconLocation();
- mWakeUpCoordinator.addListener(this);
+ if (!StatusBarNoHunBehavior.isEnabled()) {
+ mView.setOnDrawingRectChangedListener(this::updateIsolatedIconLocation);
+ updateIsolatedIconLocation();
+ mDarkIconDispatcher.addDarkReceiver(this);
+ mWakeUpCoordinator.addListener(this);
+ }
getShadeHeadsUpTracker().addTrackingHeadsUpListener(mSetTrackingHeadsUp);
getShadeHeadsUpTracker().setHeadsUpAppearanceController(this);
mStackScrollerController.addOnExpandedHeightChangedListener(mSetExpandedHeight);
- mDarkIconDispatcher.addDarkReceiver(this);
}
private ShadeHeadsUpTracker getShadeHeadsUpTracker() {
@@ -191,22 +196,25 @@
@Override
protected void onViewDetached() {
mHeadsUpManager.removeListener(this);
- mView.setOnDrawingRectChangedListener(null);
- mHeadsUpNotificationIconInteractor.setIsolatedIconLocation(null);
- mWakeUpCoordinator.removeListener(this);
+ if (!StatusBarNoHunBehavior.isEnabled()) {
+ mView.setOnDrawingRectChangedListener(null);
+ mHeadsUpNotificationIconInteractor.setIsolatedIconLocation(null);
+ mDarkIconDispatcher.removeDarkReceiver(this);
+ mWakeUpCoordinator.removeListener(this);
+ }
getShadeHeadsUpTracker().removeTrackingHeadsUpListener(mSetTrackingHeadsUp);
getShadeHeadsUpTracker().setHeadsUpAppearanceController(null);
mStackScrollerController.removeOnExpandedHeightChangedListener(mSetExpandedHeight);
- mDarkIconDispatcher.removeDarkReceiver(this);
}
private void updateIsolatedIconLocation() {
+ StatusBarNoHunBehavior.assertInLegacyMode();
mHeadsUpNotificationIconInteractor.setIsolatedIconLocation(mView.getIconDrawingRect());
}
@Override
public void onHeadsUpPinned(NotificationEntry entry) {
- updateTopEntry();
+ updatePinnedStatus();
updateHeader(entry);
updateHeadsUpAndPulsingRoundness(entry);
}
@@ -217,7 +225,10 @@
mPhoneStatusBarTransitions.onHeadsUpStateChanged(isHeadsUp);
}
- private void updateTopEntry() {
+ private void updatePinnedStatus() {
+ if (StatusBarNoHunBehavior.isEnabled()) {
+ return;
+ }
NotificationEntry newEntry = null;
if (shouldHeadsUpStatusBarBeVisible()) {
newEntry = mHeadsUpManager.getTopEntry();
@@ -239,6 +250,7 @@
}
private static @Nullable String getIsolatedIconKey(NotificationEntry newEntry) {
+ StatusBarNoHunBehavior.assertInLegacyMode();
if (newEntry == null) {
return null;
}
@@ -259,6 +271,9 @@
}
private void setPinnedStatus(PinnedStatus pinnedStatus) {
+ if (StatusBarNoHunBehavior.isEnabled()) {
+ return;
+ }
if (mPinnedStatus != pinnedStatus) {
mPinnedStatus = pinnedStatus;
@@ -292,6 +307,7 @@
}
private void updateParentClipping(boolean shouldClip) {
+ StatusBarNoHunBehavior.assertInLegacyMode();
ViewClippingUtil.setClippingDeactivated(
mView, !shouldClip, mParentClippingParams);
}
@@ -319,6 +335,8 @@
*
*/
private void hide(View view, int endState, Runnable callback) {
+ StatusBarNoHunBehavior.assertInLegacyMode();
+
if (mAnimationsEnabled) {
CrossFadeHelper.fadeOut(view, CONTENT_FADE_DURATION /* duration */,
0 /* delay */, () -> {
@@ -336,6 +354,8 @@
}
private void show(View view) {
+ StatusBarNoHunBehavior.assertInLegacyMode();
+
if (mAnimationsEnabled) {
CrossFadeHelper.fadeIn(view, CONTENT_FADE_DURATION /* duration */,
CONTENT_FADE_DELAY /* delay */);
@@ -351,6 +371,9 @@
@VisibleForTesting
public PinnedStatus getPinnedStatus() {
+ if (StatusBarNoHunBehavior.isEnabled()) {
+ return PinnedStatus.NotPinned;
+ }
return mPinnedStatus;
}
@@ -375,6 +398,10 @@
*/
@Deprecated
public boolean shouldHeadsUpStatusBarBeVisible() {
+ if (StatusBarNoHunBehavior.isEnabled()) {
+ return false;
+ }
+
if (StatusBarNotifChips.isEnabled()) {
return canShowHeadsUp()
&& mHeadsUpManager.pinnedHeadsUpStatus() == PinnedStatus.PinnedBySystem;
@@ -388,7 +415,7 @@
@Override
public void onHeadsUpUnPinned(NotificationEntry entry) {
- updateTopEntry();
+ updatePinnedStatus();
updateHeader(entry);
updateHeadsUpAndPulsingRoundness(entry);
}
@@ -406,7 +433,7 @@
updateHeadsUpHeaders();
}
if (isExpanded() != oldIsExpanded) {
- updateTopEntry();
+ updatePinnedStatus();
}
}
@@ -476,15 +503,18 @@
@Override
public void onDarkChanged(ArrayList<Rect> areas, float darkIntensity, int tint) {
+ StatusBarNoHunBehavior.assertInLegacyMode();
mView.onDarkChanged(areas, darkIntensity, tint);
}
public void onStateChanged() {
- updateTopEntry();
+ StatusBarNoHunBehavior.assertInLegacyMode();
+ updatePinnedStatus();
}
@Override
public void onFullyHiddenChanged(boolean isFullyHidden) {
- updateTopEntry();
+ StatusBarNoHunBehavior.assertInLegacyMode();
+ updatePinnedStatus();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
index c396512..1a97ab6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
@@ -41,6 +41,7 @@
import com.android.settingslib.Utils;
import com.android.systemui.res.R;
import com.android.systemui.statusbar.StatusBarIconView;
+import com.android.systemui.statusbar.headsup.shared.StatusBarNoHunBehavior;
import com.android.systemui.statusbar.notification.stack.AnimationFilter;
import com.android.systemui.statusbar.notification.stack.AnimationProperties;
import com.android.systemui.statusbar.notification.stack.ViewState;
@@ -163,12 +164,12 @@
private IconState mFirstVisibleIconState;
private float mVisualOverflowStart;
private boolean mIsShowingOverflowDot;
- private StatusBarIconView mIsolatedIcon;
- private Rect mIsolatedIconLocation;
+ @Nullable private StatusBarIconView mIsolatedIcon;
+ @Nullable private Rect mIsolatedIconLocation;
private final int[] mAbsolutePosition = new int[2];
- private View mIsolatedIconForAnimation;
+ @Nullable private View mIsolatedIconForAnimation;
private int mThemedTextColorPrimary;
- private Runnable mIsolatedIconAnimationEndRunnable;
+ @Nullable private Runnable mIsolatedIconAnimationEndRunnable;
private boolean mUseIncreasedIconScale;
public NotificationIconContainer(Context context, AttributeSet attrs) {
@@ -379,6 +380,9 @@
if (areAnimationsEnabled(icon) && !isReplacingIcon) {
addTransientView(icon, 0);
boolean isIsolatedIcon = child == mIsolatedIcon;
+ if (StatusBarNoHunBehavior.isEnabled()) {
+ isIsolatedIcon = false;
+ }
icon.setVisibleState(StatusBarIconView.STATE_HIDDEN, true /* animate */,
() -> removeTransientView(icon),
isIsolatedIcon ? CONTENT_FADE_DURATION : 0);
@@ -539,7 +543,7 @@
iconState.setXTranslation(getRtlIconTranslationX(iconState, view));
}
}
- if (mIsolatedIcon != null) {
+ if (!StatusBarNoHunBehavior.isEnabled() && mIsolatedIcon != null) {
IconState iconState = mIconStates.get(mIsolatedIcon);
if (iconState != null) {
// Most of the time the icon isn't yet added when this is called but only happening
@@ -685,17 +689,20 @@
public void showIconIsolatedAnimated(StatusBarIconView icon,
@Nullable Runnable onAnimationEnd) {
+ StatusBarNoHunBehavior.assertInLegacyMode();
mIsolatedIconForAnimation = icon != null ? icon : mIsolatedIcon;
mIsolatedIconAnimationEndRunnable = onAnimationEnd;
showIconIsolated(icon);
}
public void showIconIsolated(StatusBarIconView icon) {
+ StatusBarNoHunBehavior.assertInLegacyMode();
mIsolatedIcon = icon;
updateState();
}
public void setIsolatedIconLocation(Rect isolatedIconLocation, boolean requireUpdate) {
+ StatusBarNoHunBehavior.assertInLegacyMode();
mIsolatedIconLocation = isolatedIconLocation;
if (requireUpdate) {
updateState();
@@ -794,7 +801,7 @@
animationProperties.setDuration(CANNED_ANIMATION_DURATION);
animate = true;
}
- if (mIsolatedIconForAnimation != null) {
+ if (!StatusBarNoHunBehavior.isEnabled() && mIsolatedIconForAnimation != null) {
if (view == mIsolatedIconForAnimation) {
animationProperties = UNISOLATION_PROPERTY;
animationProperties.setDelay(
@@ -843,6 +850,7 @@
@Nullable
private Consumer<Property> getEndAction() {
+ if (StatusBarNoHunBehavior.isEnabled()) return null;
if (mIsolatedIconAnimationEndRunnable == null) return null;
final Runnable endRunnable = mIsolatedIconAnimationEndRunnable;
return prop -> {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index d43fed0..2cd8eaf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -31,7 +31,6 @@
import android.annotation.IntDef;
import android.graphics.Color;
import android.os.Handler;
-import android.os.Trace;
import android.util.Log;
import android.util.MathUtils;
import android.util.Pair;
@@ -53,6 +52,7 @@
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.systemui.Dumpable;
+import com.android.systemui.Flags;
import com.android.systemui.animation.ShadeInterpolation;
import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants;
import com.android.systemui.dagger.SysUISingleton;
@@ -82,6 +82,9 @@
import com.android.systemui.util.wakelock.DelayedWakeLock;
import com.android.systemui.util.wakelock.WakeLock;
+import kotlinx.coroutines.CoroutineDispatcher;
+import kotlinx.coroutines.ExperimentalCoroutinesApi;
+
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -90,9 +93,6 @@
import javax.inject.Inject;
-import kotlinx.coroutines.CoroutineDispatcher;
-import kotlinx.coroutines.ExperimentalCoroutinesApi;
-
/**
* Controls both the scrim behind the notifications and in front of the notifications (when a
* security method gets shown).
@@ -226,6 +226,8 @@
private float mAdditionalScrimBehindAlphaKeyguard = 0f;
// Combined scrim behind keyguard alpha of default scrim + additional scrim
private float mScrimBehindAlphaKeyguard = KEYGUARD_SCRIM_ALPHA;
+
+ static final float TRANSPARENT_BOUNCER_SCRIM_ALPHA = 0.54f;
private final float mDefaultScrimAlpha;
private float mRawPanelExpansionFraction;
@@ -340,7 +342,10 @@
LargeScreenShadeInterpolator largeScreenShadeInterpolator) {
mScrimStateListener = lightBarController::setScrimState;
mLargeScreenShadeInterpolator = largeScreenShadeInterpolator;
- mDefaultScrimAlpha = BUSY_SCRIM_ALPHA;
+ // All scrims default alpha need to match bouncer background alpha to make sure the
+ // transitions involving the bouncer are smooth and don't overshoot the bouncer alpha.
+ mDefaultScrimAlpha =
+ Flags.bouncerUiRevamp() ? TRANSPARENT_BOUNCER_SCRIM_ALPHA : BUSY_SCRIM_ALPHA;
mKeyguardStateController = keyguardStateController;
mDarkenWhileDragging = !mKeyguardStateController.canDismissLockScreen();
@@ -974,8 +979,6 @@
mBehindTint,
interpolatedFraction);
}
- } else if (mState == ScrimState.AUTH_SCRIMMED_SHADE) {
- mNotificationsAlpha = (float) Math.pow(getInterpolatedFraction(), 0.8f);
} else if (mState == ScrimState.KEYGUARD || mState == ScrimState.SHADE_LOCKED
|| mState == ScrimState.PULSING || mState == ScrimState.GLANCEABLE_HUB) {
Pair<Integer, Float> result = calculateBackStateForState(mState);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
index 8dcb663..1493729 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
@@ -16,17 +16,24 @@
package com.android.systemui.statusbar.phone;
+import static com.android.systemui.statusbar.phone.ScrimController.BUSY_SCRIM_ALPHA;
+import static com.android.systemui.statusbar.phone.ScrimController.TRANSPARENT_BOUNCER_SCRIM_ALPHA;
+
import android.graphics.Color;
import com.android.app.tracing.coroutines.TrackTracer;
+import com.android.systemui.Flags;
import com.android.systemui.dock.DockManager;
import com.android.systemui.res.R;
import com.android.systemui.scrim.ScrimView;
import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
+import kotlinx.coroutines.ExperimentalCoroutinesApi;
+
/**
* Possible states of the ScrimController state machine.
*/
+@ExperimentalCoroutinesApi
public enum ScrimState {
/**
@@ -92,40 +99,19 @@
}
},
- AUTH_SCRIMMED_SHADE {
- @Override
- public void prepare(ScrimState previousState) {
- // notif scrim alpha values are determined by ScrimController#applyState
- // based on the shade expansion
-
- mFrontTint = mBackgroundColor;
- mFrontAlpha = .66f;
-
- mBehindTint = mBackgroundColor;
- mBehindAlpha = 1f;
- }
- },
-
- AUTH_SCRIMMED {
- @Override
- public void prepare(ScrimState previousState) {
- mNotifTint = previousState.mNotifTint;
- mNotifAlpha = previousState.mNotifAlpha;
-
- mBehindTint = previousState.mBehindTint;
- mBehindAlpha = previousState.mBehindAlpha;
-
- mFrontTint = mBackgroundColor;
- mFrontAlpha = .66f;
- }
- },
-
/**
* Showing password challenge on the keyguard.
*/
BOUNCER {
@Override
public void prepare(ScrimState previousState) {
+ if (Flags.bouncerUiRevamp()) {
+ mBehindAlpha = mClipQsScrim ? 0.0f : TRANSPARENT_BOUNCER_SCRIM_ALPHA;
+ mNotifAlpha = mClipQsScrim ? TRANSPARENT_BOUNCER_SCRIM_ALPHA : 0;
+ mBehindTint = mNotifTint = mSurfaceColor;
+ mFrontAlpha = 0f;
+ return;
+ }
mBehindAlpha = mClipQsScrim ? 1 : mDefaultScrimAlpha;
mBehindTint = mClipQsScrim ? mBackgroundColor : mSurfaceColor;
mNotifAlpha = mClipQsScrim ? mDefaultScrimAlpha : 0;
@@ -136,6 +122,10 @@
@Override
public void setSurfaceColor(int surfaceColor) {
super.setSurfaceColor(surfaceColor);
+ if (Flags.bouncerUiRevamp()) {
+ mBehindTint = mNotifTint = mSurfaceColor;
+ return;
+ }
if (!mClipQsScrim) {
mBehindTint = mSurfaceColor;
}
@@ -146,15 +136,38 @@
* Showing password challenge on top of a FLAG_SHOW_WHEN_LOCKED activity.
*/
BOUNCER_SCRIMMED {
+ @ExperimentalCoroutinesApi
@Override
public void prepare(ScrimState previousState) {
+ if (Flags.bouncerUiRevamp()) {
+ mBehindAlpha = 0f;
+ mFrontAlpha = TRANSPARENT_BOUNCER_SCRIM_ALPHA;
+ mFrontTint = mSurfaceColor;
+ return;
+ }
mBehindAlpha = 0;
mFrontAlpha = mDefaultScrimAlpha;
}
+
+ @Override
+ public boolean shouldBlendWithMainColor() {
+ return !Flags.bouncerUiRevamp();
+ }
},
SHADE_LOCKED {
@Override
+ public void setDefaultScrimAlpha(float defaultScrimAlpha) {
+ super.setDefaultScrimAlpha(defaultScrimAlpha);
+ if (!Flags.notificationShadeBlur()) {
+ // Temporary change that prevents the shade from being semi-transparent when
+ // bouncer blur is enabled but notification shade blur is not enabled. This is
+ // required to perf test these two flags independently.
+ mDefaultScrimAlpha = BUSY_SCRIM_ALPHA;
+ }
+ }
+
+ @Override
public void prepare(ScrimState previousState) {
mBehindAlpha = mClipQsScrim ? 1 : mDefaultScrimAlpha;
mNotifAlpha = 1f;
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 c47ed17..fcc3af6 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 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/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
index 1f1be26..c541cff 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
@@ -65,6 +65,7 @@
import com.android.systemui.statusbar.disableflags.DisableFlagsLogger;
import com.android.systemui.statusbar.events.SystemStatusAnimationCallback;
import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
+import com.android.systemui.statusbar.headsup.shared.StatusBarNoHunBehavior;
import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerStatusBarViewBinder;
import com.android.systemui.statusbar.phone.NotificationIconContainer;
import com.android.systemui.statusbar.phone.PhoneStatusBarView;
@@ -676,6 +677,11 @@
boolean headsUpVisible = mHomeStatusBarComponent
.getHeadsUpAppearanceController()
.shouldHeadsUpStatusBarBeVisible();
+ if (StatusBarNoHunBehavior.isEnabled()) {
+ // With this flag enabled, we have no custom HUN behavior, so just always consider it
+ // to be not visible.
+ headsUpVisible = false;
+ }
if (SceneContainerFlag.isEnabled()) {
// With the scene container, only use the value calculated by the view model to
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 4eb69ba..a29934fa 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.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 @@
notificationIconView = currentInfo.notificationIconView,
intent = currentInfo.intent,
notificationKey = currentInfo.key,
+ promotedContent = currentInfo.promotedContent,
)
} else {
return OngoingCallModel.NoCall
@@ -215,6 +217,7 @@
notifModel.statusBarChipIconView,
notifModel.contentIntent,
notifModel.uid,
+ notifModel.promotedContent,
isOngoing = true,
statusBarSwipedAway = callNotificationInfo?.statusBarSwipedAway ?: false,
)
@@ -334,6 +337,11 @@
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 2ab2b68..9f8b455 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 @@
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 2bfbf48..99141f5 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 @@
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 1a5dcc1..7d00e9d 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 @@
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 @@
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 @@
* @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 b320379..dacb859 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 @@
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/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt
index 8daa803..7e76d77 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt
@@ -41,8 +41,8 @@
import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor
import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment
import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization
+import com.android.systemui.statusbar.pipeline.shared.ui.model.VisibilityModel
import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.HomeStatusBarViewModel
-import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.HomeStatusBarViewModel.VisibilityModel
import javax.inject.Inject
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt
index b1cc208..9c1171f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt
@@ -57,6 +57,7 @@
import com.android.systemui.statusbar.pipeline.shared.ui.binder.HomeStatusBarIconBlockListBinder
import com.android.systemui.statusbar.pipeline.shared.ui.binder.HomeStatusBarViewBinder
import com.android.systemui.statusbar.pipeline.shared.ui.binder.StatusBarVisibilityChangeListener
+import com.android.systemui.statusbar.pipeline.shared.ui.model.VisibilityModel
import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.HomeStatusBarViewModel
import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.HomeStatusBarViewModel.HomeStatusBarViewModelFactory
import javax.inject.Inject
@@ -304,11 +305,7 @@
fun Disambiguation(viewModel: HomeStatusBarViewModel) {
val clockVisibilityModel =
viewModel.isClockVisible.collectAsStateWithLifecycle(
- initialValue =
- HomeStatusBarViewModel.VisibilityModel(
- visibility = View.GONE,
- shouldAnimateChange = false,
- )
+ initialValue = VisibilityModel(visibility = View.GONE, shouldAnimateChange = false)
)
if (clockVisibilityModel.value.visibility == View.VISIBLE) {
Box(modifier = Modifier.fillMaxSize().alpha(0.5f), contentAlignment = Alignment.Center) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/model/SystemInfoCombinedVisibilityModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/model/SystemInfoCombinedVisibilityModel.kt
new file mode 100644
index 0000000..e272252
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/model/SystemInfoCombinedVisibilityModel.kt
@@ -0,0 +1,44 @@
+/*
+ * 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.statusbar.pipeline.shared.ui.model
+
+import com.android.systemui.log.table.Diffable
+import com.android.systemui.log.table.TableRowLogger
+import com.android.systemui.statusbar.events.shared.model.SystemEventAnimationState
+
+/** The combined visibility + animation state for the system info status bar area */
+data class SystemInfoCombinedVisibilityModel(
+ val baseVisibility: VisibilityModel,
+ val animationState: SystemEventAnimationState,
+) : Diffable<SystemInfoCombinedVisibilityModel> {
+ override fun logDiffs(prevVal: SystemInfoCombinedVisibilityModel, row: TableRowLogger) {
+ if (animationState != prevVal.animationState) {
+ row.logChange(COL_ANIM, animationState.name)
+ }
+
+ baseVisibility.logDiffs(prevVal.baseVisibility, row)
+ }
+
+ override fun logFull(row: TableRowLogger) {
+ row.logChange(COL_ANIM, animationState.name)
+ baseVisibility.logFull(row)
+ }
+
+ companion object {
+ const val COL_ANIM = "animState"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/model/VisibilityModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/model/VisibilityModel.kt
new file mode 100644
index 0000000..7b39ada
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/model/VisibilityModel.kt
@@ -0,0 +1,49 @@
+/*
+ * 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.statusbar.pipeline.shared.ui.model
+
+import android.view.View
+import com.android.systemui.log.table.Diffable
+import com.android.systemui.log.table.TableRowLogger
+import com.android.systemui.util.visibilityString
+
+/** Models the current visibility for a specific child view of status bar. */
+data class VisibilityModel(
+ @View.Visibility val visibility: Int,
+ /** True if a visibility change should be animated. */
+ val shouldAnimateChange: Boolean,
+) : Diffable<VisibilityModel> {
+ override fun logDiffs(prevVal: VisibilityModel, row: TableRowLogger) {
+ if (visibility != prevVal.visibility) {
+ row.logChange(COL_VIS, visibilityString(visibility))
+ }
+
+ if (shouldAnimateChange != prevVal.shouldAnimateChange) {
+ row.logChange(COL_ANIMATE, shouldAnimateChange)
+ }
+ }
+
+ override fun logFull(row: TableRowLogger) {
+ row.logChange(COL_VIS, visibilityString(visibility))
+ row.logChange(COL_ANIMATE, shouldAnimateChange)
+ }
+
+ companion object {
+ const val COL_VIS = "vis"
+ const val COL_ANIMATE = "animate"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt
index 6ff4354..5acedf1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt
@@ -28,6 +28,8 @@
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.TransitionState
+import com.android.systemui.log.table.TableLogBufferFactory
+import com.android.systemui.log.table.logDiffsForTable
import com.android.systemui.plugins.DarkIconDispatcher
import com.android.systemui.scene.domain.interactor.SceneContainerOcclusionInteractor
import com.android.systemui.scene.domain.interactor.SceneInteractor
@@ -39,10 +41,10 @@
import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipsViewModel
import com.android.systemui.statusbar.events.domain.interactor.SystemStatusEventAnimationInteractor
-import com.android.systemui.statusbar.events.shared.model.SystemEventAnimationState
import com.android.systemui.statusbar.events.shared.model.SystemEventAnimationState.Idle
import com.android.systemui.statusbar.featurepods.popups.shared.model.PopupChipModel
import com.android.systemui.statusbar.featurepods.popups.ui.viewmodel.StatusBarPopupChipsViewModel
+import com.android.systemui.statusbar.headsup.shared.StatusBarNoHunBehavior
import com.android.systemui.statusbar.layout.ui.viewmodel.StatusBarContentInsetsViewModelStore
import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationInteractor
@@ -52,7 +54,8 @@
import com.android.systemui.statusbar.phone.domain.interactor.LightsOutInteractor
import com.android.systemui.statusbar.pipeline.shared.domain.interactor.HomeStatusBarIconBlockListInteractor
import com.android.systemui.statusbar.pipeline.shared.domain.interactor.HomeStatusBarInteractor
-import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.HomeStatusBarViewModel.VisibilityModel
+import com.android.systemui.statusbar.pipeline.shared.ui.model.SystemInfoCombinedVisibilityModel
+import com.android.systemui.statusbar.pipeline.shared.ui.model.VisibilityModel
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
@@ -154,19 +157,6 @@
*/
val areaTint: Flow<StatusBarTintColor>
- /** Models the current visibility for a specific child view of status bar. */
- data class VisibilityModel(
- @View.Visibility val visibility: Int,
- /** True if a visibility change should be animated. */
- val shouldAnimateChange: Boolean,
- )
-
- /** The combined visibility + animation state for the system info status bar area */
- data class SystemInfoCombinedVisibilityModel(
- val baseVisibility: VisibilityModel,
- val animationState: SystemEventAnimationState,
- )
-
/** Interface for the assisted factory, to allow for providing a fake in tests */
interface HomeStatusBarViewModelFactory {
fun create(displayId: Int): HomeStatusBarViewModel
@@ -177,6 +167,7 @@
@AssistedInject
constructor(
@Assisted thisDisplayId: Int,
+ tableLoggerFactory: TableLogBufferFactory,
homeStatusBarInteractor: HomeStatusBarInteractor,
homeStatusBarIconBlockListInteractor: HomeStatusBarIconBlockListInteractor,
lightsOutInteractor: LightsOutInteractor,
@@ -195,9 +186,19 @@
statusBarContentInsetsViewModelStore: StatusBarContentInsetsViewModelStore,
@Application coroutineScope: CoroutineScope,
) : HomeStatusBarViewModel {
+
+ val tableLogger = tableLoggerFactory.getOrCreate(tableLogBufferName(thisDisplayId), 200)
+
override val isTransitioningFromLockscreenToOccluded: StateFlow<Boolean> =
keyguardTransitionInteractor
.isInTransition(Edge.create(from = LOCKSCREEN, to = OCCLUDED))
+ .distinctUntilChanged()
+ .logDiffsForTable(
+ tableLogBuffer = tableLogger,
+ columnPrefix = "",
+ columnName = COL_LOCK_TO_OCCLUDED,
+ initialValue = false,
+ )
.stateIn(coroutineScope, SharingStarted.WhileSubscribed(), initialValue = false)
override val transitionFromLockscreenToDreamStartedEvent: Flow<Unit> =
@@ -224,20 +225,33 @@
// which lives elsewhere.)
currentScene == Scenes.Gone || isOccluded
}
+ .distinctUntilChanged()
+ .logDiffsForTable(
+ tableLogBuffer = tableLogger,
+ columnPrefix = "",
+ columnName = COL_ALLOWED_BY_SCENE,
+ initialValue = false,
+ )
.stateIn(coroutineScope, SharingStarted.WhileSubscribed(), initialValue = false)
override val areNotificationsLightsOut: Flow<Boolean> =
if (NotificationsLiveDataStoreRefactor.isUnexpectedlyInLegacyMode()) {
- emptyFlow()
- } else {
- combine(
- notificationsInteractor.areAnyNotificationsPresent,
- lightsOutInteractor.isLowProfile(thisDisplayId) ?: flowOf(false),
- ) { hasNotifications, isLowProfile ->
- hasNotifications && isLowProfile
- }
- .distinctUntilChanged()
- }
+ emptyFlow()
+ } else {
+ combine(
+ notificationsInteractor.areAnyNotificationsPresent,
+ lightsOutInteractor.isLowProfile(thisDisplayId) ?: flowOf(false),
+ ) { hasNotifications, isLowProfile ->
+ hasNotifications && isLowProfile
+ }
+ .distinctUntilChanged()
+ }
+ .logDiffsForTable(
+ tableLogBuffer = tableLogger,
+ columnPrefix = "",
+ columnName = COL_NOTIF_LIGHTS_OUT,
+ initialValue = false,
+ )
override val areaTint: Flow<StatusBarTintColor> =
darkIconInteractor
@@ -276,19 +290,26 @@
override val shouldHomeStatusBarBeVisible =
combine(
- isHomeStatusBarAllowed,
- keyguardInteractor.isSecureCameraActive,
- headsUpNotificationInteractor.statusBarHeadsUpStatus,
- ) { isHomeStatusBarAllowed, isSecureCameraActive, headsUpState ->
- // When launching the camera over the lockscreen, the status icons would typically
- // become visible momentarily before animating out, since we're not yet aware that the
- // launching camera activity is fullscreen. Even once the activity finishes launching,
- // it takes a short time before WM decides that the top app wants to hide the icons and
- // tells us to hide them.
- // To ensure that this high-visibility animation is smooth, keep the icons hidden during
- // a camera launch. See b/257292822.
- headsUpState.isPinned || (isHomeStatusBarAllowed && !isSecureCameraActive)
- }
+ isHomeStatusBarAllowed,
+ keyguardInteractor.isSecureCameraActive,
+ headsUpNotificationInteractor.statusBarHeadsUpStatus,
+ ) { isHomeStatusBarAllowed, isSecureCameraActive, headsUpState ->
+ // When launching the camera over the lockscreen, the status icons would typically
+ // become visible momentarily before animating out, since we're not yet aware that
+ // the launching camera activity is fullscreen. Even once the activity finishes
+ // launching, it takes a short time before WM decides that the top app wants to hide
+ // the icons and tells us to hide them. To ensure that this high-visibility
+ // animation is smooth, keep the icons hidden during a camera launch. See
+ // b/257292822.
+ headsUpState.isPinned || (isHomeStatusBarAllowed && !isSecureCameraActive)
+ }
+ .distinctUntilChanged()
+ .logDiffsForTable(
+ tableLogBuffer = tableLogger,
+ columnPrefix = "",
+ columnName = COL_VISIBLE,
+ initialValue = false,
+ )
private val isAnyChipVisible =
if (StatusBarNotifChips.isEnabled) {
@@ -297,54 +318,88 @@
primaryOngoingActivityChip.map { it is OngoingActivityChipModel.Shown }
}
+ /**
+ * True if we need to hide the usual start side content in order to show the heads up
+ * notification info.
+ */
+ private val hideStartSideContentForHeadsUp: Flow<Boolean> =
+ if (StatusBarNoHunBehavior.isEnabled) {
+ flowOf(false)
+ } else {
+ headsUpNotificationInteractor.statusBarHeadsUpStatus.map {
+ it == PinnedStatus.PinnedBySystem
+ }
+ }
+
override val shouldShowOperatorNameView: Flow<Boolean> =
combine(
- shouldHomeStatusBarBeVisible,
- headsUpNotificationInteractor.statusBarHeadsUpStatus,
- homeStatusBarInteractor.visibilityViaDisableFlags,
- homeStatusBarInteractor.shouldShowOperatorName,
- ) { shouldStatusBarBeVisible, headsUpStatus, visibilityViaDisableFlags, shouldShowOperator
- ->
- val hideForHeadsUp = headsUpStatus == PinnedStatus.PinnedBySystem
- shouldStatusBarBeVisible &&
- !hideForHeadsUp &&
- visibilityViaDisableFlags.isSystemInfoAllowed &&
- shouldShowOperator
- }
+ shouldHomeStatusBarBeVisible,
+ hideStartSideContentForHeadsUp,
+ homeStatusBarInteractor.visibilityViaDisableFlags,
+ homeStatusBarInteractor.shouldShowOperatorName,
+ ) {
+ shouldStatusBarBeVisible,
+ hideStartSideContentForHeadsUp,
+ visibilityViaDisableFlags,
+ shouldShowOperator ->
+ shouldStatusBarBeVisible &&
+ !hideStartSideContentForHeadsUp &&
+ visibilityViaDisableFlags.isSystemInfoAllowed &&
+ shouldShowOperator
+ }
+ .distinctUntilChanged()
+ .logDiffsForTable(
+ tableLogBuffer = tableLogger,
+ columnPrefix = "",
+ columnName = COL_SHOW_OPERATOR_NAME,
+ initialValue = false,
+ )
override val isClockVisible: Flow<VisibilityModel> =
combine(
- shouldHomeStatusBarBeVisible,
- headsUpNotificationInteractor.statusBarHeadsUpStatus,
- homeStatusBarInteractor.visibilityViaDisableFlags,
- ) { shouldStatusBarBeVisible, headsUpStatus, visibilityViaDisableFlags ->
- val hideClockForHeadsUp = headsUpStatus == PinnedStatus.PinnedBySystem
- val showClock =
- shouldStatusBarBeVisible &&
- visibilityViaDisableFlags.isClockAllowed &&
- !hideClockForHeadsUp
- // Always use View.INVISIBLE here, so that animations work
- VisibilityModel(showClock.toVisibleOrInvisible(), visibilityViaDisableFlags.animate)
- }
+ shouldHomeStatusBarBeVisible,
+ hideStartSideContentForHeadsUp,
+ homeStatusBarInteractor.visibilityViaDisableFlags,
+ ) { shouldStatusBarBeVisible, hideStartSideContentForHeadsUp, visibilityViaDisableFlags
+ ->
+ val showClock =
+ shouldStatusBarBeVisible &&
+ visibilityViaDisableFlags.isClockAllowed &&
+ !hideStartSideContentForHeadsUp
+ // Always use View.INVISIBLE here, so that animations work
+ VisibilityModel(showClock.toVisibleOrInvisible(), visibilityViaDisableFlags.animate)
+ }
+ .distinctUntilChanged()
+ .logDiffsForTable(
+ tableLogBuffer = tableLogger,
+ columnPrefix = COL_PREFIX_CLOCK,
+ initialValue = VisibilityModel(false.toVisibleOrInvisible(), false),
+ )
override val isNotificationIconContainerVisible: Flow<VisibilityModel> =
combine(
- shouldHomeStatusBarBeVisible,
- isAnyChipVisible,
- homeStatusBarInteractor.visibilityViaDisableFlags,
- ) { shouldStatusBarBeVisible, anyChipVisible, visibilityViaDisableFlags ->
- val showNotificationIconContainer =
- if (anyChipVisible) {
- false
- } else {
- shouldStatusBarBeVisible &&
- visibilityViaDisableFlags.areNotificationIconsAllowed
- }
- VisibilityModel(
- showNotificationIconContainer.toVisibleOrGone(),
- visibilityViaDisableFlags.animate,
+ shouldHomeStatusBarBeVisible,
+ isAnyChipVisible,
+ homeStatusBarInteractor.visibilityViaDisableFlags,
+ ) { shouldStatusBarBeVisible, anyChipVisible, visibilityViaDisableFlags ->
+ val showNotificationIconContainer =
+ if (anyChipVisible) {
+ false
+ } else {
+ shouldStatusBarBeVisible &&
+ visibilityViaDisableFlags.areNotificationIconsAllowed
+ }
+ VisibilityModel(
+ showNotificationIconContainer.toVisibleOrGone(),
+ visibilityViaDisableFlags.animate,
+ )
+ }
+ .distinctUntilChanged()
+ .logDiffsForTable(
+ tableLogBuffer = tableLogger,
+ columnPrefix = COL_PREFIX_NOTIF_CONTAINER,
+ initialValue = VisibilityModel(false.toVisibleOrInvisible(), false),
)
- }
private val isSystemInfoVisible =
combine(shouldHomeStatusBarBeVisible, homeStatusBarInteractor.visibilityViaDisableFlags) {
@@ -357,18 +412,19 @@
override val systemInfoCombinedVis =
combine(isSystemInfoVisible, animations.animationState) { sysInfoVisible, animationState ->
- HomeStatusBarViewModel.SystemInfoCombinedVisibilityModel(
- sysInfoVisible,
- animationState,
- )
+ SystemInfoCombinedVisibilityModel(sysInfoVisible, animationState)
}
+ .distinctUntilChanged()
+ .logDiffsForTable(
+ tableLogBuffer = tableLogger,
+ columnPrefix = COL_PREFIX_SYSTEM_INFO,
+ initialValue =
+ SystemInfoCombinedVisibilityModel(VisibilityModel(View.VISIBLE, false), Idle),
+ )
.stateIn(
coroutineScope,
SharingStarted.WhileSubscribed(),
- HomeStatusBarViewModel.SystemInfoCombinedVisibilityModel(
- VisibilityModel(View.VISIBLE, false),
- Idle,
- ),
+ SystemInfoCombinedVisibilityModel(VisibilityModel(View.VISIBLE, false), Idle),
)
override val iconBlockList: Flow<List<String>> =
@@ -393,6 +449,19 @@
HomeStatusBarViewModel.HomeStatusBarViewModelFactory {
override fun create(displayId: Int): HomeStatusBarViewModelImpl
}
+
+ companion object {
+ private const val COL_LOCK_TO_OCCLUDED = "Lock->Occluded"
+ private const val COL_ALLOWED_BY_SCENE = "allowedByScene"
+ private const val COL_NOTIF_LIGHTS_OUT = "notifLightsOut"
+ private const val COL_SHOW_OPERATOR_NAME = "showOperatorName"
+ private const val COL_VISIBLE = "visible"
+ private const val COL_PREFIX_CLOCK = "clock"
+ private const val COL_PREFIX_NOTIF_CONTAINER = "notifContainer"
+ private const val COL_PREFIX_SYSTEM_INFO = "systemInfo"
+
+ fun tableLogBufferName(displayId: Int) = "HomeStatusBarViewModel[$displayId]"
+ }
}
/** Lookup the color for a given view in the status bar */
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 cb26679..520c563 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 @@
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 9ff0d18..db5f130 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.theme.PlatformTheme
import com.android.internal.annotations.VisibleForTesting
import com.android.internal.jank.InteractionJankMonitor
+import com.android.settingslib.notification.modes.EnableDndDialogFactory
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.qualifiers.Main
import com.android.systemui.dialog.ui.composable.AlertDialogContent
import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.qs.tiles.dialog.QSEnableDndDialogMetricsLogger
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 @@
class ModesDialogDelegate
@Inject
constructor(
+ val context: Context,
private val sysuiDialogFactory: SystemUIDialogFactory,
private val dialogTransitionAnimator: DialogTransitionAnimator,
private val activityStarter: ActivityStarter,
@@ -72,6 +77,7 @@
) : SystemUIDialog.Delegate {
// NOTE: This should only be accessed/written from the main thread.
@VisibleForTesting var currentDialog: ComponentSystemUIDialog? = null
+ private val dndDurationDialogLogger by lazy { QSEnableDndDialogMetricsLogger(context) }
override fun createDialog(): SystemUIDialog {
Assert.isMainThread()
@@ -195,6 +201,26 @@
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 =
+ EnableDndDialogFactory(
+ context,
+ R.style.Theme_SystemUI_Dialog,
+ /* cancelIsNeutral= */ true,
+ dndDurationDialogLogger,
+ )
+ .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 1c13a83..07f1c34 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 @@
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 @@
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 @@
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/statusbar/ui/viewmodel/KeyguardStatusBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModel.kt
index a98a9e0..12ef68d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModel.kt
@@ -24,6 +24,7 @@
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.statusbar.domain.interactor.KeyguardStatusBarInteractor
+import com.android.systemui.statusbar.headsup.shared.StatusBarNoHunBehavior
import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationInteractor
import com.android.systemui.statusbar.policy.BatteryController
import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback
@@ -59,7 +60,7 @@
) {
private val showingHeadsUpStatusBar: Flow<Boolean> =
- if (SceneContainerFlag.isEnabled) {
+ if (SceneContainerFlag.isEnabled && !StatusBarNoHunBehavior.isEnabled) {
headsUpNotificationInteractor.statusBarHeadsUpStatus.map { it.isPinned }
} else {
flowOf(false)
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 c43f31b..a2125c8 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.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 @@
}
@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 0000000..3bb0dd7
--- /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 c8a5840..69b7e89 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 @@
onBackTutorialClicked: () -> Unit,
onHomeTutorialClicked: () -> Unit,
onRecentAppsTutorialClicked: () -> Unit,
+ onSwitchAppsTutorialClicked: () -> Unit,
onDoneButtonClicked: () -> Unit,
lastSelectedScreen: Screen,
) {
@@ -86,6 +87,7 @@
onBackTutorialClicked = onBackTutorialClicked,
onHomeTutorialClicked = onHomeTutorialClicked,
onRecentAppsTutorialClicked = onRecentAppsTutorialClicked,
+ onSwitchAppsTutorialClicked = onSwitchAppsTutorialClicked,
modifier = Modifier.weight(1f).padding(60.dp),
lastSelectedScreen,
)
@@ -95,6 +97,7 @@
onBackTutorialClicked = onBackTutorialClicked,
onHomeTutorialClicked = onHomeTutorialClicked,
onRecentAppsTutorialClicked = onRecentAppsTutorialClicked,
+ onSwitchAppsTutorialClicked = onSwitchAppsTutorialClicked,
modifier = Modifier.weight(1f).padding(60.dp),
lastSelectedScreen,
)
@@ -113,6 +116,7 @@
onBackTutorialClicked: () -> Unit,
onHomeTutorialClicked: () -> Unit,
onRecentAppsTutorialClicked: () -> Unit,
+ onSwitchAppsTutorialClicked: () -> Unit,
modifier: Modifier = Modifier,
lastSelectedScreen: Screen,
) {
@@ -121,10 +125,11 @@
verticalAlignment = Alignment.CenterVertically,
modifier = modifier,
) {
- ThreeTutorialButtons(
+ FourTutorialButtons(
onBackTutorialClicked,
onHomeTutorialClicked,
onRecentAppsTutorialClicked,
+ onSwitchAppsTutorialClicked,
modifier = Modifier.weight(1f).fillMaxSize(),
lastSelectedScreen,
)
@@ -136,6 +141,7 @@
onBackTutorialClicked: () -> Unit,
onHomeTutorialClicked: () -> Unit,
onRecentAppsTutorialClicked: () -> Unit,
+ onSwitchAppsTutorialClicked: () -> Unit,
modifier: Modifier = Modifier,
lastSelectedScreen: Screen,
) {
@@ -144,10 +150,11 @@
horizontalAlignment = Alignment.CenterHorizontally,
modifier = modifier,
) {
- ThreeTutorialButtons(
+ FourTutorialButtons(
onBackTutorialClicked,
onHomeTutorialClicked,
onRecentAppsTutorialClicked,
+ onSwitchAppsTutorialClicked,
modifier = Modifier.weight(1f).fillMaxSize(),
lastSelectedScreen,
)
@@ -155,21 +162,24 @@
}
@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 @@
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 @@
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 0000000..470048b
--- /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 3264300..0a13912 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.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.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 @@
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 @@
backGestureViewModel,
homeGestureViewModel,
recentAppsGestureViewModel,
+ switchAppsGestureScreenViewModel,
easterEggGestureViewModel,
closeTutorial = ::finishTutorial,
)
@@ -108,6 +113,7 @@
backGestureViewModel: BackGestureScreenViewModel,
homeGestureViewModel: HomeGestureScreenViewModel,
recentAppsGestureViewModel: RecentAppsGestureScreenViewModel,
+ switchAppsGestureScreenViewModel: SwitchAppsGestureScreenViewModel,
easterEggGestureViewModel: EasterEggGestureViewModel,
closeTutorial: () -> Unit,
) {
@@ -128,6 +134,10 @@
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 @@
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 0000000..b1e163b
--- /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 0000000..6593db4
--- /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 c56dcf3..c6d5e7a 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 @@
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 @@
@Inject
constructor(
private val gesturesInteractor: TouchpadGesturesInteractor,
- private val logger: InputDeviceTutorialLogger
+ private val logger: InputDeviceTutorialLogger,
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
@@ -65,4 +65,5 @@
BACK_GESTURE,
HOME_GESTURE,
RECENT_APPS_GESTURE,
+ SWITCH_APPS_GESTURE,
}
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java
index 05ee35b..8f5fccd 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java
@@ -75,8 +75,7 @@
private static final String[] RESET_EXCEPTION_LIST = new String[] {
QSHost.TILES_SETTING,
Settings.Secure.DOZE_ALWAYS_ON,
- Settings.Secure.MEDIA_CONTROLS_RESUME,
- Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION
+ Settings.Secure.MEDIA_CONTROLS_RESUME
};
private final Observer mObserver = new Observer();
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderIconProvider.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderIconProvider.kt
index 9cf02f2..ef147c7 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderIconProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderIconProvider.kt
@@ -21,6 +21,7 @@
import android.graphics.drawable.Drawable
import android.media.AudioManager
import androidx.annotation.DrawableRes
+import com.android.settingslib.R as SettingsR
import com.android.settingslib.volume.domain.interactor.AudioVolumeInteractor
import com.android.settingslib.volume.shared.model.AudioStream
import com.android.settingslib.volume.shared.model.RingerMode
@@ -30,8 +31,10 @@
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOf
+@SuppressLint("UseCompatLoadingForDrawables")
class VolumeDialogSliderIconProvider
@Inject
constructor(
@@ -40,7 +43,30 @@
private val audioVolumeInteractor: AudioVolumeInteractor,
) {
- @SuppressLint("UseCompatLoadingForDrawables")
+ fun getAudioSharingIcon(isMuted: Boolean): Flow<Drawable> {
+ return flow {
+ val iconRes =
+ if (isMuted) {
+ R.drawable.ic_volume_media_bt_mute
+ } else {
+ R.drawable.ic_volume_media_bt
+ }
+ emit(context.getDrawable(iconRes)!!)
+ }
+ }
+
+ fun getCastIcon(isMuted: Boolean): Flow<Drawable> {
+ return flow {
+ val iconRes =
+ if (isMuted) {
+ SettingsR.drawable.ic_volume_remote_mute
+ } else {
+ SettingsR.drawable.ic_volume_remote
+ }
+ emit(context.getDrawable(iconRes)!!)
+ }
+ }
+
fun getStreamIcon(
stream: Int,
level: Int,
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt
index 89dd035..a752f1f 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt
@@ -57,12 +57,12 @@
class VolumeDialogSliderViewModel
@Inject
constructor(
+ private val sliderType: VolumeDialogSliderType,
private val interactor: VolumeDialogSliderInteractor,
private val visibilityInteractor: VolumeDialogVisibilityInteractor,
@VolumeDialog private val coroutineScope: CoroutineScope,
private val volumeDialogSliderIconProvider: VolumeDialogSliderIconProvider,
private val systemClock: SystemClock,
- private val sliderType: VolumeDialogSliderType,
private val logger: VolumeDialogLogger,
) {
@@ -82,14 +82,24 @@
model
.flatMapLatest { streamModel ->
with(streamModel) {
- volumeDialogSliderIconProvider.getStreamIcon(
- stream = stream,
- level = level,
- levelMin = levelMin,
- levelMax = levelMax,
- isMuted = muteSupported && muted,
- isRoutedToBluetooth = routedToBluetooth,
- )
+ val isMuted = muteSupported && muted
+ when (sliderType) {
+ is VolumeDialogSliderType.Stream ->
+ volumeDialogSliderIconProvider.getStreamIcon(
+ stream = sliderType.audioStream,
+ level = level,
+ levelMin = levelMin,
+ levelMax = levelMax,
+ isMuted = isMuted,
+ isRoutedToBluetooth = routedToBluetooth,
+ )
+ is VolumeDialogSliderType.RemoteMediaStream -> {
+ volumeDialogSliderIconProvider.getCastIcon(isMuted)
+ }
+ is VolumeDialogSliderType.AudioSharingStream -> {
+ volumeDialogSliderIconProvider.getAudioSharingIcon(isMuted)
+ }
+ }
}
.map { icon -> streamModel.toStateModel(icon) }
}
diff --git a/packages/SystemUI/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java b/packages/SystemUI/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java
index 411e06e..0c8dc11 100644
--- a/packages/SystemUI/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java
+++ b/packages/SystemUI/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java
@@ -293,7 +293,7 @@
intent = getSysUiWalletIntent();
}
startQuickAccessViaIntent(intent, hasCard, activityStarter,
- animationController);
+ animationController, mQuickAccessWalletClient.getUser());
});
}
@@ -323,7 +323,8 @@
private void startQuickAccessViaIntent(Intent intent,
boolean hasCard,
ActivityStarter activityStarter,
- ActivityTransitionAnimator.Controller animationController) {
+ ActivityTransitionAnimator.Controller animationController,
+ UserHandle user) {
if (hasCard) {
activityStarter.startActivity(intent, true /* dismissShade */,
animationController, true /* showOverLockscreenWhenLocked */);
@@ -331,7 +332,9 @@
activityStarter.postStartActivityDismissingKeyguard(
intent,
/* delay= */ 0,
- animationController);
+ animationController,
+ null,
+ user);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletActivity.java b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletActivity.java
index 111492c..18e1b6e 100644
--- a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletActivity.java
@@ -163,15 +163,13 @@
if (mKeyguardStateController.isUnlocked()) {
mUiEventLogger.log(WalletUiEvent.QAW_SHOW_ALL);
- mActivityStarter.startActivity(
- mWalletClient.createWalletIntent(), true);
+ startWalletActivity();
finish();
} else {
mUiEventLogger.log(WalletUiEvent.QAW_UNLOCK_FROM_SHOW_ALL_BUTTON);
mKeyguardDismissUtil.executeWhenUnlocked(() -> {
mUiEventLogger.log(WalletUiEvent.QAW_SHOW_ALL);
- mActivityStarter.startActivity(
- mWalletClient.createWalletIntent(), true);
+ startWalletActivity();
finish();
return false;
}, false, true);
@@ -193,6 +191,11 @@
});
}
+ private void startWalletActivity() {
+ mActivityStarter.startActivity(mWalletClient.createWalletIntent(), true,
+ null, true, mWalletClient.getUser());
+ }
+
@Override
protected void onStart() {
super.onStart();
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 8b6c860..0000000
--- 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 2491ca7..d2069cf 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 @@
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 @@
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/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
index bac2c47..1729a4d 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
@@ -55,6 +55,7 @@
import com.android.systemui.plugins.clocks.ThemeConfig
import com.android.systemui.plugins.clocks.ZenData
import com.android.systemui.plugins.clocks.ZenData.ZenMode
+import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.res.R
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.policy.BatteryController
@@ -131,6 +132,7 @@
@Mock private lateinit var parentView: View
@Mock private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
@Mock private lateinit var userTracker: UserTracker
+ @Mock private lateinit var powerInteractor: PowerInteractor
@Mock private lateinit var zenModeController: ZenModeController
private var zenModeControllerCallback: ZenModeController.Callback? = null
@@ -178,6 +180,7 @@
zenModeController,
zenModeInteractor,
userTracker,
+ powerInteractor,
)
underTest.clock = clock
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt
index 82bf5e2..a3c3d2c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt
@@ -205,6 +205,7 @@
biometricSettingsRepository = biometricSettingsRepository,
backgroundDispatcher = testDispatcher,
appContext = mContext,
+ accessibilityManager = mock(),
communalSettingsInteractor = kosmos.communalSettingsInteractor,
sceneInteractor = { kosmos.sceneInteractor },
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
index 111d819..21519b0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
@@ -322,6 +322,7 @@
biometricSettingsRepository = biometricSettingsRepository,
backgroundDispatcher = testDispatcher,
appContext = mContext,
+ accessibilityManager = mock(),
communalSettingsInteractor = kosmos.communalSettingsInteractor,
sceneInteractor = { kosmos.sceneInteractor },
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt
index 8c00047..caf08ef 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt
@@ -325,6 +325,7 @@
biometricSettingsRepository = biometricSettingsRepository,
backgroundDispatcher = testDispatcher,
appContext = mContext,
+ accessibilityManager = mock(),
communalSettingsInteractor = kosmos.communalSettingsInteractor,
sceneInteractor = { kosmos.sceneInteractor },
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
index 0b2b867..b5a2271 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
@@ -294,6 +294,7 @@
biometricSettingsRepository = biometricSettingsRepository,
backgroundDispatcher = kosmos.testDispatcher,
appContext = mContext,
+ accessibilityManager = mock(),
communalSettingsInteractor = kosmos.communalSettingsInteractor,
sceneInteractor = { kosmos.sceneInteractor },
),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt
index 3ddd4b5..2815b9769 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt
@@ -41,13 +41,11 @@
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.FlagsParameterization
-import android.provider.Settings
import android.service.notification.StatusBarNotification
import android.testing.TestableLooper.RunWithLooper
import androidx.media.utils.MediaConstants
import androidx.test.filters.SmallTest
import com.android.dx.mockito.inline.extended.ExtendedMockito
-import com.android.internal.logging.InstanceId
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.Flags
import com.android.systemui.InstanceIdSequenceFake
@@ -65,10 +63,7 @@
import com.android.systemui.media.controls.domain.resume.ResumeMediaBrowser
import com.android.systemui.media.controls.shared.mediaLogger
import com.android.systemui.media.controls.shared.mockMediaLogger
-import com.android.systemui.media.controls.shared.model.EXTRA_KEY_TRIGGER_SOURCE
-import com.android.systemui.media.controls.shared.model.EXTRA_VALUE_TRIGGER_PERIODIC
import com.android.systemui.media.controls.shared.model.MediaData
-import com.android.systemui.media.controls.shared.model.SmartspaceMediaData
import com.android.systemui.media.controls.shared.model.SmartspaceMediaDataProvider
import com.android.systemui.media.controls.util.MediaUiEventLogger
import com.android.systemui.media.controls.util.fakeMediaControllerFactory
@@ -76,7 +71,6 @@
import com.android.systemui.res.R
import com.android.systemui.statusbar.SbnBuilder
import com.android.systemui.testKosmos
-import com.android.systemui.tuner.TunerService
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
@@ -95,12 +89,10 @@
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Captor
import org.mockito.Mock
-import org.mockito.Mockito
import org.mockito.Mockito.mock
import org.mockito.Mockito.never
import org.mockito.Mockito.reset
import org.mockito.Mockito.verify
-import org.mockito.Mockito.verifyNoMoreInteractions
import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoSession
import org.mockito.junit.MockitoJUnit
@@ -126,10 +118,6 @@
private const val USER_ID = 0
private val DISMISS_INTENT = Intent().apply { action = "dismiss" }
-private fun <T> anyObject(): T {
- return Mockito.anyObject<T>()
-}
-
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWithLooper(setAsMainLooper = true)
@@ -168,8 +156,6 @@
lateinit var remoteCastNotification: StatusBarNotification
@Captor lateinit var mediaDataCaptor: ArgumentCaptor<MediaData>
private val clock = FakeSystemClock()
- @Mock private lateinit var tunerService: TunerService
- @Captor lateinit var tunableCaptor: ArgumentCaptor<TunerService.Tunable>
@Captor lateinit var stateCallbackCaptor: ArgumentCaptor<(String, PlaybackState) -> Unit>
@Captor lateinit var sessionCallbackCaptor: ArgumentCaptor<(String) -> Unit>
@Captor lateinit var smartSpaceConfigBuilderCaptor: ArgumentCaptor<SmartspaceConfig>
@@ -197,13 +183,6 @@
private val mediaControllerFactory = kosmos.fakeMediaControllerFactory
private val instanceIdSequence = InstanceIdSequenceFake(1 shl 20)
- private val originalSmartspaceSetting =
- Settings.Secure.getInt(
- context.contentResolver,
- Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION,
- 1,
- )
-
private lateinit var staticMockSession: MockitoSession
@Before
@@ -219,11 +198,6 @@
backgroundExecutor = FakeExecutor(clock)
uiExecutor = FakeExecutor(clock)
smartspaceMediaDataProvider = SmartspaceMediaDataProvider()
- Settings.Secure.putInt(
- context.contentResolver,
- Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION,
- 1,
- )
mediaDataManager =
LegacyMediaDataManagerImpl(
context = context,
@@ -246,7 +220,6 @@
useMediaResumption = true,
useQsMediaPlayer = true,
systemClock = clock,
- tunerService = tunerService,
mediaFlags = kosmos.mediaFlags,
logger = logger,
smartspaceManager = smartspaceManager,
@@ -254,8 +227,6 @@
mediaDataLoader = { kosmos.mediaDataLoader },
mediaLogger = kosmos.mediaLogger,
)
- verify(tunerService)
- .addTunable(capture(tunableCaptor), eq(Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION))
verify(mediaTimeoutListener).stateCallback = capture(stateCallbackCaptor)
verify(mediaTimeoutListener).sessionCallback = capture(sessionCallbackCaptor)
session = MediaSession(context, "MediaDataManagerTestSession")
@@ -332,11 +303,6 @@
staticMockSession.finishMocking()
session.release()
mediaDataManager.destroy()
- Settings.Secure.putInt(
- context.contentResolver,
- Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION,
- originalSmartspaceSetting,
- )
}
@Test
@@ -1236,272 +1202,6 @@
}
@Test
- fun testOnSmartspaceMediaDataLoaded_hasNewValidMediaTarget_callsListener() {
- smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
- verify(logger).getNewInstanceId()
- val instanceId = instanceIdSequence.lastInstanceId
-
- verify(listener)
- .onSmartspaceMediaDataLoaded(
- eq(KEY_MEDIA_SMARTSPACE),
- eq(
- SmartspaceMediaData(
- targetId = KEY_MEDIA_SMARTSPACE,
- isActive = true,
- packageName = PACKAGE_NAME,
- cardAction = mediaSmartspaceBaseAction,
- recommendations = validRecommendationList,
- dismissIntent = DISMISS_INTENT,
- headphoneConnectionTimeMillis = SMARTSPACE_CREATION_TIME,
- instanceId = InstanceId.fakeInstanceId(instanceId),
- expiryTimeMs = SMARTSPACE_EXPIRY_TIME,
- )
- ),
- eq(false),
- )
- }
-
- @Test
- fun testOnSmartspaceMediaDataLoaded_hasNewInvalidMediaTarget_callsListener() {
- whenever(mediaSmartspaceTarget.iconGrid).thenReturn(listOf())
- smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
- verify(logger).getNewInstanceId()
- val instanceId = instanceIdSequence.lastInstanceId
-
- verify(listener)
- .onSmartspaceMediaDataLoaded(
- eq(KEY_MEDIA_SMARTSPACE),
- eq(
- EMPTY_SMARTSPACE_MEDIA_DATA.copy(
- targetId = KEY_MEDIA_SMARTSPACE,
- isActive = true,
- dismissIntent = DISMISS_INTENT,
- headphoneConnectionTimeMillis = SMARTSPACE_CREATION_TIME,
- instanceId = InstanceId.fakeInstanceId(instanceId),
- expiryTimeMs = SMARTSPACE_EXPIRY_TIME,
- )
- ),
- eq(false),
- )
- }
-
- @Test
- fun testOnSmartspaceMediaDataLoaded_hasNullIntent_callsListener() {
- val recommendationExtras =
- Bundle().apply {
- putString("package_name", PACKAGE_NAME)
- putParcelable("dismiss_intent", null)
- }
- whenever(mediaSmartspaceBaseAction.extras).thenReturn(recommendationExtras)
- whenever(mediaSmartspaceTarget.baseAction).thenReturn(mediaSmartspaceBaseAction)
- whenever(mediaSmartspaceTarget.iconGrid).thenReturn(listOf())
-
- smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
- verify(logger).getNewInstanceId()
- val instanceId = instanceIdSequence.lastInstanceId
-
- verify(listener)
- .onSmartspaceMediaDataLoaded(
- eq(KEY_MEDIA_SMARTSPACE),
- eq(
- EMPTY_SMARTSPACE_MEDIA_DATA.copy(
- targetId = KEY_MEDIA_SMARTSPACE,
- isActive = true,
- dismissIntent = null,
- headphoneConnectionTimeMillis = SMARTSPACE_CREATION_TIME,
- instanceId = InstanceId.fakeInstanceId(instanceId),
- expiryTimeMs = SMARTSPACE_EXPIRY_TIME,
- )
- ),
- eq(false),
- )
- }
-
- @Test
- fun testOnSmartspaceMediaDataLoaded_hasNoneMediaTarget_notCallsListener() {
- smartspaceMediaDataProvider.onTargetsAvailable(listOf())
- verify(logger, never()).getNewInstanceId()
- verify(listener, never())
- .onSmartspaceMediaDataLoaded(anyObject(), anyObject(), anyBoolean())
- }
-
- @Test
- fun testOnSmartspaceMediaDataLoaded_hasNoneMediaTarget_callsRemoveListener() {
- smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
- verify(logger).getNewInstanceId()
-
- smartspaceMediaDataProvider.onTargetsAvailable(listOf())
- uiExecutor.advanceClockToLast()
- uiExecutor.runAllReady()
-
- verify(listener).onSmartspaceMediaDataRemoved(eq(KEY_MEDIA_SMARTSPACE), eq(false))
- verifyNoMoreInteractions(logger)
- }
-
- @Test
- fun testOnSmartspaceMediaDataLoaded_persistentEnabled_headphoneTrigger_isActive() {
- fakeFeatureFlags.set(MEDIA_RETAIN_RECOMMENDATIONS, true)
- smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
- val instanceId = instanceIdSequence.lastInstanceId
-
- verify(listener)
- .onSmartspaceMediaDataLoaded(
- eq(KEY_MEDIA_SMARTSPACE),
- eq(
- SmartspaceMediaData(
- targetId = KEY_MEDIA_SMARTSPACE,
- isActive = true,
- packageName = PACKAGE_NAME,
- cardAction = mediaSmartspaceBaseAction,
- recommendations = validRecommendationList,
- dismissIntent = DISMISS_INTENT,
- headphoneConnectionTimeMillis = SMARTSPACE_CREATION_TIME,
- instanceId = InstanceId.fakeInstanceId(instanceId),
- expiryTimeMs = SMARTSPACE_EXPIRY_TIME,
- )
- ),
- eq(false),
- )
- }
-
- @Test
- fun testOnSmartspaceMediaDataLoaded_persistentEnabled_periodicTrigger_notActive() {
- fakeFeatureFlags.set(MEDIA_RETAIN_RECOMMENDATIONS, true)
- val extras =
- Bundle().apply {
- putString("package_name", PACKAGE_NAME)
- putParcelable("dismiss_intent", DISMISS_INTENT)
- putString(EXTRA_KEY_TRIGGER_SOURCE, EXTRA_VALUE_TRIGGER_PERIODIC)
- }
- whenever(mediaSmartspaceBaseAction.extras).thenReturn(extras)
-
- smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
- val instanceId = instanceIdSequence.lastInstanceId
-
- verify(listener)
- .onSmartspaceMediaDataLoaded(
- eq(KEY_MEDIA_SMARTSPACE),
- eq(
- SmartspaceMediaData(
- targetId = KEY_MEDIA_SMARTSPACE,
- isActive = false,
- packageName = PACKAGE_NAME,
- cardAction = mediaSmartspaceBaseAction,
- recommendations = validRecommendationList,
- dismissIntent = DISMISS_INTENT,
- headphoneConnectionTimeMillis = SMARTSPACE_CREATION_TIME,
- instanceId = InstanceId.fakeInstanceId(instanceId),
- expiryTimeMs = SMARTSPACE_EXPIRY_TIME,
- )
- ),
- eq(false),
- )
- }
-
- @Test
- fun testOnSmartspaceMediaDataLoaded_persistentEnabled_noTargets_inactive() {
- fakeFeatureFlags.set(MEDIA_RETAIN_RECOMMENDATIONS, true)
-
- smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
- val instanceId = instanceIdSequence.lastInstanceId
-
- smartspaceMediaDataProvider.onTargetsAvailable(listOf())
- uiExecutor.advanceClockToLast()
- uiExecutor.runAllReady()
-
- verify(listener)
- .onSmartspaceMediaDataLoaded(
- eq(KEY_MEDIA_SMARTSPACE),
- eq(
- SmartspaceMediaData(
- targetId = KEY_MEDIA_SMARTSPACE,
- isActive = false,
- packageName = PACKAGE_NAME,
- cardAction = mediaSmartspaceBaseAction,
- recommendations = validRecommendationList,
- dismissIntent = DISMISS_INTENT,
- headphoneConnectionTimeMillis = SMARTSPACE_CREATION_TIME,
- instanceId = InstanceId.fakeInstanceId(instanceId),
- expiryTimeMs = SMARTSPACE_EXPIRY_TIME,
- )
- ),
- eq(false),
- )
- verify(listener, never()).onSmartspaceMediaDataRemoved(eq(KEY_MEDIA_SMARTSPACE), eq(false))
- }
-
- @Test
- fun testSetRecommendationInactive_notifiesListeners() {
- fakeFeatureFlags.set(MEDIA_RETAIN_RECOMMENDATIONS, true)
-
- smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
- val instanceId = instanceIdSequence.lastInstanceId
-
- mediaDataManager.setRecommendationInactive(KEY_MEDIA_SMARTSPACE)
- uiExecutor.advanceClockToLast()
- uiExecutor.runAllReady()
-
- verify(listener)
- .onSmartspaceMediaDataLoaded(
- eq(KEY_MEDIA_SMARTSPACE),
- eq(
- SmartspaceMediaData(
- targetId = KEY_MEDIA_SMARTSPACE,
- isActive = false,
- packageName = PACKAGE_NAME,
- cardAction = mediaSmartspaceBaseAction,
- recommendations = validRecommendationList,
- dismissIntent = DISMISS_INTENT,
- headphoneConnectionTimeMillis = SMARTSPACE_CREATION_TIME,
- instanceId = InstanceId.fakeInstanceId(instanceId),
- expiryTimeMs = SMARTSPACE_EXPIRY_TIME,
- )
- ),
- eq(false),
- )
- }
-
- @Test
- fun testOnSmartspaceMediaDataLoaded_settingDisabled_doesNothing() {
- // WHEN media recommendation setting is off
- Settings.Secure.putInt(
- context.contentResolver,
- Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION,
- 0,
- )
- tunableCaptor.value.onTuningChanged(Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION, "0")
-
- smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
-
- // THEN smartspace signal is ignored
- verify(listener, never())
- .onSmartspaceMediaDataLoaded(anyObject(), anyObject(), anyBoolean())
- }
-
- @Test
- fun testMediaRecommendationDisabled_removesSmartspaceData() {
- // GIVEN a media recommendation card is present
- smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
- verify(listener)
- .onSmartspaceMediaDataLoaded(eq(KEY_MEDIA_SMARTSPACE), anyObject(), anyBoolean())
-
- // WHEN the media recommendation setting is turned off
- Settings.Secure.putInt(
- context.contentResolver,
- Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION,
- 0,
- )
- tunableCaptor.value.onTuningChanged(Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION, "0")
-
- // THEN listeners are notified
- uiExecutor.advanceClockToLast()
- foregroundExecutor.advanceClockToLast()
- uiExecutor.runAllReady()
- foregroundExecutor.runAllReady()
- verify(listener).onSmartspaceMediaDataRemoved(eq(KEY_MEDIA_SMARTSPACE), eq(true))
- }
-
- @Test
fun testOnMediaDataChanged_updatesLastActiveTime() {
val currentTime = clock.elapsedRealtime()
addNotificationAndLoad()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt
index e5483c0..b9ebce8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt
@@ -42,13 +42,11 @@
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.FlagsParameterization
-import android.provider.Settings
import android.service.notification.StatusBarNotification
import android.testing.TestableLooper.RunWithLooper
import androidx.media.utils.MediaConstants
import androidx.test.filters.SmallTest
import com.android.dx.mockito.inline.extended.ExtendedMockito
-import com.android.internal.logging.InstanceId
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.Flags
import com.android.systemui.InstanceIdSequenceFake
@@ -71,21 +69,16 @@
import com.android.systemui.media.controls.domain.resume.ResumeMediaBrowser
import com.android.systemui.media.controls.shared.mediaLogger
import com.android.systemui.media.controls.shared.mockMediaLogger
-import com.android.systemui.media.controls.shared.model.EXTRA_KEY_TRIGGER_SOURCE
-import com.android.systemui.media.controls.shared.model.EXTRA_VALUE_TRIGGER_PERIODIC
import com.android.systemui.media.controls.shared.model.MediaData
-import com.android.systemui.media.controls.shared.model.SmartspaceMediaData
import com.android.systemui.media.controls.shared.model.SmartspaceMediaDataProvider
import com.android.systemui.media.controls.util.MediaUiEventLogger
import com.android.systemui.media.controls.util.fakeMediaControllerFactory
import com.android.systemui.media.controls.util.mediaFlags
-import com.android.systemui.plugins.activityStarter
import com.android.systemui.res.R
import com.android.systemui.statusbar.SbnBuilder
import com.android.systemui.statusbar.notificationLockscreenUserManager
import com.android.systemui.testKosmos
import com.android.systemui.util.concurrency.FakeExecutor
-import com.android.systemui.util.settings.fakeSettings
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -102,11 +95,9 @@
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Captor
import org.mockito.Mock
-import org.mockito.Mockito
import org.mockito.Mockito.never
import org.mockito.Mockito.reset
import org.mockito.Mockito.verify
-import org.mockito.Mockito.verifyNoMoreInteractions
import org.mockito.MockitoSession
import org.mockito.junit.MockitoJUnit
import org.mockito.kotlin.any
@@ -133,10 +124,6 @@
private const val USER_ID = 0
private val DISMISS_INTENT = Intent().apply { action = "dismiss" }
-private fun <T> anyObject(): T {
- return Mockito.anyObject<T>()
-}
-
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWithLooper(setAsMainLooper = true)
@@ -146,7 +133,6 @@
private val kosmos = testKosmos().apply { mediaLogger = mockMediaLogger }
private val testDispatcher = kosmos.testDispatcher
private val testScope = kosmos.testScope
- private val settings = kosmos.fakeSettings
@JvmField @Rule val mockito = MockitoJUnit.rule()
@Mock lateinit var controller: MediaController
@@ -201,7 +187,6 @@
}
private val fakeFeatureFlags = kosmos.fakeFeatureFlagsClassic
- private val activityStarter = kosmos.activityStarter
private val mediaControllerFactory = kosmos.fakeMediaControllerFactory
private val notificationLockscreenUserManager = kosmos.notificationLockscreenUserManager
private val mediaFilterRepository = kosmos.mediaFilterRepository
@@ -209,13 +194,6 @@
private val instanceIdSequence = InstanceIdSequenceFake(1 shl 20)
- private val originalSmartspaceSetting =
- Settings.Secure.getInt(
- context.contentResolver,
- Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION,
- 1,
- )
-
private lateinit var staticMockSession: MockitoSession
@Before
@@ -231,11 +209,6 @@
backgroundExecutor = FakeExecutor(clock)
uiExecutor = FakeExecutor(clock)
smartspaceMediaDataProvider = SmartspaceMediaDataProvider()
- Settings.Secure.putInt(
- context.contentResolver,
- Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION,
- 1,
- )
mediaDataProcessor =
MediaDataProcessor(
context = context,
@@ -248,12 +221,10 @@
mediaControllerFactory = mediaControllerFactory,
broadcastDispatcher = broadcastDispatcher,
dumpManager = dumpManager,
- activityStarter = activityStarter,
smartspaceMediaDataProvider = smartspaceMediaDataProvider,
useMediaResumption = true,
useQsMediaPlayer = true,
systemClock = clock,
- secureSettings = settings,
mediaFlags = kosmos.mediaFlags,
logger = logger,
smartspaceManager = smartspaceManager,
@@ -355,11 +326,6 @@
staticMockSession.finishMocking()
session.release()
mediaDataProcessor.destroy()
- Settings.Secure.putInt(
- context.contentResolver,
- Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION,
- originalSmartspaceSetting,
- )
}
@Test
@@ -1255,264 +1221,6 @@
}
@Test
- fun testOnSmartspaceMediaDataLoaded_hasNewValidMediaTarget_callsListener() {
- smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
- verify(logger).getNewInstanceId()
- val instanceId = instanceIdSequence.lastInstanceId
-
- verify(listener)
- .onSmartspaceMediaDataLoaded(
- eq(KEY_MEDIA_SMARTSPACE),
- eq(
- SmartspaceMediaData(
- targetId = KEY_MEDIA_SMARTSPACE,
- isActive = true,
- packageName = PACKAGE_NAME,
- cardAction = mediaSmartspaceBaseAction,
- recommendations = validRecommendationList,
- dismissIntent = DISMISS_INTENT,
- headphoneConnectionTimeMillis = SMARTSPACE_CREATION_TIME,
- instanceId = InstanceId.fakeInstanceId(instanceId),
- expiryTimeMs = SMARTSPACE_EXPIRY_TIME,
- )
- ),
- eq(false),
- )
- }
-
- @Test
- fun testOnSmartspaceMediaDataLoaded_hasNewInvalidMediaTarget_callsListener() {
- whenever(mediaSmartspaceTarget.iconGrid).thenReturn(listOf())
- smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
- verify(logger).getNewInstanceId()
- val instanceId = instanceIdSequence.lastInstanceId
-
- verify(listener)
- .onSmartspaceMediaDataLoaded(
- eq(KEY_MEDIA_SMARTSPACE),
- eq(
- SmartspaceMediaData(
- targetId = KEY_MEDIA_SMARTSPACE,
- isActive = true,
- dismissIntent = DISMISS_INTENT,
- headphoneConnectionTimeMillis = SMARTSPACE_CREATION_TIME,
- instanceId = InstanceId.fakeInstanceId(instanceId),
- expiryTimeMs = SMARTSPACE_EXPIRY_TIME,
- )
- ),
- eq(false),
- )
- }
-
- @Test
- fun testOnSmartspaceMediaDataLoaded_hasNullIntent_callsListener() {
- val recommendationExtras =
- Bundle().apply {
- putString("package_name", PACKAGE_NAME)
- putParcelable("dismiss_intent", null)
- }
- whenever(mediaSmartspaceBaseAction.extras).thenReturn(recommendationExtras)
- whenever(mediaSmartspaceTarget.baseAction).thenReturn(mediaSmartspaceBaseAction)
- whenever(mediaSmartspaceTarget.iconGrid).thenReturn(listOf())
-
- smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
- verify(logger).getNewInstanceId()
- val instanceId = instanceIdSequence.lastInstanceId
-
- verify(listener)
- .onSmartspaceMediaDataLoaded(
- eq(KEY_MEDIA_SMARTSPACE),
- eq(
- SmartspaceMediaData(
- targetId = KEY_MEDIA_SMARTSPACE,
- isActive = true,
- dismissIntent = null,
- headphoneConnectionTimeMillis = SMARTSPACE_CREATION_TIME,
- instanceId = InstanceId.fakeInstanceId(instanceId),
- expiryTimeMs = SMARTSPACE_EXPIRY_TIME,
- )
- ),
- eq(false),
- )
- }
-
- @Test
- fun testOnSmartspaceMediaDataLoaded_hasNoneMediaTarget_notCallsListener() {
- smartspaceMediaDataProvider.onTargetsAvailable(listOf())
- verify(logger, never()).getNewInstanceId()
- verify(listener, never())
- .onSmartspaceMediaDataLoaded(anyObject(), anyObject(), anyBoolean())
- }
-
- @Test
- fun testOnSmartspaceMediaDataLoaded_hasNoneMediaTarget_callsRemoveListener() {
- smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
- verify(logger).getNewInstanceId()
-
- smartspaceMediaDataProvider.onTargetsAvailable(listOf())
- uiExecutor.advanceClockToLast()
- uiExecutor.runAllReady()
-
- verify(listener).onSmartspaceMediaDataRemoved(eq(KEY_MEDIA_SMARTSPACE), eq(false))
- verifyNoMoreInteractions(logger)
- }
-
- @Test
- fun testOnSmartspaceMediaDataLoaded_persistentEnabled_headphoneTrigger_isActive() {
- fakeFeatureFlags.set(MEDIA_RETAIN_RECOMMENDATIONS, true)
- smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
- val instanceId = instanceIdSequence.lastInstanceId
-
- verify(listener)
- .onSmartspaceMediaDataLoaded(
- eq(KEY_MEDIA_SMARTSPACE),
- eq(
- SmartspaceMediaData(
- targetId = KEY_MEDIA_SMARTSPACE,
- isActive = true,
- packageName = PACKAGE_NAME,
- cardAction = mediaSmartspaceBaseAction,
- recommendations = validRecommendationList,
- dismissIntent = DISMISS_INTENT,
- headphoneConnectionTimeMillis = SMARTSPACE_CREATION_TIME,
- instanceId = InstanceId.fakeInstanceId(instanceId),
- expiryTimeMs = SMARTSPACE_EXPIRY_TIME,
- )
- ),
- eq(false),
- )
- }
-
- @Test
- fun testOnSmartspaceMediaDataLoaded_persistentEnabled_periodicTrigger_notActive() {
- fakeFeatureFlags.set(MEDIA_RETAIN_RECOMMENDATIONS, true)
- val extras =
- Bundle().apply {
- putString("package_name", PACKAGE_NAME)
- putParcelable("dismiss_intent", DISMISS_INTENT)
- putString(EXTRA_KEY_TRIGGER_SOURCE, EXTRA_VALUE_TRIGGER_PERIODIC)
- }
- whenever(mediaSmartspaceBaseAction.extras).thenReturn(extras)
-
- smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
- val instanceId = instanceIdSequence.lastInstanceId
-
- verify(listener)
- .onSmartspaceMediaDataLoaded(
- eq(KEY_MEDIA_SMARTSPACE),
- eq(
- SmartspaceMediaData(
- targetId = KEY_MEDIA_SMARTSPACE,
- isActive = false,
- packageName = PACKAGE_NAME,
- cardAction = mediaSmartspaceBaseAction,
- recommendations = validRecommendationList,
- dismissIntent = DISMISS_INTENT,
- headphoneConnectionTimeMillis = SMARTSPACE_CREATION_TIME,
- instanceId = InstanceId.fakeInstanceId(instanceId),
- expiryTimeMs = SMARTSPACE_EXPIRY_TIME,
- )
- ),
- eq(false),
- )
- }
-
- @Test
- fun testOnSmartspaceMediaDataLoaded_persistentEnabled_noTargets_inactive() {
- fakeFeatureFlags.set(MEDIA_RETAIN_RECOMMENDATIONS, true)
-
- smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
- val instanceId = instanceIdSequence.lastInstanceId
-
- smartspaceMediaDataProvider.onTargetsAvailable(listOf())
- uiExecutor.advanceClockToLast()
- uiExecutor.runAllReady()
-
- verify(listener)
- .onSmartspaceMediaDataLoaded(
- eq(KEY_MEDIA_SMARTSPACE),
- eq(
- SmartspaceMediaData(
- targetId = KEY_MEDIA_SMARTSPACE,
- isActive = false,
- packageName = PACKAGE_NAME,
- cardAction = mediaSmartspaceBaseAction,
- recommendations = validRecommendationList,
- dismissIntent = DISMISS_INTENT,
- headphoneConnectionTimeMillis = SMARTSPACE_CREATION_TIME,
- instanceId = InstanceId.fakeInstanceId(instanceId),
- expiryTimeMs = SMARTSPACE_EXPIRY_TIME,
- )
- ),
- eq(false),
- )
- verify(listener, never()).onSmartspaceMediaDataRemoved(eq(KEY_MEDIA_SMARTSPACE), eq(false))
- }
-
- @Test
- fun testSetRecommendationInactive_notifiesListeners() {
- fakeFeatureFlags.set(MEDIA_RETAIN_RECOMMENDATIONS, true)
-
- smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
- val instanceId = instanceIdSequence.lastInstanceId
-
- mediaDataProcessor.setRecommendationInactive(KEY_MEDIA_SMARTSPACE)
- uiExecutor.advanceClockToLast()
- uiExecutor.runAllReady()
-
- verify(listener)
- .onSmartspaceMediaDataLoaded(
- eq(KEY_MEDIA_SMARTSPACE),
- eq(
- SmartspaceMediaData(
- targetId = KEY_MEDIA_SMARTSPACE,
- isActive = false,
- packageName = PACKAGE_NAME,
- cardAction = mediaSmartspaceBaseAction,
- recommendations = validRecommendationList,
- dismissIntent = DISMISS_INTENT,
- headphoneConnectionTimeMillis = SMARTSPACE_CREATION_TIME,
- instanceId = InstanceId.fakeInstanceId(instanceId),
- expiryTimeMs = SMARTSPACE_EXPIRY_TIME,
- )
- ),
- eq(false),
- )
- }
-
- @Test
- fun testOnSmartspaceMediaDataLoaded_settingDisabled_doesNothing() {
- // WHEN media recommendation setting is off
- settings.putInt(Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION, 0)
- testScope.runCurrent()
-
- smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
-
- // THEN smartspace signal is ignored
- verify(listener, never())
- .onSmartspaceMediaDataLoaded(anyObject(), anyObject(), anyBoolean())
- }
-
- @Test
- fun testMediaRecommendationDisabled_removesSmartspaceData() {
- // GIVEN a media recommendation card is present
- smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
- verify(listener)
- .onSmartspaceMediaDataLoaded(eq(KEY_MEDIA_SMARTSPACE), anyObject(), anyBoolean())
-
- // WHEN the media recommendation setting is turned off
- settings.putInt(Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION, 0)
- testScope.runCurrent()
-
- // THEN listeners are notified
- uiExecutor.advanceClockToLast()
- foregroundExecutor.advanceClockToLast()
- uiExecutor.runAllReady()
- foregroundExecutor.runAllReady()
- verify(listener).onSmartspaceMediaDataRemoved(eq(KEY_MEDIA_SMARTSPACE), eq(true))
- }
-
- @Test
fun testOnMediaDataChanged_updatesLastActiveTime() {
val currentTime = clock.elapsedRealtime()
addNotificationAndLoad()
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 a17f100..e5376d2 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 @@
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 @@
.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 @@
new RecordingControllerLogger(logcatLogBuffer("RecordingControllerTest")),
mMediaProjectionMetricsLogger,
mScreenCaptureDisabledDialogDelegate,
- mScreenRecordPermissionDialogDelegateFactory
+ mScreenRecordPermissionDialogDelegateFactory,
+ mScreenRecordPermissionViewBinderFactory
);
mController.addCallback(mCallback);
}
@@ -238,6 +246,26 @@
}
@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 72d1db3..281ce16 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_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.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.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 @@
}
@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/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
index 38ddb3e..a02d333 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
@@ -30,7 +30,6 @@
import static org.junit.Assert.assertFalse;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyFloat;
-import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
@@ -1425,8 +1424,7 @@
HashSet<ScrimState> regularStates = new HashSet<>(Arrays.asList(
ScrimState.UNINITIALIZED, ScrimState.KEYGUARD, BOUNCER,
ScrimState.DREAMING, ScrimState.BOUNCER_SCRIMMED, ScrimState.BRIGHTNESS_MIRROR,
- ScrimState.UNLOCKED, SHADE_LOCKED, ScrimState.AUTH_SCRIMMED,
- ScrimState.AUTH_SCRIMMED_SHADE, ScrimState.GLANCEABLE_HUB,
+ ScrimState.UNLOCKED, SHADE_LOCKED, ScrimState.GLANCEABLE_HUB,
ScrimState.GLANCEABLE_HUB_OVER_DREAM));
for (ScrimState state : ScrimState.values()) {
@@ -1451,79 +1449,6 @@
}
@Test
- public void testAuthScrim_setClipQSScrimTrue_notifScrimOpaque_whenShadeFullyExpanded() {
- // GIVEN device has an activity showing ('UNLOCKED' state can occur on the lock screen
- // with the camera app occluding the keyguard)
- mScrimController.legacyTransitionTo(ScrimState.UNLOCKED);
- mScrimController.setClipsQsScrim(true);
- mScrimController.setRawPanelExpansionFraction(1);
- // notifications scrim alpha change require calling setQsPosition
- mScrimController.setQsPosition(0, 300);
- finishAnimationsImmediately();
-
- // WHEN the user triggers the auth bouncer
- mScrimController.legacyTransitionTo(ScrimState.AUTH_SCRIMMED_SHADE);
- finishAnimationsImmediately();
-
- assertEquals("Behind scrim should be opaque",
- mScrimBehind.getViewAlpha(), 1, 0.0);
- assertEquals("Notifications scrim should be opaque",
- mNotificationsScrim.getViewAlpha(), 1, 0.0);
-
- assertScrimTinted(Map.of(
- mScrimInFront, true,
- mScrimBehind, true,
- mNotificationsScrim, false
- ));
- }
-
-
- @Test
- public void testAuthScrim_setClipQSScrimFalse_notifScrimOpaque_whenShadeFullyExpanded() {
- // GIVEN device has an activity showing ('UNLOCKED' state can occur on the lock screen
- // with the camera app occluding the keyguard)
- mScrimController.legacyTransitionTo(ScrimState.UNLOCKED);
- mScrimController.setClipsQsScrim(false);
- mScrimController.setRawPanelExpansionFraction(1);
- // notifications scrim alpha change require calling setQsPosition
- mScrimController.setQsPosition(0, 300);
- finishAnimationsImmediately();
-
- // WHEN the user triggers the auth bouncer
- mScrimController.legacyTransitionTo(ScrimState.AUTH_SCRIMMED_SHADE);
- finishAnimationsImmediately();
-
- assertEquals("Behind scrim should be opaque",
- mScrimBehind.getViewAlpha(), 1, 0.0);
- assertEquals("Notifications scrim should be opaque",
- mNotificationsScrim.getViewAlpha(), 1, 0.0);
-
- assertScrimTinted(Map.of(
- mScrimInFront, true,
- mScrimBehind, true,
- mNotificationsScrim, false
- ));
- }
-
- @Test
- public void testAuthScrimKeyguard() {
- // GIVEN device is on the keyguard
- mScrimController.legacyTransitionTo(ScrimState.KEYGUARD);
- finishAnimationsImmediately();
-
- // WHEN the user triggers the auth bouncer
- mScrimController.legacyTransitionTo(ScrimState.AUTH_SCRIMMED);
- finishAnimationsImmediately();
-
- // THEN the front scrim is updated and the KEYGUARD scrims are the same as the
- // KEYGUARD scrim state
- assertScrimAlpha(Map.of(
- mScrimInFront, SEMI_TRANSPARENT,
- mScrimBehind, SEMI_TRANSPARENT,
- mNotificationsScrim, TRANSPARENT));
- }
-
- @Test
public void testScrimsVisible_whenShadeVisible() {
mScrimController.setClipsQsScrim(true);
mScrimController.legacyTransitionTo(ScrimState.UNLOCKED);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
index f9df6c7..0d0415e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
@@ -67,6 +67,7 @@
import com.android.systemui.statusbar.data.repository.StatusBarConfigurationControllerStore;
import com.android.systemui.statusbar.disableflags.DisableFlagsLogger;
import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
+import com.android.systemui.statusbar.headsup.shared.StatusBarNoHunBehavior;
import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerStatusBarViewBinder;
import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
import com.android.systemui.statusbar.phone.StatusBarHideIconsForBouncerManager;
@@ -624,13 +625,14 @@
assertEquals(View.GONE, getSecondaryOngoingActivityChipView().getVisibility());
}
- @Test
- @DisableFlags({
- StatusBarNotifChips.FLAG_NAME,
- StatusBarRootModernization.FLAG_NAME,
- StatusBarChipsModernization.FLAG_NAME
- })
- public void hasOngoingActivityButAlsoHun_chipHidden_notifsFlagOff() {
+ @Test
+ @DisableFlags({
+ StatusBarNotifChips.FLAG_NAME,
+ StatusBarRootModernization.FLAG_NAME,
+ StatusBarChipsModernization.FLAG_NAME,
+ StatusBarNoHunBehavior.FLAG_NAME,
+ })
+ public void hasOngoingActivityButAlsoHun_chipHidden_notifChipsFlagOff() {
CollapsedStatusBarFragment fragment = resumeAndGetFragment();
mCollapsedStatusBarViewBinder.getListener().onOngoingActivityStatusChanged(
@@ -646,8 +648,12 @@
@Test
@EnableFlags({StatusBarNotifChips.FLAG_NAME})
- @DisableFlags({StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME})
- public void hasOngoingActivitiesButAlsoHun_chipsHidden_notifsFlagOn() {
+ @DisableFlags({
+ StatusBarRootModernization.FLAG_NAME,
+ StatusBarChipsModernization.FLAG_NAME,
+ StatusBarNoHunBehavior.FLAG_NAME
+ })
+ public void hasOngoingActivitiesButAlsoHun_chipsHidden_notifChipsFlagOn() {
CollapsedStatusBarFragment fragment = resumeAndGetFragment();
mCollapsedStatusBarViewBinder.getListener().onOngoingActivityStatusChanged(
@@ -662,13 +668,31 @@
assertEquals(View.GONE, getSecondaryOngoingActivityChipView().getVisibility());
}
+ @Test
+ @EnableFlags({StatusBarNotifChips.FLAG_NAME, StatusBarNoHunBehavior.FLAG_NAME})
+ @DisableFlags({StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME})
+ public void hasOngoingActivitiesButAlsoHun_noHunBehaviorFlagOn_chipsNotHidden() {
+ CollapsedStatusBarFragment fragment = resumeAndGetFragment();
+
+ mCollapsedStatusBarViewBinder.getListener().onOngoingActivityStatusChanged(
+ /* hasPrimaryOngoingActivity= */ true,
+ /* hasSecondaryOngoingActivity= */ true,
+ /* shouldAnimate= */ false);
+ when(mHeadsUpAppearanceController.shouldHeadsUpStatusBarBeVisible()).thenReturn(true);
+
+ fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
+
+ assertEquals(View.VISIBLE, getPrimaryOngoingActivityChipView().getVisibility());
+ assertEquals(View.VISIBLE, getSecondaryOngoingActivityChipView().getVisibility());
+ }
+
@Test
@DisableFlags({
StatusBarNotifChips.FLAG_NAME,
StatusBarRootModernization.FLAG_NAME,
StatusBarChipsModernization.FLAG_NAME
})
- public void primaryOngoingActivityEnded_chipHidden_notifsFlagOff() {
+ public void primaryOngoingActivityEnded_chipHidden_notifChipsFlagOff() {
resumeAndGetFragment();
// Ongoing activity started
@@ -948,9 +972,13 @@
assertEquals(View.VISIBLE, getClockView().getVisibility());
}
- @Test
- @DisableFlags({StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME})
- public void disable_shouldHeadsUpStatusBarBeVisibleTrue_clockDisabled() {
+ @Test
+ @DisableFlags({
+ StatusBarRootModernization.FLAG_NAME,
+ StatusBarChipsModernization.FLAG_NAME,
+ StatusBarNoHunBehavior.FLAG_NAME,
+ })
+ public void disable_shouldHeadsUpStatusBarBeVisibleTrue_clockDisabled() {
CollapsedStatusBarFragment fragment = resumeAndGetFragment();
when(mHeadsUpAppearanceController.shouldHeadsUpStatusBarBeVisible()).thenReturn(true);
@@ -960,7 +988,11 @@
}
@Test
- @DisableFlags({StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME})
+ @DisableFlags({
+ StatusBarRootModernization.FLAG_NAME,
+ StatusBarChipsModernization.FLAG_NAME,
+ StatusBarNoHunBehavior.FLAG_NAME,
+ })
public void disable_shouldHeadsUpStatusBarBeVisibleFalse_clockNotDisabled() {
CollapsedStatusBarFragment fragment = resumeAndGetFragment();
when(mHeadsUpAppearanceController.shouldHeadsUpStatusBarBeVisible()).thenReturn(false);
@@ -971,6 +1003,18 @@
}
@Test
+ @DisableFlags({StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME})
+ @EnableFlags(StatusBarNoHunBehavior.FLAG_NAME)
+ public void disable_shouldHeadsUpStatusBarBeVisibleTrue_butNoHunBehaviorOn_clockNotDisabled() {
+ CollapsedStatusBarFragment fragment = resumeAndGetFragment();
+ when(mHeadsUpAppearanceController.shouldHeadsUpStatusBarBeVisible()).thenReturn(true);
+
+ fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
+
+ assertEquals(View.VISIBLE, getClockView().getVisibility());
+ }
+
+ @Test
public void setUp_fragmentCreatesDaggerComponent() {
CollapsedStatusBarFragment fragment = resumeAndGetFragment();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallet/controller/QuickAccessWalletControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/wallet/controller/QuickAccessWalletControllerTest.java
index 733e2ed..8e97c86 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wallet/controller/QuickAccessWalletControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wallet/controller/QuickAccessWalletControllerTest.java
@@ -17,6 +17,7 @@
package com.android.systemui.wallet.controller;
import static android.service.quickaccesswallet.Flags.FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotSame;
@@ -35,6 +36,7 @@
import android.app.PendingIntent;
import android.app.role.RoleManager;
import android.content.Intent;
+import android.os.UserHandle;
import android.platform.test.annotations.EnableFlags;
import android.service.quickaccesswallet.GetWalletCardsRequest;
import android.service.quickaccesswallet.QuickAccessWalletClient;
@@ -59,7 +61,6 @@
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import org.mockito.Spy;
import java.util.List;
@@ -98,6 +99,7 @@
when(mQuickAccessWalletClient.isWalletServiceAvailable()).thenReturn(true);
when(mQuickAccessWalletClient.isWalletFeatureAvailable()).thenReturn(true);
when(mQuickAccessWalletClient.isWalletFeatureAvailableWhenDeviceLocked()).thenReturn(true);
+ when(mQuickAccessWalletClient.getUser()).thenReturn(UserHandle.of(0));
mClock.setElapsedRealtime(100L);
doAnswer(invocation -> {
@@ -269,7 +271,8 @@
public void getQuickAccessUiIntent_noCards_noPendingIntent_startsWalletActivity() {
mController.startQuickAccessUiIntent(mActivityStarter, mAnimationController, false);
verify(mActivityStarter).postStartActivityDismissingKeyguard(mIntentCaptor.capture(), eq(0),
- any(ActivityTransitionAnimator.Controller.class));
+ any(ActivityTransitionAnimator.Controller.class), eq(null),
+ eq(UserHandle.of(0)));
Intent intent = mIntentCaptor.getValue();
assertEquals(intent.getAction(), Intent.ACTION_VIEW);
assertEquals(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index 7508838..fab7922 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -358,6 +358,8 @@
private Display mDefaultDisplay;
@Mock
private Lazy<ViewCapture> mLazyViewCapture;
+ @Mock
+ private SyncTransactionQueue mSyncQueue;
private final KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
private ShadeInteractor mShadeInteractor;
@@ -400,8 +402,6 @@
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
doReturn(true).when(mTransitions).isRegistered();
}
- mTaskViewRepository = new TaskViewRepository();
- mTaskViewTransitions = new TaskViewTransitions(mTransitions, mTaskViewRepository);
mTestableLooper = TestableLooper.get(this);
@@ -518,6 +518,9 @@
Optional.empty(),
Optional.empty(),
syncExecutor);
+ mTaskViewRepository = new TaskViewRepository();
+ mTaskViewTransitions = new TaskViewTransitions(mTransitions, mTaskViewRepository,
+ mShellTaskOrganizer, mSyncQueue);
mBubbleProperties = new FakeBubbleProperties();
mBubbleController = new TestableBubbleController(
mContext,
@@ -542,6 +545,7 @@
mock(DragAndDropController.class),
syncExecutor,
mock(Handler.class),
+ mTaskViewRepository,
mTaskViewTransitions,
mTransitions,
mock(SyncTransactionQueue.class),
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
similarity index 94%
rename from packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/DismissKeyguardInteractor.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissTransitionInteractor.kt
index 82a5311..56a7b4db5 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 @@
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/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorKosmos.kt
index 3b1199a..ba64ed7 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorKosmos.kt
@@ -18,6 +18,7 @@
import android.app.admin.devicePolicyManager
import android.content.applicationContext
+import android.view.accessibility.AccessibilityManager
import com.android.internal.widget.lockPatternUtils
import com.android.keyguard.logging.KeyguardQuickAffordancesLogger
import com.android.systemui.animation.dialogTransitionAnimator
@@ -54,6 +55,7 @@
dockManager = dockManager,
biometricSettingsRepository = biometricSettingsRepository,
communalSettingsInteractor = communalSettingsInteractor,
+ accessibilityManager = mock<AccessibilityManager>(),
backgroundDispatcher = testDispatcher,
appContext = applicationContext,
sceneInteractor = { sceneInteractor },
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
index abbfa93..1c0f97d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
@@ -56,9 +56,11 @@
aodToGoneTransitionViewModel = aodToGoneTransitionViewModel,
aodToLockscreenTransitionViewModel = aodToLockscreenTransitionViewModel,
aodToOccludedTransitionViewModel = aodToOccludedTransitionViewModel,
+ aodToPrimaryBouncerTransitionViewModel = aodToPrimaryBouncerTransitionViewModel,
dozingToGoneTransitionViewModel = dozingToGoneTransitionViewModel,
dozingToLockscreenTransitionViewModel = dozingToLockscreenTransitionViewModel,
dozingToOccludedTransitionViewModel = dozingToOccludedTransitionViewModel,
+ dozingToPrimaryBouncerTransitionViewModel = dozingToPrimaryBouncerTransitionViewModel,
dreamingToAodTransitionViewModel = dreamingToAodTransitionViewModel,
dreamingToGoneTransitionViewModel = dreamingToGoneTransitionViewModel,
dreamingToLockscreenTransitionViewModel = dreamingToLockscreenTransitionViewModel,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LightRevealScrimViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LightRevealScrimViewModelKosmos.kt
new file mode 100644
index 0000000..93da1f3
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LightRevealScrimViewModelKosmos.kt
@@ -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.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import com.android.systemui.keyguard.domain.interactor.lightRevealScrimInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+
+val Kosmos.lightRevealScrimViewModel by Fixture {
+ LightRevealScrimViewModel(interactor = lightRevealScrimInteractor)
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorKosmos.kt
index 174e653..fcaad6b 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorKosmos.kt
@@ -31,9 +31,7 @@
import com.android.systemui.media.controls.util.fakeMediaControllerFactory
import com.android.systemui.media.controls.util.mediaFlags
import com.android.systemui.media.controls.util.mediaUiEventLogger
-import com.android.systemui.plugins.activityStarter
import com.android.systemui.util.Utils
-import com.android.systemui.util.settings.fakeSettings
import com.android.systemui.util.time.systemClock
val Kosmos.mediaDataProcessor by
@@ -49,12 +47,10 @@
mediaControllerFactory = fakeMediaControllerFactory,
broadcastDispatcher = broadcastDispatcher,
dumpManager = dumpManager,
- activityStarter = activityStarter,
smartspaceMediaDataProvider = SmartspaceMediaDataProvider(),
useMediaResumption = Utils.useMediaResumption(applicationContext),
useQsMediaPlayer = Utils.useQsMediaPlayer(applicationContext),
systemClock = systemClock,
- secureSettings = fakeSettings,
mediaFlags = mediaFlags,
logger = mediaUiEventLogger,
smartspaceManager = SmartspaceManager(applicationContext),
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 3c37101..13cbddf 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.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 @@
qsTileIntentUserInputHandler,
Provider { modesDialogDelegate }.get(),
zenModeInteractor,
+ modesDialogEventLogger,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt
index 0eca818..609f97d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt
@@ -4,6 +4,7 @@
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.systemui.classifier.domain.interactor.falsingInteractor
import com.android.systemui.haptics.msdl.msdlPlayer
+import com.android.systemui.keyguard.ui.viewmodel.lightRevealScrimViewModel
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.power.domain.interactor.powerInteractor
@@ -19,6 +20,7 @@
import com.android.systemui.scene.ui.viewmodel.splitEdgeDetector
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.statusbar.domain.interactor.remoteInputInteractor
+import com.android.systemui.wallpapers.ui.viewmodel.wallpaperViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import org.mockito.kotlin.mock
@@ -96,6 +98,8 @@
hapticsViewModelFactory = sceneContainerHapticsViewModelFactory,
view = view,
motionEventHandlerReceiver = motionEventHandlerReceiver,
+ lightRevealScrim = lightRevealScrimViewModel,
+ wallpaperViewModel = wallpaperViewModel,
)
}
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
index 60e092c..8461da7 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
@@ -28,9 +28,11 @@
import com.android.systemui.keyguard.ui.viewmodel.aodToGoneTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.aodToLockscreenTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.aodToOccludedTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.aodToPrimaryBouncerTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.dozingToGlanceableHubTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.dozingToLockscreenTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.dozingToOccludedTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.dozingToPrimaryBouncerTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.dreamingToLockscreenTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.glanceableHubToLockscreenTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.goneToAodTransitionViewModel
@@ -78,9 +80,11 @@
aodToGoneTransitionViewModel = aodToGoneTransitionViewModel,
aodToLockscreenTransitionViewModel = aodToLockscreenTransitionViewModel,
aodToOccludedTransitionViewModel = aodToOccludedTransitionViewModel,
+ aodToPrimaryBouncerTransitionViewModel = aodToPrimaryBouncerTransitionViewModel,
dozingToGlanceableHubTransitionViewModel = dozingToGlanceableHubTransitionViewModel,
dozingToLockscreenTransitionViewModel = dozingToLockscreenTransitionViewModel,
dozingToOccludedTransitionViewModel = dozingToOccludedTransitionViewModel,
+ dozingToPrimaryBouncerTransitionViewModel = dozingToPrimaryBouncerTransitionViewModel,
dreamingToLockscreenTransitionViewModel = dreamingToLockscreenTransitionViewModel,
goneToAodTransitionViewModel = goneToAodTransitionViewModel,
goneToDozingTransitionViewModel = goneToDozingTransitionViewModel,
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 766b280..f4e74fe 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 @@
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 @@
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/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelKosmos.kt
index db7e31b..f8bf3c3 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelKosmos.kt
@@ -21,6 +21,7 @@
import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.log.table.tableLogBufferFactory
import com.android.systemui.scene.domain.interactor.sceneContainerOcclusionInteractor
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.shade.domain.interactor.shadeInteractor
@@ -39,6 +40,7 @@
Kosmos.Fixture {
HomeStatusBarViewModelImpl(
testableContext.displayId,
+ tableLogBufferFactory,
homeStatusBarInteractor,
homeStatusBarIconBlockListInteractor,
lightsOutInteractor,
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 6c98d19..ef043e0 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 @@
var Kosmos.modesDialogDelegate: ModesDialogDelegate by
Kosmos.Fixture {
ModesDialogDelegate(
+ mockedContext,
systemUIDialogFactory,
dialogTransitionAnimator,
activityStarter,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryKosmos.kt
index 1d8c891..ddb9a3f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryKosmos.kt
@@ -26,13 +26,11 @@
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
import com.android.systemui.user.data.repository.userRepository
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-@OptIn(ExperimentalCoroutinesApi::class)
val Kosmos.wallpaperRepository by Fixture {
WallpaperRepositoryImpl(
context = applicationContext,
- scope = testScope,
+ scope = testScope.backgroundScope,
bgDispatcher = testDispatcher,
broadcastDispatcher = broadcastDispatcher,
userRepository = userRepository,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/wallpapers/ui/viewmodel/WallpaperViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/wallpapers/ui/viewmodel/WallpaperViewModelKosmos.kt
new file mode 100644
index 0000000..bb6596e
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/wallpapers/ui/viewmodel/WallpaperViewModelKosmos.kt
@@ -0,0 +1,23 @@
+/*
+ * 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.wallpapers.ui.viewmodel
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.wallpapers.domain.interactor.wallpaperInteractor
+
+val Kosmos.wallpaperViewModel by Fixture { WallpaperViewModel(interactor = wallpaperInteractor) }
diff --git a/packages/SystemUI/utils/Android.bp b/packages/SystemUI/utils/Android.bp
index 1ef3816..1efb11b 100644
--- a/packages/SystemUI/utils/Android.bp
+++ b/packages/SystemUI/utils/Android.bp
@@ -29,4 +29,5 @@
"kotlin-stdlib",
"kotlinx_coroutines",
],
+ kotlincflags: ["-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi"],
}
diff --git a/packages/SystemUI/utils/src/com/android/systemui/utils/coroutines/flow/LatestConflated.kt b/packages/SystemUI/utils/src/com/android/systemui/utils/coroutines/flow/LatestConflated.kt
index 5f8c660..4cfb7d5 100644
--- a/packages/SystemUI/utils/src/com/android/systemui/utils/coroutines/flow/LatestConflated.kt
+++ b/packages/SystemUI/utils/src/com/android/systemui/utils/coroutines/flow/LatestConflated.kt
@@ -14,12 +14,11 @@
* limitations under the License.
*/
-@file:OptIn(ExperimentalCoroutinesApi::class, ExperimentalTypeInference::class)
+@file:OptIn(ExperimentalTypeInference::class)
package com.android.systemui.utils.coroutines.flow
import kotlin.experimental.ExperimentalTypeInference
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.FlowCollector
import kotlinx.coroutines.flow.conflate
diff --git a/ravenwood/texts/ravenwood-annotation-allowed-classes.txt b/ravenwood/texts/ravenwood-annotation-allowed-classes.txt
index f8315fe..383e75b 100644
--- a/ravenwood/texts/ravenwood-annotation-allowed-classes.txt
+++ b/ravenwood/texts/ravenwood-annotation-allowed-classes.txt
@@ -115,6 +115,7 @@
android.util.Xml
android.util.proto.EncodedBuffer
+android.util.proto.ProtoFieldFilter
android.util.proto.ProtoInputStream
android.util.proto.ProtoOutputStream
android.util.proto.ProtoParseException
diff --git a/ravenwood/texts/ravenwood-standard-options.txt b/ravenwood/texts/ravenwood-standard-options.txt
index 3ec3e3c..27223d8b 100644
--- a/ravenwood/texts/ravenwood-standard-options.txt
+++ b/ravenwood/texts/ravenwood-standard-options.txt
@@ -5,6 +5,8 @@
# Keep all classes / methods / fields, but make the methods throw.
--default-throw
+--delete-finals
+
# Uncomment below lines to enable each feature.
#--default-method-call-hook
diff --git a/ravenwood/tools/hoststubgen/hoststubgen-standard-options.txt b/ravenwood/tools/hoststubgen/hoststubgen-standard-options.txt
index 001943c..9c46a16 100644
--- a/ravenwood/tools/hoststubgen/hoststubgen-standard-options.txt
+++ b/ravenwood/tools/hoststubgen/hoststubgen-standard-options.txt
@@ -2,6 +2,8 @@
--debug
+--delete-finals
+
# Uncomment below lines to enable each feature.
#--default-method-call-hook
diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
index cc704b2..9859475 100644
--- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
+++ b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
@@ -411,6 +411,8 @@
stats = stats,
enablePreTrace = options.enablePreTrace.get,
enablePostTrace = options.enablePostTrace.get,
+ deleteClassFinals = options.deleteFinals.get,
+ deleteMethodFinals = options.deleteFinals.get,
)
outVisitor = BaseAdapter.getVisitor(
classInternalName, classes, outVisitor, filter,
diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt
index 55e853e..ae9276f 100644
--- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt
+++ b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt
@@ -106,6 +106,8 @@
var cleanUpOnError: SetOnce<Boolean> = SetOnce(false),
+ var deleteFinals: SetOnce<Boolean> = SetOnce(false),
+
var enableClassChecker: SetOnce<Boolean> = SetOnce(false),
var enablePreTrace: SetOnce<Boolean> = SetOnce(false),
var enablePostTrace: SetOnce<Boolean> = SetOnce(false),
@@ -218,6 +220,8 @@
"--gen-keep-all-file" ->
ret.inputJarAsKeepAllFile.set(nextArg())
+ "--delete-finals" -> ret.deleteFinals.set(true)
+
// Following options are for debugging.
"--enable-class-checker" -> ret.enableClassChecker.set(true)
"--no-class-checker" -> ret.enableClassChecker.set(false)
@@ -293,6 +297,7 @@
defaultMethodCallHook=$defaultMethodCallHook,
policyOverrideFiles=${policyOverrideFiles.toTypedArray().contentToString()},
defaultPolicy=$defaultPolicy,
+ deleteFinals=$deleteFinals,
cleanUpOnError=$cleanUpOnError,
enableClassChecker=$enableClassChecker,
enablePreTrace=$enablePreTrace,
diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/visitors/BaseAdapter.kt b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/visitors/BaseAdapter.kt
index 261ef59c..a08d1d6 100644
--- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/visitors/BaseAdapter.kt
+++ b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/visitors/BaseAdapter.kt
@@ -50,7 +50,13 @@
val errors: HostStubGenErrors,
val stats: HostStubGenStats?,
val enablePreTrace: Boolean,
- val enablePostTrace: Boolean
+ val enablePostTrace: Boolean,
+ val deleteClassFinals: Boolean,
+ val deleteMethodFinals: Boolean,
+ // We don't remove finals from fields, because final fields have a stronger memory
+ // guarantee than non-final fields, see:
+ // https://docs.oracle.com/javase/specs/jls/se22/html/jls-17.html#jls-17.5
+ // i.e. changing a final field to non-final _could_ result in different behavior.
)
protected lateinit var currentPackageName: String
@@ -58,14 +64,33 @@
protected var redirectionClass: String? = null
protected lateinit var classPolicy: FilterPolicyWithReason
+ private fun isEnum(access: Int): Boolean {
+ return (access and Opcodes.ACC_ENUM) != 0
+ }
+
+ protected fun modifyClassAccess(access: Int): Int {
+ if (options.deleteClassFinals && !isEnum(access)) {
+ return access and Opcodes.ACC_FINAL.inv()
+ }
+ return access
+ }
+
+ protected fun modifyMethodAccess(access: Int): Int {
+ if (options.deleteMethodFinals) {
+ return access and Opcodes.ACC_FINAL.inv()
+ }
+ return access
+ }
+
override fun visit(
version: Int,
- access: Int,
+ origAccess: Int,
name: String,
signature: String?,
superName: String?,
interfaces: Array<String>,
) {
+ val access = modifyClassAccess(origAccess)
super.visit(version, access, name, signature, superName, interfaces)
currentClassName = name
currentPackageName = getPackageNameFromFullClassName(name)
@@ -130,13 +155,14 @@
}
}
- override fun visitMethod(
- access: Int,
+ final override fun visitMethod(
+ origAccess: Int,
name: String,
descriptor: String,
signature: String?,
exceptions: Array<String>?,
): MethodVisitor? {
+ val access = modifyMethodAccess(origAccess)
if (skipMemberModificationNestCount > 0) {
return super.visitMethod(access, name, descriptor, signature, exceptions)
}
@@ -176,6 +202,7 @@
if (newAccess == NOT_COMPATIBLE) {
return null
}
+ newAccess = modifyMethodAccess(newAccess)
log.v(
"Emitting %s.%s%s as %s %s", currentClassName, name, descriptor,
diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/visitors/ImplGeneratingAdapter.kt b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/visitors/ImplGeneratingAdapter.kt
index 567a69e..70e7d46 100644
--- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/visitors/ImplGeneratingAdapter.kt
+++ b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/visitors/ImplGeneratingAdapter.kt
@@ -51,12 +51,13 @@
override fun visit(
version: Int,
- access: Int,
+ origAccess: Int,
name: String,
signature: String?,
superName: String?,
interfaces: Array<String>
) {
+ val access = modifyClassAccess(origAccess)
super.visit(version, access, name, signature, superName, interfaces)
classLoadHooks = filter.getClassLoadHooks(currentClassName)
diff --git a/ravenwood/tools/hoststubgen/test-tiny-framework/golden-output.RELEASE_TARGET_JAVA_21/01-hoststubgen-test-tiny-framework-orig-dump.txt b/ravenwood/tools/hoststubgen/test-tiny-framework/golden-output.RELEASE_TARGET_JAVA_21/01-hoststubgen-test-tiny-framework-orig-dump.txt
index 5e5ca62..b009b09 100644
--- a/ravenwood/tools/hoststubgen/test-tiny-framework/golden-output.RELEASE_TARGET_JAVA_21/01-hoststubgen-test-tiny-framework-orig-dump.txt
+++ b/ravenwood/tools/hoststubgen/test-tiny-framework/golden-output.RELEASE_TARGET_JAVA_21/01-hoststubgen-test-tiny-framework-orig-dump.txt
@@ -7,6 +7,8 @@
this_class: #x // android/hosttest/annotation/HostSideTestClassLoadHook
super_class: #x // java/lang/Object
interfaces: 1, fields: 0, methods: 1, attributes: 2
+Constant pool:
+{
public abstract java.lang.String value();
descriptor: ()Ljava/lang/String;
flags: (0x0401) ACC_PUBLIC, ACC_ABSTRACT
@@ -30,6 +32,8 @@
this_class: #x // android/hosttest/annotation/HostSideTestIgnore
super_class: #x // java/lang/Object
interfaces: 1, fields: 0, methods: 0, attributes: 2
+Constant pool:
+{
}
SourceFile: "HostSideTestIgnore.java"
RuntimeVisibleAnnotations:
@@ -50,6 +54,8 @@
this_class: #x // android/hosttest/annotation/HostSideTestKeep
super_class: #x // java/lang/Object
interfaces: 1, fields: 0, methods: 0, attributes: 2
+Constant pool:
+{
}
SourceFile: "HostSideTestKeep.java"
RuntimeVisibleAnnotations:
@@ -70,6 +76,8 @@
this_class: #x // android/hosttest/annotation/HostSideTestRedirect
super_class: #x // java/lang/Object
interfaces: 1, fields: 0, methods: 0, attributes: 2
+Constant pool:
+{
}
SourceFile: "HostSideTestRedirect.java"
RuntimeVisibleAnnotations:
@@ -90,6 +98,8 @@
this_class: #x // android/hosttest/annotation/HostSideTestRedirectionClass
super_class: #x // java/lang/Object
interfaces: 1, fields: 0, methods: 1, attributes: 2
+Constant pool:
+{
public abstract java.lang.String value();
descriptor: ()Ljava/lang/String;
flags: (0x0401) ACC_PUBLIC, ACC_ABSTRACT
@@ -113,6 +123,8 @@
this_class: #x // android/hosttest/annotation/HostSideTestRemove
super_class: #x // java/lang/Object
interfaces: 1, fields: 0, methods: 0, attributes: 2
+Constant pool:
+{
}
SourceFile: "HostSideTestRemove.java"
RuntimeVisibleAnnotations:
@@ -133,6 +145,8 @@
this_class: #x // android/hosttest/annotation/HostSideTestStaticInitializerKeep
super_class: #x // java/lang/Object
interfaces: 1, fields: 0, methods: 0, attributes: 2
+Constant pool:
+{
}
SourceFile: "HostSideTestStaticInitializerKeep.java"
RuntimeVisibleAnnotations:
@@ -153,6 +167,8 @@
this_class: #x // android/hosttest/annotation/HostSideTestSubstitute
super_class: #x // java/lang/Object
interfaces: 1, fields: 0, methods: 1, attributes: 2
+Constant pool:
+{
public abstract java.lang.String suffix();
descriptor: ()Ljava/lang/String;
flags: (0x0401) ACC_PUBLIC, ACC_ABSTRACT
@@ -176,6 +192,8 @@
this_class: #x // android/hosttest/annotation/HostSideTestThrow
super_class: #x // java/lang/Object
interfaces: 1, fields: 0, methods: 0, attributes: 2
+Constant pool:
+{
}
SourceFile: "HostSideTestThrow.java"
RuntimeVisibleAnnotations:
@@ -196,6 +214,8 @@
this_class: #x // android/hosttest/annotation/HostSideTestWholeClassKeep
super_class: #x // java/lang/Object
interfaces: 1, fields: 0, methods: 0, attributes: 2
+Constant pool:
+{
}
SourceFile: "HostSideTestWholeClassKeep.java"
RuntimeVisibleAnnotations:
@@ -216,6 +236,8 @@
this_class: #x // android/hosttest/annotation/tests/HostSideTestSuppress
super_class: #x // java/lang/Object
interfaces: 1, fields: 0, methods: 0, attributes: 2
+Constant pool:
+{
}
SourceFile: "HostSideTestSuppress.java"
RuntimeVisibleAnnotations:
@@ -232,6 +254,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy
super_class: #x // java/lang/Object
interfaces: 0, fields: 0, methods: 2, attributes: 3
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.IPretendingAidl$Stub$Proxy();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -273,6 +297,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub
super_class: #x // java/lang/Object
interfaces: 0, fields: 0, methods: 2, attributes: 3
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.IPretendingAidl$Stub();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -314,6 +340,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/IPretendingAidl
super_class: #x // java/lang/Object
interfaces: 0, fields: 0, methods: 0, attributes: 3
+Constant pool:
+{
}
SourceFile: "IPretendingAidl.java"
NestMembers:
@@ -331,6 +359,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/R$Nested
super_class: #x // java/lang/Object
interfaces: 0, fields: 1, methods: 2, attributes: 3
+Constant pool:
+{
public static int[] ARRAY;
descriptor: [I
flags: (0x0009) ACC_PUBLIC, ACC_STATIC
@@ -376,6 +406,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/R
super_class: #x // java/lang/Object
interfaces: 0, fields: 0, methods: 1, attributes: 3
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.R();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -396,13 +428,15 @@
public static #x= #x of #x; // Nested=class com/android/hoststubgen/test/tinyframework/R$Nested of class com/android/hoststubgen/test/tinyframework/R
## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations.class
Compiled from "TinyFrameworkAnnotations.java"
-public class com.android.hoststubgen.test.tinyframework.TinyFrameworkAnnotations
+public final class com.android.hoststubgen.test.tinyframework.TinyFrameworkAnnotations
minor version: 0
major version: 65
- flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+ flags: (0x0031) ACC_PUBLIC, ACC_FINAL, ACC_SUPER
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations
super_class: #x // java/lang/Object
interfaces: 0, fields: 2, methods: 9, attributes: 2
+Constant pool:
+{
public int keep;
descriptor: I
flags: (0x0001) ACC_PUBLIC
@@ -433,9 +467,9 @@
x: #x()
android.hosttest.annotation.HostSideTestKeep
- public int addOne(int);
+ public final int addOne(int);
descriptor: (I)I
- flags: (0x0001) ACC_PUBLIC
+ flags: (0x0011) ACC_PUBLIC, ACC_FINAL
Code:
stack=2, locals=2, args_size=2
x: iload_1
@@ -505,18 +539,18 @@
0 4 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations;
0 4 1 value I
- public static native int nativeAddThree(int);
+ public static final native int nativeAddThree(int);
descriptor: (I)I
- flags: (0x0109) ACC_PUBLIC, ACC_STATIC, ACC_NATIVE
+ flags: (0x0119) ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_NATIVE
RuntimeInvisibleAnnotations:
x: #x(#x=s#x)
android.hosttest.annotation.HostSideTestSubstitute(
suffix="_host"
)
- private static int nativeAddThree_host(int);
+ private static final int nativeAddThree_host(int);
descriptor: (I)I
- flags: (0x000a) ACC_PRIVATE, ACC_STATIC
+ flags: (0x001a) ACC_PRIVATE, ACC_STATIC, ACC_FINAL
Code:
stack=2, locals=1, args_size=1
x: iload_0
@@ -578,6 +612,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassLoadHook
super_class: #x // java/lang/Object
interfaces: 0, fields: 1, methods: 3, attributes: 2
+Constant pool:
+{
public static final java.util.Set<java.lang.Class<?>> sLoadedClasses;
descriptor: Ljava/util/Set;
flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL
@@ -640,6 +676,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWideAnnotations
super_class: #x // java/lang/Object
interfaces: 0, fields: 2, methods: 6, attributes: 2
+Constant pool:
+{
public int keep;
descriptor: I
flags: (0x0001) ACC_PUBLIC
@@ -764,6 +802,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializerDefault
super_class: #x // java/lang/Object
interfaces: 0, fields: 2, methods: 2, attributes: 2
+Constant pool:
+{
public static boolean sInitialized;
descriptor: Z
flags: (0x0009) ACC_PUBLIC, ACC_STATIC
@@ -818,6 +858,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializerStub
super_class: #x // java/lang/Object
interfaces: 0, fields: 2, methods: 2, attributes: 2
+Constant pool:
+{
public static boolean sInitialized;
descriptor: Z
flags: (0x0009) ACC_PUBLIC, ACC_STATIC
@@ -878,6 +920,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex
super_class: #x // java/lang/Enum
interfaces: 0, fields: 6, methods: 7, attributes: 3
+Constant pool:
+{
public static final com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex RED;
descriptor: Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
flags: (0x4019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
@@ -1081,6 +1125,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple
super_class: #x // java/lang/Enum
interfaces: 0, fields: 3, methods: 5, attributes: 3
+Constant pool:
+{
public static final com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple CAT;
descriptor: Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
flags: (0x4019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
@@ -1202,6 +1248,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkExceptionTester
super_class: #x // java/lang/Object
interfaces: 0, fields: 0, methods: 2, attributes: 2
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.TinyFrameworkExceptionTester();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -1256,6 +1304,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy
super_class: #x // java/lang/Object
interfaces: 0, fields: 2, methods: 17, attributes: 1
+Constant pool:
+{
public int stub;
descriptor: I
flags: (0x0001) ACC_PUBLIC
@@ -1507,6 +1557,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas$Nested
super_class: #x // java/lang/Object
interfaces: 0, fields: 2, methods: 8, attributes: 5
+Constant pool:
+{
public final java.util.function.Supplier<java.lang.Integer> mSupplier;
descriptor: Ljava/util/function/Supplier;
flags: (0x0011) ACC_PUBLIC, ACC_FINAL
@@ -1661,6 +1713,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas
super_class: #x // java/lang/Object
interfaces: 0, fields: 2, methods: 8, attributes: 5
+Constant pool:
+{
public final java.util.function.Supplier<java.lang.Integer> mSupplier;
descriptor: Ljava/util/function/Supplier;
flags: (0x0011) ACC_PUBLIC, ACC_FINAL
@@ -1816,6 +1870,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo
super_class: #x // java/lang/Object
interfaces: 0, fields: 0, methods: 3, attributes: 3
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallReplace$ReplaceTo();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -1873,6 +1929,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
super_class: #x // java/lang/Object
interfaces: 0, fields: 0, methods: 5, attributes: 5
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallReplace();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -1984,6 +2042,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative
super_class: #x // java/lang/Object
interfaces: 0, fields: 1, methods: 14, attributes: 2
+Constant pool:
+{
int value;
descriptor: I
flags: (0x0000)
@@ -2157,6 +2217,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host
super_class: #x // java/lang/Object
interfaces: 0, fields: 0, methods: 7, attributes: 2
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.TinyFrameworkNative_host();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -2263,6 +2325,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1
super_class: #x // java/lang/Object
interfaces: 1, fields: 0, methods: 3, attributes: 5
+Constant pool:
+{
com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$1(com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses);
descriptor: (Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;)V
flags: (0x0000)
@@ -2321,6 +2385,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2
super_class: #x // java/lang/Object
interfaces: 1, fields: 0, methods: 3, attributes: 5
+Constant pool:
+{
com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$2();
descriptor: ()V
flags: (0x0000)
@@ -2375,6 +2441,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3
super_class: #x // java/lang/Object
interfaces: 1, fields: 0, methods: 3, attributes: 5
+Constant pool:
+{
com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$3(com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses);
descriptor: (Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;)V
flags: (0x0000)
@@ -2433,6 +2501,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4
super_class: #x // java/lang/Object
interfaces: 1, fields: 0, methods: 3, attributes: 5
+Constant pool:
+{
com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$4();
descriptor: ()V
flags: (0x0000)
@@ -2487,6 +2557,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass
super_class: #x // java/lang/Object
interfaces: 0, fields: 1, methods: 1, attributes: 3
+Constant pool:
+{
public int value;
descriptor: I
flags: (0x0001) ACC_PUBLIC
@@ -2521,6 +2593,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass
super_class: #x // java/lang/Object
interfaces: 0, fields: 1, methods: 1, attributes: 3
+Constant pool:
+{
public int value;
descriptor: I
flags: (0x0001) ACC_PUBLIC
@@ -2558,6 +2632,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1
super_class: #x // java/lang/Object
interfaces: 1, fields: 0, methods: 3, attributes: 5
+Constant pool:
+{
com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$StaticNestedClass$1();
descriptor: ()V
flags: (0x0000)
@@ -2613,6 +2689,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$Double$NestedClass
super_class: #x // java/lang/Object
interfaces: 0, fields: 1, methods: 1, attributes: 3
+Constant pool:
+{
public int value;
descriptor: I
flags: (0x0001) ACC_PUBLIC
@@ -2647,6 +2725,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass
super_class: #x // java/lang/Object
interfaces: 0, fields: 1, methods: 2, attributes: 3
+Constant pool:
+{
public int value;
descriptor: I
flags: (0x0001) ACC_PUBLIC
@@ -2694,6 +2774,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass
super_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass
interfaces: 0, fields: 0, methods: 1, attributes: 3
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$SubClass(int);
descriptor: (I)V
flags: (0x0001) ACC_PUBLIC
@@ -2723,6 +2805,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
super_class: #x // java/lang/Object
interfaces: 0, fields: 2, methods: 4, attributes: 4
+Constant pool:
+{
public final java.util.function.Supplier<java.lang.Integer> mSupplier;
descriptor: Ljava/util/function/Supplier;
flags: (0x0011) ACC_PUBLIC, ACC_FINAL
@@ -2827,6 +2911,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkPackageRedirect
super_class: #x // java/lang/Object
interfaces: 0, fields: 0, methods: 2, attributes: 2
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.TinyFrameworkPackageRedirect();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -2869,6 +2955,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkRenamedClassCaller
super_class: #x // java/lang/Object
interfaces: 0, fields: 0, methods: 2, attributes: 2
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.TinyFrameworkRenamedClassCaller();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -2911,6 +2999,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed
super_class: #x // java/lang/Object
interfaces: 0, fields: 1, methods: 2, attributes: 2
+Constant pool:
+{
private final int mValue;
descriptor: I
flags: (0x0012) ACC_PRIVATE, ACC_FINAL
@@ -2958,6 +3048,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/packagetest/A
super_class: #x // java/lang/Object
interfaces: 0, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.packagetest.A();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -2981,6 +3073,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/packagetest/B
super_class: #x // java/lang/Object
interfaces: 0, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.packagetest.B();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -3004,6 +3098,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/packagetest/sub/A
super_class: #x // java/lang/Object
interfaces: 0, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.packagetest.sub.A();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -3027,6 +3123,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/packagetest/sub/B
super_class: #x // java/lang/Object
interfaces: 0, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.packagetest.sub.B();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -3050,6 +3148,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/C1
super_class: #x // java/lang/Object
interfaces: 0, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.subclasstest.C1();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -3073,6 +3173,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/C2
super_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/C1
interfaces: 0, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.subclasstest.C2();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -3096,6 +3198,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/C3
super_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/C2
interfaces: 0, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.subclasstest.C3();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -3119,6 +3223,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/CA
super_class: #x // java/lang/Object
interfaces: 0, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.subclasstest.CA();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -3142,6 +3248,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/CB
super_class: #x // java/lang/Object
interfaces: 0, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.subclasstest.CB();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -3165,6 +3273,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/Class_C1
super_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/C1
interfaces: 0, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.subclasstest.Class_C1();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -3188,6 +3298,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/Class_C2
super_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/C2
interfaces: 0, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.subclasstest.Class_C2();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -3211,6 +3323,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/Class_C3
super_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/C3
interfaces: 0, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.subclasstest.Class_C3();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -3234,6 +3348,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/Class_CA
super_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/CA
interfaces: 0, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.subclasstest.Class_CA();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -3257,6 +3373,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/Class_CB
super_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/CB
interfaces: 0, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.subclasstest.Class_CB();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -3280,6 +3398,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/Class_CB_IA
super_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/CB
interfaces: 1, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.subclasstest.Class_CB_IA();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -3303,6 +3423,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/Class_I1
super_class: #x // java/lang/Object
interfaces: 1, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.subclasstest.Class_I1();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -3326,6 +3448,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/Class_I1_IA
super_class: #x // java/lang/Object
interfaces: 2, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.subclasstest.Class_I1_IA();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -3349,6 +3473,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/Class_I2
super_class: #x // java/lang/Object
interfaces: 1, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.subclasstest.Class_I2();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -3372,6 +3498,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/Class_I3
super_class: #x // java/lang/Object
interfaces: 1, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.subclasstest.Class_I3();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -3395,6 +3523,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/Class_I3_IA
super_class: #x // java/lang/Object
interfaces: 2, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.subclasstest.Class_I3_IA();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -3418,6 +3548,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/Class_IA
super_class: #x // java/lang/Object
interfaces: 1, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.subclasstest.Class_IA();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -3441,6 +3573,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/Class_IA_I1
super_class: #x // java/lang/Object
interfaces: 2, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.subclasstest.Class_IA_I1();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -3464,6 +3598,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/Class_IA_I3
super_class: #x // java/lang/Object
interfaces: 2, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.subclasstest.Class_IA_I3();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -3487,6 +3623,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/Class_IB
super_class: #x // java/lang/Object
interfaces: 1, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.subclasstest.Class_IB();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -3510,6 +3648,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/Class_IB_IA
super_class: #x // java/lang/Object
interfaces: 2, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.subclasstest.Class_IB_IA();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -3533,6 +3673,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/Class_None
super_class: #x // java/lang/Object
interfaces: 0, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.subclasstest.Class_None();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -3556,6 +3698,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/I1
super_class: #x // java/lang/Object
interfaces: 0, fields: 0, methods: 0, attributes: 1
+Constant pool:
+{
}
SourceFile: "I1.java"
## Class: com/android/hoststubgen/test/tinyframework/subclasstest/I2.class
@@ -3567,6 +3711,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/I2
super_class: #x // java/lang/Object
interfaces: 1, fields: 0, methods: 0, attributes: 1
+Constant pool:
+{
}
SourceFile: "I2.java"
## Class: com/android/hoststubgen/test/tinyframework/subclasstest/I3.class
@@ -3578,6 +3724,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/I3
super_class: #x // java/lang/Object
interfaces: 1, fields: 0, methods: 0, attributes: 1
+Constant pool:
+{
}
SourceFile: "I3.java"
## Class: com/android/hoststubgen/test/tinyframework/subclasstest/IA.class
@@ -3589,6 +3737,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/IA
super_class: #x // java/lang/Object
interfaces: 0, fields: 0, methods: 0, attributes: 1
+Constant pool:
+{
}
SourceFile: "IA.java"
## Class: com/android/hoststubgen/test/tinyframework/subclasstest/IB.class
@@ -3600,6 +3750,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/IB
super_class: #x // java/lang/Object
interfaces: 0, fields: 0, methods: 0, attributes: 1
+Constant pool:
+{
}
SourceFile: "IB.java"
## Class: com/supported/UnsupportedClass.class
@@ -3611,6 +3763,8 @@
this_class: #x // com/supported/UnsupportedClass
super_class: #x // java/lang/Object
interfaces: 0, fields: 1, methods: 2, attributes: 2
+Constant pool:
+{
private final int mValue;
descriptor: I
flags: (0x0012) ACC_PRIVATE, ACC_FINAL
@@ -3658,6 +3812,8 @@
this_class: #x // com/unsupported/UnsupportedClass
super_class: #x // java/lang/Object
interfaces: 0, fields: 0, methods: 2, attributes: 2
+Constant pool:
+{
public com.unsupported.UnsupportedClass(int);
descriptor: (I)V
flags: (0x0001) ACC_PUBLIC
diff --git a/ravenwood/tools/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt b/ravenwood/tools/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt
index 103e152..ad41342 100644
--- a/ravenwood/tools/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt
+++ b/ravenwood/tools/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt
@@ -7,6 +7,8 @@
this_class: #x // android/hosttest/annotation/HostSideTestClassLoadHook
super_class: #x // java/lang/Object
interfaces: 1, fields: 0, methods: 1, attributes: 2
+Constant pool:
+{
public abstract java.lang.String value();
descriptor: ()Ljava/lang/String;
flags: (0x0401) ACC_PUBLIC, ACC_ABSTRACT
@@ -30,6 +32,8 @@
this_class: #x // android/hosttest/annotation/HostSideTestIgnore
super_class: #x // java/lang/Object
interfaces: 1, fields: 0, methods: 0, attributes: 2
+Constant pool:
+{
}
SourceFile: "HostSideTestIgnore.java"
RuntimeVisibleAnnotations:
@@ -50,6 +54,8 @@
this_class: #x // android/hosttest/annotation/HostSideTestKeep
super_class: #x // java/lang/Object
interfaces: 1, fields: 0, methods: 0, attributes: 2
+Constant pool:
+{
}
SourceFile: "HostSideTestKeep.java"
RuntimeVisibleAnnotations:
@@ -70,6 +76,8 @@
this_class: #x // android/hosttest/annotation/HostSideTestRedirect
super_class: #x // java/lang/Object
interfaces: 1, fields: 0, methods: 0, attributes: 2
+Constant pool:
+{
}
SourceFile: "HostSideTestRedirect.java"
RuntimeVisibleAnnotations:
@@ -90,6 +98,8 @@
this_class: #x // android/hosttest/annotation/HostSideTestRedirectionClass
super_class: #x // java/lang/Object
interfaces: 1, fields: 0, methods: 1, attributes: 2
+Constant pool:
+{
public abstract java.lang.String value();
descriptor: ()Ljava/lang/String;
flags: (0x0401) ACC_PUBLIC, ACC_ABSTRACT
@@ -113,6 +123,8 @@
this_class: #x // android/hosttest/annotation/HostSideTestRemove
super_class: #x // java/lang/Object
interfaces: 1, fields: 0, methods: 0, attributes: 2
+Constant pool:
+{
}
SourceFile: "HostSideTestRemove.java"
RuntimeVisibleAnnotations:
@@ -133,6 +145,8 @@
this_class: #x // android/hosttest/annotation/HostSideTestStaticInitializerKeep
super_class: #x // java/lang/Object
interfaces: 1, fields: 0, methods: 0, attributes: 2
+Constant pool:
+{
}
SourceFile: "HostSideTestStaticInitializerKeep.java"
RuntimeVisibleAnnotations:
@@ -153,6 +167,8 @@
this_class: #x // android/hosttest/annotation/HostSideTestSubstitute
super_class: #x // java/lang/Object
interfaces: 1, fields: 0, methods: 1, attributes: 2
+Constant pool:
+{
public abstract java.lang.String suffix();
descriptor: ()Ljava/lang/String;
flags: (0x0401) ACC_PUBLIC, ACC_ABSTRACT
@@ -176,6 +192,8 @@
this_class: #x // android/hosttest/annotation/HostSideTestThrow
super_class: #x // java/lang/Object
interfaces: 1, fields: 0, methods: 0, attributes: 2
+Constant pool:
+{
}
SourceFile: "HostSideTestThrow.java"
RuntimeVisibleAnnotations:
@@ -196,6 +214,8 @@
this_class: #x // android/hosttest/annotation/HostSideTestWholeClassKeep
super_class: #x // java/lang/Object
interfaces: 1, fields: 0, methods: 0, attributes: 2
+Constant pool:
+{
}
SourceFile: "HostSideTestWholeClassKeep.java"
RuntimeVisibleAnnotations:
@@ -216,6 +236,8 @@
this_class: #x // android/hosttest/annotation/tests/HostSideTestSuppress
super_class: #x // java/lang/Object
interfaces: 1, fields: 0, methods: 0, attributes: 2
+Constant pool:
+{
}
SourceFile: "HostSideTestSuppress.java"
RuntimeVisibleAnnotations:
@@ -232,6 +254,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy
super_class: #x // java/lang/Object
interfaces: 0, fields: 0, methods: 2, attributes: 3
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.IPretendingAidl$Stub$Proxy();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -273,6 +297,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub
super_class: #x // java/lang/Object
interfaces: 0, fields: 0, methods: 2, attributes: 3
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.IPretendingAidl$Stub();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -314,6 +340,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/IPretendingAidl
super_class: #x // java/lang/Object
interfaces: 0, fields: 0, methods: 0, attributes: 3
+Constant pool:
+{
}
SourceFile: "IPretendingAidl.java"
NestMembers:
@@ -331,6 +359,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/R$Nested
super_class: #x // java/lang/Object
interfaces: 0, fields: 1, methods: 2, attributes: 3
+Constant pool:
+{
public static int[] ARRAY;
descriptor: [I
flags: (0x0009) ACC_PUBLIC, ACC_STATIC
@@ -376,6 +406,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/R
super_class: #x // java/lang/Object
interfaces: 0, fields: 0, methods: 1, attributes: 3
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.R();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -396,13 +428,15 @@
public static #x= #x of #x; // Nested=class com/android/hoststubgen/test/tinyframework/R$Nested of class com/android/hoststubgen/test/tinyframework/R
## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations.class
Compiled from "TinyFrameworkAnnotations.java"
-public class com.android.hoststubgen.test.tinyframework.TinyFrameworkAnnotations
+public final class com.android.hoststubgen.test.tinyframework.TinyFrameworkAnnotations
minor version: 0
major version: 61
- flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+ flags: (0x0031) ACC_PUBLIC, ACC_FINAL, ACC_SUPER
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations
super_class: #x // java/lang/Object
interfaces: 0, fields: 2, methods: 9, attributes: 2
+Constant pool:
+{
public int keep;
descriptor: I
flags: (0x0001) ACC_PUBLIC
@@ -433,9 +467,9 @@
x: #x()
android.hosttest.annotation.HostSideTestKeep
- public int addOne(int);
+ public final int addOne(int);
descriptor: (I)I
- flags: (0x0001) ACC_PUBLIC
+ flags: (0x0011) ACC_PUBLIC, ACC_FINAL
Code:
stack=2, locals=2, args_size=2
x: iload_1
@@ -505,18 +539,18 @@
0 4 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations;
0 4 1 value I
- public static native int nativeAddThree(int);
+ public static final native int nativeAddThree(int);
descriptor: (I)I
- flags: (0x0109) ACC_PUBLIC, ACC_STATIC, ACC_NATIVE
+ flags: (0x0119) ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_NATIVE
RuntimeInvisibleAnnotations:
x: #x(#x=s#x)
android.hosttest.annotation.HostSideTestSubstitute(
suffix="_host"
)
- private static int nativeAddThree_host(int);
+ private static final int nativeAddThree_host(int);
descriptor: (I)I
- flags: (0x000a) ACC_PRIVATE, ACC_STATIC
+ flags: (0x001a) ACC_PRIVATE, ACC_STATIC, ACC_FINAL
Code:
stack=2, locals=1, args_size=1
x: iload_0
@@ -578,6 +612,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassLoadHook
super_class: #x // java/lang/Object
interfaces: 0, fields: 1, methods: 3, attributes: 2
+Constant pool:
+{
public static final java.util.Set<java.lang.Class<?>> sLoadedClasses;
descriptor: Ljava/util/Set;
flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL
@@ -640,6 +676,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWideAnnotations
super_class: #x // java/lang/Object
interfaces: 0, fields: 2, methods: 6, attributes: 2
+Constant pool:
+{
public int keep;
descriptor: I
flags: (0x0001) ACC_PUBLIC
@@ -764,6 +802,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializerDefault
super_class: #x // java/lang/Object
interfaces: 0, fields: 2, methods: 2, attributes: 2
+Constant pool:
+{
public static boolean sInitialized;
descriptor: Z
flags: (0x0009) ACC_PUBLIC, ACC_STATIC
@@ -818,6 +858,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializerStub
super_class: #x // java/lang/Object
interfaces: 0, fields: 2, methods: 2, attributes: 2
+Constant pool:
+{
public static boolean sInitialized;
descriptor: Z
flags: (0x0009) ACC_PUBLIC, ACC_STATIC
@@ -878,6 +920,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex
super_class: #x // java/lang/Enum
interfaces: 0, fields: 6, methods: 7, attributes: 3
+Constant pool:
+{
public static final com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex RED;
descriptor: Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
flags: (0x4019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
@@ -1081,6 +1125,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple
super_class: #x // java/lang/Enum
interfaces: 0, fields: 3, methods: 5, attributes: 3
+Constant pool:
+{
public static final com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple CAT;
descriptor: Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
flags: (0x4019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
@@ -1202,6 +1248,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkExceptionTester
super_class: #x // java/lang/Object
interfaces: 0, fields: 0, methods: 2, attributes: 2
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.TinyFrameworkExceptionTester();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -1256,6 +1304,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy
super_class: #x // java/lang/Object
interfaces: 0, fields: 2, methods: 17, attributes: 1
+Constant pool:
+{
public int stub;
descriptor: I
flags: (0x0001) ACC_PUBLIC
@@ -1507,6 +1557,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas$Nested
super_class: #x // java/lang/Object
interfaces: 0, fields: 2, methods: 8, attributes: 5
+Constant pool:
+{
public final java.util.function.Supplier<java.lang.Integer> mSupplier;
descriptor: Ljava/util/function/Supplier;
flags: (0x0011) ACC_PUBLIC, ACC_FINAL
@@ -1661,6 +1713,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas
super_class: #x // java/lang/Object
interfaces: 0, fields: 2, methods: 8, attributes: 5
+Constant pool:
+{
public final java.util.function.Supplier<java.lang.Integer> mSupplier;
descriptor: Ljava/util/function/Supplier;
flags: (0x0011) ACC_PUBLIC, ACC_FINAL
@@ -1816,6 +1870,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo
super_class: #x // java/lang/Object
interfaces: 0, fields: 0, methods: 3, attributes: 3
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallReplace$ReplaceTo();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -1873,6 +1929,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
super_class: #x // java/lang/Object
interfaces: 0, fields: 0, methods: 5, attributes: 5
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallReplace();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -1984,6 +2042,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative
super_class: #x // java/lang/Object
interfaces: 0, fields: 1, methods: 14, attributes: 2
+Constant pool:
+{
int value;
descriptor: I
flags: (0x0000)
@@ -2157,6 +2217,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host
super_class: #x // java/lang/Object
interfaces: 0, fields: 0, methods: 7, attributes: 2
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.TinyFrameworkNative_host();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -2263,6 +2325,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1
super_class: #x // java/lang/Object
interfaces: 1, fields: 1, methods: 3, attributes: 5
+Constant pool:
+{
final com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses this$0;
descriptor: Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;
flags: (0x1010) ACC_FINAL, ACC_SYNTHETIC
@@ -2328,6 +2392,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2
super_class: #x // java/lang/Object
interfaces: 1, fields: 0, methods: 3, attributes: 5
+Constant pool:
+{
com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$2();
descriptor: ()V
flags: (0x0000)
@@ -2382,6 +2448,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3
super_class: #x // java/lang/Object
interfaces: 1, fields: 1, methods: 3, attributes: 5
+Constant pool:
+{
final com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses this$0;
descriptor: Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;
flags: (0x1010) ACC_FINAL, ACC_SYNTHETIC
@@ -2447,6 +2515,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4
super_class: #x // java/lang/Object
interfaces: 1, fields: 0, methods: 3, attributes: 5
+Constant pool:
+{
com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$4();
descriptor: ()V
flags: (0x0000)
@@ -2501,6 +2571,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass
super_class: #x // java/lang/Object
interfaces: 0, fields: 1, methods: 1, attributes: 3
+Constant pool:
+{
public int value;
descriptor: I
flags: (0x0001) ACC_PUBLIC
@@ -2535,6 +2607,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass
super_class: #x // java/lang/Object
interfaces: 0, fields: 2, methods: 1, attributes: 3
+Constant pool:
+{
public int value;
descriptor: I
flags: (0x0001) ACC_PUBLIC
@@ -2579,6 +2653,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1
super_class: #x // java/lang/Object
interfaces: 1, fields: 0, methods: 3, attributes: 5
+Constant pool:
+{
com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$StaticNestedClass$1();
descriptor: ()V
flags: (0x0000)
@@ -2634,6 +2710,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$Double$NestedClass
super_class: #x // java/lang/Object
interfaces: 0, fields: 1, methods: 1, attributes: 3
+Constant pool:
+{
public int value;
descriptor: I
flags: (0x0001) ACC_PUBLIC
@@ -2668,6 +2746,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass
super_class: #x // java/lang/Object
interfaces: 0, fields: 1, methods: 2, attributes: 3
+Constant pool:
+{
public int value;
descriptor: I
flags: (0x0001) ACC_PUBLIC
@@ -2715,6 +2795,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass
super_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass
interfaces: 0, fields: 0, methods: 1, attributes: 3
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$SubClass(int);
descriptor: (I)V
flags: (0x0001) ACC_PUBLIC
@@ -2744,6 +2826,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
super_class: #x // java/lang/Object
interfaces: 0, fields: 2, methods: 4, attributes: 4
+Constant pool:
+{
public final java.util.function.Supplier<java.lang.Integer> mSupplier;
descriptor: Ljava/util/function/Supplier;
flags: (0x0011) ACC_PUBLIC, ACC_FINAL
@@ -2848,6 +2932,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkPackageRedirect
super_class: #x // java/lang/Object
interfaces: 0, fields: 0, methods: 2, attributes: 2
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.TinyFrameworkPackageRedirect();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -2890,6 +2976,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkRenamedClassCaller
super_class: #x // java/lang/Object
interfaces: 0, fields: 0, methods: 2, attributes: 2
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.TinyFrameworkRenamedClassCaller();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -2932,6 +3020,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed
super_class: #x // java/lang/Object
interfaces: 0, fields: 1, methods: 2, attributes: 2
+Constant pool:
+{
private final int mValue;
descriptor: I
flags: (0x0012) ACC_PRIVATE, ACC_FINAL
@@ -2979,6 +3069,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/packagetest/A
super_class: #x // java/lang/Object
interfaces: 0, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.packagetest.A();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -3002,6 +3094,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/packagetest/B
super_class: #x // java/lang/Object
interfaces: 0, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.packagetest.B();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -3025,6 +3119,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/packagetest/sub/A
super_class: #x // java/lang/Object
interfaces: 0, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.packagetest.sub.A();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -3048,6 +3144,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/packagetest/sub/B
super_class: #x // java/lang/Object
interfaces: 0, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.packagetest.sub.B();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -3071,6 +3169,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/C1
super_class: #x // java/lang/Object
interfaces: 0, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.subclasstest.C1();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -3094,6 +3194,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/C2
super_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/C1
interfaces: 0, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.subclasstest.C2();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -3117,6 +3219,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/C3
super_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/C2
interfaces: 0, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.subclasstest.C3();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -3140,6 +3244,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/CA
super_class: #x // java/lang/Object
interfaces: 0, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.subclasstest.CA();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -3163,6 +3269,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/CB
super_class: #x // java/lang/Object
interfaces: 0, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.subclasstest.CB();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -3186,6 +3294,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/Class_C1
super_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/C1
interfaces: 0, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.subclasstest.Class_C1();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -3209,6 +3319,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/Class_C2
super_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/C2
interfaces: 0, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.subclasstest.Class_C2();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -3232,6 +3344,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/Class_C3
super_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/C3
interfaces: 0, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.subclasstest.Class_C3();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -3255,6 +3369,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/Class_CA
super_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/CA
interfaces: 0, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.subclasstest.Class_CA();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -3278,6 +3394,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/Class_CB
super_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/CB
interfaces: 0, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.subclasstest.Class_CB();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -3301,6 +3419,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/Class_CB_IA
super_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/CB
interfaces: 1, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.subclasstest.Class_CB_IA();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -3324,6 +3444,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/Class_I1
super_class: #x // java/lang/Object
interfaces: 1, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.subclasstest.Class_I1();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -3347,6 +3469,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/Class_I1_IA
super_class: #x // java/lang/Object
interfaces: 2, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.subclasstest.Class_I1_IA();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -3370,6 +3494,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/Class_I2
super_class: #x // java/lang/Object
interfaces: 1, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.subclasstest.Class_I2();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -3393,6 +3519,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/Class_I3
super_class: #x // java/lang/Object
interfaces: 1, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.subclasstest.Class_I3();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -3416,6 +3544,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/Class_I3_IA
super_class: #x // java/lang/Object
interfaces: 2, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.subclasstest.Class_I3_IA();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -3439,6 +3569,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/Class_IA
super_class: #x // java/lang/Object
interfaces: 1, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.subclasstest.Class_IA();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -3462,6 +3594,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/Class_IA_I1
super_class: #x // java/lang/Object
interfaces: 2, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.subclasstest.Class_IA_I1();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -3485,6 +3619,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/Class_IA_I3
super_class: #x // java/lang/Object
interfaces: 2, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.subclasstest.Class_IA_I3();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -3508,6 +3644,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/Class_IB
super_class: #x // java/lang/Object
interfaces: 1, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.subclasstest.Class_IB();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -3531,6 +3669,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/Class_IB_IA
super_class: #x // java/lang/Object
interfaces: 2, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.subclasstest.Class_IB_IA();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -3554,6 +3694,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/Class_None
super_class: #x // java/lang/Object
interfaces: 0, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
public com.android.hoststubgen.test.tinyframework.subclasstest.Class_None();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -3577,6 +3719,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/I1
super_class: #x // java/lang/Object
interfaces: 0, fields: 0, methods: 0, attributes: 1
+Constant pool:
+{
}
SourceFile: "I1.java"
## Class: com/android/hoststubgen/test/tinyframework/subclasstest/I2.class
@@ -3588,6 +3732,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/I2
super_class: #x // java/lang/Object
interfaces: 1, fields: 0, methods: 0, attributes: 1
+Constant pool:
+{
}
SourceFile: "I2.java"
## Class: com/android/hoststubgen/test/tinyframework/subclasstest/I3.class
@@ -3599,6 +3745,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/I3
super_class: #x // java/lang/Object
interfaces: 1, fields: 0, methods: 0, attributes: 1
+Constant pool:
+{
}
SourceFile: "I3.java"
## Class: com/android/hoststubgen/test/tinyframework/subclasstest/IA.class
@@ -3610,6 +3758,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/IA
super_class: #x // java/lang/Object
interfaces: 0, fields: 0, methods: 0, attributes: 1
+Constant pool:
+{
}
SourceFile: "IA.java"
## Class: com/android/hoststubgen/test/tinyframework/subclasstest/IB.class
@@ -3621,6 +3771,8 @@
this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/IB
super_class: #x // java/lang/Object
interfaces: 0, fields: 0, methods: 0, attributes: 1
+Constant pool:
+{
}
SourceFile: "IB.java"
## Class: com/supported/UnsupportedClass.class
@@ -3632,6 +3784,8 @@
this_class: #x // com/supported/UnsupportedClass
super_class: #x // java/lang/Object
interfaces: 0, fields: 1, methods: 2, attributes: 2
+Constant pool:
+{
private final int mValue;
descriptor: I
flags: (0x0012) ACC_PRIVATE, ACC_FINAL
@@ -3679,6 +3833,8 @@
this_class: #x // com/unsupported/UnsupportedClass
super_class: #x // java/lang/Object
interfaces: 0, fields: 0, methods: 2, attributes: 2
+Constant pool:
+{
public com.unsupported.UnsupportedClass(int);
descriptor: (I)V
flags: (0x0001) ACC_PUBLIC
diff --git a/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations.java b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations.java
index 3415deb..674937d 100644
--- a/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations.java
+++ b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations.java
@@ -28,7 +28,7 @@
@HostSideTestKeep
@HostSideTestClassLoadHook(
"com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook.onClassLoaded")
-public class TinyFrameworkAnnotations {
+public final class TinyFrameworkAnnotations {
@HostSideTestKeep
public TinyFrameworkAnnotations() {
}
@@ -42,7 +42,7 @@
public int remove;
@HostSideTestKeep
- public int addOne(int value) {
+ public final int addOne(int value) {
return value + 1;
}
@@ -61,10 +61,10 @@
}
@HostSideTestSubstitute(suffix = "_host")
- public static native int nativeAddThree(int value);
+ public final static native int nativeAddThree(int value);
// This method is private, but at runtime, it'll inherit the visibility of the original method
- private static int nativeAddThree_host(int value) {
+ private final static int nativeAddThree_host(int value) {
return value + 3;
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
index 79888b0..70c4c13 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.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.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 @@
private MouseKeysInterceptor mMouseKeysInterceptor;
+ private MagnificationKeyHandler mMagnificationKeyHandler;
+
private boolean mInstalled;
private int mUserId;
@@ -235,74 +235,6 @@
*/
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 @@
});
}
- 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 @@
}
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 @@
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 @@
mMouseKeysInterceptor = null;
}
- if (mKeyGestureEventHandlerInstalled) {
- mInputManager.unregisterKeyGestureEventHandler(mKeyGestureEventHandler);
- mKeyGestureEventHandlerInstalled = false;
+ if (mMagnificationKeyHandler != null) {
+ mMagnificationKeyHandler.onDestroy();
+ mMagnificationKeyHandler = null;
}
}
@@ -1365,6 +1303,8 @@
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/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 7275881..875b655 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -35,8 +35,6 @@
import static android.accessibilityservice.AccessibilityTrace.FLAGS_PACKAGE_BROADCAST_RECEIVER;
import static android.accessibilityservice.AccessibilityTrace.FLAGS_USER_BROADCAST_RECEIVER;
import static android.accessibilityservice.AccessibilityTrace.FLAGS_WINDOW_MANAGER_INTERNAL;
-import static android.companion.virtual.VirtualDeviceManager.ACTION_VIRTUAL_DEVICE_REMOVED;
-import static android.companion.virtual.VirtualDeviceManager.EXTRA_VIRTUAL_DEVICE_ID;
import static android.content.Context.DEVICE_ID_DEFAULT;
import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU;
import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_GESTURE;
@@ -1116,22 +1114,6 @@
mContext.registerReceiverAsUser(
receiver, UserHandle.ALL, filter, null, mMainHandler,
Context.RECEIVER_EXPORTED);
-
- if (!android.companion.virtual.flags.Flags.vdmPublicApis()) {
- final BroadcastReceiver virtualDeviceReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- final int deviceId = intent.getIntExtra(
- EXTRA_VIRTUAL_DEVICE_ID, DEVICE_ID_DEFAULT);
- mProxyManager.clearConnections(deviceId);
- }
- };
-
- final IntentFilter virtualDeviceFilter = new IntentFilter(
- ACTION_VIRTUAL_DEVICE_REMOVED);
- mContext.registerReceiver(virtualDeviceReceiver, virtualDeviceFilter,
- Context.RECEIVER_NOT_EXPORTED);
- }
}
/**
diff --git a/services/accessibility/java/com/android/server/accessibility/AutoclickController.java b/services/accessibility/java/com/android/server/accessibility/AutoclickController.java
index b94fa2f..8b758d2 100644
--- a/services/accessibility/java/com/android/server/accessibility/AutoclickController.java
+++ b/services/accessibility/java/com/android/server/accessibility/AutoclickController.java
@@ -39,6 +39,8 @@
import android.view.WindowManager;
import android.view.accessibility.AccessibilityManager;
+import androidx.annotation.VisibleForTesting;
+
/**
* Implements "Automatically click on mouse stop" feature.
*
@@ -69,10 +71,10 @@
private final int mUserId;
// Lazily created on the first mouse motion event.
- private ClickScheduler mClickScheduler;
- private AutoclickSettingsObserver mAutoclickSettingsObserver;
- private AutoclickIndicatorScheduler mAutoclickIndicatorScheduler;
- private AutoclickIndicatorView mAutoclickIndicatorView;
+ @VisibleForTesting ClickScheduler mClickScheduler;
+ @VisibleForTesting AutoclickSettingsObserver mAutoclickSettingsObserver;
+ @VisibleForTesting AutoclickIndicatorScheduler mAutoclickIndicatorScheduler;
+ @VisibleForTesting AutoclickIndicatorView mAutoclickIndicatorView;
private WindowManager mWindowManager;
public AutoclickController(Context context, int userId, AccessibilityTraceManager trace) {
@@ -360,7 +362,8 @@
* moving. The click is first scheduled when a mouse movement is detected, and then further
* delayed on every sufficient mouse movement.
*/
- final private class ClickScheduler implements Runnable {
+ @VisibleForTesting
+ final class ClickScheduler implements Runnable {
/**
* Minimal distance pointer has to move relative to anchor in order for movement not to be
* discarded as noise. Anchor is the position of the last MOVE event that was not considered
@@ -474,6 +477,11 @@
}
}
+ @VisibleForTesting
+ int getDelayForTesting() {
+ return mDelay;
+ }
+
/**
* Updates the time at which click sequence should occur.
*
diff --git a/services/accessibility/java/com/android/server/accessibility/AutoclickIndicatorView.java b/services/accessibility/java/com/android/server/accessibility/AutoclickIndicatorView.java
index bf50151..f87dcdb2 100644
--- a/services/accessibility/java/com/android/server/accessibility/AutoclickIndicatorView.java
+++ b/services/accessibility/java/com/android/server/accessibility/AutoclickIndicatorView.java
@@ -28,6 +28,8 @@
import android.view.accessibility.AccessibilityManager;
import android.view.animation.LinearInterpolator;
+import androidx.annotation.VisibleForTesting;
+
// A visual indicator for the autoclick feature.
public class AutoclickIndicatorView extends View {
private static final String TAG = AutoclickIndicatorView.class.getSimpleName();
@@ -37,7 +39,7 @@
static final int MINIMAL_ANIMATION_DURATION = 50;
- private float mRadius = AUTOCLICK_CURSOR_AREA_SIZE_DEFAULT;
+ private int mRadius = AUTOCLICK_CURSOR_AREA_SIZE_DEFAULT;
private final Paint mPaint;
@@ -112,6 +114,11 @@
mRadius = radius;
}
+ @VisibleForTesting
+ int getRadiusForTesting() {
+ return mRadius;
+ }
+
public void redrawIndicator() {
showIndicator = true;
invalidate();
diff --git a/services/accessibility/java/com/android/server/accessibility/ProxyManager.java b/services/accessibility/java/com/android/server/accessibility/ProxyManager.java
index f855145..8adee24 100644
--- a/services/accessibility/java/com/android/server/accessibility/ProxyManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/ProxyManager.java
@@ -217,7 +217,7 @@
private void registerVirtualDeviceListener() {
VirtualDeviceManager vdm = mContext.getSystemService(VirtualDeviceManager.class);
- if (vdm == null || !android.companion.virtual.flags.Flags.vdmPublicApis()) {
+ if (vdm == null) {
return;
}
if (mVirtualDeviceListener == null) {
@@ -234,7 +234,7 @@
private void unregisterVirtualDeviceListener() {
VirtualDeviceManager vdm = mContext.getSystemService(VirtualDeviceManager.class);
- if (vdm == null || !android.companion.virtual.flags.Flags.vdmPublicApis()) {
+ if (vdm == null) {
return;
}
vdm.unregisterVirtualDeviceListener(mVirtualDeviceListener);
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
index 0cbbf6d..2c106d3 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
@@ -16,8 +16,6 @@
package com.android.server.accessibility.gestures;
-import static android.accessibilityservice.AccessibilityTrace.FLAGS_GESTURE;
-import static android.accessibilityservice.AccessibilityTrace.FLAGS_INPUT_FILTER;
import static android.view.MotionEvent.ACTION_CANCEL;
import static android.view.MotionEvent.ACTION_DOWN;
import static android.view.MotionEvent.ACTION_HOVER_ENTER;
@@ -86,8 +84,6 @@
public class TouchExplorer extends BaseEventStreamTransformation
implements GestureManifold.Listener {
- private static final long LOGGING_FLAGS = FLAGS_GESTURE | FLAGS_INPUT_FILTER;
-
// Tag for logging received events.
private static final String LOG_TAG = "TouchExplorer";
@@ -261,10 +257,6 @@
@Override
public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
- if (mAms.getTraceManager().isA11yTracingEnabledForTypes(LOGGING_FLAGS)) {
- mAms.getTraceManager().logTrace(LOG_TAG + ".onMotionEvent", LOGGING_FLAGS,
- "event=" + event + ";rawEvent=" + rawEvent + ";policyFlags=" + policyFlags);
- }
if (!event.isFromSource(InputDevice.SOURCE_TOUCHSCREEN)) {
super.onMotionEvent(event, rawEvent, policyFlags);
return;
@@ -323,9 +315,8 @@
@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
- if (mAms.getTraceManager().isA11yTracingEnabledForTypes(LOGGING_FLAGS)) {
- mAms.getTraceManager().logTrace(LOG_TAG + ".onAccessibilityEvent",
- LOGGING_FLAGS, "event=" + event);
+ if (DEBUG) {
+ Slog.v(LOG_TAG, "Received A11y Event. event=" + event);
}
final int eventType = event.getEventType();
@@ -383,9 +374,9 @@
@Override
public void onDoubleTapAndHold(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
- if (mAms.getTraceManager().isA11yTracingEnabledForTypes(LOGGING_FLAGS)) {
- mAms.getTraceManager().logTrace(LOG_TAG + ".onDoubleTapAndHold", LOGGING_FLAGS,
- "event=" + event + ";rawEvent=" + rawEvent + ";policyFlags=" + policyFlags);
+ if (DEBUG) {
+ Slog.i(LOG_TAG, "Double tap and hold. event="
+ + event + ";rawEvent=" + rawEvent + ";policyFlags=" + policyFlags);
}
if (mDispatcher.longPressWithTouchEvents(event, policyFlags)) {
sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags);
@@ -403,9 +394,9 @@
@Override
public boolean onDoubleTap(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
- if (mAms.getTraceManager().isA11yTracingEnabledForTypes(LOGGING_FLAGS)) {
- mAms.getTraceManager().logTrace(LOG_TAG + ".onDoubleTap", LOGGING_FLAGS,
- "event=" + event + ";rawEvent=" + rawEvent + ";policyFlags=" + policyFlags);
+ if (DEBUG) {
+ Slog.i(LOG_TAG, "Double tap. event="
+ + event + ";rawEvent=" + rawEvent + ";policyFlags=" + policyFlags);
}
mAms.onTouchInteractionEnd();
// Remove pending event deliveries.
@@ -463,8 +454,8 @@
@Override
public boolean onGestureStarted() {
- if (mAms.getTraceManager().isA11yTracingEnabledForTypes(LOGGING_FLAGS)) {
- mAms.getTraceManager().logTrace(LOG_TAG + ".onGestureStarted", LOGGING_FLAGS);
+ if (DEBUG) {
+ Slog.i(LOG_TAG, "Gesture started.");
}
// We have to perform gesture detection, so
// clear the current state and try to detect.
@@ -479,9 +470,8 @@
@Override
public boolean onGestureCompleted(AccessibilityGestureEvent gestureEvent) {
- if (mAms.getTraceManager().isA11yTracingEnabledForTypes(LOGGING_FLAGS)) {
- mAms.getTraceManager().logTrace(LOG_TAG + ".onGestureCompleted",
- LOGGING_FLAGS, "event=" + gestureEvent);
+ if (DEBUG) {
+ Slog.i(LOG_TAG, "Gesture completed. gestureEvent=" + gestureEvent);
}
endGestureDetection(true);
mSendTouchInteractionEndDelayed.cancel();
@@ -491,10 +481,11 @@
@Override
public boolean onGestureCancelled(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
- if (mAms.getTraceManager().isA11yTracingEnabledForTypes(LOGGING_FLAGS)) {
- mAms.getTraceManager().logTrace(LOG_TAG + ".onGestureCancelled", LOGGING_FLAGS,
- "event=" + event + ";rawEvent=" + rawEvent + ";policyFlags=" + policyFlags);
+ if (DEBUG) {
+ Slog.i(LOG_TAG, "Gesture cancelled. event="
+ + event + ";rawEvent=" + rawEvent + ";policyFlags=" + policyFlags);
}
+
if (mState.isGestureDetecting()) {
endGestureDetection(event.getActionMasked() == ACTION_UP);
return true;
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 2e131b6..75ec8ea 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 @@
* 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 @@
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 0000000..a65580c
--- /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/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index cffdfbd..8ef44ad 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -5666,6 +5666,16 @@
}
public boolean canAccessAppWidget(Widget widget, int uid, String packageName) {
+ if (packageName != null
+ && widget.provider != null
+ && isDifferentPackageFromProvider(widget.provider, packageName)
+ && widget.host != null
+ && isDifferentPackageFromHost(widget.host, packageName)) {
+ // An AppWidget can only be accessed by either
+ // 1. The package that provides the AppWidget.
+ // 2. The package that hosts the AppWidget.
+ return false;
+ }
if (isHostInPackageForUid(widget.host, uid, packageName)) {
// Apps hosting the AppWidget have access to it.
return true;
@@ -5768,6 +5778,22 @@
&& provider.id.componentName.getPackageName().equals(packageName);
}
+ private boolean isDifferentPackageFromHost(
+ @NonNull final Host host, @NonNull final String packageName) {
+ if (host.id == null || host.id.packageName == null) {
+ return true;
+ }
+ return !packageName.equals(host.id.packageName);
+ }
+
+ private boolean isDifferentPackageFromProvider(
+ @NonNull final Provider provider, @NonNull final String packageName) {
+ if (provider.id == null || provider.id.componentName == null) {
+ return true;
+ }
+ return !packageName.equals(provider.id.componentName.getPackageName());
+ }
+
private boolean isProfileEnabled(int profileId) {
final long identity = Binder.clearCallingIdentity();
try {
diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
index 5af2346..2143aaa 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 @@
// 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 @@
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 @@
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 @@
* 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 @@
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 @@
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 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 @@
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 @@
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 @@
// 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 @@
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 @@
// 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 @@
// 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 @@
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 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 @@
}
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 @@
}
} 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 @@
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 @@
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 @@
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 @@
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 @@
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);
+ 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);
+ // 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);
+ 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 @@
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 @@
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 @@
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 @@
// 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 @@
* 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));
- }
-
- 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;
- }
-
- 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);
-
+ private BroadcastReceiver mPackageTrackingReceiver =
+ new BroadcastReceiver() {
+ public void onReceive(Context context, Intent intent) {
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]));
+ 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;
+
+ 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;
}
- }
- mBackupHandler.post(
- () ->
- mTransportManager.onPackageChanged(
- packageName, components));
- return;
- }
+ String packageName = uri.getSchemeSpecificPart();
+ if (packageName != null) {
+ packageList = new String[] {packageName};
+ }
- 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);
- }
+ 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 (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);
- }
-
- 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);
+ if (DEBUG) {
+ Slog.i(TAG, mLogIdMsg + "Package " + packageName + " changed");
+ for (int i = 0; i < components.length; i++) {
+ Slog.i(TAG, mLogIdMsg + " * " + components[i]);
+ }
}
- writeFullBackupScheduleAsync();
+
+ mBackupHandler.post(
+ () ->
+ mTransportManager.onPackageChanged(
+ packageName, components));
+ return;
}
- mBackupHandler.post(
- () -> mTransportManager.onPackageAdded(packageName));
- } catch (NameNotFoundException e) {
- Slog.w(TAG, addUserIdToLogMessage(mUserId,
- "Can't resolve new app " + packageName));
+ 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;
+ }
+
+ 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();
+ }
+
+ mBackupHandler.post(
+ () -> mTransportManager.onPackageAdded(packageName));
+ } catch (NameNotFoundException e) {
+ Slog.w(TAG, mLogIdMsg + "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);
+ }
+ }
+
+ for (String packageName : packageList) {
+ mBackupHandler.post(
+ () -> mTransportManager.onPackageRemoved(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);
- }
- }
-
- 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 @@
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 @@
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 @@
// 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 @@
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 @@
}
}
- 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 @@
// 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 @@
// 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 @@
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 @@
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 @@
/**
* 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 @@
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 @@
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 @@
* 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 @@
* 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 @@
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 @@
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 @@
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 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 @@
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 @@
}
/** 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 @@
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 @@
// 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 @@
// ----- 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 @@
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 @@
}
}
- /**
- * 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 @@
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 @@
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 @@
}
/**
- * 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 @@
// 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 @@
// 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 @@
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 @@
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 @@
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 @@
}
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 @@
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 @@
}
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 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 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 @@
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 @@
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 @@
}
// ...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 @@
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 @@
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 @@
}
}
- /**
- * 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 @@
} 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 @@
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 @@
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 @@
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 @@
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 @@
* 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 @@
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..."));
+ 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);
+ 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 @@
} 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 @@
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 @@
}
}
- Slog.d(TAG, addUserIdToLogMessage(mUserId, "Done with full transport backup."));
+ Slog.d(TAG, mLogIdMsg + "Done with full transport backup.");
}
/**
@@ -2711,13 +2731,11 @@
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 @@
}
// 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 @@
* 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 @@
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 @@
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 @@
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 @@
while (!params.latch.get()) {
try {
params.latch.wait();
- } catch (InterruptedException e) { /* never interrupted */ }
+ } catch (InterruptedException e) {
+ /* never interrupted */
+ }
}
}
}
@@ -2851,15 +2877,20 @@
* 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 @@
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 @@
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 @@
}
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 @@
}
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 @@
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 @@
} 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 @@
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 @@
// 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 @@
/** 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 @@
/** 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 @@
/** 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 @@
* @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 @@
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 @@
* 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 @@
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 @@
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 @@
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 @@
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 @@
} catch (RemoteException e) {
Slog.e(
TAG,
- addUserIdToLogMessage(
- mUserId,
- "ISelectBackupTransportCallback listener not"
- + " available"));
+ mLogIdMsg
+ + "ISelectBackupTransportCallback listener not"
+ + " available");
}
});
} finally {
@@ -3306,8 +3319,11 @@
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 @@
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 @@
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 @@
* 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 @@
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 @@
* 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 @@
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 @@
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 @@
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 @@
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 @@
}
// 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 @@
/** 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 @@
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 @@
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 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 @@
* 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 @@
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 @@
}
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 @@
// 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 @@
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 @@
}
}
- 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 @@
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 @@
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 @@
@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/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 0e2e505..a37b2b9 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -612,7 +612,7 @@
@Override
public void enablePermissionsSync(int associationId) {
- if (getCallingUid() != SYSTEM_UID) {
+ if (UserHandle.getAppId(Binder.getCallingUid()) == SYSTEM_UID) {
throw new SecurityException("Caller must be system UID");
}
mSystemDataTransferProcessor.enablePermissionsSync(associationId);
@@ -620,7 +620,7 @@
@Override
public void disablePermissionsSync(int associationId) {
- if (getCallingUid() != SYSTEM_UID) {
+ if (UserHandle.getAppId(Binder.getCallingUid()) == SYSTEM_UID) {
throw new SecurityException("Caller must be system UID");
}
mSystemDataTransferProcessor.disablePermissionsSync(associationId);
@@ -628,7 +628,7 @@
@Override
public PermissionSyncRequest getPermissionSyncRequest(int associationId) {
- if (getCallingUid() != SYSTEM_UID) {
+ if (UserHandle.getAppId(Binder.getCallingUid()) == SYSTEM_UID) {
throw new SecurityException("Caller must be system UID");
}
return mSystemDataTransferProcessor.getPermissionSyncRequest(associationId);
@@ -704,7 +704,7 @@
@Override
public byte[] getBackupPayload(int userId) {
- if (getCallingUid() != SYSTEM_UID) {
+ if (UserHandle.getAppId(Binder.getCallingUid()) == SYSTEM_UID) {
throw new SecurityException("Caller must be system");
}
return mBackupRestoreProcessor.getBackupPayload(userId);
@@ -712,7 +712,7 @@
@Override
public void applyRestoredPayload(byte[] payload, int userId) {
- if (getCallingUid() != SYSTEM_UID) {
+ if (UserHandle.getAppId(Binder.getCallingUid()) == SYSTEM_UID) {
throw new SecurityException("Caller must be system");
}
mBackupRestoreProcessor.applyRestoredPayload(payload, userId);
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
index 2804945..3508f2f 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
@@ -106,7 +106,7 @@
boolean selfManaged = getNextBooleanArg();
final MacAddress macAddress = MacAddress.fromString(address);
mAssociationRequestsProcessor.createAssociation(userId, packageName, macAddress,
- deviceProfile, deviceProfile, /* associatedDevice */ null, false,
+ deviceProfile, deviceProfile, /* associatedDevice */ null, selfManaged,
/* callback */ null, /* resultReceiver */ null, /* deviceIcon */ null);
}
break;
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 5edd9d7..c385fba 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -482,21 +482,15 @@
}
mVirtualDeviceLog.logCreated(deviceId, mOwnerUid);
- if (Flags.vdmPublicApis()) {
- mPublicVirtualDeviceObject = new VirtualDevice(
- this, getDeviceId(), getPersistentDeviceId(), mParams.getName(),
- getDisplayName());
- } else {
- mPublicVirtualDeviceObject = new VirtualDevice(
- this, getDeviceId(), getPersistentDeviceId(), mParams.getName());
- }
+ mPublicVirtualDeviceObject = new VirtualDevice(
+ this, getDeviceId(), getPersistentDeviceId(), mParams.getName(), getDisplayName());
mActivityPolicyExemptions = new ArraySet<>(
mParams.getDevicePolicy(POLICY_TYPE_ACTIVITY) == DEVICE_POLICY_DEFAULT
? 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);
@@ -813,7 +807,7 @@
}
// Clear any previously set custom IME components.
- if (Flags.vdmCustomIme() && mParams.getInputMethodComponent() != null) {
+ if (mParams.getInputMethodComponent() != null) {
InputMethodManagerInternal.get().setVirtualDeviceInputMethodForAllUsers(
mDeviceId, null);
}
@@ -1357,10 +1351,6 @@
}
private boolean hasCustomAudioInputSupportInternal() {
- if (!Flags.vdmPublicApis()) {
- return false;
- }
-
if (!android.media.audiopolicy.Flags.audioMixTestApi()) {
return false;
}
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
index a60fa69..4dd8b14 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
@@ -318,16 +318,14 @@
mVirtualDevices.remove(deviceId);
}
- if (Flags.vdmPublicApis()) {
- mVirtualDeviceListeners.broadcast(listener -> {
- try {
- listener.onVirtualDeviceClosed(deviceId);
- } catch (RemoteException e) {
- Slog.i(TAG, "Failed to invoke onVirtualDeviceClosed listener: "
- + e.getMessage());
- }
- });
- }
+ mVirtualDeviceListeners.broadcast(listener -> {
+ try {
+ listener.onVirtualDeviceClosed(deviceId);
+ } catch (RemoteException e) {
+ Slog.i(TAG, "Failed to invoke onVirtualDeviceClosed listener: "
+ + e.getMessage());
+ }
+ });
Intent i = new Intent(VirtualDeviceManager.ACTION_VIRTUAL_DEVICE_REMOVED);
i.putExtra(VirtualDeviceManager.EXTRA_VIRTUAL_DEVICE_ID, deviceId);
@@ -498,16 +496,14 @@
mVirtualDevices.put(deviceId, virtualDevice);
}
- if (Flags.vdmPublicApis()) {
- mVirtualDeviceListeners.broadcast(listener -> {
- try {
- listener.onVirtualDeviceCreated(deviceId);
- } catch (RemoteException e) {
- Slog.i(TAG, "Failed to invoke onVirtualDeviceCreated listener: "
- + e.getMessage());
- }
- });
- }
+ mVirtualDeviceListeners.broadcast(listener -> {
+ try {
+ listener.onVirtualDeviceCreated(deviceId);
+ } catch (RemoteException e) {
+ Slog.i(TAG, "Failed to invoke onVirtualDeviceCreated listener: "
+ + e.getMessage());
+ }
+ });
Counter.logIncrementWithUid(
"virtual_devices.value_virtual_devices_created_with_uid_count",
attributionSource.getUid());
@@ -824,13 +820,12 @@
@Override
public void onAuthenticationPrompt(int uid) {
- synchronized (mVirtualDeviceManagerLock) {
- for (int i = 0; i < mVirtualDevices.size(); i++) {
- VirtualDeviceImpl device = mVirtualDevices.valueAt(i);
- device.showToastWhereUidIsRunning(uid,
- R.string.app_streaming_blocked_message_for_fingerprint_dialog,
- Toast.LENGTH_LONG, Looper.getMainLooper());
- }
+ ArrayList<VirtualDeviceImpl> virtualDevicesSnapshot = getVirtualDevicesSnapshot();
+ for (int i = 0; i < virtualDevicesSnapshot.size(); i++) {
+ VirtualDeviceImpl device = virtualDevicesSnapshot.get(i);
+ device.showToastWhereUidIsRunning(uid,
+ R.string.app_streaming_blocked_message_for_fingerprint_dialog,
+ Toast.LENGTH_LONG, Looper.getMainLooper());
}
}
diff --git a/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java b/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java
index abfb826..df47c98 100644
--- a/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java
+++ b/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java
@@ -98,6 +98,8 @@
private static final int MSG_INVALIDATE_TOKEN = 1;
private static final int MAX_TOKEN_VALID_DURATION_MS = 1_000 * 60 * 10; // 10 minutes
+ private static final boolean DEBUG = false;
+
private final Context mContext;
private final ActivityTaskManagerInternal mAtmInternal;
private final PackageManagerInternal mPackageManager;
@@ -121,6 +123,7 @@
final Bundle data,
final int activityIndex,
final int activityCount) {
+
final IContextualSearchCallback callback;
synchronized (mLock) {
callback = mStateCallback;
@@ -160,7 +163,7 @@
public ContextualSearchManagerService(@NonNull Context context) {
super(context);
- if (DEBUG_USER) Log.d(TAG, "ContextualSearchManagerService created");
+ if (DEBUG) Log.d(TAG, "ContextualSearchManagerService created");
mContext = context;
mAtmInternal = Objects.requireNonNull(
LocalServices.getService(ActivityTaskManagerInternal.class));
@@ -206,7 +209,7 @@
mTemporaryHandler.removeMessages(MSG_RESET_TEMPORARY_PACKAGE);
mTemporaryHandler = null;
}
- if (DEBUG_USER) Log.d(TAG, "mTemporaryPackage reset.");
+ if (DEBUG) Log.d(TAG, "mTemporaryPackage reset.");
mTemporaryPackage = null;
updateSecureSetting();
}
@@ -239,7 +242,7 @@
mTemporaryPackage = temporaryPackage;
updateSecureSetting();
mTemporaryHandler.sendEmptyMessageDelayed(MSG_RESET_TEMPORARY_PACKAGE, durationMs);
- if (DEBUG_USER) Log.d(TAG, "mTemporaryPackage set to " + mTemporaryPackage);
+ if (DEBUG) Log.d(TAG, "mTemporaryPackage set to " + mTemporaryPackage);
}
}
@@ -256,7 +259,7 @@
+ durationMs + ")");
}
mTokenValidDurationMs = durationMs;
- if (DEBUG_USER) Log.d(TAG, "mTokenValidDurationMs set to " + durationMs);
+ if (DEBUG) Log.d(TAG, "mTokenValidDurationMs set to " + durationMs);
}
}
@@ -268,12 +271,12 @@
private Intent getResolvedLaunchIntent(int userId) {
synchronized (this) {
- if(DEBUG_USER) Log.d(TAG, "Attempting to getResolvedLaunchIntent");
+ if(DEBUG) Log.d(TAG, "Attempting to getResolvedLaunchIntent");
// If mTemporaryPackage is not null, use it to get the ContextualSearch intent.
String csPkgName = getContextualSearchPackageName();
if (csPkgName.isEmpty()) {
// Return null if csPackageName is not specified.
- if (DEBUG_USER) Log.w(TAG, "getContextualSearchPackageName is empty");
+ if (DEBUG) Log.w(TAG, "getContextualSearchPackageName is empty");
return null;
}
Intent launchIntent = new Intent(
@@ -282,12 +285,12 @@
ResolveInfo resolveInfo = mContext.getPackageManager().resolveActivityAsUser(
launchIntent, MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE, userId);
if (resolveInfo == null) {
- if (DEBUG_USER) Log.w(TAG, "resolveInfo is null");
+ if (DEBUG) Log.w(TAG, "resolveInfo is null");
return null;
}
ComponentName componentName = resolveInfo.getComponentInfo().getComponentName();
if (componentName == null) {
- if (DEBUG_USER) Log.w(TAG, "componentName is null");
+ if (DEBUG) Log.w(TAG, "componentName is null");
return null;
}
launchIntent.setComponent(componentName);
@@ -298,11 +301,11 @@
private Intent getContextualSearchIntent(int entrypoint, int userId, CallbackToken mToken) {
final Intent launchIntent = getResolvedLaunchIntent(userId);
if (launchIntent == null) {
- if (DEBUG_USER) Log.w(TAG, "Failed getContextualSearchIntent: launchIntent is null");
+ if (DEBUG) Log.w(TAG, "Failed getContextualSearchIntent: launchIntent is null");
return null;
}
- if (DEBUG_USER) Log.d(TAG, "Launch component: " + launchIntent.getComponent());
+ if (DEBUG) Log.d(TAG, "Launch component: " + launchIntent.getComponent());
launchIntent.addFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_NO_ANIMATION
| FLAG_ACTIVITY_NO_USER_ACTION | FLAG_ACTIVITY_CLEAR_TASK);
launchIntent.putExtra(
@@ -355,7 +358,7 @@
TYPE_NAVIGATION_BAR_PANEL,
TYPE_POINTER));
} else {
- if (DEBUG_USER) Log.w(TAG, "Can't capture contextual screenshot: mWmInternal is null");
+ if (DEBUG) Log.w(TAG, "Can't capture contextual screenshot: mWmInternal is null");
shb = null;
}
final Bitmap bm = shb != null ? shb.asBitmap() : null;
@@ -429,7 +432,7 @@
mTokenHandler.removeMessages(MSG_INVALIDATE_TOKEN);
mTokenHandler = null;
}
- if (DEBUG_USER) Log.d(TAG, "mToken invalidated.");
+ if (DEBUG) Log.d(TAG, "mToken invalidated.");
mToken = null;
}
}
@@ -459,7 +462,7 @@
@Override
public void startContextualSearch(int entrypoint) {
synchronized (this) {
- if (DEBUG_USER) Log.d(TAG, "startContextualSearch entrypoint: " + entrypoint);
+ if (DEBUG) Log.d(TAG, "startContextualSearch entrypoint: " + entrypoint);
enforcePermission("startContextualSearch");
final int callingUserId = Binder.getCallingUserHandle().getIdentifier();
@@ -474,7 +477,7 @@
getContextualSearchIntent(entrypoint, callingUserId, mToken);
if (launchIntent != null) {
int result = invokeContextualSearchIntent(launchIntent, callingUserId);
- if (DEBUG_USER) Log.d(TAG, "Launch result: " + result);
+ if (DEBUG) Log.d(TAG, "Launch result: " + result);
}
});
}
@@ -484,11 +487,11 @@
public void getContextualSearchState(
@NonNull IBinder token,
@NonNull IContextualSearchCallback callback) {
- if (DEBUG_USER) {
+ if (DEBUG) {
Log.i(TAG, "getContextualSearchState token: " + token + ", callback: " + callback);
}
if (mToken == null || !mToken.getToken().equals(token)) {
- if (DEBUG_USER) {
+ if (DEBUG) {
Log.e(TAG, "getContextualSearchState: invalid token, returning error");
}
try {
diff --git a/services/core/java/com/android/server/BootReceiver.java b/services/core/java/com/android/server/BootReceiver.java
index 1588e04..7a5b866 100644
--- a/services/core/java/com/android/server/BootReceiver.java
+++ b/services/core/java/com/android/server/BootReceiver.java
@@ -40,7 +40,9 @@
import android.util.EventLog;
import android.util.Slog;
import android.util.Xml;
+import android.util.proto.ProtoFieldFilter;
import android.util.proto.ProtoOutputStream;
+import android.util.proto.ProtoParseException;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
@@ -49,10 +51,13 @@
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.am.DropboxRateLimiter;
+import com.android.server.os.TombstoneProtos.Tombstone;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileDescriptor;
@@ -64,6 +69,7 @@
import java.nio.file.attribute.PosixFilePermissions;
import java.util.HashMap;
import java.util.Iterator;
+import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -392,6 +398,129 @@
writeTimestamps(timestamps);
}
+ /**
+ * Processes a tombstone file and adds it to the DropBox after filtering and applying
+ * rate limiting.
+ * Filtering removes memory sections from the tombstone proto to reduce size while preserving
+ * critical information. The filtered tombstone is then added to DropBox in both proto
+ * and text formats, with the text format derived from the filtered proto.
+ * Rate limiting is applied as it is the case with other crash types.
+ *
+ * @param ctx Context
+ * @param tombstone path to the tombstone
+ * @param processName the name of the process corresponding to the tombstone
+ * @param tmpFileLock the lock for reading/writing tmp files
+ */
+ public static void filterAndAddTombstoneToDropBox(
+ Context ctx, File tombstone, String processName, ReentrantLock tmpFileLock) {
+ final DropBoxManager db = ctx.getSystemService(DropBoxManager.class);
+ if (db == null) {
+ Slog.e(TAG, "Can't log tombstone: DropBoxManager not available");
+ return;
+ }
+ File filteredProto = null;
+ // Check if we should rate limit and abort early if needed.
+ DropboxRateLimiter.RateLimitResult rateLimitResult =
+ sDropboxRateLimiter.shouldRateLimit(TAG_TOMBSTONE_PROTO_WITH_HEADERS, processName);
+ if (rateLimitResult.shouldRateLimit()) return;
+
+ HashMap<String, Long> timestamps = readTimestamps();
+ try {
+ tmpFileLock.lock();
+ Slog.i(TAG, "Filtering tombstone file: " + tombstone.getName());
+ // Create a temporary tombstone without memory sections.
+ filteredProto = createTempTombstoneWithoutMemory(tombstone);
+ Slog.i(TAG, "Generated tombstone file: " + filteredProto.getName());
+
+ if (recordFileTimestamp(tombstone, timestamps)) {
+ // We need to attach the count indicating the number of dropped dropbox entries
+ // due to rate limiting. Do this by enclosing the proto tombsstone in a
+ // container proto that has the dropped entry count and the proto tombstone as
+ // bytes (to avoid the complexity of reading and writing nested protos).
+ Slog.i(TAG, "Adding tombstone " + filteredProto.getName() + " to dropbox");
+ addAugmentedProtoToDropbox(filteredProto, db, rateLimitResult);
+ }
+ // Always add the text version of the tombstone to the DropBox, in order to
+ // match the previous behaviour.
+ Slog.i(TAG, "Adding text tombstone version of " + filteredProto.getName()
+ + " to dropbox");
+ addTextTombstoneFromProtoToDropbox(filteredProto, db, timestamps, rateLimitResult);
+
+ } catch (IOException | ProtoParseException e) {
+ Slog.e(TAG, "Failed to log tombstone '" + tombstone.getName()
+ + "' to DropBox. Error during processing or writing: " + e.getMessage(), e);
+ } finally {
+ if (filteredProto != null) {
+ filteredProto.delete();
+ }
+ tmpFileLock.unlock();
+ }
+ writeTimestamps(timestamps);
+ }
+
+ /**
+ * Creates a temporary tombstone file by filtering out memory mapping fields.
+ * This ensures that the unneeded memory mapping data is removed from the tombstone
+ * before adding it to Dropbox
+ *
+ * @param tombstone the original tombstone file to process
+ * @return a temporary file containing the filtered tombstone data
+ * @throws IOException if an I/O error occurs during processing
+ */
+ private static File createTempTombstoneWithoutMemory(File tombstone) throws IOException {
+ // Process the proto tombstone file and write it to a temporary file
+ File tombstoneProto =
+ File.createTempFile(tombstone.getName(), ".pb.tmp", TOMBSTONE_TMP_DIR);
+ ProtoFieldFilter protoFilter =
+ new ProtoFieldFilter(fieldNumber -> fieldNumber != (int) Tombstone.MEMORY_MAPPINGS);
+
+ try (FileInputStream fis = new FileInputStream(tombstone);
+ BufferedInputStream bis = new BufferedInputStream(fis);
+ FileOutputStream fos = new FileOutputStream(tombstoneProto);
+ BufferedOutputStream bos = new BufferedOutputStream(fos)) {
+ protoFilter.filter(bis, bos);
+ return tombstoneProto;
+ }
+ }
+
+ private static void addTextTombstoneFromProtoToDropbox(File tombstone, DropBoxManager db,
+ HashMap<String, Long> timestamps, DropboxRateLimiter.RateLimitResult rateLimitResult) {
+ File tombstoneTextFile = null;
+
+ try {
+ tombstoneTextFile = File.createTempFile(tombstone.getName(),
+ ".pb.txt.tmp", TOMBSTONE_TMP_DIR);
+
+ // Create a ProcessBuilder to execute pbtombstone
+ ProcessBuilder pb = new ProcessBuilder("/system/bin/pbtombstone", tombstone.getPath());
+ pb.redirectOutput(tombstoneTextFile);
+ Process process = pb.start();
+
+ // Wait 10 seconds for the process to complete
+ if (!process.waitFor(10, TimeUnit.SECONDS)) {
+ Slog.e(TAG, "pbtombstone timed out");
+ process.destroyForcibly();
+ return;
+ }
+
+ int exitCode = process.exitValue();
+ if (exitCode != 0) {
+ Slog.e(TAG, "pbtombstone failed with exit code " + exitCode);
+ } else {
+ final String headers = getBootHeadersToLogAndUpdate()
+ + rateLimitResult.createHeader();
+ addFileToDropBox(db, timestamps, headers, tombstoneTextFile.getPath(), LOG_SIZE,
+ TAG_TOMBSTONE);
+ }
+ } catch (IOException | InterruptedException e) {
+ Slog.e(TAG, "Failed to process tombstone with pbtombstone", e);
+ } finally {
+ if (tombstoneTextFile != null) {
+ tombstoneTextFile.delete();
+ }
+ }
+ }
+
private static void addAugmentedProtoToDropbox(
File tombstone, DropBoxManager db,
DropboxRateLimiter.RateLimitResult rateLimitResult) throws IOException {
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 2216f27..f7eaa15 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -2553,13 +2553,12 @@
final AppZygote appZygote = createAppZygoteForProcessIfNeeded(app);
// We can't isolate app data and storage data as parent zygote already did that.
- startResult = appZygote.getProcess().start(entryPoint,
- app.processName, uid, uid, gids, runtimeFlags, mountExternal,
+ startResult = appZygote.startProcess(entryPoint,
+ app.processName, uid, gids, runtimeFlags, mountExternal,
app.info.targetSdkVersion, seInfo, requiredAbi, instructionSet,
- app.info.dataDir, null, app.info.packageName,
- /*zygotePolicyFlags=*/ ZYGOTE_POLICY_FLAG_EMPTY, isTopApp,
- app.getDisabledCompatChanges(), pkgDataInfoMap, allowlistedAppDataInfoMap,
- false, false, false,
+ app.info.dataDir, app.info.packageName, isTopApp,
+ app.getDisabledCompatChanges(), pkgDataInfoMap,
+ allowlistedAppDataInfoMap,
new String[]{PROC_START_SEQ_IDENT + app.getStartSeq()});
} else {
regularZygote = true;
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index d3a5254..a54a663 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -16,12 +16,23 @@
package com.android.server.am;
+import static com.android.aconfig_new_storage.Flags.enableAconfigStorageDaemon;
+import static com.android.aconfig_new_storage.Flags.enableAconfigdFromMainline;
+import static com.android.aconfig_new_storage.Flags.supportClearLocalOverridesImmediately;
+import static com.android.aconfig_new_storage.Flags.supportImmediateLocalOverrides;
+
+import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
+
+import android.aconfigd.Aconfigd.StorageRequestMessage;
+import android.aconfigd.Aconfigd.StorageRequestMessages;
+import android.aconfigd.Aconfigd.StorageReturnMessage;
+import android.aconfigd.Aconfigd.StorageReturnMessages;
import android.annotation.NonNull;
import android.content.ContentResolver;
import android.database.ContentObserver;
-import android.net.Uri;
-import android.net.LocalSocketAddress;
import android.net.LocalSocket;
+import android.net.LocalSocketAddress;
+import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.SystemProperties;
@@ -35,28 +46,13 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.providers.settings.Flags;
-import android.aconfigd.Aconfigd.StorageRequestMessage;
-import android.aconfigd.Aconfigd.StorageRequestMessages;
-import android.aconfigd.Aconfigd.StorageReturnMessage;
-import android.aconfigd.Aconfigd.StorageReturnMessages;
-import static com.android.aconfig_new_storage.Flags.enableAconfigStorageDaemon;
-import static com.android.aconfig_new_storage.Flags.supportImmediateLocalOverrides;
-import static com.android.aconfig_new_storage.Flags.supportClearLocalOverridesImmediately;
-import static com.android.aconfig_new_storage.Flags.enableAconfigdFromMainline;
-
+import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.DataOutputStream;
-import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.HashSet;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.List;
-import java.util.ArrayList;
-import java.util.Set;
-import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
/**
* Maps system settings to system properties.
@@ -271,6 +267,7 @@
"wear_sysui",
"wear_system_managed_surfaces",
"wear_watchfaces",
+ "web_apps_on_chromeos_and_android",
"window_surfaces",
"windowing_frontend",
"xr",
diff --git a/services/core/java/com/android/server/am/UidObserverController.java b/services/core/java/com/android/server/am/UidObserverController.java
index f58e3f8..23edafc 100644
--- a/services/core/java/com/android/server/am/UidObserverController.java
+++ b/services/core/java/com/android/server/am/UidObserverController.java
@@ -78,6 +78,7 @@
* This is for verifying the UID report flow.
*/
private static final boolean VALIDATE_UID_STATES = true;
+ @GuardedBy("mLock")
private final ActiveUids mValidateUids;
UidObserverController(@NonNull Handler handler) {
@@ -285,31 +286,30 @@
}
mUidObservers.finishBroadcast();
- if (VALIDATE_UID_STATES && mUidObservers.getRegisteredCallbackCount() > 0) {
- for (int j = 0; j < numUidChanges; ++j) {
- final ChangeRecord item = mActiveUidChanges[j];
- if ((item.change & UidRecord.CHANGE_GONE) != 0) {
- mValidateUids.remove(item.uid);
- } else {
- UidRecord validateUid = mValidateUids.get(item.uid);
- if (validateUid == null) {
- validateUid = new UidRecord(item.uid, null);
- mValidateUids.put(item.uid, validateUid);
+ synchronized (mLock) {
+ if (VALIDATE_UID_STATES && mUidObservers.getRegisteredCallbackCount() > 0) {
+ for (int j = 0; j < numUidChanges; ++j) {
+ final ChangeRecord item = mActiveUidChanges[j];
+ if ((item.change & UidRecord.CHANGE_GONE) != 0) {
+ mValidateUids.remove(item.uid);
+ } else {
+ UidRecord validateUid = mValidateUids.get(item.uid);
+ if (validateUid == null) {
+ validateUid = new UidRecord(item.uid, null);
+ mValidateUids.put(item.uid, validateUid);
+ }
+ if ((item.change & UidRecord.CHANGE_IDLE) != 0) {
+ validateUid.setIdle(true);
+ } else if ((item.change & UidRecord.CHANGE_ACTIVE) != 0) {
+ validateUid.setIdle(false);
+ }
+ validateUid.setSetProcState(item.procState);
+ validateUid.setCurProcState(item.procState);
+ validateUid.setSetCapability(item.capability);
+ validateUid.setCurCapability(item.capability);
}
- if ((item.change & UidRecord.CHANGE_IDLE) != 0) {
- validateUid.setIdle(true);
- } else if ((item.change & UidRecord.CHANGE_ACTIVE) != 0) {
- validateUid.setIdle(false);
- }
- validateUid.setSetProcState(item.procState);
- validateUid.setCurProcState(item.procState);
- validateUid.setSetCapability(item.capability);
- validateUid.setCurCapability(item.capability);
}
}
- }
-
- synchronized (mLock) {
for (int j = 0; j < numUidChanges; j++) {
final ChangeRecord changeRecord = mActiveUidChanges[j];
changeRecord.isPending = false;
@@ -436,7 +436,9 @@
}
UidRecord getValidateUidRecord(int uid) {
- return mValidateUids.get(uid);
+ synchronized (mLock) {
+ return mValidateUids.get(uid);
+ }
}
void dump(@NonNull PrintWriter pw, @Nullable String dumpPackage) {
@@ -491,12 +493,16 @@
boolean dumpValidateUids(@NonNull PrintWriter pw, @Nullable String dumpPackage, int dumpAppId,
@NonNull String header, boolean needSep) {
- return mValidateUids.dump(pw, dumpPackage, dumpAppId, header, needSep);
+ synchronized (mLock) {
+ return mValidateUids.dump(pw, dumpPackage, dumpAppId, header, needSep);
+ }
}
void dumpValidateUidsProto(@NonNull ProtoOutputStream proto, @Nullable String dumpPackage,
int dumpAppId, long fieldId) {
- mValidateUids.dumpProto(proto, dumpPackage, dumpAppId, fieldId);
+ synchronized (mLock) {
+ mValidateUids.dumpProto(proto, dumpPackage, dumpAppId, fieldId);
+ }
}
static final class ChangeRecord {
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index d76c04a..27e9e44 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 @@
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 2d74564..d1fcb9d 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.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 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 @@
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 @@
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 @@
: 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 @@
@Override
public void dismiss() {
super.dismiss();
- stopFreezingScreen();
asyncTraceEnd("dialog", 0);
}
@@ -205,7 +192,6 @@
if (DEBUG) Slog.d(TAG, "show called");
show();
startShowAnimation(() -> {
- startFreezingScreen();
onShown.run();
});
}
@@ -223,24 +209,6 @@
}
}
- 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 @@
}
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 @@
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/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index d2073aa..8e09e3b 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -33,6 +33,7 @@
import static android.app.AppOpsManager.MODE_ERRORED;
import static android.app.AppOpsManager.MODE_FOREGROUND;
import static android.app.AppOpsManager.MODE_IGNORED;
+import static android.app.AppOpsManager.OP_BLUETOOTH_CONNECT;
import static android.app.AppOpsManager.OP_CAMERA;
import static android.app.AppOpsManager.OP_CAMERA_SANDBOXED;
import static android.app.AppOpsManager.OP_FLAGS_ALL;
@@ -3267,6 +3268,11 @@
packageName);
}
if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
+ // TODO(b/333931259): Remove extra logging after this issue is diagnosed.
+ if (code == OP_BLUETOOTH_CONNECT) {
+ Slog.e(TAG, "noting OP_BLUETOOTH_CONNECT returned MODE_ERRORED as incoming "
+ + "package: " + packageName + " and uid: " + uid + " is invalid");
+ }
return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
packageName);
}
@@ -3306,6 +3312,13 @@
}
} catch (SecurityException e) {
logVerifyAndGetBypassFailure(uid, e, "noteOperation");
+ // TODO(b/333931259): Remove extra logging after this issue is diagnosed.
+ if (code == OP_BLUETOOTH_CONNECT) {
+ Slog.e(TAG, "noting OP_BLUETOOTH_CONNECT returned MODE_ERRORED as"
+ + " verifyAndGetBypass returned a SecurityException for package: "
+ + packageName + " and uid: " + uid + " and attributionTag: "
+ + attributionTag, e);
+ }
return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
packageName);
}
@@ -3323,6 +3336,17 @@
if (DEBUG) Slog.d(TAG, "noteOperation: no op for code " + code + " uid " + uid
+ " package " + packageName + "flags: " +
AppOpsManager.flagsToString(flags));
+ // TODO(b/333931259): Remove extra logging after this issue is diagnosed.
+ if (code == OP_BLUETOOTH_CONNECT) {
+ Slog.e(TAG, "noting OP_BLUETOOTH_CONNECT returned MODE_ERRORED as"
+ + " #getOpsLocked returned null for"
+ + " uid: " + uid
+ + " packageName: " + packageName
+ + " attributionTag: " + attributionTag
+ + " pvr.isAttributionTagValid: " + pvr.isAttributionTagValid
+ + " pvr.bypass: " + pvr.bypass);
+ Slog.e(TAG, "mUidStates.get(" + uid + "): " + mUidStates.get(uid));
+ }
return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
packageName);
}
@@ -3367,6 +3391,11 @@
attributedOp.rejected(uidState.getState(), flags);
scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag,
virtualDeviceId, flags, uidMode);
+ // TODO(b/333931259): Remove extra logging after this issue is diagnosed.
+ if (code == OP_BLUETOOTH_CONNECT && uidMode == MODE_ERRORED) {
+ Slog.e(TAG, "noting OP_BLUETOOTH_CONNECT returned MODE_ERRORED as"
+ + " uid mode is MODE_ERRORED");
+ }
return new SyncNotedAppOp(uidMode, code, attributionTag, packageName);
}
} else {
@@ -3386,6 +3415,11 @@
attributedOp.rejected(uidState.getState(), flags);
scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag,
virtualDeviceId, flags, mode);
+ // TODO(b/333931259): Remove extra logging after this issue is diagnosed.
+ if (code == OP_BLUETOOTH_CONNECT && mode == MODE_ERRORED) {
+ Slog.e(TAG, "noting OP_BLUETOOTH_CONNECT returned MODE_ERRORED as"
+ + " package mode is MODE_ERRORED");
+ }
return new SyncNotedAppOp(mode, code, attributionTag, packageName);
}
}
diff --git a/services/core/java/com/android/server/appop/DiscreteOpsDbHelper.java b/services/core/java/com/android/server/appop/DiscreteOpsDbHelper.java
index 695032e..86f5d9b 100644
--- a/services/core/java/com/android/server/appop/DiscreteOpsDbHelper.java
+++ b/services/core/java/com/android/server/appop/DiscreteOpsDbHelper.java
@@ -27,9 +27,13 @@
import android.database.sqlite.SQLiteOpenHelper;
import android.database.sqlite.SQLiteRawStatement;
import android.os.Environment;
+import android.os.SystemClock;
+import android.permission.flags.Flags;
import android.util.IntArray;
import android.util.Slog;
+import com.android.internal.util.FrameworkStatsLog;
+
import java.io.File;
import java.util.ArrayList;
import java.util.List;
@@ -76,6 +80,10 @@
if (opEvents.isEmpty()) {
return;
}
+ long startTime = 0;
+ if (Flags.sqliteDiscreteOpEventLoggingEnabled()) {
+ startTime = SystemClock.elapsedRealtime();
+ }
SQLiteDatabase db = getWritableDatabase();
// TODO (b/383157289) what if database is busy and can't start a transaction? will read
@@ -117,6 +125,11 @@
+ " file size (bytes) : " + getDatabaseFile().length(), exception);
}
}
+ if (Flags.sqliteDiscreteOpEventLoggingEnabled()) {
+ long timeTaken = SystemClock.elapsedRealtime() - startTime;
+ FrameworkStatsLog.write(FrameworkStatsLog.SQLITE_DISCRETE_OP_EVENT_REPORTED,
+ -1, timeTaken, getDatabaseFile().length());
+ }
}
private void bindTextOrNull(SQLiteRawStatement statement, int index, @Nullable String text) {
@@ -181,7 +194,10 @@
uidFilter, packageNameFilter,
attributionTagFilter, opCodesFilter, opFlagsFilter);
String sql = buildSql(conditions, orderByColumn, limit);
-
+ long startTime = 0;
+ if (Flags.sqliteDiscreteOpEventLoggingEnabled()) {
+ startTime = SystemClock.elapsedRealtime();
+ }
SQLiteDatabase db = getReadableDatabase();
List<DiscreteOpsSqlRegistry.DiscreteOp> results = new ArrayList<>();
db.beginTransactionReadOnly();
@@ -225,6 +241,11 @@
} finally {
db.endTransaction();
}
+ if (Flags.sqliteDiscreteOpEventLoggingEnabled()) {
+ long timeTaken = SystemClock.elapsedRealtime() - startTime;
+ FrameworkStatsLog.write(FrameworkStatsLog.SQLITE_DISCRETE_OP_EVENT_REPORTED,
+ timeTaken, -1, getDatabaseFile().length());
+ }
return results;
}
diff --git a/services/core/java/com/android/server/appop/HistoricalRegistry.java b/services/core/java/com/android/server/appop/HistoricalRegistry.java
index ba391d0..5aa2a6b 100644
--- a/services/core/java/com/android/server/appop/HistoricalRegistry.java
+++ b/services/core/java/com/android/server/appop/HistoricalRegistry.java
@@ -205,18 +205,8 @@
mContext = context;
if (Flags.enableSqliteAppopsAccesses()) {
mDiscreteRegistry = new DiscreteOpsSqlRegistry(context);
- if (DiscreteOpsXmlRegistry.getDiscreteOpsDir().exists()) {
- DiscreteOpsSqlRegistry sqlRegistry = (DiscreteOpsSqlRegistry) mDiscreteRegistry;
- DiscreteOpsXmlRegistry xmlRegistry = new DiscreteOpsXmlRegistry(context);
- DiscreteOpsMigrationHelper.migrateDiscreteOpsToSqlite(xmlRegistry, sqlRegistry);
- }
} else {
mDiscreteRegistry = new DiscreteOpsXmlRegistry(context);
- if (DiscreteOpsDbHelper.getDatabaseFile().exists()) { // roll-back sqlite
- DiscreteOpsSqlRegistry sqlRegistry = new DiscreteOpsSqlRegistry(context);
- DiscreteOpsXmlRegistry xmlRegistry = (DiscreteOpsXmlRegistry) mDiscreteRegistry;
- DiscreteOpsMigrationHelper.migrateDiscreteOpsToXml(sqlRegistry, xmlRegistry);
- }
}
}
@@ -267,6 +257,19 @@
}
}
}
+ if (Flags.enableSqliteAppopsAccesses()) {
+ if (DiscreteOpsXmlRegistry.getDiscreteOpsDir().exists()) {
+ DiscreteOpsSqlRegistry sqlRegistry = (DiscreteOpsSqlRegistry) mDiscreteRegistry;
+ DiscreteOpsXmlRegistry xmlRegistry = new DiscreteOpsXmlRegistry(mContext);
+ DiscreteOpsMigrationHelper.migrateDiscreteOpsToSqlite(xmlRegistry, sqlRegistry);
+ }
+ } else {
+ if (DiscreteOpsDbHelper.getDatabaseFile().exists()) { // roll-back sqlite
+ DiscreteOpsSqlRegistry sqlRegistry = new DiscreteOpsSqlRegistry(mContext);
+ DiscreteOpsXmlRegistry xmlRegistry = (DiscreteOpsXmlRegistry) mDiscreteRegistry;
+ DiscreteOpsMigrationHelper.migrateDiscreteOpsToXml(sqlRegistry, xmlRegistry);
+ }
+ }
}
private boolean isPersistenceInitializedMLocked() {
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index b9b0670..12b666f 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.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 @@
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 @@
mMusicFxHelper = new MusicFxHelper(mContext, mAudioHandler);
mHardeningEnforcer = new HardeningEnforcer(mContext, isPlatformAutomotive(), mAppOps,
- context.getPackageManager());
+ context.getPackageManager(),
+ mHardeningLogger);
}
private void initVolumeStreamStates() {
@@ -11911,7 +11953,7 @@
mLoudnessCodecHelper.startLoudnessCodecUpdates(sessionId);
}
- /** @see LoudnessCodecController#release() */
+ /** @see LoudnessCodecController#close() */
@Override
public void stopLoudnessCodecUpdates(int sessionId) {
mLoudnessCodecHelper.stopLoudnessCodecUpdates(sessionId);
@@ -12691,6 +12733,7 @@
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 @@
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 @@
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 6611110..f69a810 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 @@
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 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 1c01fb9..e2e06b6 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 @@
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/clipboard/ClipboardService.java b/services/core/java/com/android/server/clipboard/ClipboardService.java
index 78f7187..6122fda 100644
--- a/services/core/java/com/android/server/clipboard/ClipboardService.java
+++ b/services/core/java/com/android/server/clipboard/ClipboardService.java
@@ -17,8 +17,6 @@
package com.android.server.clipboard;
import static android.app.ActivityManagerInternal.ALLOW_FULL_ONLY;
-import static android.companion.virtual.VirtualDeviceManager.ACTION_VIRTUAL_DEVICE_REMOVED;
-import static android.companion.virtual.VirtualDeviceManager.EXTRA_VIRTUAL_DEVICE_ID;
import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_CUSTOM;
import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_CLIPBOARD;
import static android.content.Context.DEVICE_ID_DEFAULT;
@@ -46,7 +44,6 @@
import android.content.IClipboard;
import android.content.IOnPrimaryClipChangedListener;
import android.content.Intent;
-import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.UserInfo;
@@ -219,35 +216,7 @@
@Override
public void onStart() {
publishBinderService(Context.CLIPBOARD_SERVICE, new ClipboardImpl());
- if (!android.companion.virtual.flags.Flags.vdmPublicApis() && mVdmInternal != null) {
- registerVirtualDeviceBroadcastReceiver();
- } else if (android.companion.virtual.flags.Flags.vdmPublicApis() && mVdm != null) {
- registerVirtualDeviceListener();
- }
- }
-
- private void registerVirtualDeviceBroadcastReceiver() {
- if (mVirtualDeviceRemovedReceiver != null) {
- return;
- }
- mVirtualDeviceRemovedReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (!intent.getAction().equals(ACTION_VIRTUAL_DEVICE_REMOVED)) {
- return;
- }
- final int removedDeviceId =
- intent.getIntExtra(EXTRA_VIRTUAL_DEVICE_ID, DEVICE_ID_INVALID);
- synchronized (mLock) {
- for (int i = mClipboards.numMaps() - 1; i >= 0; i--) {
- mClipboards.delete(mClipboards.keyAt(i), removedDeviceId);
- }
- }
- }
- };
- IntentFilter filter = new IntentFilter(ACTION_VIRTUAL_DEVICE_REMOVED);
- getContext().registerReceiver(mVirtualDeviceRemovedReceiver, filter,
- Context.RECEIVER_NOT_EXPORTED);
+ registerVirtualDeviceListener();
}
private void registerVirtualDeviceListener() {
diff --git a/services/core/java/com/android/server/display/BrightnessTracker.java b/services/core/java/com/android/server/display/BrightnessTracker.java
index c19d2c9..21c9b87 100644
--- a/services/core/java/com/android/server/display/BrightnessTracker.java
+++ b/services/core/java/com/android/server/display/BrightnessTracker.java
@@ -16,6 +16,9 @@
package com.android.server.display;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
@@ -1181,6 +1184,14 @@
}
public RootTaskInfo getFocusedStack() throws RemoteException {
+ if (UserManager.isVisibleBackgroundUsersEnabled()) {
+ // In MUMD (Multiple Users on Multiple Displays) system, the top most focused stack
+ // could be on the secondary display with a user signed on its display so get the
+ // root task info only on the default display.
+ return ActivityTaskManager.getService().getRootTaskInfoOnDisplay(
+ WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_UNDEFINED,
+ Display.DEFAULT_DISPLAY);
+ }
return ActivityTaskManager.getService().getFocusedRootTaskInfo();
}
diff --git a/services/core/java/com/android/server/display/ColorFade.java b/services/core/java/com/android/server/display/ColorFade.java
index 93d9b8d..25a2f60 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 @@
.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/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index ca001b9c..3598b9b 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -29,6 +29,7 @@
import static android.Manifest.permission.RESTRICT_DISPLAY_MODES;
import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED;
import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE;
+import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE;
import static android.hardware.display.DisplayManagerGlobal.InternalEventFlag;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR;
@@ -380,6 +381,8 @@
private final SparseArray<DisplayPowerController> mDisplayPowerControllers =
new SparseArray<>();
+ private int mMaxImportanceForRRCallbacks = IMPORTANCE_VISIBLE;
+
/** {@link DisplayBlanker} used by all {@link DisplayPowerController}s. */
private final DisplayBlanker mDisplayBlanker = new DisplayBlanker() {
// Synchronized to avoid race conditions when updating multiple display states.
@@ -3445,8 +3448,11 @@
}
private void sendDisplayEventFrameRateOverrideLocked(int displayId) {
+ int event = (mFlags.isFramerateOverrideTriggersRrCallbacksEnabled())
+ ? DisplayManagerGlobal.EVENT_DISPLAY_REFRESH_RATE_CHANGED
+ : DisplayManagerGlobal.EVENT_DISPLAY_BASIC_CHANGED;
Message msg = mHandler.obtainMessage(MSG_DELIVER_DISPLAY_EVENT_FRAME_RATE_OVERRIDE,
- displayId, DisplayManagerGlobal.EVENT_DISPLAY_BASIC_CHANGED);
+ displayId, event);
mHandler.sendMessage(msg);
}
@@ -3633,6 +3639,7 @@
pw.println(" mWifiDisplayScanRequestCount=" + mWifiDisplayScanRequestCount);
pw.println(" mStableDisplaySize=" + mStableDisplaySize);
pw.println(" mMinimumBrightnessCurve=" + mMinimumBrightnessCurve);
+ pw.println(" mMaxImportanceForRRCallbacks=" + mMaxImportanceForRRCallbacks);
if (mUserPreferredMode != null) {
pw.println(" mUserPreferredMode=" + mUserPreferredMode);
@@ -3761,6 +3768,10 @@
}
}
+ void overrideMaxImportanceForRRCallbacks(int importance) {
+ mMaxImportanceForRRCallbacks = importance;
+ }
+
boolean requestDisplayPower(int displayId, int requestedState) {
synchronized (mSyncRoot) {
final var display = mLogicalDisplayMapper.getDisplayLocked(displayId);
@@ -4144,6 +4155,18 @@
mPackageName = packageNames == null ? null : packageNames[0];
}
+ public boolean shouldReceiveRefreshRateWithChangeUpdate(int event) {
+ if (mFlags.isRefreshRateEventForForegroundAppsEnabled()
+ && event == DisplayManagerGlobal.EVENT_DISPLAY_REFRESH_RATE_CHANGED) {
+ int procState = mActivityManagerInternal.getUidProcessState(mUid);
+ int importance = ActivityManager.RunningAppProcessInfo
+ .procStateToImportance(procState);
+ return importance <= mMaxImportanceForRRCallbacks || mUid <= Process.SYSTEM_UID;
+ }
+
+ return true;
+ }
+
public void updateEventFlagsMask(@InternalEventFlag long internalEventFlag) {
mInternalEventFlagsMask.set(internalEventFlag);
}
@@ -4251,6 +4274,11 @@
}
}
+ if (!shouldReceiveRefreshRateWithChangeUpdate(event)) {
+ // The client is not visible to the user and is not a system service, so do nothing.
+ return true;
+ }
+
try {
transmitDisplayEvent(displayId, event);
return true;
@@ -4382,26 +4410,34 @@
// would be unusual to do so. The method returns true on success.
// This is only used if {@link deferDisplayEventsWhenFrozen()} is true.
public boolean dispatchPending() {
- try {
- synchronized (mCallback) {
- if (mPendingEvents == null || mPendingEvents.isEmpty() || !mAlive) {
- return true;
- }
- if (!isReadyLocked()) {
- return false;
- }
- for (int i = 0; i < mPendingEvents.size(); i++) {
- Event displayEvent = mPendingEvents.get(i);
- if (DEBUG) {
- Slog.d(TAG, "Send pending display event #" + i + " "
- + displayEvent.displayId + "/"
- + displayEvent.event + " to " + mUid + "/" + mPid);
- }
- transmitDisplayEvent(displayEvent.displayId, displayEvent.event);
- }
- mPendingEvents.clear();
+ Event[] pending;
+ synchronized (mCallback) {
+ if (mPendingEvents == null || mPendingEvents.isEmpty() || !mAlive) {
return true;
}
+ if (!isReadyLocked()) {
+ return false;
+ }
+ pending = new Event[mPendingEvents.size()];
+ pending = mPendingEvents.toArray(pending);
+ mPendingEvents.clear();
+ }
+ try {
+ for (int i = 0; i < pending.length; i++) {
+ Event displayEvent = pending[i];
+ if (DEBUG) {
+ Slog.d(TAG, "Send pending display event #" + i + " "
+ + displayEvent.displayId + "/"
+ + displayEvent.event + " to " + mUid + "/" + mPid);
+ }
+
+ if (!shouldReceiveRefreshRateWithChangeUpdate(displayEvent.event)) {
+ continue;
+ }
+
+ transmitDisplayEvent(displayEvent.displayId, displayEvent.event);
+ }
+ return true;
} catch (RemoteException ex) {
Slog.w(TAG, "Failed to notify process "
+ mPid + " that display topology changed, assuming it died.", ex);
diff --git a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
index f6b2591..e23756f 100644
--- a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
+++ b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
@@ -112,6 +112,8 @@
return requestDisplayPower(Display.STATE_UNKNOWN);
case "power-off":
return requestDisplayPower(Display.STATE_OFF);
+ case "override-max-importance-rr-callbacks":
+ return overrideMaxImportanceForRRCallbacks();
default:
return handleDefaultCommands(cmd);
}
@@ -631,4 +633,21 @@
mService.requestDisplayPower(displayId, state);
return 0;
}
+
+ private int overrideMaxImportanceForRRCallbacks() {
+ final String importanceString = getNextArg();
+ if (importanceString == null) {
+ getErrPrintWriter().println("Error: no importance specified");
+ return 1;
+ }
+ final int importance;
+ try {
+ importance = Integer.parseInt(importanceString);
+ } catch (NumberFormatException e) {
+ getErrPrintWriter().println("Error: invalid importance: '" + importanceString + "'");
+ return 1;
+ }
+ mService.overrideMaxImportanceForRRCallbacks(importance);
+ return 0;
+ }
}
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index d37dd30..b49c01b 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 @@
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 @@
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/display/feature/DisplayManagerFlags.java b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
index 43aa6f4..d435144 100644
--- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
+++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
@@ -263,6 +263,16 @@
Flags::baseDensityForExternalDisplays
);
+ private final FlagState mFramerateOverrideTriggersRrCallbacks = new FlagState(
+ Flags.FLAG_FRAMERATE_OVERRIDE_TRIGGERS_RR_CALLBACKS,
+ Flags::framerateOverrideTriggersRrCallbacks
+ );
+
+ private final FlagState mRefreshRateEventForForegroundApps = new FlagState(
+ Flags.FLAG_REFRESH_RATE_EVENT_FOR_FOREGROUND_APPS,
+ Flags::refreshRateEventForForegroundApps
+ );
+
/**
* @return {@code true} if 'port' is allowed in display layout configuration file.
*/
@@ -564,6 +574,22 @@
return mBaseDensityForExternalDisplays.isEnabled();
}
+ /**
+ * @return {@code true} if the flag triggering refresh rate callbacks when framerate is
+ * overridden is enabled
+ */
+ public boolean isFramerateOverrideTriggersRrCallbacksEnabled() {
+ return mFramerateOverrideTriggersRrCallbacks.isEnabled();
+ }
+
+
+ /**
+ * @return {@code true} if the flag for sending refresh rate events only for the apps in
+ * foreground is enabled
+ */
+ public boolean isRefreshRateEventForForegroundAppsEnabled() {
+ return mRefreshRateEventForForegroundApps.isEnabled();
+ }
/**
* dumps all flagstates
@@ -620,6 +646,8 @@
pw.println(" " + mSubscribeGranularDisplayEvents);
pw.println(" " + mEnableDisplayContentModeManagementFlagState);
pw.println(" " + mBaseDensityForExternalDisplays);
+ pw.println(" " + mFramerateOverrideTriggersRrCallbacks);
+ pw.println(" " + mRefreshRateEventForForegroundApps);
}
private static class FlagState {
diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig
index 00a9dcb..63cd2d7 100644
--- a/services/core/java/com/android/server/display/feature/display_flags.aconfig
+++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig
@@ -479,3 +479,25 @@
bug: "382954433"
is_fixed_read_only: true
}
+
+flag {
+ name: "framerate_override_triggers_rr_callbacks"
+ namespace: "display_manager"
+ description: "Feature flag to trigger the RR callbacks when framerate overridding happens."
+ bug: "390113266"
+ is_fixed_read_only: true
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
+ name: "refresh_rate_event_for_foreground_apps"
+ namespace: "display_manager"
+ description: "Send Refresh Rate events only for the apps in foreground."
+ bug: "390107600"
+ is_fixed_read_only: true
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java
index 0c04be1..67b1ec3 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.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 @@
/** 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 @@
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 @@
@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 @@
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 @@
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 @@
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 @@
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 @@
return mIsDocked;
}
+ if ((mWhenToDream & DREAM_ON_POSTURED) == DREAM_ON_POSTURED) {
+ return mIsPostured;
+ }
+
return false;
}
}
@@ -646,6 +674,14 @@
}
}
+ 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 @@
}
}
+ @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 ff1a74a..9118c46 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 @@
@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 @@
// 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 @@
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 @@
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 93fdbc7..fd755e3 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 @@
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 @@
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/input/InputSettingsObserver.java b/services/core/java/com/android/server/input/InputSettingsObserver.java
index e25ea4b..d1f07cb 100644
--- a/services/core/java/com/android/server/input/InputSettingsObserver.java
+++ b/services/core/java/com/android/server/input/InputSettingsObserver.java
@@ -90,6 +90,8 @@
(reason) -> updateTouchpadRightClickZoneEnabled()),
Map.entry(Settings.System.getUriFor(Settings.System.TOUCHPAD_SYSTEM_GESTURES),
(reason) -> updateTouchpadSystemGesturesEnabled()),
+ Map.entry(Settings.System.getUriFor(Settings.System.TOUCHPAD_ACCELERATION_ENABLED),
+ (reason) -> updateTouchpadAccelerationEnabled()),
Map.entry(Settings.System.getUriFor(Settings.System.SHOW_TOUCHES),
(reason) -> updateShowTouches()),
Map.entry(Settings.System.getUriFor(Settings.System.POINTER_LOCATION),
@@ -241,6 +243,11 @@
mNative.setTouchpadSystemGesturesEnabled(InputSettings.useTouchpadSystemGestures(mContext));
}
+ private void updateTouchpadAccelerationEnabled() {
+ mNative.setTouchpadAccelerationEnabled(
+ InputSettings.isTouchpadAccelerationEnabled(mContext));
+ }
+
private void updateShowTouches() {
mNative.setShowTouches(getBoolean(Settings.System.SHOW_TOUCHES, false));
}
diff --git a/services/core/java/com/android/server/input/NativeInputManagerService.java b/services/core/java/com/android/server/input/NativeInputManagerService.java
index 4d38c84..f34338a 100644
--- a/services/core/java/com/android/server/input/NativeInputManagerService.java
+++ b/services/core/java/com/android/server/input/NativeInputManagerService.java
@@ -158,6 +158,8 @@
void setTouchpadSystemGesturesEnabled(boolean enabled);
+ void setTouchpadAccelerationEnabled(boolean enabled);
+
void setShowTouches(boolean enabled);
void setNonInteractiveDisplays(int[] displayIds);
@@ -463,6 +465,9 @@
public native void setTouchpadSystemGesturesEnabled(boolean enabled);
@Override
+ public native void setTouchpadAccelerationEnabled(boolean enabled);
+
+ @Override
public native void setShowTouches(boolean enabled);
@Override
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 45c7cff..7b81fc9 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 @@
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/location/contexthub/ContextHubService.java b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
index 2615a76..2b0ca14 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubService.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
@@ -334,7 +334,7 @@
if (Flags.offloadApi() && Flags.offloadImplementation()) {
HubInfoRegistry registry;
try {
- registry = new HubInfoRegistry(mContextHubWrapper);
+ registry = new HubInfoRegistry(mContext, mContextHubWrapper);
mEndpointManager =
new ContextHubEndpointManager(
mContext, mContextHubWrapper, registry, mTransactionManager);
@@ -821,6 +821,13 @@
mHubInfoRegistry.unregisterEndpointDiscoveryCallback(callback);
}
+ @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
+ @Override
+ public void onDiscoveryCallbackFinished() throws RemoteException {
+ super.onDiscoveryCallbackFinished_enforcePermission();
+ mHubInfoRegistry.onDiscoveryCallbackFinished();
+ }
+
private void checkEndpointDiscoveryPreconditions() {
if (mHubInfoRegistry == null) {
Log.e(TAG, "Hub endpoint registry failed to initialize");
diff --git a/services/core/java/com/android/server/location/contexthub/HubInfoRegistry.java b/services/core/java/com/android/server/location/contexthub/HubInfoRegistry.java
index bf54fd7..711383b 100644
--- a/services/core/java/com/android/server/location/contexthub/HubInfoRegistry.java
+++ b/services/core/java/com/android/server/location/contexthub/HubInfoRegistry.java
@@ -16,12 +16,18 @@
package com.android.server.location.contexthub;
+import android.content.Context;
import android.hardware.contexthub.HubEndpointInfo;
import android.hardware.contexthub.HubServiceInfo;
import android.hardware.contexthub.IContextHubEndpointDiscoveryCallback;
import android.hardware.location.HubInfo;
-import android.os.DeadObjectException;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.PowerManager;
+import android.os.PowerManager.WakeLock;
+import android.os.Process;
import android.os.RemoteException;
+import android.os.WorkSource;
import android.util.ArrayMap;
import android.util.IndentingPrintWriter;
import android.util.Log;
@@ -34,10 +40,15 @@
import java.util.List;
import java.util.Objects;
import java.util.Optional;
+import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.BiConsumer;
class HubInfoRegistry implements ContextHubHalEndpointCallback.IEndpointLifecycleCallback {
private static final String TAG = "HubInfoRegistry";
+
+ /** The duration of wakelocks acquired during discovery callbacks */
+ private static final long WAKELOCK_TIMEOUT_MILLIS = 5 * 1000;
+
private final Object mLock = new Object();
private final IContextHubWrapper mContextHubWrapper;
@@ -53,21 +64,37 @@
* A wrapper class that is used to store arguments to
* ContextHubManager.registerEndpointCallback.
*/
- private static class DiscoveryCallback {
+ private static class DiscoveryCallback implements IBinder.DeathRecipient {
+ private final HubInfoRegistry mHubInfoRegistry;
private final IContextHubEndpointDiscoveryCallback mCallback;
private final Optional<Long> mEndpointId;
private final Optional<String> mServiceDescriptor;
- DiscoveryCallback(IContextHubEndpointDiscoveryCallback callback, long endpointId) {
+ // True if the binder death recipient fired
+ private final AtomicBoolean mBinderDied = new AtomicBoolean(false);
+
+ DiscoveryCallback(
+ HubInfoRegistry registry,
+ IContextHubEndpointDiscoveryCallback callback,
+ long endpointId)
+ throws RemoteException {
+ mHubInfoRegistry = registry;
mCallback = callback;
mEndpointId = Optional.of(endpointId);
mServiceDescriptor = Optional.empty();
+ attachDeathRecipient();
}
- DiscoveryCallback(IContextHubEndpointDiscoveryCallback callback, String serviceDescriptor) {
+ DiscoveryCallback(
+ HubInfoRegistry registry,
+ IContextHubEndpointDiscoveryCallback callback,
+ String serviceDescriptor)
+ throws RemoteException {
+ mHubInfoRegistry = registry;
mCallback = callback;
mEndpointId = Optional.empty();
mServiceDescriptor = Optional.of(serviceDescriptor);
+ attachDeathRecipient();
}
public IContextHubEndpointDiscoveryCallback getCallback() {
@@ -79,6 +106,10 @@
* @return true if info matches
*/
public boolean isMatch(HubEndpointInfo info) {
+ if (mBinderDied.get()) {
+ Log.w(TAG, "Callback died, isMatch returning false");
+ return false;
+ }
if (mEndpointId.isPresent()) {
return mEndpointId.get() == info.getIdentifier().getEndpoint();
}
@@ -91,6 +122,17 @@
}
return false;
}
+
+ @Override
+ public void binderDied() {
+ Log.d(TAG, "Binder died for discovery callback");
+ mBinderDied.set(true);
+ mHubInfoRegistry.unregisterEndpointDiscoveryCallback(mCallback);
+ }
+
+ private void attachDeathRecipient() throws RemoteException {
+ mCallback.asBinder().linkToDeath(this, 0 /* flags */);
+ }
}
/* The list of discovery callbacks registered with the service */
@@ -99,7 +141,11 @@
private final Object mCallbackLock = new Object();
- HubInfoRegistry(IContextHubWrapper contextHubWrapper) throws InstantiationException {
+ /** Wakelock held while endpoint callbacks are being invoked */
+ private final WakeLock mWakeLock;
+
+ HubInfoRegistry(Context context, IContextHubWrapper contextHubWrapper)
+ throws InstantiationException {
mContextHubWrapper = contextHubWrapper;
try {
refreshCachedHubs();
@@ -109,6 +155,16 @@
Log.e(TAG, error, e);
throw new InstantiationException(error);
}
+
+ PowerManager powerManager = context.getSystemService(PowerManager.class);
+ if (powerManager == null) {
+ String error = "PowerManager was null";
+ Log.e(TAG, error);
+ throw new InstantiationError(error);
+ }
+ mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
+ mWakeLock.setWorkSource(new WorkSource(Process.myUid(), context.getPackageName()));
+ mWakeLock.setReferenceCounted(true);
}
/** Retrieve the list of hubs available. */
@@ -178,12 +234,7 @@
try {
cb.onEndpointsStarted(infoList);
} catch (RemoteException e) {
- if (e instanceof DeadObjectException) {
- Log.w(TAG, "onEndpointStarted: callback died, unregistering");
- unregisterEndpointDiscoveryCallback(cb);
- } else {
- Log.e(TAG, "Exception while calling onEndpointsStarted", e);
- }
+ Log.e(TAG, "Exception while calling onEndpointsStarted", e);
}
});
}
@@ -208,12 +259,7 @@
cb.onEndpointsStopped(
infoList, ContextHubServiceUtil.toAppHubEndpointReason(reason));
} catch (RemoteException e) {
- if (e instanceof DeadObjectException) {
- Log.w(TAG, "onEndpointStopped: callback died, unregistering");
- unregisterEndpointDiscoveryCallback(cb);
- } else {
- Log.e(TAG, "Exception while calling onEndpointsStopped", e);
- }
+ Log.e(TAG, "Exception while calling onEndpointsStopped", e);
}
});
}
@@ -254,7 +300,11 @@
Objects.requireNonNull(callback, "callback cannot be null");
synchronized (mCallbackLock) {
checkCallbackAlreadyRegistered(callback);
- mEndpointDiscoveryCallbacks.add(new DiscoveryCallback(callback, endpointId));
+ try {
+ mEndpointDiscoveryCallbacks.add(new DiscoveryCallback(this, callback, endpointId));
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException while adding discovery callback", e);
+ }
}
}
@@ -264,7 +314,12 @@
Objects.requireNonNull(callback, "callback cannot be null");
synchronized (mCallbackLock) {
checkCallbackAlreadyRegistered(callback);
- mEndpointDiscoveryCallbacks.add(new DiscoveryCallback(callback, serviceDescriptor));
+ try {
+ mEndpointDiscoveryCallbacks.add(
+ new DiscoveryCallback(this, callback, serviceDescriptor));
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException while adding discovery callback", e);
+ }
}
}
@@ -282,6 +337,11 @@
}
}
+ /* package */
+ void onDiscoveryCallbackFinished() {
+ releaseWakeLock();
+ }
+
private void checkCallbackAlreadyRegistered(
IContextHubEndpointDiscoveryCallback callback) {
synchronized (mCallbackLock) {
@@ -315,6 +375,7 @@
}
}
+ acquireWakeLock();
consumer.accept(
discoveryCallback.getCallback(),
infoList.toArray(new HubEndpointInfo[infoList.size()]));
@@ -322,6 +383,26 @@
}
}
+ private void acquireWakeLock() {
+ Binder.withCleanCallingIdentity(
+ () -> {
+ mWakeLock.acquire(WAKELOCK_TIMEOUT_MILLIS);
+ });
+ }
+
+ private void releaseWakeLock() {
+ Binder.withCleanCallingIdentity(
+ () -> {
+ if (mWakeLock.isHeld()) {
+ try {
+ mWakeLock.release();
+ } catch (RuntimeException e) {
+ Log.e(TAG, "Releasing the wakelock fails - ", e);
+ }
+ }
+ });
+ }
+
void dump(IndentingPrintWriter ipw) {
synchronized (mLock) {
dumpLocked(ipw);
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 efc1b99..6ad7ea7 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,34 @@
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.SharedPreferences;
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;
@@ -36,12 +52,14 @@
import android.media.quality.SoundProfileHandle;
import android.os.Binder;
import android.os.Bundle;
+import android.os.Environment;
import android.os.IBinder;
import android.os.PersistableBundle;
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;
@@ -53,6 +71,7 @@
import org.json.JSONException;
import org.json.JSONObject;
+import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
@@ -60,6 +79,7 @@
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;
@@ -71,24 +91,46 @@
private static final boolean DEBUG = false;
private static final String TAG = "MediaQualityService";
+ private static final String ALLOWLIST = "allowlist";
+ private static final String PICTURE_PROFILE_PREFERENCE = "picture_profile_preference";
+ private static final String SOUND_PROFILE_PREFERENCE = "sound_profile_preference";
+ private static final String COMMA_DELIMITER = ",";
private static final int MAX_UUID_GENERATION_ATTEMPTS = 10;
private final Context mContext;
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;
+ private SharedPreferences mPictureProfileSharedPreference;
+ private SharedPreferences mSoundProfileSharedPreference;
public MediaQualityService(Context context) {
super(context);
mContext = context;
+ mHalAmbientBacklightCallback = new HalAmbientBacklightCallback();
mPackageManager = mContext.getPackageManager();
mPictureProfileTempIdMap = new BiMap<>();
mSoundProfileTempIdMap = new BiMap<>();
mMediaQualityDbHelper = new MediaQualityDbHelper(mContext);
mMediaQualityDbHelper.setWriteAheadLoggingEnabled(true);
mMediaQualityDbHelper.setIdleConnectionTimeout(30);
+
+ // The package info in the context isn't initialized in the way it is for normal apps,
+ // so the standard, name-based context.getSharedPreferences doesn't work. Instead, we
+ // build the path manually below using the same policy that appears in ContextImpl.
+ final Context deviceContext = mContext.createDeviceProtectedStorageContext();
+ final File pictureProfilePrefs = new File(Environment.getDataSystemDirectory(),
+ PICTURE_PROFILE_PREFERENCE);
+ mPictureProfileSharedPreference = deviceContext.getSharedPreferences(
+ pictureProfilePrefs, Context.MODE_PRIVATE);
+ final File soundProfilePrefs = new File(Environment.getDataSystemDirectory(),
+ SOUND_PROFILE_PREFERENCE);
+ mSoundProfileSharedPreference = deviceContext.getSharedPreferences(
+ soundProfilePrefs, Context.MODE_PRIVATE);
}
@Override
@@ -97,6 +139,13 @@
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 +331,208 @@
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 +750,77 @@
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 +1219,86 @@
@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
@@ -937,6 +1313,11 @@
notifyOnPictureProfileError(null, PictureProfile.ERROR_NO_PERMISSION,
Binder.getCallingUid(), Binder.getCallingPid());
}
+ String allowlist = mPictureProfileSharedPreference.getString(ALLOWLIST, null);
+ if (allowlist != null) {
+ String[] stringArray = allowlist.split(COMMA_DELIMITER);
+ return new ArrayList<>(Arrays.asList(stringArray));
+ }
return new ArrayList<>();
}
@@ -946,6 +1327,9 @@
notifyOnPictureProfileError(null, PictureProfile.ERROR_NO_PERMISSION,
Binder.getCallingUid(), Binder.getCallingPid());
}
+ SharedPreferences.Editor editor = mPictureProfileSharedPreference.edit();
+ editor.putString(ALLOWLIST, String.join(COMMA_DELIMITER, packages));
+ editor.commit();
}
@Override
@@ -954,6 +1338,11 @@
notifyOnSoundProfileError(null, SoundProfile.ERROR_NO_PERMISSION,
Binder.getCallingUid(), Binder.getCallingPid());
}
+ String allowlist = mSoundProfileSharedPreference.getString(ALLOWLIST, null);
+ if (allowlist != null) {
+ String[] stringArray = allowlist.split(COMMA_DELIMITER);
+ return new ArrayList<>(Arrays.asList(stringArray));
+ }
return new ArrayList<>();
}
@@ -963,6 +1352,9 @@
notifyOnSoundProfileError(null, SoundProfile.ERROR_NO_PERMISSION,
Binder.getCallingUid(), Binder.getCallingPid());
}
+ SharedPreferences.Editor editor = mSoundProfileSharedPreference.edit();
+ editor.putString(ALLOWLIST, String.join(COMMA_DELIMITER, packages));
+ editor.commit();
}
@Override
@@ -979,10 +1371,10 @@
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 +1384,10 @@
public boolean isAutoPictureQualityEnabled(UserHandle user) {
try {
if (mMediaQuality != null) {
- return mMediaQuality.getAutoPqEnabled();
+ if (mMediaQuality.isAutoPqSupported()) {
+ return 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 +1403,12 @@
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 +1416,12 @@
public boolean isSuperResolutionEnabled(UserHandle user) {
try {
if (mMediaQuality != null) {
- return mMediaQuality.getAutoSrEnabled();
+ if (mMediaQuality.isAutoSrSupported()) {
+ return 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 +1435,12 @@
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 +1448,12 @@
public boolean isAutoSoundQualityEnabled(UserHandle user) {
try {
if (mMediaQuality != null) {
- return mMediaQuality.getAutoAqEnabled();
+ if (mMediaQuality.isAutoAqSupported()) {
+ return 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 +1511,167 @@
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 837003f..588e879 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -356,6 +356,7 @@
import com.android.internal.util.XmlUtils;
import com.android.internal.util.function.TriPredicate;
import com.android.internal.widget.LockPatternUtils;
+import com.android.modules.expresslog.Counter;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.DeviceIdleInternal;
@@ -762,6 +763,7 @@
private int mWarnRemoteViewsSizeBytes;
private int mStripRemoteViewsSizeBytes;
+ protected String[] mDefaultUnsupportedAdjustments;
@VisibleForTesting
protected boolean mShowReviewPermissionsNotification;
@@ -2938,6 +2940,9 @@
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 +3038,9 @@
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());
@@ -4374,8 +4378,8 @@
public @NonNull List<String> getUnsupportedAdjustmentTypes() {
checkCallerIsSystemOrSystemUiOrShell();
synchronized (mNotificationLock) {
- return new ArrayList(mAssistants.mNasUnsupported.getOrDefault(
- UserHandle.getUserId(Binder.getCallingUid()), new HashSet<>()));
+ return new ArrayList(mAssistants.getUnsupportedAdjustments(
+ UserHandle.getUserId(Binder.getCallingUid())));
}
}
@@ -4397,16 +4401,16 @@
@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();
}
@@ -7133,6 +7137,13 @@
Slog.e(TAG, "exiting pullStats: bad request");
return 0;
}
+
+ @Override
+ public void incrementCounter(String metricId) {
+ if (android.app.Flags.nmBinderPerfLogNmThrottling() && metricId != null) {
+ Counter.logIncrementWithUid(metricId, Binder.getCallingUid());
+ }
+ }
};
private void handleNotificationPermissionChange(String pkg, @UserIdInt int userId) {
@@ -7211,11 +7222,12 @@
toRemove.add(potentialKey);
}
if (notificationClassification() && adjustments.containsKey(KEY_TYPE)) {
+ mAssistants.setNasUnsupportedDefaults(r.getSbn().getNormalizedUserId());
if (!mAssistants.isAdjustmentKeyTypeAllowed(adjustments.getInt(KEY_TYPE))) {
toRemove.add(potentialKey);
} else if (notificationClassificationUi()
&& !mAssistants.isTypeAdjustmentAllowedForPackage(
- r.getSbn().getPackageName(), adjustments.getInt(KEY_TYPE))) {
+ r.getSbn().getPackageName())) {
toRemove.add(potentialKey);
}
}
@@ -7552,24 +7564,6 @@
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();
@@ -11882,14 +11876,10 @@
static final String TAG_ENABLED_NOTIFICATION_ASSISTANTS = "enabled_assistants";
private static final String ATT_TYPES = "types";
- private static final String ATT_DENIED = "denied_adjustments";
+ private static final String ATT_DENIED = "user_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_NAS_UNSUPPORTED = "nas_unsupported_adjustments";
+ private static final String ATT_TYPES_DENIED_APPS = "types_denied_apps";
private final Object mLock = new Object();
@@ -11905,14 +11895,8 @@
@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;
@@ -12113,104 +12097,41 @@
}
}
- /**
- * 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 !mClassificationTypeDeniedPackages.contains(pkg);
}
}
- return false;
+ return true;
}
@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 packagesWithModifications;
- }
- return new ArraySet<String>();
- }
-
- @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);
}
}
@@ -12572,7 +12493,7 @@
}
} else {
if (android.service.notification.Flags.notificationClassification()) {
- mNasUnsupported.put(userId, new HashSet<>());
+ setNasUnsupportedDefaults(userId);
}
}
super.setPackageOrComponentEnabled(pkgOrComponent, userId, isPrimary, enabled, userSet);
@@ -12613,8 +12534,8 @@
if (!android.service.notification.Flags.notificationClassification()) {
return;
}
- HashSet<String> disabledAdjustments =
- mNasUnsupported.getOrDefault(info.userid, new HashSet<>());
+ setNasUnsupportedDefaults(info.userid);
+ HashSet<String> disabledAdjustments = mNasUnsupported.get(info.userid);
if (supported) {
disabledAdjustments.remove(key);
} else {
@@ -12630,7 +12551,15 @@
if (!android.service.notification.Flags.notificationClassification()) {
return new HashSet<>();
}
- return mNasUnsupported.getOrDefault(userId, new HashSet<>());
+ setNasUnsupportedDefaults(userId);
+ return mNasUnsupported.get(userId);
+ }
+
+ private void setNasUnsupportedDefaults(@UserIdInt int userId) {
+ if (mNasUnsupported != null && !mNasUnsupported.containsKey(userId)) {
+ mNasUnsupported.put(userId, new HashSet(List.of(mDefaultUnsupportedAdjustments)));
+ handleSavePolicyFile();
+ }
}
@Override
@@ -12677,25 +12606,16 @@
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 +12642,12 @@
}
}
}
- } 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(",")));
}
}
}
@@ -12765,7 +12672,7 @@
List<String> unsupportedAdjustments = new ArrayList(
mNasUnsupported.getOrDefault(
UserHandle.getUserId(Binder.getCallingUid()),
- new HashSet<>())
+ new HashSet(List.of(mDefaultUnsupportedAdjustments)))
);
bundlesAllowed = !unsupportedAdjustments.contains(Adjustment.KEY_TYPE);
}
diff --git a/services/core/java/com/android/server/notification/ZenLog.java b/services/core/java/com/android/server/notification/ZenLog.java
index 1aa5ac0..7e853d9 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.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 @@
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 @@
}
}
- 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 0a63f3f..b39b6fd 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -16,6 +16,7 @@
package com.android.server.notification;
+import static android.app.AutomaticZenRule.TYPE_DRIVING;
import static android.app.AutomaticZenRule.TYPE_UNKNOWN;
import static android.app.NotificationManager.AUTOMATIC_RULE_STATUS_ACTIVATED;
import static android.app.NotificationManager.AUTOMATIC_RULE_STATUS_DEACTIVATED;
@@ -46,6 +47,7 @@
import static com.android.internal.util.FrameworkStatsLog.DND_MODE_RULE;
import static com.android.internal.util.Preconditions.checkArgument;
+import static com.android.server.notification.Flags.preventZenDeviceEffectsWhileDriving;
import static java.util.Objects.requireNonNull;
@@ -659,7 +661,8 @@
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 +974,27 @@
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 +1002,14 @@
}
}
}
- 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 +1020,7 @@
for (ZenRule rule : rules) {
applyConditionAndReconsiderOverride(rule, condition, origin);
- setConfigLocked(config, rule.component, origin, "conditionChanged", callingUid);
+ setConfigLocked(config, rule.component, origin, reason, callingUid);
}
}
@@ -2111,13 +2116,14 @@
}
@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 +2132,7 @@
@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 +2155,7 @@
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);
@@ -2375,6 +2381,26 @@
}
if (Flags.modesApi()) {
+ // Prevent other rules from applying grayscale if Driving is active (but allow it
+ // if _Driving itself_ wants grayscale).
+ if (Flags.modesUi() && preventZenDeviceEffectsWhileDriving()) {
+ boolean hasActiveDriving = false;
+ boolean hasActiveDrivingWithGrayscale = false;
+ for (ZenRule rule : mConfig.automaticRules.values()) {
+ if (rule.isActive() && rule.type == TYPE_DRIVING) {
+ hasActiveDriving = true;
+ if (rule.zenDeviceEffects != null
+ && rule.zenDeviceEffects.shouldDisplayGrayscale()) {
+ hasActiveDrivingWithGrayscale = true;
+ break; // Further rules won't affect decision.
+ }
+ }
+ }
+ if (hasActiveDriving && !hasActiveDrivingWithGrayscale) {
+ deviceEffectsBuilder.setShouldDisplayGrayscale(false);
+ }
+ }
+
ZenDeviceEffects deviceEffects = deviceEffectsBuilder.build();
if (!deviceEffects.equals(mConsolidatedDeviceEffects)) {
mConsolidatedDeviceEffects = deviceEffects;
diff --git a/services/core/java/com/android/server/notification/flags.aconfig b/services/core/java/com/android/server/notification/flags.aconfig
index b4a8aee..822ff48 100644
--- a/services/core/java/com/android/server/notification/flags.aconfig
+++ b/services/core/java/com/android/server/notification/flags.aconfig
@@ -190,3 +190,13 @@
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "prevent_zen_device_effects_while_driving"
+ namespace: "systemui"
+ description: "Don't apply certain device effects (such as grayscale) from active zen rules, if a rule of TYPE_DRIVING is active"
+ bug: "390389174"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/services/core/java/com/android/server/os/NativeTombstoneManager.java b/services/core/java/com/android/server/os/NativeTombstoneManager.java
index f23d782..33c1229 100644
--- a/services/core/java/com/android/server/os/NativeTombstoneManager.java
+++ b/services/core/java/com/android/server/os/NativeTombstoneManager.java
@@ -137,16 +137,26 @@
return;
}
- String processName = "UNKNOWN";
final boolean isProtoFile = filename.endsWith(".pb");
+
+ // Only process the pb tombstone output, the text version will be generated in
+ // BootReceiver.filterAndAddTombstoneToDropBox through pbtombstone
+ if (Flags.protoTombstone() && !isProtoFile) {
+ return;
+ }
+
File protoPath = isProtoFile ? path : new File(path.getAbsolutePath() + ".pb");
- Optional<TombstoneFile> parsedTombstone = handleProtoTombstone(protoPath, isProtoFile);
- if (parsedTombstone.isPresent()) {
- processName = parsedTombstone.get().getProcessName();
- }
- BootReceiver.addTombstoneToDropBox(mContext, path, isProtoFile, processName, mTmpFileLock);
+ final String processName = handleProtoTombstone(protoPath, isProtoFile)
+ .map(TombstoneFile::getProcessName)
+ .orElse("UNKNOWN");
+ if (Flags.protoTombstone()) {
+ BootReceiver.filterAndAddTombstoneToDropBox(mContext, path, processName, mTmpFileLock);
+ } else {
+ BootReceiver.addTombstoneToDropBox(mContext, path, isProtoFile,
+ processName, mTmpFileLock);
+ }
// TODO(b/339371242): An optimizer on WearOS is misbehaving and this member is being garbage
// collected as it's never referenced inside this class outside of the constructor. But,
// it's a file watcher, and needs to stay alive to do its job. So, add a cheap check here to
diff --git a/services/core/java/com/android/server/os/core_os_flags.aconfig b/services/core/java/com/android/server/os/core_os_flags.aconfig
index efdc9b8..5e35cf5 100644
--- a/services/core/java/com/android/server/os/core_os_flags.aconfig
+++ b/services/core/java/com/android/server/os/core_os_flags.aconfig
@@ -3,7 +3,7 @@
flag {
name: "proto_tombstone"
- namespace: "proto_tombstone_ns"
+ namespace: "stability"
description: "Use proto tombstones as source of truth for adding to dropbox"
bug: "323857385"
}
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 4cca855..8eb5b6f 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -856,39 +856,45 @@
if (DEBUG_INSTALL) Log.v(TAG, "+ starting restore round-trip " + token);
final boolean succeeded = request.getReturnCode() == PackageManager.INSTALL_SUCCEEDED;
- if (succeeded && doRestore) {
- // Pass responsibility to the Backup Manager. It will perform a
- // restore if appropriate, then pass responsibility back to the
- // Package Manager to run the post-install observer callbacks
- // and broadcasts.
- request.closeFreezer();
- doRestore = performBackupManagerRestore(userId, token, request);
- }
+ if (succeeded) {
+ request.onRestoreStarted();
+ if (doRestore) {
+ // Pass responsibility to the Backup Manager. It will perform a
+ // restore if appropriate, then pass responsibility back to the
+ // Package Manager to run the post-install observer callbacks
+ // and broadcasts.
+ // Note: MUST close freezer before backup/restore, otherwise test
+ // of CtsBackupHostTestCases will fail.
+ request.closeFreezer();
+ doRestore = performBackupManagerRestore(userId, token, request);
+ }
- // If this is an update to a package that might be potentially downgraded, then we
- // need to check with the rollback manager whether there's any userdata that might
- // need to be snapshotted or restored for the package.
- //
- // TODO(narayan): Get this working for cases where userId == UserHandle.USER_ALL.
- if (succeeded && !doRestore && update) {
- doRestore = performRollbackManagerRestore(userId, token, request);
- }
+ // If this is an update to a package that might be potentially downgraded, then we
+ // need to check with the rollback manager whether there's any userdata that might
+ // need to be snapshotted or restored for the package.
+ //
+ // TODO(narayan): Get this working for cases where userId == UserHandle.USER_ALL.
+ if (!doRestore && update) {
+ doRestore = performRollbackManagerRestore(userId, token, request);
+ }
- if (succeeded && doRestore && !request.hasPostInstallRunnable()) {
- boolean hasNeverBeenRestored =
- packageSetting != null && packageSetting.isPendingRestore();
- request.setPostInstallRunnable(() -> {
- // Permissions should be restored on each user that has the app installed for the
- // first time, unless it's an unarchive install for an archived app, in which case
- // the permissions should be restored on each user that has the app updated.
- int[] userIdsToRestorePermissions = hasNeverBeenRestored
- ? request.getUpdateBroadcastUserIds()
- : request.getFirstTimeBroadcastUserIds();
- for (int restorePermissionUserId : userIdsToRestorePermissions) {
- mPm.restorePermissionsAndUpdateRolesForNewUserInstall(request.getName(),
- restorePermissionUserId);
- }
- });
+ if (doRestore && !request.hasPostInstallRunnable()) {
+ boolean hasNeverBeenRestored =
+ packageSetting != null && packageSetting.isPendingRestore();
+ request.setPostInstallRunnable(() -> {
+ // Permissions should be restored on each user that has the app installed for
+ // the first time, unless it's an unarchive install for an archived app, in
+ // which case the permissions should be restored on each user that has the
+ // app updated.
+ int[] userIdsToRestorePermissions = hasNeverBeenRestored
+ ? request.getUpdateBroadcastUserIds()
+ : request.getFirstTimeBroadcastUserIds();
+ for (int restorePermissionUserId : userIdsToRestorePermissions) {
+ mPm.restorePermissionsAndUpdateRolesForNewUserInstall(request.getName(),
+ restorePermissionUserId);
+ }
+ });
+ }
}
if (doRestore) {
@@ -898,8 +904,11 @@
}
}
} else {
- // No restore possible, or the Backup Manager was mysteriously not
- // available -- just fire the post-install work request directly.
+ // No restore possible, or the Backup Manager was mysteriously not available.
+ // we don't need to wait for restore to complete before closing the freezer,
+ // so we can close the freezer right away.
+ // Also just fire the post-install work request directly.
+ request.closeFreezer();
if (DEBUG_INSTALL) Log.v(TAG, "No restore - queue post-install for " + token);
Trace.asyncTraceBegin(TRACE_TAG_PACKAGE_MANAGER, "postInstall", token);
diff --git a/services/core/java/com/android/server/pm/InstallRequest.java b/services/core/java/com/android/server/pm/InstallRequest.java
index c96c160..fbf5db5 100644
--- a/services/core/java/com/android/server/pm/InstallRequest.java
+++ b/services/core/java/com/android/server/pm/InstallRequest.java
@@ -1016,6 +1016,18 @@
}
}
+ public void onRestoreStarted() {
+ if (mPackageMetrics != null) {
+ mPackageMetrics.onStepStarted(PackageMetrics.STEP_RESTORE);
+ }
+ }
+
+ public void onRestoreFinished() {
+ if (mPackageMetrics != null) {
+ mPackageMetrics.onStepFinished(PackageMetrics.STEP_RESTORE);
+ }
+ }
+
public void onDexoptFinished(DexoptResult dexoptResult) {
// Only report external profile warnings when installing from adb. The goal is to warn app
// developers if they have provided bad external profiles, so it's not beneficial to report
diff --git a/services/core/java/com/android/server/pm/PackageHandler.java b/services/core/java/com/android/server/pm/PackageHandler.java
index 9916be6..7b1eb58 100644
--- a/services/core/java/com/android/server/pm/PackageHandler.java
+++ b/services/core/java/com/android/server/pm/PackageHandler.java
@@ -99,6 +99,7 @@
}
break;
}
+ request.onRestoreFinished();
request.closeFreezer();
request.onInstallCompleted();
request.runPostInstallRunnable();
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index f60e086..61429a4 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 @@
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 @@
}
}
- //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/pm/PackageMetrics.java b/services/core/java/com/android/server/pm/PackageMetrics.java
index 856d6a7..994ee42 100644
--- a/services/core/java/com/android/server/pm/PackageMetrics.java
+++ b/services/core/java/com/android/server/pm/PackageMetrics.java
@@ -72,6 +72,7 @@
public static final int STEP_COMMIT = 4;
public static final int STEP_DEXOPT = 5;
public static final int STEP_FREEZE_INSTALL = 6;
+ public static final int STEP_RESTORE = 7;
@IntDef(prefix = {"STEP_"}, value = {
STEP_PREPARE,
@@ -79,7 +80,8 @@
STEP_RECONCILE,
STEP_COMMIT,
STEP_DEXOPT,
- STEP_FREEZE_INSTALL
+ STEP_FREEZE_INSTALL,
+ STEP_RESTORE
})
@Retention(RetentionPolicy.SOURCE)
public @interface StepInt {
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index 9d840d0..b905041 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -26,6 +26,7 @@
import static android.app.AppOpsManager.MODE_ALLOWED;
import static android.app.AppOpsManager.MODE_ERRORED;
import static android.app.AppOpsManager.MODE_IGNORED;
+import static android.app.AppOpsManager.OP_BLUETOOTH_CONNECT;
import static android.content.pm.ApplicationInfo.AUTO_REVOKE_DISALLOWED;
import static android.content.pm.ApplicationInfo.AUTO_REVOKE_DISCOURAGED;
import static android.permission.flags.Flags.serverSideAttributionRegistration;
@@ -1668,7 +1669,22 @@
throw new SecurityException(msg + ":" + e.getMessage());
}
}
- return Math.max(checkedOpResult, notedOpResult);
+ int result = Math.max(checkedOpResult, notedOpResult);
+ // TODO(b/333931259): Remove extra logging after this issue is diagnosed.
+ if (op == OP_BLUETOOTH_CONNECT && result == MODE_ERRORED) {
+ if (result == checkedOpResult) {
+ Slog.e(LOG_TAG, "BLUETOOTH_CONNECT permission hard denied as"
+ + " checkOp for resolvedAttributionSource "
+ + resolvedAttributionSource + " and op " + op
+ + " returned MODE_ERRORED");
+ } else {
+ Slog.e(LOG_TAG, "BLUETOOTH_CONNECT permission hard denied as"
+ + " noteOp for resolvedAttributionSource "
+ + resolvedAttributionSource + " and op " + notedOp
+ + " returned MODE_ERRORED");
+ }
+ }
+ return result;
}
}
diff --git a/services/core/java/com/android/server/policy/PermissionPolicyService.java b/services/core/java/com/android/server/policy/PermissionPolicyService.java
index deaa8d8..44d787f 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 @@
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/OWNERS b/services/core/java/com/android/server/power/OWNERS
index c1fad33..a1531a8 100644
--- a/services/core/java/com/android/server/power/OWNERS
+++ b/services/core/java/com/android/server/power/OWNERS
@@ -2,6 +2,8 @@
santoscordon@google.com
petsjonkin@google.com
brup@google.com
+flc@google.com
+wilczynskip@google.com
per-file ThermalManagerService.java=file:/THERMAL_OWNERS
per-file LowPowerStandbyController.java=qingxun@google.com
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index f9e4022..090707d 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_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 @@
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 @@
// 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 @@
// 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 @@
// 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 @@
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 @@
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 @@
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 @@
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 @@
private boolean shouldNapAtBedTimeLocked() {
return mDreamsActivateOnSleepSetting
|| (mDreamsActivateOnDockSetting
- && mDockState != Intent.EXTRA_DOCK_STATE_UNDOCKED);
+ && mDockState != Intent.EXTRA_DOCK_STATE_UNDOCKED)
+ || (mDreamsActivateWhilePosturedSetting && mDevicePostured);
}
/**
@@ -4489,6 +4513,17 @@
}
}
+ 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 @@
+ mDreamsActivatedOnSleepByDefaultConfig);
pw.println(" mDreamsActivatedOnDockByDefaultConfig="
+ mDreamsActivatedOnDockByDefaultConfig);
+ pw.println(" mDreamsActivatedWhilePosturedByDefaultConfig="
+ + mDreamsActivatedWhilePosturedByDefaultConfig);
pw.println(" mDreamsEnabledOnBatteryConfig="
+ mDreamsEnabledOnBatteryConfig);
pw.println(" mDreamsBatteryLevelMinimumWhenPoweredConfig="
@@ -4805,6 +4842,8 @@
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 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 6798a61..2452dc5 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.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 @@
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 40ea931..7f2c68f 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.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 @@
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 @@
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 @@
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 @@
}
}
+ 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/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index b7b4cc0..48dd2eb 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -87,6 +87,7 @@
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.IndentingPrintWriter;
+import android.util.IntArray;
import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
@@ -124,6 +125,7 @@
import com.android.server.power.ShutdownCheckPoints;
import com.android.server.power.ShutdownThread;
import com.android.server.wm.ActivityTaskManagerInternal;
+import com.android.systemui.shared.Flags;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -341,15 +343,19 @@
@Override
public void onDisplayAdded(int displayId) {
- synchronized (mLock) {
- mDisplayUiState.put(displayId, new UiState());
+ if (Flags.statusBarConnectedDisplays()) {
+ synchronized (mLock) {
+ mDisplayUiState.put(displayId, new UiState());
+ }
}
}
@Override
public void onDisplayRemoved(int displayId) {
- synchronized (mLock) {
- mDisplayUiState.remove(displayId);
+ if (Flags.statusBarConnectedDisplays()) {
+ synchronized (mLock) {
+ mDisplayUiState.remove(displayId);
+ }
}
}
@@ -1320,53 +1326,66 @@
return mTracingEnabled;
}
- // TODO(b/117478341): make it aware of multi-display if needed.
@Override
public void disable(int what, IBinder token, String pkg) {
disableForUser(what, token, pkg, mCurrentUserId);
}
- // TODO(b/117478341): make it aware of multi-display if needed.
+ /**
+ * Disable additional status bar features for user for all displays. Pass the bitwise-or of the
+ * {@code #DISABLE_*} flags. To re-enable everything, pass {@code #DISABLE_NONE}.
+ *
+ * Warning: Only pass {@code #DISABLE_*} flags into this function, do not use
+ * {@code #DISABLE2_*} flags.
+ */
@Override
public void disableForUser(int what, IBinder token, String pkg, int userId) {
enforceStatusBar();
enforceValidCallingUser();
synchronized (mLock) {
- disableLocked(DEFAULT_DISPLAY, userId, what, token, pkg, 1);
+ IntArray displayIds = new IntArray();
+ for (int i = 0; i < mDisplayUiState.size(); i++) {
+ displayIds.add(mDisplayUiState.keyAt(i));
+ }
+ disableLocked(displayIds, userId, what, token, pkg, 1);
}
}
- // TODO(b/117478341): make it aware of multi-display if needed.
/**
- * Disable additional status bar features. Pass the bitwise-or of the DISABLE2_* flags.
- * To re-enable everything, pass {@link #DISABLE2_NONE}.
+ * Disable additional status bar features. Pass the bitwise-or of the {@code #DISABLE2_*} flags.
+ * To re-enable everything, pass {@code #DISABLE2_NONE}.
*
- * Warning: Only pass DISABLE2_* flags into this function, do not use DISABLE_* flags.
+ * Warning: Only pass {@code #DISABLE2_*} flags into this function, do not use
+ * {@code #DISABLE_*} flags.
*/
@Override
public void disable2(int what, IBinder token, String pkg) {
disable2ForUser(what, token, pkg, mCurrentUserId);
}
- // TODO(b/117478341): make it aware of multi-display if needed.
/**
- * Disable additional status bar features for a given user. Pass the bitwise-or of the
- * DISABLE2_* flags. To re-enable everything, pass {@link #DISABLE_NONE}.
+ * Disable additional status bar features for a given user for all displays. Pass the bitwise-or
+ * of the {@code #DISABLE2_*} flags. To re-enable everything, pass {@code #DISABLE2_NONE}.
*
- * Warning: Only pass DISABLE2_* flags into this function, do not use DISABLE_* flags.
+ * Warning: Only pass {@code #DISABLE2_*} flags into this function, do not use
+ * {@code #DISABLE_*} flags.
*/
@Override
public void disable2ForUser(int what, IBinder token, String pkg, int userId) {
enforceStatusBar();
synchronized (mLock) {
- disableLocked(DEFAULT_DISPLAY, userId, what, token, pkg, 2);
+ IntArray displayIds = new IntArray();
+ for (int i = 0; i < mDisplayUiState.size(); i++) {
+ displayIds.add(mDisplayUiState.keyAt(i));
+ }
+ disableLocked(displayIds, userId, what, token, pkg, 2);
}
}
- private void disableLocked(int displayId, int userId, int what, IBinder token, String pkg,
- int whichFlag) {
+ private void disableLocked(IntArray displayIds, int userId, int what, IBinder token,
+ String pkg, int whichFlag) {
// It's important that the the callback and the call to mBar get done
// in the same order when multiple threads are calling this function
// so they are paired correctly. The messages on the handler will be
@@ -1376,18 +1395,27 @@
// Ensure state for the current user is applied, even if passed a non-current user.
final int net1 = gatherDisableActionsLocked(mCurrentUserId, 1);
final int net2 = gatherDisableActionsLocked(mCurrentUserId, 2);
- final UiState state = getUiState(displayId);
- if (!state.disableEquals(net1, net2)) {
- state.setDisabled(net1, net2);
- mHandler.post(() -> mNotificationDelegate.onSetDisabled(net1));
- IStatusBar bar = mBar;
- if (bar != null) {
- try {
- bar.disable(displayId, net1, net2);
- } catch (RemoteException ex) {
+ boolean shouldCallNotificationOnSetDisabled = false;
+ IStatusBar bar = mBar;
+ for (int displayId : displayIds.toArray()) {
+ final UiState state = getUiState(displayId);
+ if (!state.disableEquals(net1, net2)) {
+ shouldCallNotificationOnSetDisabled = true;
+ state.setDisabled(net1, net2);
+ if (bar != null) {
+ try {
+ // TODO(b/388244660): Create IStatusBar#disableForAllDisplays to avoid
+ // multiple IPC calls.
+ bar.disable(displayId, net1, net2);
+ } catch (RemoteException ex) {
+ Slog.e(TAG, "Unable to disable Status bar.", ex);
+ }
}
}
}
+ if (shouldCallNotificationOnSetDisabled) {
+ mHandler.post(() -> mNotificationDelegate.onSetDisabled(net1));
+ }
}
/**
@@ -1539,7 +1567,8 @@
if (SPEW) Slog.d(TAG, "setDisableFlags(0x" + Integer.toHexString(flags) + ")");
synchronized (mLock) {
- disableLocked(displayId, mCurrentUserId, flags, mSysUiVisToken, cause, 1);
+ disableLocked(IntArray.wrap(new int[]{displayId}), mCurrentUserId, flags,
+ mSysUiVisToken, cause, 1);
}
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index a24522a5..f4870d5 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -364,6 +364,7 @@
import com.android.internal.os.TimeoutRecord;
import com.android.internal.os.TransferPipe;
import com.android.internal.policy.AttributeCache;
+import com.android.internal.policy.PhoneWindow;
import com.android.internal.protolog.ProtoLog;
import com.android.internal.util.XmlUtils;
import com.android.modules.utils.TypedXmlPullParser;
@@ -405,7 +406,7 @@
/**
* An entry in the history task, representing an activity.
*/
-final class ActivityRecord extends WindowToken implements WindowManagerService.AppFreezeListener {
+final class ActivityRecord extends WindowToken {
private static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityRecord" : TAG_ATM;
private static final String TAG_ADD_REMOVE = TAG + POSTFIX_ADD_REMOVE;
private static final String TAG_APP = TAG + POSTFIX_APP;
@@ -735,9 +736,6 @@
*/
boolean mAllowCrossUidActivitySwitchFromBelow;
- /** Have we been asked to have this token keep the screen frozen? */
- private boolean mFreezingScreen;
-
// These are used for determining when all windows associated with
// an activity have been drawn, so they can be made visible together
// at the same time.
@@ -783,11 +781,6 @@
@NonNull
final AppCompatController mAppCompatController;
- // Whether the activity is eligible to be letterboxed for fixed orientation with respect to its
- // requested orientation, even when it's letterbox for another reason (e.g., size compat mode)
- // and therefore #isLetterboxedForFixedOrientationAndAspectRatio returns false.
- private boolean mIsEligibleForFixedOrientationLetterbox;
-
/**
* Whether the activity is to be displayed. See {@link android.R.attr#windowNoDisplay}.
*/
@@ -2025,8 +2018,8 @@
|| ent.array.getBoolean(R.styleable.Window_windowShowWallpaper, false);
mStyleFillsParent = mOccludesParent;
mNoDisplay = ent.array.getBoolean(R.styleable.Window_windowNoDisplay, false);
- mOptOutEdgeToEdge = ent.array.getBoolean(
- R.styleable.Window_windowOptOutEdgeToEdgeEnforcement, false);
+ mOptOutEdgeToEdge = PhoneWindow.isOptingOutEdgeToEdgeEnforcement(
+ aInfo.applicationInfo, false /* local */, ent.array);
} else {
mStyleFillsParent = mOccludesParent = true;
mNoDisplay = false;
@@ -2828,7 +2821,10 @@
}
void removeStartingWindow() {
- boolean prevEligibleForLetterboxEducation = isEligibleForLetterboxEducation();
+ final AppCompatLetterboxPolicy letterboxPolicy = mAppCompatController
+ .getAppCompatLetterboxPolicy();
+ boolean prevEligibleForLetterboxEducation =
+ letterboxPolicy.isEligibleForLetterboxEducation();
if (mStartingData != null
&& mStartingData.mRemoveAfterTransaction == AFTER_TRANSITION_FINISH) {
@@ -2841,8 +2837,8 @@
removeStartingWindowAnimation(true /* prepareAnimation */);
final Task task = getTask();
- if (prevEligibleForLetterboxEducation != isEligibleForLetterboxEducation()
- && task != null) {
+ if (task != null && prevEligibleForLetterboxEducation
+ != letterboxPolicy.isEligibleForLetterboxEducation()) {
// Trigger TaskInfoChanged to update the letterbox education.
task.dispatchTaskInfoChangedIfNeeded(true /* force */);
}
@@ -4512,8 +4508,6 @@
removeIfPossible();
}
- stopFreezingScreen(true, true);
-
final DisplayContent dc = getDisplayContent();
if (dc.mFocusedApp == this) {
ProtoLog.v(WM_DEBUG_FOCUS_LIGHT,
@@ -5794,9 +5788,7 @@
+ " visibleRequested=%b, isInTransition=%b, runningAnimation=%b, caller=%s",
this, isVisible(), mVisibleRequested, isInTransition(), runningAnimation,
Debug.getCallers(5));
- if (!visible) {
- stopFreezingScreen(true, true);
- } else {
+ if (visible) {
// If we are being set visible, and the starting window is not yet displayed,
// then make sure it doesn't get displayed.
if (mStartingWindow != null && !mStartingWindow.isDrawn()
@@ -5804,9 +5796,6 @@
mStartingWindow.clearPolicyVisibilityFlag(LEGACY_POLICY_VISIBILITY);
mStartingWindow.mLegacyPolicyVisibilityAfterAnim = false;
}
- // We are becoming visible, so better freeze the screen with the windows that are
- // getting visible so we also wait for them.
- forAllWindows(mWmService::makeWindowFreezingScreenIfNeededLocked, true);
}
// dispatchTaskInfoChangedIfNeeded() right after ActivityRecord#setVisibility() can report
// the stale visible state, because the state will be updated after the app transition.
@@ -6843,123 +6832,6 @@
rootTask.removeLaunchTickMessages();
}
- boolean mayFreezeScreenLocked() {
- return mayFreezeScreenLocked(app);
- }
-
- private boolean mayFreezeScreenLocked(WindowProcessController app) {
- // Only freeze the screen if this activity is currently attached to
- // an application, and that application is not blocked or unresponding.
- // In any other case, we can't count on getting the screen unfrozen,
- // so it is best to leave as-is.
- return hasProcess() && !app.isCrashing() && !app.isNotResponding();
- }
-
- void startFreezingScreenLocked(WindowProcessController app, int configChanges) {
- if (mayFreezeScreenLocked(app)) {
- if (getParent() == null) {
- Slog.w(TAG_WM,
- "Attempted to freeze screen with non-existing app token: " + token);
- return;
- }
-
- // Window configuration changes only effect windows, so don't require a screen freeze.
- int freezableConfigChanges = configChanges & ~(CONFIG_WINDOW_CONFIGURATION);
- if (freezableConfigChanges == 0 && okToDisplay()) {
- ProtoLog.v(WM_DEBUG_ORIENTATION, "Skipping set freeze of %s", token);
- return;
- }
-
- startFreezingScreen();
- }
- }
-
- void startFreezingScreen() {
- startFreezingScreen(ROTATION_UNDEFINED /* overrideOriginalDisplayRotation */);
- }
-
- void startFreezingScreen(int overrideOriginalDisplayRotation) {
- if (mTransitionController.isShellTransitionsEnabled()) {
- return;
- }
- ProtoLog.i(WM_DEBUG_ORIENTATION,
- "Set freezing of %s: visible=%b freezing=%b visibleRequested=%b. %s",
- token, isVisible(), mFreezingScreen, mVisibleRequested,
- new RuntimeException().fillInStackTrace());
- if (!mVisibleRequested) {
- return;
- }
-
- // If the override is given, the rotation of display doesn't change but we still want to
- // cover the activity whose configuration is changing by freezing the display and running
- // the rotation animation.
- final boolean forceRotation = overrideOriginalDisplayRotation != ROTATION_UNDEFINED;
- if (!mFreezingScreen) {
- mFreezingScreen = true;
- mWmService.registerAppFreezeListener(this);
- mWmService.mAppsFreezingScreen++;
- if (mWmService.mAppsFreezingScreen == 1) {
- if (forceRotation) {
- // Make sure normal rotation animation will be applied.
- mDisplayContent.getDisplayRotation().cancelSeamlessRotation();
- }
- mWmService.startFreezingDisplay(0 /* exitAnim */, 0 /* enterAnim */,
- mDisplayContent, overrideOriginalDisplayRotation);
- mWmService.mH.removeMessages(H.APP_FREEZE_TIMEOUT);
- mWmService.mH.sendEmptyMessageDelayed(H.APP_FREEZE_TIMEOUT, 2000);
- }
- }
- if (forceRotation) {
- // The rotation of the real display won't change, so in order to unfreeze the screen
- // via {@link #checkAppWindowsReadyToShow}, the windows have to be able to call
- // {@link WindowState#reportResized} (it is skipped if the window is freezing) to update
- // the drawn state.
- return;
- }
- final int count = mChildren.size();
- for (int i = 0; i < count; i++) {
- final WindowState w = mChildren.get(i);
- w.onStartFreezingScreen();
- }
- }
-
- boolean isFreezingScreen() {
- return mFreezingScreen;
- }
-
- @Override
- public void onAppFreezeTimeout() {
- Slog.w(TAG_WM, "Force clearing freeze: " + this);
- stopFreezingScreen(true, true);
- }
-
- void stopFreezingScreen(boolean unfreezeSurfaceNow, boolean force) {
- if (!mFreezingScreen) {
- return;
- }
- ProtoLog.v(WM_DEBUG_ORIENTATION,
- "Clear freezing of %s force=%b", this, force);
- final int count = mChildren.size();
- boolean unfrozeWindows = false;
- for (int i = 0; i < count; i++) {
- final WindowState w = mChildren.get(i);
- unfrozeWindows |= w.onStopFreezingScreen();
- }
- if (force || unfrozeWindows) {
- ProtoLog.v(WM_DEBUG_ORIENTATION, "No longer freezing: %s", this);
- mFreezingScreen = false;
- mWmService.unregisterAppFreezeListener(this);
- mWmService.mAppsFreezingScreen--;
- mWmService.mLastFinishedFreezeSource = this;
- }
- if (unfreezeSurfaceNow) {
- if (unfrozeWindows) {
- mWmService.mWindowPlacerLocked.performSurfacePlacement();
- }
- mWmService.stopFreezingDisplayLocked();
- }
- }
-
void onFirstWindowDrawn(WindowState win) {
firstWindowDrawn = true;
// stop tracking
@@ -7102,24 +6974,11 @@
return;
}
- // The token has now changed state to having all windows shown... what to do, what to do?
- if (mFreezingScreen) {
- showAllWindowsLocked();
- stopFreezingScreen(false, true);
- ProtoLog.i(WM_DEBUG_ORIENTATION,
- "Setting mOrientationChangeComplete=true because wtoken %s "
- + "numInteresting=%d numDrawn=%d",
- this, mNumInterestingWindows, mNumDrawnWindows);
- // This will set mOrientationChangeComplete and cause a pass through layout.
- setAppLayoutChanges(FINISH_LAYOUT_REDO_WALLPAPER,
- "checkAppWindowsReadyToShow: freezingScreen");
- } else {
- setAppLayoutChanges(FINISH_LAYOUT_REDO_ANIM, "checkAppWindowsReadyToShow");
+ setAppLayoutChanges(FINISH_LAYOUT_REDO_ANIM, "checkAppWindowsReadyToShow");
- // We can now show all of the drawn windows!
- if (!getDisplayContent().mOpeningApps.contains(this) && canShowWindows()) {
- showAllWindowsLocked();
- }
+ // We can now show all of the drawn windows!
+ if (!getDisplayContent().mOpeningApps.contains(this) && canShowWindows()) {
+ showAllWindowsLocked();
}
}
@@ -7205,10 +7064,10 @@
if (DEBUG_STARTING_WINDOW_VERBOSE && w == mStartingWindow) {
Slog.d(TAG, "updateWindows: starting " + w + " isOnScreen=" + w.isOnScreen()
- + " allDrawn=" + allDrawn + " freezingScreen=" + mFreezingScreen);
+ + " allDrawn=" + allDrawn);
}
- if (allDrawn && !mFreezingScreen) {
+ if (allDrawn) {
return false;
}
@@ -7249,10 +7108,8 @@
mNumDrawnWindows++;
if (DEBUG_VISIBILITY || WM_DEBUG_ORIENTATION.isLogToLogcat()) {
- Slog.v(TAG, "tokenMayBeDrawn: "
- + this + " w=" + w + " numInteresting=" + mNumInterestingWindows
- + " freezingScreen=" + mFreezingScreen
- + " mAppFreezing=" + w.mAppFreezing);
+ Slog.v(TAG, "tokenMayBeDrawn: " + this + " w=" + w
+ + " numInteresting=" + mNumInterestingWindows);
}
isInterestingAndDrawn = true;
@@ -8200,8 +8057,6 @@
}
mDisplayContent.mPinnedTaskController.onCancelFixedRotationTransform();
- // Perform rotation animation according to the rotation of this activity.
- startFreezingScreen(originalDisplayRotation);
// This activity may relaunch or perform configuration change so once it has reported drawn,
// the screen can be unfrozen.
ensureActivityConfiguration();
@@ -8439,9 +8294,11 @@
mTmpConfig.updateFrom(resolvedConfig);
newParentConfiguration = mTmpConfig;
}
-
- mAppCompatController.getAspectRatioPolicy().reset();
- mIsEligibleForFixedOrientationLetterbox = false;
+ final AppCompatAspectRatioPolicy aspectRatioPolicy =
+ mAppCompatController.getAspectRatioPolicy();
+ aspectRatioPolicy.reset();
+ mAppCompatController.getAppCompatLetterboxPolicy()
+ .resetFixedOrientationLetterboxEligibility();
mResolveConfigHint.resolveTmpOverrides(mDisplayContent, newParentConfiguration,
isFixedRotationTransforming());
@@ -8471,12 +8328,7 @@
// If activity in fullscreen mode is letterboxed because of fixed orientation then bounds
// are already calculated in resolveFixedOrientationConfiguration.
// Don't apply aspect ratio if app is overridden to fullscreen by device user/manufacturer.
- if (!mAppCompatController.getAspectRatioPolicy()
- .isLetterboxedForFixedOrientationAndAspectRatio()
- && !mAppCompatController.getAspectRatioOverrides()
- .hasFullscreenOverride()) {
- resolveAspectRatioRestriction(newParentConfiguration);
- }
+ aspectRatioPolicy.resolveAspectRatioRestrictionIfNeeded(newParentConfiguration);
final AppCompatDisplayInsets appCompatDisplayInsets = getAppCompatDisplayInsets();
final AppCompatSizeCompatModePolicy scmPolicy =
mAppCompatController.getSizeCompatModePolicy();
@@ -8508,8 +8360,7 @@
// Fixed orientation letterboxing is possible on both large screen devices
// with ignoreOrientationRequest enabled and on phones in split screen even with
// ignoreOrientationRequest disabled.
- && (mAppCompatController.getAspectRatioPolicy()
- .isLetterboxedForFixedOrientationAndAspectRatio()
+ && (aspectRatioPolicy.isLetterboxedForFixedOrientationAndAspectRatio()
// Limiting check for aspect ratio letterboxing to devices with enabled
// ignoreOrientationRequest. This avoids affecting phones where apps may
// not expect the change of smallestScreenWidthDp after rotation which is
@@ -8517,7 +8368,7 @@
// accurate on phones shouldn't make the big difference and is expected
// to be already well-tested by apps.
|| (isIgnoreOrientationRequest
- && mAppCompatController.getAspectRatioPolicy().isAspectRatioApplied()))) {
+ && aspectRatioPolicy.isAspectRatioApplied()))) {
// TODO(b/264034555): Use mDisplayContent to calculate smallestScreenWidthDp from all
// rotations and only re-calculate if parent bounds have non-orientation size change.
resolvedConfig.smallestScreenWidthDp =
@@ -8784,28 +8635,6 @@
}
/**
- * Whether this activity is eligible for letterbox eduction.
- *
- * <p>Conditions that need to be met:
- *
- * <ul>
- * <li>{@link AppCompatConfiguration#getIsEducationEnabled} is true.
- * <li>The activity is eligible for fixed orientation letterbox.
- * <li>The activity is in fullscreen.
- * <li>The activity is portrait-only.
- * <li>The activity doesn't have a starting window (education should only be displayed
- * once the starting window is removed in {@link #removeStartingWindow}).
- * </ul>
- */
- boolean isEligibleForLetterboxEducation() {
- return mWmService.mAppCompatConfiguration.getIsEducationEnabled()
- && mIsEligibleForFixedOrientationLetterbox
- && getWindowingMode() == WINDOWING_MODE_FULLSCREEN
- && getRequestedConfigurationOrientation() == ORIENTATION_PORTRAIT
- && mStartingWindow == null;
- }
-
- /**
* In some cases, applying insets to bounds changes the orientation. For example, if a
* close-to-square display rotates to portrait to respect a portrait orientation activity, after
* insets such as the status and nav bars are applied, the activity may actually have a
@@ -8909,11 +8738,11 @@
// If the activity requires a different orientation (either by override or activityInfo),
// make it fit the available bounds by scaling down its bounds.
final int forcedOrientation = getRequestedConfigurationOrientation();
+ final boolean isEligibleForFixedOrientationLetterbox = mAppCompatController
+ .getAppCompatLetterboxPolicy()
+ .resolveFixedOrientationLetterboxEligibility(forcedOrientation, parentOrientation);
- mIsEligibleForFixedOrientationLetterbox = forcedOrientation != ORIENTATION_UNDEFINED
- && forcedOrientation != parentOrientation;
-
- if (!mIsEligibleForFixedOrientationLetterbox && (forcedOrientation == ORIENTATION_UNDEFINED
+ if (!isEligibleForFixedOrientationLetterbox && (forcedOrientation == ORIENTATION_UNDEFINED
|| orientationRespectedWithInsets)) {
return;
}
@@ -9006,37 +8835,6 @@
new Rect(resolvedBounds));
}
- /**
- * Resolves aspect ratio restrictions for an activity. If the bounds are restricted by
- * aspect ratio, the position will be adjusted later in {@link #updateResolvedBoundsPosition
- * within parent's app bounds to balance the visual appearance. The policy of aspect ratio has
- * higher priority than the requested override bounds.
- */
- private void resolveAspectRatioRestriction(Configuration newParentConfiguration) {
- final Configuration resolvedConfig = getResolvedOverrideConfiguration();
- final Rect parentAppBounds = mResolveConfigHint.mParentAppBoundsOverride;
- final Rect parentBounds = newParentConfiguration.windowConfiguration.getBounds();
- final Rect resolvedBounds = resolvedConfig.windowConfiguration.getBounds();
- // Use tmp bounds to calculate aspect ratio so we can know whether the activity should use
- // restricted size (resolved bounds may be the requested override bounds).
- mTmpBounds.setEmpty();
- final AppCompatAspectRatioPolicy aspectRatioPolicy = mAppCompatController
- .getAspectRatioPolicy();
- aspectRatioPolicy.applyAspectRatioForLetterbox(mTmpBounds, parentAppBounds, parentBounds);
- // If the out bounds is not empty, it means the activity cannot fill parent's app bounds,
- // then they should be aligned later in #updateResolvedBoundsPosition()
- if (!mTmpBounds.isEmpty()) {
- resolvedBounds.set(mTmpBounds);
- }
- if (!resolvedBounds.isEmpty() && !resolvedBounds.equals(parentBounds)) {
- // Compute the configuration based on the resolved bounds. If aspect ratio doesn't
- // restrict, the bounds should be the requested override bounds.
- mResolveConfigHint.mTmpOverrideDisplayInfo = getFixedRotationTransformDisplayInfo();
- computeConfigByResolveHint(resolvedConfig, newParentConfiguration);
- aspectRatioPolicy.setLetterboxBoundsForAspectRatio(new Rect(resolvedBounds));
- }
- }
-
@Override
public Rect getBounds() {
// TODO(b/268458693): Refactor configuration inheritance in case of translucent activities
@@ -9442,11 +9240,6 @@
mLastReportedConfiguration);
if (shouldRelaunchLocked(changes, mTmpConfig)) {
- // Aha, the activity isn't handling the change, so DIE DIE DIE.
- if (mVisible && mAtmService.mTmpUpdateConfigurationResult.mIsUpdating
- && !mTransitionController.isShellTransitionsEnabled()) {
- startFreezingScreenLocked(app, mAtmService.mTmpUpdateConfigurationResult.changes);
- }
final boolean displayMayChange = mTmpConfig.windowConfiguration.getDisplayRotation()
!= getWindowConfiguration().getDisplayRotation()
|| !mTmpConfig.windowConfiguration.getMaxBounds().equals(
@@ -9454,10 +9247,8 @@
final boolean isAppResizeOnly = !displayMayChange
&& (changes & ~(CONFIG_SCREEN_SIZE | CONFIG_SMALLEST_SCREEN_SIZE
| CONFIG_ORIENTATION | CONFIG_SCREEN_LAYOUT)) == 0;
- // Do not preserve window if it is freezing screen because the original window won't be
- // able to update drawn state that causes freeze timeout.
// TODO(b/258618073): Always preserve if possible.
- final boolean preserveWindow = isAppResizeOnly && !mFreezingScreen;
+ final boolean preserveWindow = isAppResizeOnly;
final boolean hasResizeChange = hasResizeChange(changes & ~info.getRealConfigChanged());
if (hasResizeChange) {
final boolean isDragResizing = task.isDragResizing();
@@ -9753,7 +9544,6 @@
scheduleStopForRestartProcess();
});
} else {
- startFreezingScreen();
scheduleStopForRestartProcess();
}
}
@@ -10140,7 +9930,7 @@
proto.write(OVERRIDE_ORIENTATION, getOverrideOrientation());
proto.write(SHOULD_SEND_COMPAT_FAKE_FOCUS, shouldSendCompatFakeFocus());
final AppCompatCameraOverrides cameraOverrides =
- mAppCompatController.getAppCompatCameraOverrides();
+ mAppCompatController.getCameraOverrides();
proto.write(SHOULD_FORCE_ROTATE_FOR_CAMERA_COMPAT,
cameraOverrides.shouldForceRotateForCameraCompat());
proto.write(SHOULD_REFRESH_ACTIVITY_FOR_CAMERA_COMPAT,
diff --git a/services/core/java/com/android/server/wm/ActivityRefresher.java b/services/core/java/com/android/server/wm/ActivityRefresher.java
index 597f75a..25e38b3 100644
--- a/services/core/java/com/android/server/wm/ActivityRefresher.java
+++ b/services/core/java/com/android/server/wm/ActivityRefresher.java
@@ -77,10 +77,10 @@
final boolean cycleThroughStop =
mWmService.mAppCompatConfiguration
.isCameraCompatRefreshCycleThroughStopEnabled()
- && !activity.mAppCompatController.getAppCompatCameraOverrides()
+ && !activity.mAppCompatController.getCameraOverrides()
.shouldRefreshActivityViaPauseForCameraCompat();
- activity.mAppCompatController.getAppCompatCameraOverrides().setIsRefreshRequested(true);
+ activity.mAppCompatController.getCameraOverrides().setIsRefreshRequested(true);
ProtoLog.v(WM_DEBUG_STATES,
"Refreshing activity for freeform camera compatibility treatment, "
+ "activityRecord=%s", activity);
@@ -97,25 +97,25 @@
}
}, REFRESH_CALLBACK_TIMEOUT_MS);
} catch (RemoteException e) {
- activity.mAppCompatController.getAppCompatCameraOverrides()
+ activity.mAppCompatController.getCameraOverrides()
.setIsRefreshRequested(false);
}
}
boolean isActivityRefreshing(@NonNull ActivityRecord activity) {
- return activity.mAppCompatController.getAppCompatCameraOverrides().isRefreshRequested();
+ return activity.mAppCompatController.getCameraOverrides().isRefreshRequested();
}
void onActivityRefreshed(@NonNull ActivityRecord activity) {
// TODO(b/333060789): can we tell that refresh did not happen by observing the activity
// state?
- activity.mAppCompatController.getAppCompatCameraOverrides().setIsRefreshRequested(false);
+ activity.mAppCompatController.getCameraOverrides().setIsRefreshRequested(false);
}
private boolean shouldRefreshActivity(@NonNull ActivityRecord activity,
@NonNull Configuration newConfig, @NonNull Configuration lastReportedConfig) {
return mWmService.mAppCompatConfiguration.isCameraCompatRefreshEnabled()
- && activity.mAppCompatController.getAppCompatCameraOverrides()
+ && activity.mAppCompatController.getCameraOverrides()
.shouldRefreshActivityForCameraCompat()
&& ArrayUtils.find(mEvaluators.toArray(), evaluator ->
((Evaluator) evaluator)
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index bdbd0d1..8e4d4be 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -124,6 +124,7 @@
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
+import android.os.OperationCanceledException;
import android.os.RemoteException;
import android.os.Trace;
import android.os.UserHandle;
@@ -3246,11 +3247,14 @@
private void sendCanNotEmbedActivityError(TaskFragment taskFragment,
@EmbeddingCheckResult int result) {
final String errMsg;
- switch(result) {
+ boolean fatalError = true;
+ switch (result) {
case EMBEDDING_DISALLOWED_NEW_TASK: {
errMsg = "Cannot embed " + mStartActivity + " that launched on another task"
+ ",mLaunchMode=" + launchModeToString(mLaunchMode)
+ ",mLaunchFlag=" + Integer.toHexString(mLaunchFlags);
+ // This is a known possible scenario, which should not be a fatal error.
+ fatalError = false;
break;
}
case EMBEDDING_DISALLOWED_MIN_DIMENSION_VIOLATION: {
@@ -3270,7 +3274,8 @@
mService.mWindowOrganizerController.sendTaskFragmentOperationFailure(
taskFragment.getTaskFragmentOrganizer(), mRequest.errorCallbackToken,
taskFragment, OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT,
- new SecurityException(errMsg));
+ fatalError ? new SecurityException(errMsg)
+ : new OperationCanceledException(errMsg));
} else {
// If the taskFragment is not organized, just dump error message as warning logs.
Slog.w(TAG, errMsg);
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 9e2c00e..d4f9c090 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -77,7 +77,6 @@
import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_IMMERSIVE;
import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_LOCKTASK;
import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_TASKS;
-import static com.android.sdksandbox.flags.Flags.sandboxActivitySdkBasedContext;
import static com.android.server.am.ActivityManagerService.STOCK_PM_FLAGS;
import static com.android.server.am.ActivityManagerServiceDumpActivitiesProto.ROOT_WINDOW_CONTAINER;
import static com.android.server.am.ActivityManagerServiceDumpProcessesProto.CONFIG_WILL_CHANGE;
@@ -510,7 +509,6 @@
int changes;
// If the activity was relaunched to match the new configuration.
boolean activityRelaunched;
- boolean mIsUpdating;
}
/** Current sequencing integer of the configuration, for skipping old configurations. */
@@ -1269,10 +1267,7 @@
}
static boolean isSdkSandboxActivityIntent(Context context, Intent intent) {
- return intent != null
- && (sandboxActivitySdkBasedContext()
- ? SdkSandboxActivityAuthority.isSdkSandboxActivityIntent(context, intent)
- : intent.isSandboxActivity(context));
+ return SdkSandboxActivityAuthority.isSdkSandboxActivityIntent(context, intent);
}
private int startActivityAsUser(IApplicationThread caller, String callingPackage,
@@ -4698,14 +4693,12 @@
if (values != null) {
changes = updateGlobalConfigurationLocked(values, initLocale, persistent, userId);
mTmpUpdateConfigurationResult.changes = changes;
- mTmpUpdateConfigurationResult.mIsUpdating = true;
}
if (!deferResume) {
kept = ensureConfigAndVisibilityAfterUpdate(starting, changes);
}
} finally {
- mTmpUpdateConfigurationResult.mIsUpdating = false;
continueWindowLayout();
}
mTmpUpdateConfigurationResult.activityRelaunched = !kept;
diff --git a/services/core/java/com/android/server/wm/AppCompatAspectRatioOverrides.java b/services/core/java/com/android/server/wm/AppCompatAspectRatioOverrides.java
index 086b11c..fa04955 100644
--- a/services/core/java/com/android/server/wm/AppCompatAspectRatioOverrides.java
+++ b/services/core/java/com/android/server/wm/AppCompatAspectRatioOverrides.java
@@ -272,7 +272,7 @@
final boolean isLandscape = isFixedOrientationLandscape(
mActivityRecord.getOverrideOrientation());
final AppCompatCameraOverrides cameraOverrides =
- mActivityRecord.mAppCompatController.getAppCompatCameraOverrides();
+ mActivityRecord.mAppCompatController.getCameraOverrides();
// Don't resize to split screen size when in book mode if letterbox position is centered
return (isBookMode && isNotCenteredHorizontally || isTabletopMode && isLandscape)
|| cameraOverrides.isCameraCompatSplitScreenAspectRatioAllowed()
diff --git a/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java b/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java
index 4ecd0be..ab1778a 100644
--- a/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java
+++ b/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java
@@ -56,6 +56,8 @@
@NonNull
private final AppCompatAspectRatioState mAppCompatAspectRatioState;
+ private final Rect mTmpBounds = new Rect();
+
AppCompatAspectRatioPolicy(@NonNull ActivityRecord activityRecord,
@NonNull TransparentPolicy transparentPolicy,
@NonNull AppCompatOverrides appCompatOverrides) {
@@ -222,6 +224,45 @@
return getMaxAspectRatio() != 0 || getMinAspectRatio() != 0;
}
+ /**
+ * Resolves aspect ratio restrictions for an activity. If the bounds are restricted by
+ * aspect ratio, the position will be adjusted later in {@link #updateResolvedBoundsPosition}
+ * within parent's app bounds to balance the visual appearance. The policy of aspect ratio has
+ * higher priority than the requested override bounds.
+ */
+ void resolveAspectRatioRestrictionIfNeeded(@NonNull Configuration newParentConfiguration) {
+ // If activity in fullscreen mode is letterboxed because of fixed orientation then bounds
+ // are already calculated in resolveFixedOrientationConfiguration.
+ // Don't apply aspect ratio if app is overridden to fullscreen by device user/manufacturer.
+ if (isLetterboxedForFixedOrientationAndAspectRatio()
+ || getOverrides().hasFullscreenOverride()) {
+ return;
+ }
+ final Configuration resolvedConfig = mActivityRecord.getResolvedOverrideConfiguration();
+ final Rect parentAppBounds =
+ mActivityRecord.mResolveConfigHint.mParentAppBoundsOverride;
+ final Rect parentBounds = newParentConfiguration.windowConfiguration.getBounds();
+ final Rect resolvedBounds = resolvedConfig.windowConfiguration.getBounds();
+ // Use tmp bounds to calculate aspect ratio so we can know whether the activity should
+ // use restricted size (resolved bounds may be the requested override bounds).
+ mTmpBounds.setEmpty();
+ applyAspectRatioForLetterbox(mTmpBounds, parentAppBounds, parentBounds);
+ // If the out bounds is not empty, it means the activity cannot fill parent's app
+ // bounds, then they should be aligned later in #updateResolvedBoundsPosition().
+ if (!mTmpBounds.isEmpty()) {
+ resolvedBounds.set(mTmpBounds);
+ }
+ if (!resolvedBounds.isEmpty() && !resolvedBounds.equals(parentBounds)) {
+ // Compute the configuration based on the resolved bounds. If aspect ratio doesn't
+ // restrict, the bounds should be the requested override bounds.
+ // TODO(b/384473893): Improve ActivityRecord usage here.
+ mActivityRecord.mResolveConfigHint.mTmpOverrideDisplayInfo =
+ mActivityRecord.getFixedRotationTransformDisplayInfo();
+ mActivityRecord.computeConfigByResolveHint(resolvedConfig, newParentConfiguration);
+ setLetterboxBoundsForAspectRatio(new Rect(resolvedBounds));
+ }
+ }
+
private boolean isParentFullscreenPortrait() {
final WindowContainer<?> parent = mActivityRecord.getParent();
return parent != null
@@ -364,6 +405,11 @@
&& !dc.getIgnoreOrientationRequest();
}
+ @NonNull
+ private AppCompatAspectRatioOverrides getOverrides() {
+ return mActivityRecord.mAppCompatController.getAspectRatioOverrides();
+ }
+
private static class AppCompatAspectRatioState {
// Whether the aspect ratio restrictions applied to the activity bounds
// in applyAspectRatio().
diff --git a/services/core/java/com/android/server/wm/AppCompatCameraPolicy.java b/services/core/java/com/android/server/wm/AppCompatCameraPolicy.java
index 6074608..276c7d2 100644
--- a/services/core/java/com/android/server/wm/AppCompatCameraPolicy.java
+++ b/services/core/java/com/android/server/wm/AppCompatCameraPolicy.java
@@ -277,7 +277,7 @@
*/
static boolean shouldOverrideMinAspectRatioForCamera(@NonNull ActivityRecord activityRecord) {
return AppCompatCameraPolicy.isCameraRunningAndWindowingModeEligible(activityRecord)
- && activityRecord.mAppCompatController.getAppCompatCameraOverrides()
+ && activityRecord.mAppCompatController.getCameraOverrides()
.isOverrideMinAspectRatioForCameraEnabled();
}
}
diff --git a/services/core/java/com/android/server/wm/AppCompatController.java b/services/core/java/com/android/server/wm/AppCompatController.java
index a94f625..b7d8aff 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 @@
@NonNull
private final AppCompatOverrides mAppCompatOverrides;
@NonNull
- private final AppCompatDeviceStateQuery mAppCompatDeviceStateQuery;
+ private final AppCompatDeviceStateQuery mDeviceStateQuery;
@NonNull
private final AppCompatLetterboxPolicy mAppCompatLetterboxPolicy;
@NonNull
@@ -50,11 +50,11 @@
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);
@@ -94,8 +94,8 @@
}
@NonNull
- AppCompatCameraOverrides getAppCompatCameraOverrides() {
- return mAppCompatOverrides.getAppCompatCameraOverrides();
+ AppCompatCameraOverrides getCameraOverrides() {
+ return mAppCompatOverrides.getCameraOverrides();
}
@NonNull
@@ -129,8 +129,8 @@
}
@NonNull
- AppCompatDeviceStateQuery getAppCompatDeviceStateQuery() {
- return mAppCompatDeviceStateQuery;
+ AppCompatDeviceStateQuery getDeviceStateQuery() {
+ return mDeviceStateQuery;
}
@NonNull
diff --git a/services/core/java/com/android/server/wm/AppCompatLetterboxPolicy.java b/services/core/java/com/android/server/wm/AppCompatLetterboxPolicy.java
index 4494586..6a8040a 100644
--- a/services/core/java/com/android/server/wm/AppCompatLetterboxPolicy.java
+++ b/services/core/java/com/android/server/wm/AppCompatLetterboxPolicy.java
@@ -16,6 +16,9 @@
package com.android.server.wm;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
@@ -28,6 +31,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.content.res.Configuration.Orientation;
import android.graphics.Point;
import android.graphics.Rect;
import android.view.SurfaceControl;
@@ -55,6 +59,11 @@
private boolean mLastShouldShowLetterboxUi;
+ // Whether the activity is eligible to be letterboxed for fixed orientation with respect to its
+ // requested orientation, even when it's letterbox for another reason (e.g., size compat mode)
+ // and therefore #isLetterboxedForFixedOrientationAndAspectRatio returns false.
+ private boolean mIsEligibleForFixedOrientationLetterbox;
+
AppCompatLetterboxPolicy(@NonNull ActivityRecord activityRecord,
@NonNull AppCompatConfiguration appCompatConfiguration) {
mActivityRecord = activityRecord;
@@ -66,6 +75,10 @@
mAppCompatConfiguration = appCompatConfiguration;
}
+ void resetFixedOrientationLetterboxEligibility() {
+ mIsEligibleForFixedOrientationLetterbox = false;
+ }
+
/** Cleans up {@link Letterbox} if it exists.*/
void stop() {
mLetterboxPolicyState.stop();
@@ -91,6 +104,43 @@
mLetterboxPolicyState.getLetterboxInnerBounds(outBounds);
}
+ /**
+ * Checks if the current activity is eligible to be letterboxed because of a fixed orientation.
+ *
+ * @param forcedOrientation The requeste orientation
+ * @param parentOrientation The orientation of the parent container.
+ * @return {@code true} if the activity can be letterboxed because of the requested fixed
+ * orientation.
+ */
+ boolean resolveFixedOrientationLetterboxEligibility(@Orientation int forcedOrientation,
+ @Orientation int parentOrientation) {
+ mIsEligibleForFixedOrientationLetterbox = forcedOrientation != ORIENTATION_UNDEFINED
+ && forcedOrientation != parentOrientation;
+ return mIsEligibleForFixedOrientationLetterbox;
+ }
+
+ /**
+ * Whether this activity is eligible for letterbox eduction.
+ *
+ * <p>Conditions that need to be met:
+ *
+ * <ul>
+ * <li>{@link AppCompatConfiguration#getIsEducationEnabled} is true.
+ * <li>The activity is eligible for fixed orientation letterbox.
+ * <li>The activity is in fullscreen.
+ * <li>The activity is portrait-only.
+ * <li>The activity doesn't have a starting window (education should only be displayed
+ * once the starting window is removed in {@link #removeStartingWindow}).
+ * </ul>
+ */
+ boolean isEligibleForLetterboxEducation() {
+ return mAppCompatConfiguration.getIsEducationEnabled()
+ && mIsEligibleForFixedOrientationLetterbox
+ && mActivityRecord.getWindowingMode() == WINDOWING_MODE_FULLSCREEN
+ && mActivityRecord.getRequestedConfigurationOrientation() == ORIENTATION_PORTRAIT
+ && mActivityRecord.mStartingWindow == null;
+ }
+
@Nullable
LetterboxDetails getLetterboxDetails() {
final WindowState w = mActivityRecord.findMainWindow();
diff --git a/services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java b/services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java
index 6202f80..35fa39d 100644
--- a/services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java
+++ b/services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java
@@ -100,7 +100,7 @@
}
if (displayContent != null
- && mAppCompatOverrides.getAppCompatCameraOverrides()
+ && mAppCompatOverrides.getCameraOverrides()
.isOverrideOrientationOnlyForCameraEnabled()
&& !AppCompatCameraPolicy
.isActivityEligibleForOrientationOverride(mActivityRecord)) {
diff --git a/services/core/java/com/android/server/wm/AppCompatOverrides.java b/services/core/java/com/android/server/wm/AppCompatOverrides.java
index 2d0ff9b..811a39c 100644
--- a/services/core/java/com/android/server/wm/AppCompatOverrides.java
+++ b/services/core/java/com/android/server/wm/AppCompatOverrides.java
@@ -29,7 +29,7 @@
@NonNull
private final AppCompatOrientationOverrides mOrientationOverrides;
@NonNull
- private final AppCompatCameraOverrides mAppCompatCameraOverrides;
+ private final AppCompatCameraOverrides mCameraOverrides;
@NonNull
private final AppCompatAspectRatioOverrides mAspectRatioOverrides;
@NonNull
@@ -46,10 +46,10 @@
@NonNull AppCompatConfiguration appCompatConfiguration,
@NonNull OptPropFactory optPropBuilder,
@NonNull AppCompatDeviceStateQuery appCompatDeviceStateQuery) {
- mAppCompatCameraOverrides = new AppCompatCameraOverrides(activityRecord,
+ mCameraOverrides = new AppCompatCameraOverrides(activityRecord,
appCompatConfiguration, optPropBuilder);
mOrientationOverrides = new AppCompatOrientationOverrides(activityRecord,
- appCompatConfiguration, optPropBuilder, mAppCompatCameraOverrides);
+ appCompatConfiguration, optPropBuilder, mCameraOverrides);
mReachabilityOverrides = new AppCompatReachabilityOverrides(activityRecord,
appCompatConfiguration, appCompatDeviceStateQuery);
mAspectRatioOverrides = new AppCompatAspectRatioOverrides(activityRecord,
@@ -69,8 +69,8 @@
}
@NonNull
- AppCompatCameraOverrides getAppCompatCameraOverrides() {
- return mAppCompatCameraOverrides;
+ AppCompatCameraOverrides getCameraOverrides() {
+ return mCameraOverrides;
}
@NonNull
diff --git a/services/core/java/com/android/server/wm/AppCompatReachabilityPolicy.java b/services/core/java/com/android/server/wm/AppCompatReachabilityPolicy.java
index 087edc1..a7c52bd 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 @@
return;
}
final AppCompatDeviceStateQuery deviceStateQuery = mActivityRecord.mAppCompatController
- .getAppCompatDeviceStateQuery();
+ .getDeviceStateQuery();
final boolean isInFullScreenBookMode = deviceStateQuery
.isDisplayFullScreenAndSeparatingHinge()
&& mAppCompatConfiguration.getIsAutomaticReachabilityInBookModeEnabled();
@@ -153,7 +153,7 @@
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/AppCompatUtils.java b/services/core/java/com/android/server/wm/AppCompatUtils.java
index 1ab0868..67f5b9b 100644
--- a/services/core/java/com/android/server/wm/AppCompatUtils.java
+++ b/services/core/java/com/android/server/wm/AppCompatUtils.java
@@ -150,10 +150,12 @@
appCompatTaskInfo.setTopActivityInSizeCompat(top.fillsParent());
}
// Whether the direct top activity is eligible for letterbox education.
- appCompatTaskInfo.setEligibleForLetterboxEducation(
- isTopActivityResumed && top.isEligibleForLetterboxEducation());
- appCompatTaskInfo.setLetterboxEducationEnabled(top.mAppCompatController
- .getAppCompatLetterboxOverrides().isLetterboxEducationEnabled());
+ appCompatTaskInfo.setEligibleForLetterboxEducation(isTopActivityResumed
+ && top.mAppCompatController.getAppCompatLetterboxPolicy()
+ .isEligibleForLetterboxEducation());
+ appCompatTaskInfo.setLetterboxEducationEnabled(
+ top.mAppCompatController.getAppCompatLetterboxOverrides()
+ .isLetterboxEducationEnabled());
final AppCompatAspectRatioOverrides aspectRatioOverrides =
top.mAppCompatController.getAspectRatioOverrides();
diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java
index a972ecb..0a2f685 100644
--- a/services/core/java/com/android/server/wm/AppTransitionController.java
+++ b/services/core/java/com/android/server/wm/AppTransitionController.java
@@ -1195,27 +1195,12 @@
private boolean transitionGoodToGo(ArraySet<? extends WindowContainer> apps,
ArrayMap<WindowContainer, Integer> outReasons) {
ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
- "Checking %d opening apps (frozen=%b timeout=%b)...", apps.size(),
- mService.mDisplayFrozen, mDisplayContent.mAppTransition.isTimeout());
+ "Checking %d opening apps (timeout=%b)...", apps.size(),
+ mDisplayContent.mAppTransition.isTimeout());
if (mDisplayContent.mAppTransition.isTimeout()) {
return true;
}
- final ScreenRotationAnimation screenRotationAnimation = mService.mRoot.getDisplayContent(
- Display.DEFAULT_DISPLAY).getRotationAnimation();
- // Imagine the case where we are changing orientation due to an app transition, but a
- // previous orientation change is still in progress. We won't process the orientation
- // change for our transition because we need to wait for the rotation animation to
- // finish.
- // If we start the app transition at this point, we will interrupt it halfway with a
- // new rotation animation after the old one finally finishes. It's better to defer the
- // app transition.
- if (screenRotationAnimation != null && screenRotationAnimation.isAnimating()
- && mDisplayContent.getDisplayRotation().needsUpdate()) {
- ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
- "Delaying app transition for screen rotation animation to finish");
- return false;
- }
for (int i = 0; i < apps.size(); i++) {
WindowContainer wc = apps.valueAt(i);
final ActivityRecord activity = getAppFromContainer(wc);
diff --git a/services/core/java/com/android/server/wm/AppWarnings.java b/services/core/java/com/android/server/wm/AppWarnings.java
index 601b17c..576e5d5 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.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 @@
}
}
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/AsyncRotationController.java b/services/core/java/com/android/server/wm/AsyncRotationController.java
index 6b6f011..d3fd0e3 100644
--- a/services/core/java/com/android/server/wm/AsyncRotationController.java
+++ b/services/core/java/com/android/server/wm/AsyncRotationController.java
@@ -135,8 +135,7 @@
// decides not to perform seamless rotation, it only affects whether to use fade animation
// when the windows are drawn. If the windows are not too slow (after rotation animation is
// done) to be drawn, the visual result can still look smooth.
- mHasScreenRotationAnimation =
- displayContent.getRotationAnimation() != null || mTransitionOp == OP_CHANGE;
+ mHasScreenRotationAnimation = mTransitionOp == OP_CHANGE;
if (mHasScreenRotationAnimation) {
// Hide the windows immediately because screen should have been covered by screenshot.
mHideImmediately = true;
diff --git a/services/core/java/com/android/server/wm/CameraCompatFreeformPolicy.java b/services/core/java/com/android/server/wm/CameraCompatFreeformPolicy.java
index f5bc9f0..230cd33 100644
--- a/services/core/java/com/android/server/wm/CameraCompatFreeformPolicy.java
+++ b/services/core/java/com/android/server/wm/CameraCompatFreeformPolicy.java
@@ -221,7 +221,7 @@
}
boolean isCameraRunningAndWindowingModeEligible(@NonNull ActivityRecord activity) {
- return activity.mAppCompatController.getAppCompatCameraOverrides()
+ return activity.mAppCompatController.getCameraOverrides()
.shouldApplyFreeformTreatmentForCameraCompat()
&& activity.inFreeformWindowingMode()
&& mCameraStateMonitor.isCameraRunningForActivity(activity);
@@ -232,7 +232,7 @@
// different camera compat aspect ratio set: this allows per-app camera compat override
// aspect ratio to be smaller than the default.
return isInFreeformCameraCompatMode(activity) && !activity.mAppCompatController
- .getAppCompatCameraOverrides().isOverrideMinAspectRatioForCameraEnabled();
+ .getCameraOverrides().isOverrideMinAspectRatioForCameraEnabled();
}
boolean isInFreeformCameraCompatMode(@NonNull ActivityRecord activity) {
@@ -307,7 +307,7 @@
boolean isTreatmentEnabledForActivity(@NonNull ActivityRecord activity,
boolean checkOrientation) {
int orientation = activity.getRequestedConfigurationOrientation();
- return activity.mAppCompatController.getAppCompatCameraOverrides()
+ return activity.mAppCompatController.getCameraOverrides()
.shouldApplyFreeformTreatmentForCameraCompat()
&& mCameraStateMonitor.isCameraRunningForActivity(activity)
&& (!checkOrientation || orientation != ORIENTATION_UNDEFINED)
@@ -333,6 +333,6 @@
|| !mCameraStateMonitor.isCameraWithIdRunningForActivity(topActivity, cameraId)) {
return false;
}
- return topActivity.mAppCompatController.getAppCompatCameraOverrides().isRefreshRequested();
+ return topActivity.mAppCompatController.getCameraOverrides().isRefreshRequested();
}
}
diff --git a/services/core/java/com/android/server/wm/DisplayAreaPolicy.java b/services/core/java/com/android/server/wm/DisplayAreaPolicy.java
index 5bec442..4b30a43 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 @@
* 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 @@
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/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 5435d8f..60c80a3 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -128,7 +128,6 @@
import static com.android.server.wm.DisplayContentProto.OPENING_APPS;
import static com.android.server.wm.DisplayContentProto.RESUMED_ACTIVITY;
import static com.android.server.wm.DisplayContentProto.ROOT_DISPLAY_AREA;
-import static com.android.server.wm.DisplayContentProto.SCREEN_ROTATION_ANIMATION;
import static com.android.server.wm.DisplayContentProto.SLEEP_TOKENS;
import static com.android.server.wm.EventLogTags.IMF_REMOVE_IME_SCREENSHOT;
import static com.android.server.wm.EventLogTags.IMF_SHOW_IME_SCREENSHOT;
@@ -150,7 +149,6 @@
import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_PLACING_SURFACES;
import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_WILL_ASSIGN_LAYERS;
import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_WILL_PLACE_SURFACES;
-import static com.android.server.wm.WindowManagerService.WINDOWS_FREEZING_SCREENS_TIMEOUT;
import static com.android.server.wm.WindowManagerService.dipToPixel;
import static com.android.server.wm.WindowState.EXCLUSION_LEFT;
import static com.android.server.wm.WindowState.EXCLUSION_RIGHT;
@@ -630,8 +628,6 @@
/** Windows removed since {@link #mCurrentFocus} was set to null. Used for ANR blaming. */
final ArrayList<WindowState> mWinRemovedSinceNullFocus = new ArrayList<>();
- private ScreenRotationAnimation mScreenRotationAnimation;
-
/**
* Sequence number for the current layout pass.
*/
@@ -1594,8 +1590,6 @@
mTransitionController.setDisplaySyncMethod(startBounds, endBounds, this);
collectDisplayChange(transition);
}
- } else if (mLastHasContent) {
- mWmService.startFreezingDisplay(0 /* exitAnim */, 0 /* enterAnim */, this);
}
sendNewConfiguration();
}
@@ -1638,7 +1632,6 @@
// DisplayContent#updateRotationUnchecked.
if (mWaitingForConfig) {
mWaitingForConfig = false;
- mWmService.mLastFinishedFreezeSource = "config-unchanged";
setLayoutNeeded();
mWmService.mWindowPlacerLocked.performSurfacePlacement();
}
@@ -1647,8 +1640,7 @@
@Override
boolean onDescendantOrientationChanged(@Nullable WindowContainer requestingContainer) {
- final Configuration config = updateOrientation(
- requestingContainer, false /* forceUpdate */);
+ final Configuration config = updateOrientationAndComputeConfig(false /* forceUpdate */);
// If display rotation class tells us that it doesn't consider app requested orientation,
// this display won't rotate just because of an app changes its requested orientation. Thus
// it indicates that this display chooses not to handle this request.
@@ -1696,28 +1688,17 @@
/**
* Update orientation of the display, returning a non-null new Configuration if it has
* changed from the current orientation. If a non-null configuration is returned, someone must
- * call {@link WindowManagerService#setNewDisplayOverrideConfiguration(Configuration,
- * DisplayContent)} to tell the window manager it can unfreeze the screen. This will typically
- * be done by calling {@link #sendNewConfiguration}.
+ * request a change transition or call {@link #sendNewConfiguration}.
*
- * @param freezeDisplayWindow Freeze the app window if the orientation is changed.
* @param forceUpdate See {@link DisplayRotation#updateRotationUnchecked(boolean)}
*/
- Configuration updateOrientation(WindowContainer<?> freezeDisplayWindow, boolean forceUpdate) {
+ Configuration updateOrientationAndComputeConfig(boolean forceUpdate) {
if (!mDisplayReady) {
return null;
}
Configuration config = null;
if (updateOrientation(forceUpdate)) {
- // If we changed the orientation but mOrientationChangeComplete is already true,
- // we used seamless rotation, and we don't need to freeze the screen.
- if (freezeDisplayWindow != null && !mWmService.mRoot.mOrientationChangeComplete) {
- final ActivityRecord activity = freezeDisplayWindow.asActivityRecord();
- if (activity != null && activity.mayFreezeScreenLocked()) {
- activity.startFreezingScreen();
- }
- }
config = new Configuration();
computeScreenConfiguration(config);
}
@@ -2254,8 +2235,6 @@
mDisplayRotation.isRotatingSeamlessly() && !shellTransitions;
final Transaction transaction =
shellTransitions ? getSyncTransaction() : getPendingTransaction();
- ScreenRotationAnimation screenRotationAnimation = rotateSeamlessly
- ? null : getRotationAnimation();
// We need to update our screen size information to match the new rotation. If the rotation
// has actually changed then this method will return true and, according to the comment at
// the top of the method, the caller is obligated to call computeNewConfigurationLocked().
@@ -2263,12 +2242,6 @@
// #computeScreenConfiguration() later.
updateDisplayAndOrientation(null /* outConfig */);
- // NOTE: We disable the rotation in the emulator because
- // it doesn't support hardware OpenGL emulation yet.
- if (screenRotationAnimation != null && screenRotationAnimation.hasScreenshot()) {
- screenRotationAnimation.setRotation(transaction, rotation);
- }
-
if (!shellTransitions) {
forAllWindows(w -> {
w.seamlesslyRotateIfAllowed(transaction, oldRotation, rotation, rotateSeamlessly);
@@ -2932,20 +2905,6 @@
@ScreenOrientation
@Override
int getOrientation() {
- if (mWmService.mDisplayFrozen) {
- if (mWmService.mPolicy.isKeyguardLocked()) {
- // Use the last orientation the while the display is frozen with the keyguard
- // locked. This could be the keyguard forced orientation or from a SHOW_WHEN_LOCKED
- // window. We don't want to check the show when locked window directly though as
- // things aren't stable while the display is frozen, for example the window could be
- // momentarily unavailable due to activity relaunch.
- ProtoLog.v(WM_DEBUG_ORIENTATION,
- "Display id=%d is frozen while keyguard locked, return %d",
- mDisplayId, getLastOrientation());
- return getLastOrientation();
- }
- }
-
final int compatOrientation = mAppCompatCameraPolicy.getOrientation();
if (compatOrientation != SCREEN_ORIENTATION_UNSPECIFIED) {
mLastOrientationSource = null;
@@ -3413,12 +3372,10 @@
mAppTransition.removeAppTransitionTimeoutCallbacks();
mTransitionController.unregisterLegacyListener(mFixedRotationTransitionListener);
handleAnimatingStoppedAndTransition();
- mWmService.stopFreezingDisplayLocked();
mDeviceStateController.unregisterDeviceStateCallback(mDeviceStateConsumer);
super.removeImmediately();
if (DEBUG_DISPLAY) Slog.v(TAG_WM, "Removing display=" + this);
mPointerEventDispatcher.dispose();
- setRotationAnimation(null);
// Unlink death from remote to clear the reference from binder -> mRemoteInsetsDeath
// -> this DisplayContent.
setRemoteInsetsController(null);
@@ -3514,24 +3471,6 @@
RotationUtils.rotateBounds(inOutBounds, mTmpRect, oldRotation, newRotation);
}
- public void setRotationAnimation(ScreenRotationAnimation screenRotationAnimation) {
- final ScreenRotationAnimation prev = mScreenRotationAnimation;
- mScreenRotationAnimation = screenRotationAnimation;
- if (prev != null) {
- prev.kill();
- }
-
- // Hide the windows which are not significant in rotation animation. So that the windows
- // don't need to block the unfreeze time.
- if (screenRotationAnimation != null && screenRotationAnimation.hasScreenshot()) {
- startAsyncRotationIfNeeded();
- }
- }
-
- public ScreenRotationAnimation getRotationAnimation() {
- return mScreenRotationAnimation;
- }
-
/**
* Collects this display into an already-collecting transition.
*/
@@ -3595,12 +3534,6 @@
}
}
- /** If the display is in transition, there should be a screenshot covering it. */
- @Override
- boolean inTransition() {
- return mScreenRotationAnimation != null || super.inTransition();
- }
-
@Override
public void dumpDebug(ProtoOutputStream proto, long fieldId,
@WindowTracingLogLevel int logLevel) {
@@ -3616,10 +3549,6 @@
proto.write(DPI, mBaseDisplayDensity);
mDisplayInfo.dumpDebug(proto, DISPLAY_INFO);
mDisplayRotation.dumpDebug(proto, DISPLAY_ROTATION);
- final ScreenRotationAnimation screenRotationAnimation = getRotationAnimation();
- if (screenRotationAnimation != null) {
- screenRotationAnimation.dumpDebug(proto, SCREEN_ROTATION_ANIMATION);
- }
mDisplayFrames.dumpDebug(proto, DISPLAY_FRAMES);
proto.write(MIN_SIZE_OF_RESIZEABLE_TASK_DP, mMinSizeOfResizeableTaskDp);
if (mTransitionController.isShellTransitionsEnabled()) {
@@ -3766,16 +3695,6 @@
pw.println();
- final ScreenRotationAnimation rotationAnimation = getRotationAnimation();
- if (rotationAnimation != null) {
- pw.println(" mScreenRotationAnimation:");
- rotationAnimation.printTo(subPrefix, pw);
- } else if (dumpAll) {
- pw.println(" no ScreenRotationAnimation ");
- }
-
- pw.println();
-
// Dump root task references
final Task rootHomeTask = getDefaultTaskDisplayArea().getRootHomeTask();
if (rootHomeTask != null) {
@@ -5068,13 +4987,6 @@
return win != null;
}
- void onWindowFreezeTimeout() {
- Slog.w(TAG_WM, "Window freeze timeout expired.");
- mWmService.mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_TIMEOUT;
-
- mWmService.mWindowPlacerLocked.performSurfacePlacement();
- }
-
/**
* Callbacks when the given type of {@link WindowContainer} animation finished running in the
* hierarchy.
@@ -5297,8 +5209,7 @@
boolean okToDisplay(boolean ignoreFrozen, boolean ignoreScreenOn) {
if (mDisplayId == DEFAULT_DISPLAY) {
- return (!mWmService.mDisplayFrozen || ignoreFrozen)
- && mWmService.mDisplayEnabled
+ return mWmService.mDisplayEnabled
&& (ignoreScreenOn || mWmService.mPolicy.isScreenOn());
}
return mDisplayInfo.state == Display.STATE_ON;
@@ -5443,10 +5354,7 @@
// We skip IME windows so they're processed just above their target.
// Note that this method check should align with {@link
// WindowState#applyImeWindowsIfNeeded} in case of any state mismatch.
- return dc.mImeLayeringTarget != null
- // Make sure that the IME window won't be skipped to report that it has
- // completed the orientation change.
- && !dc.mWmService.mDisplayFrozen;
+ return dc.mImeLayeringTarget != null;
}
/** Like {@link #forAllWindows}, but ignores {@link #skipImeWindowsDuringTraversal} */
@@ -6427,14 +6335,12 @@
changes = performDisplayOverrideConfigUpdate(values);
}
mAtmService.mTmpUpdateConfigurationResult.changes = changes;
- mAtmService.mTmpUpdateConfigurationResult.mIsUpdating = true;
}
if (!deferResume) {
kept = mAtmService.ensureConfigAndVisibilityAfterUpdate(starting, changes);
}
} finally {
- mAtmService.mTmpUpdateConfigurationResult.mIsUpdating = false;
mAtmService.continueWindowLayout();
}
@@ -6490,7 +6396,6 @@
mCurrentOverrideConfigurationChanges = 0;
if (mWaitingForConfig) {
mWaitingForConfig = false;
- mWmService.mLastFinishedFreezeSource = "new-config";
}
mAtmService.addWindowLayoutReasons(
ActivityTaskManagerService.LAYOUT_REASON_CONFIG_CHANGED);
@@ -7086,13 +6991,6 @@
}
@Override
- public void onAppTransitionCancelledLocked(boolean keyguardGoingAwayCancelled) {
- // It is only needed when freezing display in legacy transition.
- if (mTransitionController.isShellTransitionsEnabled()) return;
- continueUpdateOrientationForDiffOrienLaunchingApp();
- }
-
- @Override
public void onAppTransitionTimeoutLocked() {
continueUpdateOrientationForDiffOrienLaunchingApp();
}
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index f53bc70..f8c1755 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -22,12 +22,8 @@
import static android.view.Display.TYPE_EXTERNAL;
import static android.view.Display.TYPE_OVERLAY;
import static android.view.Display.TYPE_VIRTUAL;
-import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_CROSSFADE;
-import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_JUMPCUT;
-import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_ROTATE;
import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS;
-import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_ANIM;
import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_ORIENTATION;
import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.LID_OPEN;
import static com.android.server.wm.DisplayRotationProto.FIXED_TO_USER_ROTATION_MODE;
@@ -41,10 +37,7 @@
import static com.android.server.wm.DisplayRotationReversionController.REVERSION_TYPE_NOSENSOR;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
-import static com.android.server.wm.WindowManagerService.WINDOWS_FREEZING_SCREENS_ACTIVE;
-import static com.android.server.wm.WindowManagerService.WINDOW_FREEZE_TIMEOUT_DURATION;
-import android.annotation.AnimRes;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -104,13 +97,6 @@
private static final int ROTATION_UNDEFINED = -1;
- private static class RotationAnimationPair {
- @AnimRes
- int mEnter;
- @AnimRes
- int mExit;
- }
-
@Nullable
final FoldController mFoldController;
@@ -130,7 +116,6 @@
private final int mCarDockRotation;
private final int mDeskDockRotation;
private final int mUndockedHdmiRotation;
- private final RotationAnimationPair mTmpRotationAnim = new RotationAnimationPair();
private final RotationHistory mRotationHistory = new RotationHistory();
private final RotationLockHistory mRotationLockHistory = new RotationLockHistory();
@@ -540,14 +525,6 @@
ProtoLog.v(WM_DEBUG_ORIENTATION, "Deferring rotation, animation in progress.");
return false;
}
- if (mService.mDisplayFrozen) {
- // Even if the screen rotation animation has finished (e.g. isAnimating returns
- // false), there is still some time where we haven't yet unfrozen the display. We
- // also need to abort rotation here.
- ProtoLog.v(WM_DEBUG_ORIENTATION,
- "Deferring rotation, still finishing previous rotation");
- return false;
- }
if (mDisplayContent.mFixedRotationTransitionListener.shouldDeferRotation()) {
// Makes sure that after the transition is finished, updateOrientation() can see
@@ -644,17 +621,13 @@
return true;
}
- mService.mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_ACTIVE;
- mService.mH.sendNewMessageDelayed(WindowManagerService.H.WINDOW_FREEZE_TIMEOUT,
- mDisplayContent, WINDOW_FREEZE_TIMEOUT_DURATION);
-
if (shouldRotateSeamlessly(oldRotation, rotation, forceUpdate)) {
// The screen rotation animation uses a screenshot to freeze the screen while windows
// resize underneath. When we are rotating seamlessly, we allow the elements to
// transition to their rotated state independently and without a freeze required.
prepareSeamlessRotation();
} else {
- prepareNormalRotationAnimation();
+ cancelSeamlessRotation();
}
// Give a remote handler (system ui) some time to reposition things.
@@ -695,12 +668,6 @@
}
}
- void prepareNormalRotationAnimation() {
- cancelSeamlessRotation();
- final RotationAnimationPair anim = selectRotationAnimation();
- mService.startFreezingDisplay(anim.mExit, anim.mEnter, mDisplayContent);
- }
-
/**
* This ensures that normal rotation animation is used. E.g. {@link #mRotatingSeamlessly} was
* set by previous {@link #updateRotationUnchecked}, but another orientation change happens
@@ -820,79 +787,6 @@
}
}
- /**
- * Returns the animation to run for a rotation transition based on the top fullscreen windows
- * {@link android.view.WindowManager.LayoutParams#rotationAnimation} and whether it is currently
- * fullscreen and frontmost.
- */
- private RotationAnimationPair selectRotationAnimation() {
- // If the screen is off or non-interactive, force a jumpcut.
- final boolean forceJumpcut = !mDisplayPolicy.isScreenOnFully()
- || !mService.mPolicy.okToAnimate(false /* ignoreScreenOn */);
- final WindowState topFullscreen = mDisplayPolicy.getTopFullscreenOpaqueWindow();
- ProtoLog.i(WM_DEBUG_ANIM, "selectRotationAnimation topFullscreen=%s"
- + " rotationAnimation=%d forceJumpcut=%b",
- topFullscreen,
- topFullscreen == null ? 0 : topFullscreen.getAttrs().rotationAnimation,
- forceJumpcut);
- if (forceJumpcut) {
- mTmpRotationAnim.mExit = R.anim.rotation_animation_jump_exit;
- mTmpRotationAnim.mEnter = R.anim.rotation_animation_enter;
- return mTmpRotationAnim;
- }
- if (topFullscreen != null) {
- int animationHint = topFullscreen.getRotationAnimationHint();
- if (animationHint < 0 && mDisplayPolicy.isTopLayoutFullscreen()) {
- animationHint = topFullscreen.getAttrs().rotationAnimation;
- }
- switch (animationHint) {
- case ROTATION_ANIMATION_CROSSFADE:
- case ROTATION_ANIMATION_SEAMLESS: // Crossfade is fallback for seamless.
- mTmpRotationAnim.mExit = R.anim.rotation_animation_xfade_exit;
- mTmpRotationAnim.mEnter = R.anim.rotation_animation_enter;
- break;
- case ROTATION_ANIMATION_JUMPCUT:
- mTmpRotationAnim.mExit = R.anim.rotation_animation_jump_exit;
- mTmpRotationAnim.mEnter = R.anim.rotation_animation_enter;
- break;
- case ROTATION_ANIMATION_ROTATE:
- default:
- mTmpRotationAnim.mExit = mTmpRotationAnim.mEnter = 0;
- break;
- }
- } else {
- mTmpRotationAnim.mExit = mTmpRotationAnim.mEnter = 0;
- }
- return mTmpRotationAnim;
- }
-
- /**
- * Validate whether the current top fullscreen has specified the same
- * {@link android.view.WindowManager.LayoutParams#rotationAnimation} value as that being passed
- * in from the previous top fullscreen window.
- *
- * @param exitAnimId exiting resource id from the previous window.
- * @param enterAnimId entering resource id from the previous window.
- * @param forceDefault For rotation animations only, if true ignore the animation values and
- * just return false.
- * @return {@code true} if the previous values are still valid, false if they should be replaced
- * with the default.
- */
- boolean validateRotationAnimation(int exitAnimId, int enterAnimId, boolean forceDefault) {
- switch (exitAnimId) {
- case R.anim.rotation_animation_xfade_exit:
- case R.anim.rotation_animation_jump_exit:
- // These are the only cases that matter.
- if (forceDefault) {
- return false;
- }
- final RotationAnimationPair anim = selectRotationAnimation();
- return exitAnimId == anim.mExit && enterAnimId == anim.mEnter;
- default:
- return true;
- }
- }
-
void restoreSettings(int userRotationMode, int userRotation, int fixedToUserRotation) {
mFixedToUserRotation = fixedToUserRotation;
diff --git a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
index 3c199db..dceacc3 100644
--- a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
@@ -253,10 +253,10 @@
!= lastReportedConfig.windowConfiguration.getDisplayRotation());
return isTreatmentEnabledForDisplay()
&& isTreatmentEnabledForActivity(activity)
- && activity.mAppCompatController.getAppCompatCameraOverrides()
+ && activity.mAppCompatController.getCameraOverrides()
.shouldRefreshActivityForCameraCompat()
&& (displayRotationChanged
- || activity.mAppCompatController.getAppCompatCameraOverrides()
+ || activity.mAppCompatController.getCameraOverrides()
.isCameraCompatSplitScreenAspectRatioAllowed());
}
@@ -281,7 +281,7 @@
boolean isActivityEligibleForOrientationOverride(@NonNull ActivityRecord activity) {
return isTreatmentEnabledForDisplay()
&& isCameraRunningAndWindowingModeEligible(activity, /* mustBeFullscreen */ true)
- && activity.mAppCompatController.getAppCompatCameraOverrides()
+ && activity.mAppCompatController.getCameraOverrides()
.shouldForceRotateForCameraCompat();
}
@@ -325,7 +325,7 @@
// handle dynamic changes so we shouldn't force rotate them.
&& activity.getOverrideOrientation() != SCREEN_ORIENTATION_NOSENSOR
&& activity.getOverrideOrientation() != SCREEN_ORIENTATION_LOCKED
- && activity.mAppCompatController.getAppCompatCameraOverrides()
+ && activity.mAppCompatController.getCameraOverrides()
.shouldForceRotateForCameraCompat();
}
@@ -457,14 +457,14 @@
private boolean shouldRecomputeConfigurationForCameraCompat(
@NonNull ActivityRecord activityRecord) {
final AppCompatCameraOverrides overrides = activityRecord.mAppCompatController
- .getAppCompatCameraOverrides();
+ .getCameraOverrides();
return overrides.isOverrideOrientationOnlyForCameraEnabled()
|| overrides.isCameraCompatSplitScreenAspectRatioAllowed()
|| shouldOverrideMinAspectRatio(activityRecord);
}
private boolean shouldOverrideMinAspectRatio(@NonNull ActivityRecord activityRecord) {
- return activityRecord.mAppCompatController.getAppCompatCameraOverrides()
+ return activityRecord.mAppCompatController.getCameraOverrides()
.isOverrideMinAspectRatioForCameraEnabled()
&& isCameraRunningAndWindowingModeEligible(activityRecord,
/* mustBeFullscreen= */ true);
diff --git a/services/core/java/com/android/server/wm/DragDropController.java b/services/core/java/com/android/server/wm/DragDropController.java
index fa748d3..c418349 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 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 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 @@
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 @@
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 @@
}
}
+ @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 02a7db1..0fbf56d 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 @@
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/EventLogTags.logtags b/services/core/java/com/android/server/wm/EventLogTags.logtags
index 9d66886..5767db1 100644
--- a/services/core/java/com/android/server/wm/EventLogTags.logtags
+++ b/services/core/java/com/android/server/wm/EventLogTags.logtags
@@ -69,7 +69,7 @@
# bootanim finished:
31007 wm_boot_animation_done (time|2|3)
-# Notify keyguard occlude statuc change to SysUI.
+# Notify keyguard occlude status change to SysUI.
31008 wm_set_keyguard_occluded (occluded|1),(animate|1),(transit|1),(Channel|3)
# Back navigation.
diff --git a/services/core/java/com/android/server/wm/PageSizeMismatchDialog.java b/services/core/java/com/android/server/wm/PageSizeMismatchDialog.java
index 29922f0..24235ef 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.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 @@
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 44f000d..b9550fe 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.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 @@
}
// 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/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 7a3eb67..e2b5c83 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -78,11 +78,9 @@
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WINDOW_TRACE;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
-import static com.android.server.wm.WindowManagerService.H.WINDOW_FREEZE_TIMEOUT;
import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_NORMAL;
import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_PLACING_SURFACES;
import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_WILL_PLACE_SURFACES;
-import static com.android.server.wm.WindowManagerService.WINDOWS_FREEZING_SCREENS_NONE;
import static com.android.server.wm.WindowSurfacePlacer.SET_UPDATE_ROTATION;
import static com.android.server.wm.WindowSurfacePlacer.SET_WALLPAPER_ACTION_PENDING;
@@ -200,12 +198,6 @@
private boolean mSustainedPerformanceModeEnabled = false;
private boolean mSustainedPerformanceModeCurrent = false;
- // During an orientation change, we track whether all windows have rendered
- // at the new orientation, and this will be false from changing orientation until that occurs.
- // For seamless rotation cases this always stays true, as the windows complete their orientation
- // changes 1 by 1 without disturbing global state.
- boolean mOrientationChangeComplete = true;
-
private final Handler mHandler;
private String mCloseSystemDialogsReason;
@@ -841,20 +833,6 @@
}
}
- if (mWmService.mDisplayFrozen) {
- ProtoLog.v(WM_DEBUG_ORIENTATION,
- "With display frozen, orientationChangeComplete=%b",
- mOrientationChangeComplete);
- }
- if (mOrientationChangeComplete) {
- if (mWmService.mWindowsFreezingScreen != WINDOWS_FREEZING_SCREENS_NONE) {
- mWmService.mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_NONE;
- mWmService.mLastFinishedFreezeSource = mLastWindowFreezeSource;
- mWmService.mH.removeMessages(WINDOW_FREEZE_TIMEOUT);
- }
- mWmService.stopFreezingDisplayLocked();
- }
-
// Destroy the surface of any windows that are no longer visible.
i = mWmService.mDestroySurface.size();
if (i > 0) {
@@ -881,13 +859,11 @@
}
}
- if (!mWmService.mDisplayFrozen) {
- // Post these on a handler such that we don't call into power manager service while
- // holding the window manager lock to avoid lock contention with power manager lock.
- mHandler.obtainMessage(SET_SCREEN_BRIGHTNESS_OVERRIDE, mDisplayBrightnessOverrides)
- .sendToTarget();
- mHandler.obtainMessage(SET_USER_ACTIVITY_TIMEOUT, mUserActivityTimeout).sendToTarget();
- }
+ // Post these on a handler such that we don't call into power manager service while
+ // holding the window manager lock to avoid lock contention with power manager lock.
+ mHandler.obtainMessage(SET_SCREEN_BRIGHTNESS_OVERRIDE, mDisplayBrightnessOverrides)
+ .sendToTarget();
+ mHandler.obtainMessage(SET_USER_ACTIVITY_TIMEOUT, mUserActivityTimeout).sendToTarget();
if (mSustainedPerformanceModeCurrent != mSustainedPerformanceModeEnabled) {
mSustainedPerformanceModeEnabled = mSustainedPerformanceModeCurrent;
@@ -902,8 +878,7 @@
}
if (!mWmService.mWaitingForDrawnCallbacks.isEmpty()
- || (mOrientationChangeComplete && !isLayoutNeeded()
- && !mUpdateRotation)) {
+ || (!isLayoutNeeded() && !mUpdateRotation)) {
mWmService.checkDrawnWindowsLocked();
}
@@ -991,7 +966,7 @@
private void handleResizingWindows() {
for (int i = mWmService.mResizingWindows.size() - 1; i >= 0; i--) {
WindowState win = mWmService.mResizingWindows.get(i);
- if (win.mAppFreezing || win.getDisplayContent().mWaitingForConfig) {
+ if (win.getDisplayContent().mWaitingForConfig) {
// Don't remove this window until rotation has completed and is not waiting for the
// complete configuration.
continue;
@@ -1092,12 +1067,6 @@
mUpdateRotation = true;
doRequest = true;
}
- if (mOrientationChangeComplete) {
- mLastWindowFreezeSource = mWmService.mAnimator.mLastWindowFreezeSource;
- if (mWmService.mWindowsFreezingScreen != WINDOWS_FREEZING_SCREENS_NONE) {
- doRequest = true;
- }
- }
return doRequest;
}
@@ -1765,7 +1734,7 @@
// Force-update the orientation from the WindowManager, since we need the true configuration
// to send to the client now.
final Configuration config =
- displayContent.updateOrientation(starting, true /* forceUpdate */);
+ displayContent.updateOrientationAndComputeConfig(true /* forceUpdate */);
// Visibilities may change so let the starting activity have a chance to report. Can't do it
// when visibility is changed in each AppWindowToken because it may trigger wrong
// configuration push because the visibility of some activities may not be updated yet.
diff --git a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
deleted file mode 100644
index 4bd294b..0000000
--- a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
+++ /dev/null
@@ -1,825 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm;
-
-import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
-import static android.util.RotationUtils.deltaRotation;
-import static android.view.WindowManagerPolicyConstants.SCREEN_FREEZE_LAYER_BASE;
-
-import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_ORIENTATION;
-import static com.android.internal.protolog.WmProtoLogGroups.WM_SHOW_SURFACE_ALLOC;
-import static com.android.server.wm.AnimationSpecProto.ROTATE;
-import static com.android.server.wm.RotationAnimationSpecProto.DURATION_MS;
-import static com.android.server.wm.RotationAnimationSpecProto.END_LUMA;
-import static com.android.server.wm.RotationAnimationSpecProto.START_LUMA;
-import static com.android.server.wm.ScreenRotationAnimationProto.ANIMATION_RUNNING;
-import static com.android.server.wm.ScreenRotationAnimationProto.STARTED;
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_SCREEN_ROTATION;
-import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
-import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
-import static com.android.server.wm.utils.CoordinateTransforms.computeRotationMatrix;
-
-import android.animation.ArgbEvaluator;
-import android.content.Context;
-import android.graphics.Color;
-import android.graphics.Matrix;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.hardware.HardwareBuffer;
-import android.os.Trace;
-import android.util.Slog;
-import android.util.proto.ProtoOutputStream;
-import android.view.DisplayInfo;
-import android.view.Surface;
-import android.view.Surface.OutOfResourcesException;
-import android.view.SurfaceControl;
-import android.view.animation.Animation;
-import android.view.animation.AnimationUtils;
-import android.view.animation.Transformation;
-import android.window.ScreenCapture;
-
-import com.android.internal.R;
-import com.android.internal.policy.TransitionAnimation;
-import com.android.internal.protolog.ProtoLog;
-import com.android.server.wm.SurfaceAnimator.AnimationType;
-import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback;
-
-import java.io.PrintWriter;
-
-/**
- * This class handles the rotation animation when the device is rotated.
- *
- * <p>
- * The screen rotation animation is composed of 4 different part:
- * <ul>
- * <li> The screenshot: <p>
- * A screenshot of the whole screen prior the change of orientation is taken to hide the
- * element resizing below. The screenshot is then animated to rotate and cross-fade to
- * the new orientation with the content in the new orientation.
- *
- * <li> The windows on the display: <p>y
- * Once the device is rotated, the screen and its content are in the new orientation. The
- * animation first rotate the new content into the old orientation to then be able to
- * animate to the new orientation
- *
- * <li> The Background color frame: <p>
- * To have the animation seem more seamless, we add a color transitioning background behind the
- * exiting and entering layouts. We compute the brightness of the start and end
- * layouts and transition from the two brightness values as grayscale underneath the animation
- *
- * <li> The entering Blackframe: <p>
- * The enter Blackframe is similar to the exit Blackframe but is only used when a custom
- * rotation animation is used and matches the new content size instead of the screenshot.
- * </ul>
- *
- * Each part has its own Surface which are then animated by {@link SurfaceAnimator}s.
- */
-class ScreenRotationAnimation {
- private static final String TAG = TAG_WITH_CLASS_NAME ? "ScreenRotationAnimation" : TAG_WM;
-
- private final Context mContext;
- private final DisplayContent mDisplayContent;
- private final float[] mTmpFloats = new float[9];
- private final Transformation mRotateExitTransformation = new Transformation();
- private final Transformation mRotateEnterTransformation = new Transformation();
- // Complete transformations being applied.
- private final Matrix mSnapshotInitialMatrix = new Matrix();
- private final WindowManagerService mService;
- /** Only used for custom animations and not screen rotation. */
- private SurfaceControl mEnterBlackFrameLayer;
- /** This layer contains the actual screenshot that is to be faded out. */
- private SurfaceControl mScreenshotLayer;
- private SurfaceControl[] mRoundedCornerOverlay;
- /**
- * Only used for screen rotation and not custom animations. Layered behind all other layers
- * to avoid showing any "empty" spots
- */
- private SurfaceControl mBackColorSurface;
- private BlackFrame mEnteringBlackFrame;
-
- private final int mOriginalRotation;
- private final int mOriginalWidth;
- private final int mOriginalHeight;
- private int mCurRotation;
-
- // The current active animation to move from the old to the new rotated
- // state. Which animation is run here will depend on the old and new
- // rotations.
- private Animation mRotateExitAnimation;
- private Animation mRotateEnterAnimation;
- private Animation mRotateAlphaAnimation;
- private boolean mStarted;
- private boolean mAnimRunning;
- private boolean mFinishAnimReady;
- private long mFinishAnimStartTime;
- private SurfaceRotationAnimationController mSurfaceRotationAnimationController;
- /** Intensity of light/whiteness of the layout before rotation occurs. */
- private float mStartLuma;
- /** Intensity of light/whiteness of the layout after rotation occurs. */
- private float mEndLuma;
-
- ScreenRotationAnimation(DisplayContent displayContent, @Surface.Rotation int originalRotation) {
- mService = displayContent.mWmService;
- mContext = mService.mContext;
- mDisplayContent = displayContent;
- final Rect currentBounds = displayContent.getBounds();
- final int width = currentBounds.width();
- final int height = currentBounds.height();
-
- // Screenshot does NOT include rotation!
- final DisplayInfo displayInfo = displayContent.getDisplayInfo();
- final int realOriginalRotation = displayInfo.rotation;
-
- mOriginalRotation = originalRotation;
- // If the delta is not zero, the rotation of display may not change, but we still want to
- // apply rotation animation because there should be a top app shown as rotated. So the
- // specified original rotation customizes the direction of animation to have better look
- // when restoring the rotated app to the same rotation as current display.
- final int delta = deltaRotation(originalRotation, realOriginalRotation);
- final boolean flipped = delta == Surface.ROTATION_90 || delta == Surface.ROTATION_270;
- mOriginalWidth = flipped ? height : width;
- mOriginalHeight = flipped ? width : height;
- final int logicalWidth = displayInfo.logicalWidth;
- final int logicalHeight = displayInfo.logicalHeight;
- final boolean isSizeChanged =
- logicalWidth > mOriginalWidth == logicalHeight > mOriginalHeight
- && (logicalWidth != mOriginalWidth || logicalHeight != mOriginalHeight);
- mSurfaceRotationAnimationController = new SurfaceRotationAnimationController();
-
- // Check whether the current screen contains any secure content.
- boolean isSecure = displayContent.hasSecureWindowOnScreen();
- final int displayId = displayContent.getDisplayId();
- final SurfaceControl.Transaction t = mService.mTransactionFactory.get();
-
- try {
- final ScreenCapture.ScreenshotHardwareBuffer screenshotBuffer;
- if (isSizeChanged) {
- // Temporarily not skip screenshot for the rounded corner overlays and screenshot
- // the whole display to include the rounded corner overlays.
- setSkipScreenshotForRoundedCornerOverlays(false, t);
- ScreenCapture.LayerCaptureArgs captureArgs =
- new ScreenCapture.LayerCaptureArgs.Builder(
- displayContent.getSurfaceControl())
- .setCaptureSecureLayers(true)
- .setAllowProtected(true)
- .setSourceCrop(new Rect(0, 0, width, height))
- .setHintForSeamlessTransition(true)
- .build();
- screenshotBuffer = ScreenCapture.captureLayers(captureArgs);
- } else {
- ScreenCapture.LayerCaptureArgs captureArgs =
- new ScreenCapture.LayerCaptureArgs.Builder(
- displayContent.getSurfaceControl())
- .setCaptureSecureLayers(true)
- .setAllowProtected(true)
- .setSourceCrop(new Rect(0, 0, width, height))
- .setHintForSeamlessTransition(true)
- .build();
- screenshotBuffer = ScreenCapture.captureLayers(captureArgs);
- }
-
- if (screenshotBuffer == null) {
- Slog.w(TAG, "Unable to take screenshot of display " + displayId);
- return;
- }
-
- // If the screenshot contains secure layers, we have to make sure the
- // screenshot surface we display it in also has FLAG_SECURE so that
- // the user can not screenshot secure layers via the screenshot surface.
- if (screenshotBuffer.containsSecureLayers()) {
- isSecure = true;
- }
-
- mBackColorSurface = displayContent.makeChildSurface(null)
- .setName("BackColorSurface")
- .setColorLayer()
- .setCallsite("ScreenRotationAnimation")
- .build();
-
- String name = "RotationLayer";
- mScreenshotLayer = displayContent.makeOverlay()
- .setName(name)
- .setOpaque(true)
- .setSecure(isSecure)
- .setCallsite("ScreenRotationAnimation")
- .setBLASTLayer()
- .build();
- // This is the way to tell the input system to exclude this surface from occlusion
- // detection since we don't have a window for it. We do this because this window is
- // generated by the system as well as its content.
- InputMonitor.setTrustedOverlayInputInfo(mScreenshotLayer, t, displayId, name);
-
- mEnterBlackFrameLayer = displayContent.makeOverlay()
- .setName("EnterBlackFrameLayer")
- .setContainerLayer()
- .setCallsite("ScreenRotationAnimation")
- .build();
-
- HardwareBuffer hardwareBuffer = screenshotBuffer.getHardwareBuffer();
- Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER,
- "ScreenRotationAnimation#getMedianBorderLuma");
- mStartLuma = TransitionAnimation.getBorderLuma(hardwareBuffer,
- screenshotBuffer.getColorSpace());
- Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
-
- t.setLayer(mScreenshotLayer, SCREEN_FREEZE_LAYER_BASE);
- t.reparent(mBackColorSurface, displayContent.getSurfaceControl());
- // If hdr layers are on-screen, e.g. picture-in-picture mode, the screenshot of
- // rotation animation is an sdr image containing tone-mapping hdr content, then
- // disable dimming effect to get avoid of hdr content being dimmed during animation.
- t.setDimmingEnabled(mScreenshotLayer, !screenshotBuffer.containsHdrLayers());
- t.setLayer(mBackColorSurface, -1);
- t.setColor(mBackColorSurface, new float[]{mStartLuma, mStartLuma, mStartLuma});
- t.setAlpha(mBackColorSurface, 1);
- t.setBuffer(mScreenshotLayer, hardwareBuffer);
- t.setColorSpace(mScreenshotLayer, screenshotBuffer.getColorSpace());
- t.show(mScreenshotLayer);
- t.show(mBackColorSurface);
- hardwareBuffer.close();
-
- if (mRoundedCornerOverlay != null) {
- for (SurfaceControl sc : mRoundedCornerOverlay) {
- if (sc.isValid()) {
- t.hide(sc);
- }
- }
- }
-
- } catch (OutOfResourcesException e) {
- Slog.w(TAG, "Unable to allocate freeze surface", e);
- }
-
- // If display size is changed with the same orientation, map the bounds of screenshot to
- // the new logical display size. Currently pending transaction and RWC#mDisplayTransaction
- // are merged to global transaction, so it can be synced with display change when calling
- // DisplayManagerInternal#performTraversal(transaction).
- if (mScreenshotLayer != null && isSizeChanged) {
- displayContent.getPendingTransaction().setGeometry(mScreenshotLayer,
- new Rect(0, 0, mOriginalWidth, mOriginalHeight),
- new Rect(0, 0, logicalWidth, logicalHeight), Surface.ROTATION_0);
- }
-
- ProtoLog.i(WM_SHOW_SURFACE_ALLOC,
- " FREEZE %s: CREATE", mScreenshotLayer);
- if (originalRotation == realOriginalRotation) {
- setRotation(t, realOriginalRotation);
- } else {
- // If the given original rotation is different from real original display rotation,
- // this is playing non-zero degree rotation animation without display rotation change,
- // so the snapshot doesn't need to be transformed.
- mCurRotation = realOriginalRotation;
- mSnapshotInitialMatrix.reset();
- setRotationTransform(t, mSnapshotInitialMatrix);
- }
- t.apply();
- }
-
- void setSkipScreenshotForRoundedCornerOverlays(
- boolean skipScreenshot, SurfaceControl.Transaction t) {
- mDisplayContent.forAllWindows(w -> {
- if (!w.mToken.mRoundedCornerOverlay || !w.isVisible() || !w.mWinAnimator.hasSurface()) {
- return;
- }
- t.setSkipScreenshot(w.mWinAnimator.mSurfaceControl, skipScreenshot);
- }, false);
- if (!skipScreenshot) {
- // Use sync apply to apply the change immediately, so that the next
- // SC.captureDisplay can capture the screen decor layers.
- t.apply(true /* sync */);
- }
- }
-
- public void dumpDebug(ProtoOutputStream proto, long fieldId) {
- final long token = proto.start(fieldId);
- proto.write(STARTED, mStarted);
- proto.write(ANIMATION_RUNNING, mAnimRunning);
- proto.end(token);
- }
-
- boolean hasScreenshot() {
- return mScreenshotLayer != null;
- }
-
- private void setRotationTransform(SurfaceControl.Transaction t, Matrix matrix) {
- if (mScreenshotLayer == null) {
- return;
- }
- matrix.getValues(mTmpFloats);
- float x = mTmpFloats[Matrix.MTRANS_X];
- float y = mTmpFloats[Matrix.MTRANS_Y];
- t.setPosition(mScreenshotLayer, x, y);
- t.setMatrix(mScreenshotLayer,
- mTmpFloats[Matrix.MSCALE_X], mTmpFloats[Matrix.MSKEW_Y],
- mTmpFloats[Matrix.MSKEW_X], mTmpFloats[Matrix.MSCALE_Y]);
-
- t.setAlpha(mScreenshotLayer, (float) 1.0);
- t.show(mScreenshotLayer);
- }
-
- public void printTo(String prefix, PrintWriter pw) {
- pw.print(prefix); pw.print("mSurface="); pw.print(mScreenshotLayer);
- pw.print(prefix);
- pw.print("mEnteringBlackFrame=");
- pw.println(mEnteringBlackFrame);
- if (mEnteringBlackFrame != null) {
- mEnteringBlackFrame.printTo(prefix + " ", pw);
- }
- pw.print(prefix); pw.print("mCurRotation="); pw.print(mCurRotation);
- pw.print(" mOriginalRotation="); pw.println(mOriginalRotation);
- pw.print(prefix); pw.print("mOriginalWidth="); pw.print(mOriginalWidth);
- pw.print(" mOriginalHeight="); pw.println(mOriginalHeight);
- pw.print(prefix); pw.print("mStarted="); pw.print(mStarted);
- pw.print(" mAnimRunning="); pw.print(mAnimRunning);
- pw.print(" mFinishAnimReady="); pw.print(mFinishAnimReady);
- pw.print(" mFinishAnimStartTime="); pw.println(mFinishAnimStartTime);
- pw.print(prefix); pw.print("mRotateExitAnimation="); pw.print(mRotateExitAnimation);
- pw.print(" "); mRotateExitTransformation.printShortString(pw); pw.println();
- pw.print(prefix); pw.print("mRotateEnterAnimation="); pw.print(mRotateEnterAnimation);
- pw.print(" "); mRotateEnterTransformation.printShortString(pw); pw.println();
- pw.print(prefix); pw.print("mSnapshotInitialMatrix=");
- mSnapshotInitialMatrix.dump(pw); pw.println();
- }
-
- public void setRotation(SurfaceControl.Transaction t, int rotation) {
- mCurRotation = rotation;
-
- // Compute the transformation matrix that must be applied
- // to the snapshot to make it stay in the same original position
- // with the current screen rotation.
- int delta = deltaRotation(rotation, mOriginalRotation);
- computeRotationMatrix(delta, mOriginalWidth, mOriginalHeight, mSnapshotInitialMatrix);
- setRotationTransform(t, mSnapshotInitialMatrix);
- }
-
- /**
- * Returns true if animating.
- */
- private boolean startAnimation(SurfaceControl.Transaction t, long maxAnimationDuration,
- float animationScale, int finalWidth, int finalHeight, int exitAnim, int enterAnim) {
- if (mScreenshotLayer == null) {
- // Can't do animation.
- return false;
- }
- if (mStarted) {
- return true;
- }
-
- mStarted = true;
-
- // Figure out how the screen has moved from the original rotation.
- int delta = deltaRotation(mCurRotation, mOriginalRotation);
-
- final boolean customAnim;
- if (exitAnim != 0 && enterAnim != 0) {
- customAnim = true;
- mRotateExitAnimation = AnimationUtils.loadAnimation(mContext, exitAnim);
- mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext, enterAnim);
- mRotateAlphaAnimation = AnimationUtils.loadAnimation(mContext,
- R.anim.screen_rotate_alpha);
- } else {
- customAnim = false;
- switch (delta) { /* Counter-Clockwise Rotations */
- case Surface.ROTATION_0:
- mRotateExitAnimation = AnimationUtils.loadAnimation(mContext,
- R.anim.screen_rotate_0_exit);
- mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext,
- R.anim.rotation_animation_enter);
- break;
- case Surface.ROTATION_90:
- mRotateExitAnimation = AnimationUtils.loadAnimation(mContext,
- R.anim.screen_rotate_plus_90_exit);
- mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext,
- R.anim.screen_rotate_plus_90_enter);
- break;
- case Surface.ROTATION_180:
- mRotateExitAnimation = AnimationUtils.loadAnimation(mContext,
- R.anim.screen_rotate_180_exit);
- mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext,
- R.anim.screen_rotate_180_enter);
- break;
- case Surface.ROTATION_270:
- mRotateExitAnimation = AnimationUtils.loadAnimation(mContext,
- R.anim.screen_rotate_minus_90_exit);
- mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext,
- R.anim.screen_rotate_minus_90_enter);
- break;
- }
- }
-
- ProtoLog.d(WM_DEBUG_ORIENTATION, "Start rotation animation. customAnim=%s, "
- + "mCurRotation=%s, mOriginalRotation=%s",
- customAnim, Surface.rotationToString(mCurRotation),
- Surface.rotationToString(mOriginalRotation));
-
- mRotateExitAnimation.initialize(finalWidth, finalHeight, mOriginalWidth, mOriginalHeight);
- mRotateExitAnimation.restrictDuration(maxAnimationDuration);
- mRotateExitAnimation.scaleCurrentDuration(animationScale);
- mRotateEnterAnimation.initialize(finalWidth, finalHeight, mOriginalWidth, mOriginalHeight);
- mRotateEnterAnimation.restrictDuration(maxAnimationDuration);
- mRotateEnterAnimation.scaleCurrentDuration(animationScale);
-
- mAnimRunning = false;
- mFinishAnimReady = false;
- mFinishAnimStartTime = -1;
-
- if (customAnim) {
- mRotateAlphaAnimation.restrictDuration(maxAnimationDuration);
- mRotateAlphaAnimation.scaleCurrentDuration(animationScale);
- }
-
- if (customAnim && mEnteringBlackFrame == null) {
- try {
- Rect outer = new Rect(-finalWidth, -finalHeight,
- finalWidth * 2, finalHeight * 2);
- Rect inner = new Rect(0, 0, finalWidth, finalHeight);
- mEnteringBlackFrame = new BlackFrame(mService.mTransactionFactory, t, outer, inner,
- SCREEN_FREEZE_LAYER_BASE, mDisplayContent, false, mEnterBlackFrameLayer);
- } catch (OutOfResourcesException e) {
- Slog.w(TAG, "Unable to allocate black surface", e);
- }
- }
-
- if (customAnim) {
- mSurfaceRotationAnimationController.startCustomAnimation();
- } else {
- mSurfaceRotationAnimationController.startScreenRotationAnimation();
- }
-
- return true;
- }
-
- /**
- * Returns true if animating.
- */
- public boolean dismiss(SurfaceControl.Transaction t, long maxAnimationDuration,
- float animationScale, int finalWidth, int finalHeight, int exitAnim, int enterAnim) {
- if (mScreenshotLayer == null) {
- // Can't do animation.
- return false;
- }
- if (!mStarted) {
- mEndLuma = TransitionAnimation.getBorderLuma(mDisplayContent.getWindowingLayer(),
- finalWidth, finalHeight);
- startAnimation(t, maxAnimationDuration, animationScale, finalWidth, finalHeight,
- exitAnim, enterAnim);
- }
- if (!mStarted) {
- return false;
- }
- mFinishAnimReady = true;
- return true;
- }
-
- public void kill() {
- if (mSurfaceRotationAnimationController != null) {
- mSurfaceRotationAnimationController.cancel();
- mSurfaceRotationAnimationController = null;
- }
-
- if (mScreenshotLayer != null) {
- ProtoLog.i(WM_SHOW_SURFACE_ALLOC, " FREEZE %s: DESTROY", mScreenshotLayer);
- SurfaceControl.Transaction t = mService.mTransactionFactory.get();
- if (mScreenshotLayer.isValid()) {
- t.remove(mScreenshotLayer);
- }
- mScreenshotLayer = null;
-
- if (mEnterBlackFrameLayer != null) {
- if (mEnterBlackFrameLayer.isValid()) {
- t.remove(mEnterBlackFrameLayer);
- }
- mEnterBlackFrameLayer = null;
- }
- if (mBackColorSurface != null) {
- if (mBackColorSurface.isValid()) {
- t.remove(mBackColorSurface);
- }
- mBackColorSurface = null;
- }
-
- if (mRoundedCornerOverlay != null) {
- if (mDisplayContent.getRotationAnimation() == null
- || mDisplayContent.getRotationAnimation() == this) {
- setSkipScreenshotForRoundedCornerOverlays(true, t);
- for (SurfaceControl sc : mRoundedCornerOverlay) {
- if (sc.isValid()) {
- t.show(sc);
- }
- }
- }
- mRoundedCornerOverlay = null;
- }
- t.apply();
- }
-
- if (mEnteringBlackFrame != null) {
- mEnteringBlackFrame.kill();
- mEnteringBlackFrame = null;
- }
- if (mRotateExitAnimation != null) {
- mRotateExitAnimation.cancel();
- mRotateExitAnimation = null;
- }
- if (mRotateEnterAnimation != null) {
- mRotateEnterAnimation.cancel();
- mRotateEnterAnimation = null;
- }
- if (mRotateAlphaAnimation != null) {
- mRotateAlphaAnimation.cancel();
- mRotateAlphaAnimation = null;
- }
- }
-
- public boolean isAnimating() {
- return mSurfaceRotationAnimationController != null
- && mSurfaceRotationAnimationController.isAnimating();
- }
-
- public boolean isRotating() {
- return mCurRotation != mOriginalRotation;
- }
-
- /**
- * Utility class that runs a {@link ScreenRotationAnimation} on the {@link
- * SurfaceAnimationRunner}.
- * <p>
- * The rotation animation supports both screen rotation and custom animations
- *
- * For custom animations:
- * <ul>
- * <li>
- * The screenshot layer which has an added animation of it's alpha channel
- * ("screen_rotate_alpha") and that will be applied along with the custom animation.
- * </li>
- * <li> A device layer that is animated with the provided custom animation </li>
- * </ul>
- *
- * For screen rotation:
- * <ul>
- * <li> A rotation layer that is both rotated and faded out during a single animation </li>
- * <li> A device layer that is both rotated and faded in during a single animation </li>
- * <li> A background color layer that transitions colors behind the first two layers </li>
- * </ul>
- *
- * {@link ScreenRotationAnimation#startAnimation(
- * SurfaceControl.Transaction, long, float, int, int, int, int)}.
- * </ul>
- *
- * <p>
- * Thus an {@link LocalAnimationAdapter.AnimationSpec} is created for each of
- * this three {@link SurfaceControl}s which then delegates the animation to the
- * {@link ScreenRotationAnimation}.
- */
- class SurfaceRotationAnimationController {
- private SurfaceAnimator mDisplayAnimator;
- private SurfaceAnimator mScreenshotRotationAnimator;
- private SurfaceAnimator mRotateScreenAnimator;
- private SurfaceAnimator mEnterBlackFrameAnimator;
-
- void startCustomAnimation() {
- try {
- mService.mSurfaceAnimationRunner.deferStartingAnimations();
- mRotateScreenAnimator = startScreenshotAlphaAnimation();
- mDisplayAnimator = startDisplayRotation();
- if (mEnteringBlackFrame != null) {
- mEnterBlackFrameAnimator = startEnterBlackFrameAnimation();
- }
- } finally {
- mService.mSurfaceAnimationRunner.continueStartingAnimations();
- }
- }
-
- /**
- * Start the rotation animation of the display and the screenshot on the
- * {@link SurfaceAnimationRunner}.
- */
- void startScreenRotationAnimation() {
- try {
- mService.mSurfaceAnimationRunner.deferStartingAnimations();
- mDisplayAnimator = startDisplayRotation();
- mScreenshotRotationAnimator = startScreenshotRotationAnimation();
- startColorAnimation();
- } finally {
- mService.mSurfaceAnimationRunner.continueStartingAnimations();
- }
- }
-
- private SimpleSurfaceAnimatable.Builder initializeBuilder() {
- return new SimpleSurfaceAnimatable.Builder()
- .setSyncTransactionSupplier(mDisplayContent::getSyncTransaction)
- .setPendingTransactionSupplier(mDisplayContent::getPendingTransaction)
- .setCommitTransactionRunnable(mDisplayContent::commitPendingTransaction)
- .setAnimationLeashSupplier(mDisplayContent::makeOverlay);
- }
-
- private SurfaceAnimator startDisplayRotation() {
- SurfaceAnimator animator = startAnimation(initializeBuilder()
- .setAnimationLeashParent(mDisplayContent.getSurfaceControl())
- .setSurfaceControl(mDisplayContent.getWindowingLayer())
- .setParentSurfaceControl(mDisplayContent.getSurfaceControl())
- .setWidth(mDisplayContent.getSurfaceWidth())
- .setHeight(mDisplayContent.getSurfaceHeight())
- .build(),
- createWindowAnimationSpec(mRotateEnterAnimation),
- this::onAnimationEnd);
-
- // Crop the animation leash to avoid extended wallpaper from showing over
- // mBackColorSurface
- Rect displayBounds = mDisplayContent.getBounds();
- mDisplayContent.getPendingTransaction()
- .setWindowCrop(animator.mLeash, displayBounds.width(), displayBounds.height());
- return animator;
- }
-
- private SurfaceAnimator startScreenshotAlphaAnimation() {
- return startAnimation(initializeBuilder()
- .setSurfaceControl(mScreenshotLayer)
- .setAnimationLeashParent(mDisplayContent.getOverlayLayer())
- .setWidth(mDisplayContent.getSurfaceWidth())
- .setHeight(mDisplayContent.getSurfaceHeight())
- .build(),
- createWindowAnimationSpec(mRotateAlphaAnimation),
- this::onAnimationEnd);
- }
-
- private SurfaceAnimator startEnterBlackFrameAnimation() {
- return startAnimation(initializeBuilder()
- .setSurfaceControl(mEnterBlackFrameLayer)
- .setAnimationLeashParent(mDisplayContent.getOverlayLayer())
- .build(),
- createWindowAnimationSpec(mRotateEnterAnimation),
- this::onAnimationEnd);
- }
-
- private SurfaceAnimator startScreenshotRotationAnimation() {
- return startAnimation(initializeBuilder()
- .setSurfaceControl(mScreenshotLayer)
- .setAnimationLeashParent(mDisplayContent.getOverlayLayer())
- .build(),
- createWindowAnimationSpec(mRotateExitAnimation),
- this::onAnimationEnd);
- }
-
-
- /**
- * Applies the color change from {@link #mStartLuma} to {@link #mEndLuma} as a
- * grayscale color
- */
- private void startColorAnimation() {
- int colorTransitionMs = mContext.getResources().getInteger(
- R.integer.config_screen_rotation_color_transition);
- final SurfaceAnimationRunner runner = mService.mSurfaceAnimationRunner;
- final float[] rgbTmpFloat = new float[3];
- final int startColor = Color.rgb(mStartLuma, mStartLuma, mStartLuma);
- final int endColor = Color.rgb(mEndLuma, mEndLuma, mEndLuma);
- final long duration = colorTransitionMs * (long) mService.getCurrentAnimatorScale();
- final ArgbEvaluator va = ArgbEvaluator.getInstance();
- runner.startAnimation(
- new LocalAnimationAdapter.AnimationSpec() {
- @Override
- public long getDuration() {
- return duration;
- }
-
- @Override
- public void apply(SurfaceControl.Transaction t, SurfaceControl leash,
- long currentPlayTime) {
- final float fraction = getFraction(currentPlayTime);
- final int color = (Integer) va.evaluate(fraction, startColor, endColor);
- Color middleColor = Color.valueOf(color);
- rgbTmpFloat[0] = middleColor.red();
- rgbTmpFloat[1] = middleColor.green();
- rgbTmpFloat[2] = middleColor.blue();
- if (leash.isValid()) {
- t.setColor(leash, rgbTmpFloat);
- }
- }
-
- @Override
- public void dump(PrintWriter pw, String prefix) {
- pw.println(prefix + "startLuma=" + mStartLuma
- + " endLuma=" + mEndLuma
- + " durationMs=" + colorTransitionMs);
- }
-
- @Override
- public void dumpDebugInner(ProtoOutputStream proto) {
- final long token = proto.start(ROTATE);
- proto.write(START_LUMA, mStartLuma);
- proto.write(END_LUMA, mEndLuma);
- proto.write(DURATION_MS, colorTransitionMs);
- proto.end(token);
- }
- },
- mBackColorSurface, mDisplayContent.getPendingTransaction(), null);
- }
-
- private WindowAnimationSpec createWindowAnimationSpec(Animation mAnimation) {
- return new WindowAnimationSpec(mAnimation, new Point(0, 0) /* position */,
- false /* canSkipFirstFrame */, 0 /* WindowCornerRadius */);
- }
-
- /**
- * Start an animation defined by animationSpec on a new {@link SurfaceAnimator}.
- *
- * @param animatable The animatable used for the animation.
- * @param animationSpec The spec of the animation.
- * @param animationFinishedCallback Callback passed to the {@link SurfaceAnimator}
- * and called when the animation finishes.
- * @return The newly created {@link SurfaceAnimator} that as been started.
- */
- private SurfaceAnimator startAnimation(
- SurfaceAnimator.Animatable animatable,
- LocalAnimationAdapter.AnimationSpec animationSpec,
- OnAnimationFinishedCallback animationFinishedCallback) {
- SurfaceAnimator animator = new SurfaceAnimator(
- animatable, animationFinishedCallback, mService);
-
- LocalAnimationAdapter localAnimationAdapter = new LocalAnimationAdapter(
- animationSpec, mService.mSurfaceAnimationRunner);
- animator.startAnimation(mDisplayContent.getPendingTransaction(),
- localAnimationAdapter, false, ANIMATION_TYPE_SCREEN_ROTATION);
- return animator;
- }
-
- private void onAnimationEnd(@AnimationType int type, AnimationAdapter anim) {
- synchronized (mService.mGlobalLock) {
- if (isAnimating()) {
- ProtoLog.v(WM_DEBUG_ORIENTATION,
- "ScreenRotation still animating: type: %d\n"
- + "mDisplayAnimator: %s\n"
- + "mEnterBlackFrameAnimator: %s\n"
- + "mRotateScreenAnimator: %s\n"
- + "mScreenshotRotationAnimator: %s",
- type,
- mDisplayAnimator != null
- ? mDisplayAnimator.isAnimating() : null,
- mEnterBlackFrameAnimator != null
- ? mEnterBlackFrameAnimator.isAnimating() : null,
- mRotateScreenAnimator != null
- ? mRotateScreenAnimator.isAnimating() : null,
- mScreenshotRotationAnimator != null
- ? mScreenshotRotationAnimator.isAnimating() : null
- );
- return;
- }
- ProtoLog.d(WM_DEBUG_ORIENTATION, "ScreenRotationAnimation onAnimationEnd");
- mEnterBlackFrameAnimator = null;
- mScreenshotRotationAnimator = null;
- mRotateScreenAnimator = null;
- mService.mAnimator.mBulkUpdateParams |= WindowSurfacePlacer.SET_UPDATE_ROTATION;
- if (mDisplayContent.getRotationAnimation() == ScreenRotationAnimation.this) {
- // It also invokes kill().
- mDisplayContent.setRotationAnimation(null);
- mDisplayContent.mAppCompatCameraPolicy.onScreenRotationAnimationFinished();
- } else {
- kill();
- }
- mService.updateRotation(false, false);
- }
- }
-
- public void cancel() {
- if (mEnterBlackFrameAnimator != null) {
- mEnterBlackFrameAnimator.cancelAnimation();
- }
- if (mScreenshotRotationAnimator != null) {
- mScreenshotRotationAnimator.cancelAnimation();
- }
-
- if (mRotateScreenAnimator != null) {
- mRotateScreenAnimator.cancelAnimation();
- }
-
- if (mDisplayAnimator != null) {
- mDisplayAnimator.cancelAnimation();
- }
-
- if (mBackColorSurface != null) {
- mService.mSurfaceAnimationRunner.onAnimationCancelled(mBackColorSurface);
- }
- }
-
- public boolean isAnimating() {
- return mDisplayAnimator != null && mDisplayAnimator.isAnimating()
- || mEnterBlackFrameAnimator != null && mEnterBlackFrameAnimator.isAnimating()
- || mRotateScreenAnimator != null && mRotateScreenAnimator.isAnimating()
- || mScreenshotRotationAnimator != null
- && mScreenshotRotationAnimator.isAnimating();
- }
- }
-}
diff --git a/services/core/java/com/android/server/wm/SimpleSurfaceAnimatable.java b/services/core/java/com/android/server/wm/SimpleSurfaceAnimatable.java
deleted file mode 100644
index 3b3db89..0000000
--- a/services/core/java/com/android/server/wm/SimpleSurfaceAnimatable.java
+++ /dev/null
@@ -1,330 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.view.SurfaceControl;
-
-import java.util.function.BiConsumer;
-import java.util.function.Consumer;
-import java.util.function.Supplier;
-
-/**
- * An implementation of {@link SurfaceAnimator.Animatable} that is instantiated
- * using a builder pattern for more convenience over reimplementing the whole interface.
- * <p>
- * Use {@link SimpleSurfaceAnimatable.Builder} to create a new instance of this class.
- *
- * @see com.android.server.wm.SurfaceAnimator.Animatable
- */
-public class SimpleSurfaceAnimatable implements SurfaceAnimator.Animatable {
- private final int mWidth;
- private final int mHeight;
- private final boolean mShouldDeferAnimationFinish;
- private final SurfaceControl mAnimationLeashParent;
- private final SurfaceControl mSurfaceControl;
- private final SurfaceControl mParentSurfaceControl;
- private final Runnable mCommitTransactionRunnable;
- private final Supplier<SurfaceControl.Builder> mAnimationLeashFactory;
- private final Supplier<SurfaceControl.Transaction> mSyncTransaction;
- private final Supplier<SurfaceControl.Transaction> mPendingTransaction;
- private final BiConsumer<SurfaceControl.Transaction, SurfaceControl> mOnAnimationLeashCreated;
- private final Consumer<SurfaceControl.Transaction> mOnAnimationLeashLost;
- private final Consumer<Runnable> mOnAnimationFinished;
-
- /**
- * Use {@link SimpleSurfaceAnimatable.Builder} to create a new instance.
- */
- private SimpleSurfaceAnimatable(Builder builder) {
- mWidth = builder.mWidth;
- mHeight = builder.mHeight;
- mShouldDeferAnimationFinish = builder.mShouldDeferAnimationFinish;
- mAnimationLeashParent = builder.mAnimationLeashParent;
- mSurfaceControl = builder.mSurfaceControl;
- mParentSurfaceControl = builder.mParentSurfaceControl;
- mCommitTransactionRunnable = builder.mCommitTransactionRunnable;
- mAnimationLeashFactory = builder.mAnimationLeashFactory;
- mOnAnimationLeashCreated = builder.mOnAnimationLeashCreated;
- mOnAnimationLeashLost = builder.mOnAnimationLeashLost;
- mSyncTransaction = builder.mSyncTransactionSupplier;
- mPendingTransaction = builder.mPendingTransactionSupplier;
- mOnAnimationFinished = builder.mOnAnimationFinished;
- }
-
- @Override
- public SurfaceControl.Transaction getSyncTransaction() {
- return mSyncTransaction.get();
- }
-
- @NonNull
- @Override
- public SurfaceControl.Transaction getPendingTransaction() {
- return mPendingTransaction.get();
- }
-
- @Override
- public void commitPendingTransaction() {
- mCommitTransactionRunnable.run();
- }
-
- @Override
- public void onAnimationLeashCreated(SurfaceControl.Transaction t, SurfaceControl leash) {
- if (mOnAnimationLeashCreated != null) {
- mOnAnimationLeashCreated.accept(t, leash);
- }
-
- }
-
- @Override
- public void onAnimationLeashLost(SurfaceControl.Transaction t) {
- if (mOnAnimationLeashLost != null) {
- mOnAnimationLeashLost.accept(t);
- }
- }
-
- @Override
- @NonNull
- public SurfaceControl.Builder makeAnimationLeash() {
- return mAnimationLeashFactory.get();
- }
-
- @Override
- public SurfaceControl getAnimationLeashParent() {
- return mAnimationLeashParent;
- }
-
- @Override
- @Nullable
- public SurfaceControl getSurfaceControl() {
- return mSurfaceControl;
- }
-
- @Override
- public SurfaceControl getParentSurfaceControl() {
- return mParentSurfaceControl;
- }
-
- @Override
- public int getSurfaceWidth() {
- return mWidth;
- }
-
- @Override
- public int getSurfaceHeight() {
- return mHeight;
- }
-
- @Override
- public boolean shouldDeferAnimationFinish(Runnable endDeferFinishCallback) {
- if (mOnAnimationFinished != null) {
- mOnAnimationFinished.accept(endDeferFinishCallback);
- }
- return mShouldDeferAnimationFinish;
- }
-
- /**
- * Builder class to create a {@link SurfaceAnimator.Animatable} without having to
- * create a new class that implements the interface.
- */
- static class Builder {
- private int mWidth = -1;
- private int mHeight = -1;
- private boolean mShouldDeferAnimationFinish = false;
-
- @Nullable
- private SurfaceControl mAnimationLeashParent = null;
-
- @Nullable
- private SurfaceControl mSurfaceControl = null;
-
- @Nullable
- private SurfaceControl mParentSurfaceControl = null;
- private Runnable mCommitTransactionRunnable;
-
- @Nullable
- private BiConsumer<SurfaceControl.Transaction, SurfaceControl> mOnAnimationLeashCreated =
- null;
-
- @Nullable
- private Consumer<SurfaceControl.Transaction> mOnAnimationLeashLost = null;
-
- @Nullable
- private Consumer<Runnable> mOnAnimationFinished = null;
-
- @NonNull
- private Supplier<SurfaceControl.Transaction> mSyncTransactionSupplier;
-
- @NonNull
- private Supplier<SurfaceControl.Transaction> mPendingTransactionSupplier;
-
- @NonNull
- private Supplier<SurfaceControl.Builder> mAnimationLeashFactory;
-
- /**
- * Set the runnable to be called when
- * {@link SurfaceAnimator.Animatable#commitPendingTransaction()}
- * is called.
- *
- * @see SurfaceAnimator.Animatable#commitPendingTransaction()
- */
- public SimpleSurfaceAnimatable.Builder setCommitTransactionRunnable(
- @NonNull Runnable commitTransactionRunnable) {
- mCommitTransactionRunnable = commitTransactionRunnable;
- return this;
- }
-
- /**
- * Set the callback called when
- * {@link SurfaceAnimator.Animatable#onAnimationLeashCreated(SurfaceControl.Transaction,
- * SurfaceControl)} is called
- *
- * @see SurfaceAnimator.Animatable#onAnimationLeashCreated(SurfaceControl.Transaction,
- * SurfaceControl)
- */
- public SimpleSurfaceAnimatable.Builder setOnAnimationLeashCreated(
- @Nullable BiConsumer<SurfaceControl.Transaction, SurfaceControl>
- onAnimationLeashCreated) {
- mOnAnimationLeashCreated = onAnimationLeashCreated;
- return this;
- }
-
- /**
- * Set the callback called when
- * {@link SurfaceAnimator.Animatable#onAnimationLeashLost(SurfaceControl.Transaction)}
- * (SurfaceControl.Transaction, SurfaceControl)} is called
- *
- * @see SurfaceAnimator.Animatable#onAnimationLeashLost(SurfaceControl.Transaction)
- */
- public SimpleSurfaceAnimatable.Builder setOnAnimationLeashLost(
- @Nullable Consumer<SurfaceControl.Transaction> onAnimationLeashLost) {
- mOnAnimationLeashLost = onAnimationLeashLost;
- return this;
- }
-
- /**
- * @see SurfaceAnimator.Animatable#getSyncTransaction()
- */
- public Builder setSyncTransactionSupplier(
- @NonNull Supplier<SurfaceControl.Transaction> syncTransactionSupplier) {
- mSyncTransactionSupplier = syncTransactionSupplier;
- return this;
- }
-
- /**
- * @see SurfaceAnimator.Animatable#getPendingTransaction()
- */
- public Builder setPendingTransactionSupplier(
- @NonNull Supplier<SurfaceControl.Transaction> pendingTransactionSupplier) {
- mPendingTransactionSupplier = pendingTransactionSupplier;
- return this;
- }
-
- /**
- * Set the {@link Supplier} responsible for creating a new animation leash.
- *
- * @see SurfaceAnimator.Animatable#makeAnimationLeash()
- */
- public SimpleSurfaceAnimatable.Builder setAnimationLeashSupplier(
- @NonNull Supplier<SurfaceControl.Builder> animationLeashFactory) {
- mAnimationLeashFactory = animationLeashFactory;
- return this;
- }
-
- /**
- * @see SurfaceAnimator.Animatable#getAnimationLeashParent()
- */
- public SimpleSurfaceAnimatable.Builder setAnimationLeashParent(
- SurfaceControl animationLeashParent) {
- mAnimationLeashParent = animationLeashParent;
- return this;
- }
-
- /**
- * @see SurfaceAnimator.Animatable#getSurfaceControl()
- */
- public SimpleSurfaceAnimatable.Builder setSurfaceControl(
- @NonNull SurfaceControl surfaceControl) {
- mSurfaceControl = surfaceControl;
- return this;
- }
-
- /**
- * @see SurfaceAnimator.Animatable#getParentSurfaceControl()
- */
- public SimpleSurfaceAnimatable.Builder setParentSurfaceControl(
- SurfaceControl parentSurfaceControl) {
- mParentSurfaceControl = parentSurfaceControl;
- return this;
- }
-
- /**
- * Default to -1.
- *
- * @see SurfaceAnimator.Animatable#getSurfaceWidth()
- */
- public SimpleSurfaceAnimatable.Builder setWidth(int width) {
- mWidth = width;
- return this;
- }
-
- /**
- * Default to -1.
- *
- * @see SurfaceAnimator.Animatable#getSurfaceHeight()
- */
- public SimpleSurfaceAnimatable.Builder setHeight(int height) {
- mHeight = height;
- return this;
- }
-
- /**
- * Set the value returned by
- * {@link SurfaceAnimator.Animatable#shouldDeferAnimationFinish(Runnable)}.
- *
- * @param onAnimationFinish will be called with the runnable to execute when the animation
- * needs to be finished.
- * @see SurfaceAnimator.Animatable#shouldDeferAnimationFinish(Runnable)
- */
- public SimpleSurfaceAnimatable.Builder setShouldDeferAnimationFinish(
- boolean shouldDeferAnimationFinish,
- @Nullable Consumer<Runnable> onAnimationFinish) {
- mShouldDeferAnimationFinish = shouldDeferAnimationFinish;
- mOnAnimationFinished = onAnimationFinish;
- return this;
- }
-
- public SurfaceAnimator.Animatable build() {
- if (mSyncTransactionSupplier == null) {
- throw new IllegalArgumentException("mSyncTransactionSupplier cannot be null");
- }
- if (mPendingTransactionSupplier == null) {
- throw new IllegalArgumentException("mPendingTransactionSupplier cannot be null");
- }
- if (mAnimationLeashFactory == null) {
- throw new IllegalArgumentException("mAnimationLeashFactory cannot be null");
- }
- if (mCommitTransactionRunnable == null) {
- throw new IllegalArgumentException("mCommitTransactionRunnable cannot be null");
- }
- if (mSurfaceControl == null) {
- throw new IllegalArgumentException("mSurfaceControl cannot be null");
- }
- return new SimpleSurfaceAnimatable(this);
- }
- }
-}
diff --git a/services/core/java/com/android/server/wm/StrictModeFlash.java b/services/core/java/com/android/server/wm/StrictModeFlash.java
index cdf6b08..b6365ad 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 @@
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 295759c..c6136f3 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -5101,6 +5101,7 @@
mTranslucentActivityWaiting = r;
mPendingConvertFromTranslucentActivity = r;
mUndrawnActivitiesBelowTopTranslucent.clear();
+ updateTaskDescription();
mHandler.sendEmptyMessageDelayed(TRANSLUCENT_TIMEOUT_MSG, TRANSLUCENT_CONVERSION_TIMEOUT);
}
@@ -5110,6 +5111,7 @@
+ " but is " + r);
}
mPendingConvertFromTranslucentActivity = null;
+ updateTaskDescription();
}
/**
@@ -5297,40 +5299,29 @@
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 +5337,15 @@
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 +5396,7 @@
// "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/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 1993053..fc7437b 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -2092,12 +2092,6 @@
ProtoLog.v(WM_DEBUG_STATES, "App died during pause, not stopping: %s", prev);
prev = null;
}
- // It is possible the activity was freezing the screen before it was paused.
- // In that case go ahead and remove the freeze this activity has on the screen
- // since it is no longer visible.
- if (prev != null) {
- prev.stopFreezingScreen(true /* unfreezeNow */, true /* force */);
- }
}
if (resumeNext) {
diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
index c6a1679..aaae160 100644
--- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
@@ -842,7 +842,6 @@
return;
}
validateAndGetState(organizer);
- Slog.w(TAG, "onTaskFragmentError ", exception);
addPendingEvent(new PendingTaskFragmentEvent.Builder(
PendingTaskFragmentEvent.EVENT_ERROR, organizer)
.setErrorCallbackToken(errorCallbackToken)
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 75cefdf..37cc0d2 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -1626,6 +1626,11 @@
for (int i = 0; i < mConfigAtEndActivities.size(); ++i) {
final ActivityRecord target = mConfigAtEndActivities.get(i);
final SurfaceControl targetLeash = target.getSurfaceControl();
+ if (targetLeash == null) {
+ // activity may have been removed. In this case, no need to sync, just update state.
+ target.resumeConfigurationDispatch();
+ continue;
+ }
if (target.getSyncGroup() == null || target.getSyncGroup().isIgnoring(target)) {
if (syncId < 0) {
final BLASTSyncEngine.SyncGroup sg = mSyncEngine.prepareSyncSet(
diff --git a/services/core/java/com/android/server/wm/Watermark.java b/services/core/java/com/android/server/wm/Watermark.java
index 9780d33..eb6eeb3 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 @@
} 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/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java
index 790ae1e..80137a2 100644
--- a/services/core/java/com/android/server/wm/WindowAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowAnimator.java
@@ -146,7 +146,6 @@
boolean rootAnimating = false;
mCurrentTime = frameTimeNs / TimeUtils.NANOS_PER_MS;
mBulkUpdateParams = 0;
- root.mOrientationChangeComplete = true;
if (DEBUG_WINDOW_TRACE) {
Slog.i(TAG, "!!! animate: entry time=" + mCurrentTime);
}
@@ -203,8 +202,7 @@
}
final boolean hasPendingLayoutChanges = root.hasPendingLayoutChanges(this);
- final boolean doRequest = (mBulkUpdateParams != 0 || root.mOrientationChangeComplete)
- && root.copyAnimToLayoutParams();
+ final boolean doRequest = mBulkUpdateParams != 0 && root.copyAnimToLayoutParams();
if (hasPendingLayoutChanges || doRequest) {
mService.mWindowPlacerLocked.requestTraversal();
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 36d52dd..0e9b423 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -31,7 +31,6 @@
import static android.app.ActivityManagerInternal.ALLOW_NON_FULL;
import static android.app.AppOpsManager.OP_SYSTEM_ALERT_WINDOW;
import static android.app.StatusBarManager.DISABLE_MASK;
-import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED;
import static android.content.pm.PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT;
@@ -116,13 +115,11 @@
import static com.android.internal.protolog.WmProtoLogGroups.WM_ERROR;
import static com.android.internal.protolog.WmProtoLogGroups.WM_SHOW_TRANSACTIONS;
import static com.android.internal.util.FrameworkStatsLog.SENSITIVE_NOTIFICATION_APP_PROTECTION_APPLIED;
-import static com.android.internal.util.LatencyTracker.ACTION_ROTATE_SCREEN;
import static com.android.server.LockGuard.INDEX_WINDOW;
import static com.android.server.LockGuard.installLock;
import static com.android.server.policy.PhoneWindowManager.TRACE_WAIT_FOR_ALL_WINDOWS_DRAWN_METHOD;
import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
import static com.android.server.wm.ActivityTaskManagerService.DEMOTE_TOP_REASON_EXPANDED_NOTIFICATION_SHADE;
-import static com.android.server.wm.ActivityTaskManagerService.POWER_MODE_REASON_CHANGE_DISPLAY;
import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND;
import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING;
import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_BACKGROUND_SOLID_COLOR;
@@ -151,7 +148,6 @@
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import static com.android.server.wm.WindowManagerInternal.OnWindowRemovedListener;
import static com.android.server.wm.WindowManagerServiceDumpProto.BACK_NAVIGATION;
-import static com.android.server.wm.WindowManagerServiceDumpProto.DISPLAY_FROZEN;
import static com.android.server.wm.WindowManagerServiceDumpProto.FOCUSED_APP;
import static com.android.server.wm.WindowManagerServiceDumpProto.FOCUSED_DISPLAY_ID;
import static com.android.server.wm.WindowManagerServiceDumpProto.FOCUSED_WINDOW;
@@ -251,7 +247,6 @@
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseIntArray;
-import android.util.TimeUtils;
import android.util.TypedValue;
import android.util.proto.ProtoOutputStream;
import android.view.Choreographer;
@@ -403,8 +398,6 @@
static final int LAYOUT_REPEAT_THRESHOLD = 4;
- static final boolean PROFILE_ORIENTATION = false;
-
/** The maximum length we will accept for a loaded animation duration:
* this is 10 seconds.
*/
@@ -742,21 +735,8 @@
@Nullable
private Runnable mPointerDownOutsideFocusRunnable;
- boolean mDisplayFrozen = false;
- long mDisplayFreezeTime = 0;
- int mLastDisplayFreezeDuration = 0;
- Object mLastFinishedFreezeSource = null;
boolean mSwitchingUser = false;
- final static int WINDOWS_FREEZING_SCREENS_NONE = 0;
- final static int WINDOWS_FREEZING_SCREENS_ACTIVE = 1;
- 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
boolean mPerDisplayFocusEnabled;
@@ -766,11 +746,6 @@
// Number of windows whose insets state have been changed.
int mWindowsInsetsChanged = 0;
- // This is held as long as we have the screen frozen, to give us time to
- // perform a rotation animation when turning off shows the lock screen which
- // changes the orientation.
- private final PowerManager.WakeLock mScreenFrozenLock;
-
final TaskSnapshotController mTaskSnapshotController;
final SnapshotController mSnapshotController;
@@ -1055,12 +1030,6 @@
final DragDropController mDragDropController;
- /** For frozen screen animations. */
- private int mExitAnimId, mEnterAnimId;
-
- /** The display that the rotation animation is applying to. */
- private int mFrozenDisplayId = INVALID_DISPLAY;
-
/** Skip repeated ActivityRecords initialization. Note that AppWindowsToken's version of this
* is a long initialized to Long.MIN_VALUE so that it doesn't match this value on startup. */
int mTransactionSequence;
@@ -1163,12 +1132,6 @@
}
};
- final ArrayList<AppFreezeListener> mAppFreezeListeners = new ArrayList<>();
-
- interface AppFreezeListener {
- void onAppFreezeTimeout();
- }
-
private final ScreenRecordingCallbackController mScreenRecordingCallbackController;
private volatile boolean mDisableSecureWindows = false;
@@ -1349,9 +1312,6 @@
mAnimationsDisabled = mPowerManagerInternal
.getLowPowerState(ServiceType.ANIMATION).batterySaverEnabled;
}
- mScreenFrozenLock = mPowerManager.newWakeLock(
- PowerManager.PARTIAL_WAKE_LOCK, "SCREEN_FROZEN");
- mScreenFrozenLock.setReferenceCounted(false);
mRotationWatcherController = new RotationWatcherController(this);
mDisplayNotificationController = new DisplayWindowListenerController(this);
@@ -3354,60 +3314,6 @@
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(),
@@ -5657,11 +5563,8 @@
// -------------------------------------------------------------
final class H extends android.os.Handler {
- public static final int WINDOW_FREEZE_TIMEOUT = 11;
-
public static final int PERSIST_ANIMATION_SCALE = 14;
public static final int ENABLE_SCREEN = 16;
- public static final int APP_FREEZE_TIMEOUT = 17;
public static final int REPORT_WINDOWS_CHANGE = 19;
public static final int REPORT_HARD_KEYBOARD_STATUS_CHANGE = 22;
@@ -5669,7 +5572,6 @@
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;
@@ -5702,14 +5604,6 @@
Slog.v(TAG_WM, "handleMessage: entry what=" + msg.what);
}
switch (msg.what) {
- case WINDOW_FREEZE_TIMEOUT: {
- final DisplayContent displayContent = (DisplayContent) msg.obj;
- synchronized (mGlobalLock) {
- displayContent.onWindowFreezeTimeout();
- }
- break;
- }
-
case PERSIST_ANIMATION_SCALE: {
Settings.Global.putFloat(mContext.getContentResolver(),
Settings.Global.WINDOW_ANIMATION_SCALE, mWindowAnimationScaleSetting);
@@ -5748,28 +5642,6 @@
break;
}
- case APP_FREEZE_TIMEOUT: {
- synchronized (mGlobalLock) {
- ProtoLog.w(WM_ERROR, "App freeze timeout expired.");
- mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_TIMEOUT;
- for (int i = mAppFreezeListeners.size() - 1; i >= 0; --i) {
- mAppFreezeListeners.get(i).onAppFreezeTimeout();
- }
- }
- break;
- }
-
- case CLIENT_FREEZE_TIMEOUT: {
- synchronized (mGlobalLock) {
- if (mClientFreezingScreen) {
- mClientFreezingScreen = false;
- mLastFinishedFreezeSource = "client-timeout";
- stopFreezingDisplayLocked();
- }
- }
- break;
- }
-
case REPORT_WINDOWS_CHANGE: {
if (mWindowsChanged) {
synchronized (mGlobalLock) {
@@ -6378,22 +6250,6 @@
return win;
}
- void makeWindowFreezingScreenIfNeededLocked(WindowState w) {
- // If the screen is currently frozen, then keep it frozen until this window draws at its
- // new orientation.
- if (mFrozenDisplayId != INVALID_DISPLAY && mFrozenDisplayId == w.getDisplayId()
- && mWindowsFreezingScreen != WINDOWS_FREEZING_SCREENS_TIMEOUT) {
- ProtoLog.v(WM_DEBUG_ORIENTATION, "Changing surface while display frozen: %s", w);
- if (mWindowsFreezingScreen == WINDOWS_FREEZING_SCREENS_NONE) {
- mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_ACTIVE;
- // XXX should probably keep timeout from
- // when we first froze the display.
- mH.sendNewMessageDelayed(H.WINDOW_FREEZE_TIMEOUT, w.getDisplayContent(),
- WINDOW_FREEZE_TIMEOUT_DURATION);
- }
- }
- }
-
void checkDrawnWindowsLocked() {
if (mWaitingForDrawnCallbacks.isEmpty()) {
return;
@@ -6462,189 +6318,6 @@
return changed;
}
- void startFreezingDisplay(int exitAnim, int enterAnim) {
- startFreezingDisplay(exitAnim, enterAnim, getDefaultDisplayContentLocked());
- }
-
- void startFreezingDisplay(int exitAnim, int enterAnim, DisplayContent displayContent) {
- startFreezingDisplay(exitAnim, enterAnim, displayContent,
- ROTATION_UNDEFINED /* overrideOriginalRotation */);
- }
-
- void startFreezingDisplay(int exitAnim, int enterAnim, DisplayContent displayContent,
- int overrideOriginalRotation) {
- if (mDisplayFrozen || displayContent.getDisplayRotation().isRotatingSeamlessly()) {
- return;
- }
-
- if (!displayContent.isReady() || !displayContent.getDisplayPolicy().isScreenOnFully()
- || displayContent.getDisplayInfo().state == Display.STATE_OFF
- || !displayContent.okToAnimate()) {
- // No need to freeze the screen before the display is ready, if the screen is off,
- // or we can't currently animate.
- return;
- }
-
- displayContent.requestDisplayUpdate(() -> {
- Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "WMS.doStartFreezingDisplay");
- doStartFreezingDisplay(exitAnim, enterAnim, displayContent, overrideOriginalRotation);
- Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
- });
- }
-
- private void doStartFreezingDisplay(int exitAnim, int enterAnim, DisplayContent displayContent,
- int overrideOriginalRotation) {
- ProtoLog.d(WM_DEBUG_ORIENTATION,
- "startFreezingDisplayLocked: exitAnim=%d enterAnim=%d called by %s",
- exitAnim, enterAnim, Debug.getCallers(8));
- mScreenFrozenLock.acquire();
- // Apply launch power mode to reduce screen frozen time because orientation change may
- // relaunch activity and redraw windows. This may also help speed up user switching.
- mAtmService.startPowerMode(POWER_MODE_REASON_CHANGE_DISPLAY);
-
- mDisplayFrozen = true;
- mDisplayFreezeTime = SystemClock.elapsedRealtime();
- mLastFinishedFreezeSource = null;
-
- // {@link mDisplayFrozen} prevents us from freezing on multiple displays at the same time.
- // As a result, we only track the display that has initially froze the screen.
- mFrozenDisplayId = displayContent.getDisplayId();
-
- mInputManagerCallback.freezeInputDispatchingLw();
-
- if (displayContent.mAppTransition.isTransitionSet()) {
- displayContent.mAppTransition.freeze();
- }
-
- if (PROFILE_ORIENTATION) {
- File file = new File("/data/system/frozen");
- Debug.startMethodTracing(file.toString(), 8 * 1024 * 1024);
- }
-
- mLatencyTracker.onActionStart(ACTION_ROTATE_SCREEN);
- mExitAnimId = exitAnim;
- mEnterAnimId = enterAnim;
-
- final int originalRotation = overrideOriginalRotation != ROTATION_UNDEFINED
- ? overrideOriginalRotation
- : displayContent.getDisplayInfo().rotation;
- displayContent.setRotationAnimation(new ScreenRotationAnimation(displayContent,
- originalRotation));
- }
-
- void stopFreezingDisplayLocked() {
- if (!mDisplayFrozen) {
- return;
- }
-
- final DisplayContent displayContent = mRoot.getDisplayContent(mFrozenDisplayId);
- final int numOpeningApps;
- final boolean waitingForConfig;
- final boolean waitingForRemoteDisplayChange;
- if (displayContent != null) {
- numOpeningApps = displayContent.mOpeningApps.size();
- waitingForConfig = displayContent.mWaitingForConfig;
- waitingForRemoteDisplayChange = displayContent.mRemoteDisplayChangeController
- .isWaitingForRemoteDisplayChange();
- } else {
- waitingForConfig = waitingForRemoteDisplayChange = false;
- numOpeningApps = 0;
- }
- if (waitingForConfig || waitingForRemoteDisplayChange || mAppsFreezingScreen > 0
- || mWindowsFreezingScreen == WINDOWS_FREEZING_SCREENS_ACTIVE
- || mClientFreezingScreen || numOpeningApps > 0) {
- ProtoLog.d(WM_DEBUG_ORIENTATION, "stopFreezingDisplayLocked: Returning "
- + "waitingForConfig=%b, waitingForRemoteDisplayChange=%b, "
- + "mAppsFreezingScreen=%d, mWindowsFreezingScreen=%d, "
- + "mClientFreezingScreen=%b, mOpeningApps.size()=%d",
- waitingForConfig, waitingForRemoteDisplayChange,
- mAppsFreezingScreen, mWindowsFreezingScreen,
- mClientFreezingScreen, numOpeningApps);
- return;
- }
-
- Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "WMS.doStopFreezingDisplayLocked-"
- + mLastFinishedFreezeSource);
- doStopFreezingDisplayLocked(displayContent);
- Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
- }
-
- private void doStopFreezingDisplayLocked(DisplayContent displayContent) {
- ProtoLog.d(WM_DEBUG_ORIENTATION,
- "stopFreezingDisplayLocked: Unfreezing now");
-
- // We must make a local copy of the displayId as it can be potentially overwritten later on
- // in this method. For example, {@link startFreezingDisplayLocked} may be called as a result
- // of update rotation, but we reference the frozen display after that call in this method.
- mFrozenDisplayId = INVALID_DISPLAY;
- mDisplayFrozen = false;
- mInputManagerCallback.thawInputDispatchingLw();
- mLastDisplayFreezeDuration = (int)(SystemClock.elapsedRealtime() - mDisplayFreezeTime);
- StringBuilder sb = new StringBuilder(128);
- sb.append("Screen frozen for ");
- TimeUtils.formatDuration(mLastDisplayFreezeDuration, sb);
- if (mLastFinishedFreezeSource != null) {
- sb.append(" due to ");
- sb.append(mLastFinishedFreezeSource);
- }
- ProtoLog.i(WM_ERROR, "%s", sb.toString());
- mH.removeMessages(H.APP_FREEZE_TIMEOUT);
- mH.removeMessages(H.CLIENT_FREEZE_TIMEOUT);
- if (PROFILE_ORIENTATION) {
- Debug.stopMethodTracing();
- }
-
- boolean updateRotation = false;
-
- ScreenRotationAnimation screenRotationAnimation = displayContent == null ? null
- : displayContent.getRotationAnimation();
- if (screenRotationAnimation != null && screenRotationAnimation.hasScreenshot()) {
- ProtoLog.i(WM_DEBUG_ORIENTATION, "**** Dismissing screen rotation animation");
- DisplayInfo displayInfo = displayContent.getDisplayInfo();
- // Get rotation animation again, with new top window
- if (!displayContent.getDisplayRotation().validateRotationAnimation(
- mExitAnimId, mEnterAnimId, false /* forceDefault */)) {
- mExitAnimId = mEnterAnimId = 0;
- }
- if (screenRotationAnimation.dismiss(mTransaction, MAX_ANIMATION_DURATION,
- getTransitionAnimationScaleLocked(), displayInfo.logicalWidth,
- displayInfo.logicalHeight, mExitAnimId, mEnterAnimId)) {
- mTransaction.apply();
- } else {
- screenRotationAnimation.kill();
- displayContent.setRotationAnimation(null);
- updateRotation = true;
- }
- } else {
- if (screenRotationAnimation != null) {
- screenRotationAnimation.kill();
- displayContent.setRotationAnimation(null);
- }
- updateRotation = true;
- }
-
- boolean configChanged;
-
- // While the display is frozen we don't re-compute the orientation
- // to avoid inconsistent states. However, something interesting
- // could have actually changed during that time so re-evaluate it
- // now to catch that.
- configChanged = displayContent != null && displayContent.updateOrientation();
-
- mScreenFrozenLock.release();
-
- if (updateRotation && displayContent != null) {
- ProtoLog.d(WM_DEBUG_ORIENTATION, "Performing post-rotate rotation");
- configChanged |= displayContent.updateRotationUnchecked();
- }
-
- if (configChanged) {
- displayContent.sendNewConfiguration();
- }
- mAtmService.endPowerMode(POWER_MODE_REASON_CHANGE_DISPLAY);
- mLatencyTracker.onActionEnd(ACTION_ROTATE_SCREEN);
- }
-
static int getPropertyInt(String[] tokens, int index, int defUnits, int defDps,
DisplayMetrics dm) {
if (index < tokens.length) {
@@ -6945,7 +6618,6 @@
if (imeWindow != null) {
imeWindow.writeIdentifierToProto(proto, INPUT_METHOD_WINDOW);
}
- proto.write(DISPLAY_FROZEN, mDisplayFrozen);
proto.write(FOCUSED_DISPLAY_ID, topFocusedDisplayContent.getDisplayId());
proto.write(HARD_KEYBOARD_AVAILABLE, mHardKeyboardAvailable);
@@ -7068,13 +6740,6 @@
pw.print(' '); pw.print(dc.mMinSizeOfResizeableTaskDp);
});
pw.print(" mBlurEnabled="); pw.println(mBlurController.getBlurEnabled());
- pw.print(" mLastDisplayFreezeDuration=");
- TimeUtils.formatDuration(mLastDisplayFreezeDuration, pw);
- if ( mLastFinishedFreezeSource != null) {
- pw.print(" due to ");
- pw.print(mLastFinishedFreezeSource);
- }
- pw.println();
pw.print(" mDisableSecureWindows="); pw.println(mDisableSecureWindows);
mInputManagerCallback.dump(pw, " ");
@@ -7094,10 +6759,6 @@
mRoot.dumpLayoutNeededDisplayIds(pw);
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());
pw.print(" mLastOrientation=");
@@ -8975,16 +8636,6 @@
}
}
- void registerAppFreezeListener(AppFreezeListener listener) {
- if (!mAppFreezeListeners.contains(listener)) {
- mAppFreezeListeners.add(listener);
- }
- }
-
- void unregisterAppFreezeListener(AppFreezeListener listener) {
- mAppFreezeListeners.remove(listener);
- }
-
/** Called to inform window manager if non-Vr UI shoul be disabled or not. */
public void disableNonVrUi(boolean disable) {
synchronized (mGlobalLock) {
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 3c3a180..a1755e4d 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -52,6 +52,7 @@
import static android.window.WindowContainerTransaction.Change.CHANGE_FORCE_TRANSLUCENT;
import static android.window.WindowContainerTransaction.Change.CHANGE_HIDDEN;
import static android.window.WindowContainerTransaction.Change.CHANGE_RELATIVE_BOUNDS;
+import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REMOVE_ROOT_TASK;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_KEYGUARD_STATE;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_ADD_INSETS_FRAME_PROVIDER;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_ADD_TASK_FRAGMENT_OPERATION;
@@ -1131,6 +1132,23 @@
}
break;
}
+ case HIERARCHY_OP_TYPE_REMOVE_ROOT_TASK: {
+ final WindowContainer wc = WindowContainer.fromBinder(hop.getContainer());
+ if (wc == null || wc.asTask() == null || !wc.isAttached()
+ || !wc.asTask().isRootTask() || !wc.asTask().mCreatedByOrganizer) {
+ Slog.e(TAG, "Attempt to remove invalid task: " + wc);
+ break;
+ }
+ final Task task = wc.asTask();
+ if (task.isVisibleRequested() || task.isVisible()) {
+ effects |= TRANSACT_EFFECTS_LIFECYCLE;
+ }
+ // Removes its leaves, but not itself.
+ mService.mTaskSupervisor.removeRootTask(task);
+ // Now that the root has no leaves, remove it too. .
+ task.remove(true /* withTransition */, "remove-root-task-through-hierarchyOp");
+ break;
+ }
case HIERARCHY_OP_TYPE_SET_LAUNCH_ROOT: {
final WindowContainer wc = WindowContainer.fromBinder(hop.getContainer());
if (wc == null || !wc.isAttached()) {
@@ -1380,7 +1398,7 @@
break;
}
case HIERARCHY_OP_TYPE_RESTORE_TRANSIENT_ORDER: {
- if (!com.android.wm.shell.Flags.enableShellTopTaskTracking()) {
+ if (!com.android.wm.shell.Flags.enableRecentsBookendTransition()) {
// Only allow restoring transient order when finishing a transition
if (!chain.isFinishing()) break;
}
@@ -1416,7 +1434,7 @@
final TaskDisplayArea taskDisplayArea = thisTask.getTaskDisplayArea();
taskDisplayArea.moveRootTaskBehindRootTask(thisTask.getRootTask(), restoreAt);
- if (com.android.wm.shell.Flags.enableShellTopTaskTracking()) {
+ if (com.android.wm.shell.Flags.enableRecentsBookendTransition()) {
// Because we are in a transient launch transition, the requested visibility of
// tasks does not actually change for the transient-hide tasks, but we do want
// the restoration of these transient-hide tasks to top to be a part of this
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index 26bc09f..30dde54 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -969,14 +969,8 @@
return canUpdate;
}
+ // TODO(365884835): remove this method with external callers.
public void stopFreezingActivities() {
- synchronized (mAtm.mGlobalLock) {
- int i = mActivities.size();
- while (i > 0) {
- i--;
- mActivities.get(i).stopFreezingScreen(true /* unfreezeNow */, true /* force */);
- }
- }
}
void finishActivities() {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index da58470..a8f22ea 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -218,7 +218,6 @@
import android.util.MergedConfiguration;
import android.util.Slog;
import android.util.SparseArray;
-import android.util.TimeUtils;
import android.util.proto.ProtoOutputStream;
import android.view.Display;
import android.view.DisplayInfo;
@@ -366,7 +365,6 @@
boolean mPermanentlyHidden; // the window should never be shown again
// This is a non-system overlay window that is currently force hidden.
private boolean mForceHideNonSystemOverlayWindow;
- boolean mAppFreezing;
boolean mHidden = true; // Used to determine if to show child windows.
private boolean mDragResizing;
private boolean mDragResizingChangeReported = true;
@@ -601,11 +599,6 @@
*/
int mLastVisibleLayoutRotation = -1;
- /**
- * How long we last kept the screen frozen.
- */
- int mLastFreezeDuration;
-
/** Is this window now (or just being) removed? */
boolean mRemoved;
@@ -1475,7 +1468,6 @@
consumeInsetsChange();
onResizeHandled();
- mWmService.makeWindowFreezingScreenIfNeededLocked(this);
// Reset the drawn state if the window need to redraw for the change, so the transition
// can wait until it has finished drawing to start.
@@ -1700,7 +1692,7 @@
@Override
boolean hasContentToDisplay() {
- if (!mAppFreezing && isDrawn() && (mViewVisibility == View.VISIBLE
+ if (!isDrawn() && (mViewVisibility == View.VISIBLE
|| (isAnimating(TRANSITION | PARENTS)
&& !getDisplayContent().mAppTransition.isTransitionSet()))) {
return true;
@@ -1912,7 +1904,6 @@
*/
boolean isInteresting() {
return mActivityRecord != null
- && (!mActivityRecord.isFreezingScreen() || !mAppFreezing)
&& mViewVisibility == View.VISIBLE;
}
@@ -2398,12 +2389,12 @@
ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
"Remove %s: mSurfaceControl=%s mAnimatingExit=%b mRemoveOnExit=%b "
+ "mHasSurface=%b surfaceShowing=%b animating=%b app-animation=%b "
- + "mDisplayFrozen=%b callers=%s",
+ + "callers=%s",
this, mWinAnimator.mSurfaceControl, mAnimatingExit, mRemoveOnExit,
mHasSurface, mWinAnimator.getShown(),
isAnimating(TRANSITION | PARENTS),
mActivityRecord != null && mActivityRecord.isAnimating(PARENTS | TRANSITION),
- mWmService.mDisplayFrozen, Debug.getCallers(6));
+ Debug.getCallers(6));
// First, see if we need to run an animation. If we do, we have to hold off on removing the
// window until the animation is done. If the display is frozen, just remove immediately,
@@ -3266,32 +3257,6 @@
}
}
- void onStartFreezingScreen() {
- mAppFreezing = true;
- for (int i = mChildren.size() - 1; i >= 0; --i) {
- final WindowState c = mChildren.get(i);
- c.onStartFreezingScreen();
- }
- }
-
- boolean onStopFreezingScreen() {
- boolean unfrozeWindows = false;
- for (int i = mChildren.size() - 1; i >= 0; --i) {
- final WindowState c = mChildren.get(i);
- unfrozeWindows |= c.onStopFreezingScreen();
- }
-
- if (!mAppFreezing) {
- return unfrozeWindows;
- }
-
- mAppFreezing = false;
-
- mLastFreezeDuration = 0;
- setDisplayLayoutNeeded();
- return true;
- }
-
boolean destroySurface(boolean cleanupOnResume, boolean appStopped) {
boolean destroyedSomething = false;
@@ -4192,16 +4157,6 @@
+ " mDestroying=" + mDestroying
+ " mRemoved=" + mRemoved);
}
- if (mAppFreezing) {
- pw.println(prefix + " configOrientationChanging="
- + (getLastReportedConfiguration().orientation != getConfiguration().orientation)
- + " mAppFreezing=" + mAppFreezing);
- }
- if (mLastFreezeDuration != 0) {
- pw.print(prefix + "mLastFreezeDuration=");
- TimeUtils.formatDuration(mLastFreezeDuration, pw);
- pw.println();
- }
pw.print(prefix + "mForceSeamlesslyRotate=" + mForceSeamlesslyRotate
+ " seamlesslyRotate: pending=");
if (mPendingSeamlessRotate != null) {
@@ -4882,7 +4837,7 @@
c.updateReportedVisibility(results);
}
- if (mAppFreezing || mViewVisibility != View.VISIBLE
+ if (mViewVisibility != View.VISIBLE
|| mAttrs.type == TYPE_APPLICATION_STARTING
|| mDestroying) {
return;
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 298580e..1d8d867 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -303,8 +303,6 @@
resetDrawState();
- mService.makeWindowFreezingScreenIfNeededLocked(w);
-
int flags = SurfaceControl.HIDDEN;
final WindowManager.LayoutParams attrs = w.mAttrs;
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 911c686..e1f3f0e 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -357,6 +357,7 @@
void setTouchpadRightClickZoneEnabled(bool enabled);
void setTouchpadThreeFingerTapShortcutEnabled(bool enabled);
void setTouchpadSystemGesturesEnabled(bool enabled);
+ void setTouchpadAccelerationEnabled(bool enabled);
void setInputDeviceEnabled(uint32_t deviceId, bool enabled);
void setShowTouches(bool enabled);
void setNonInteractiveDisplays(const std::set<ui::LogicalDisplayId>& displayIds);
@@ -540,6 +541,10 @@
// True to enable system gestures (three- and four-finger swipes) on touchpads.
bool touchpadSystemGesturesEnabled{true};
+ // True if the speed of the pointer will increase as the user moves
+ // their finger faster on the touchpad.
+ bool touchpadAccelerationEnabled{true};
+
// True if a pointer icon should be shown for stylus pointers.
bool stylusPointerIconEnabled{false};
@@ -869,6 +874,7 @@
outConfig->touchpadThreeFingerTapShortcutEnabled =
mLocked.touchpadThreeFingerTapShortcutEnabled;
outConfig->touchpadSystemGesturesEnabled = mLocked.touchpadSystemGesturesEnabled;
+ outConfig->touchpadAccelerationEnabled = mLocked.touchpadAccelerationEnabled;
outConfig->disabledDevices = mLocked.disabledInputDevices;
@@ -1666,6 +1672,21 @@
InputReaderConfiguration::Change::TOUCHPAD_SETTINGS);
}
+void NativeInputManager::setTouchpadAccelerationEnabled(bool enabled) {
+ { // acquire lock
+ std::scoped_lock _l(mLock);
+
+ if (mLocked.touchpadAccelerationEnabled == enabled) {
+ return;
+ }
+
+ mLocked.touchpadAccelerationEnabled = enabled;
+ } // release lock
+
+ mInputManager->getReader().requestRefreshConfiguration(
+ InputReaderConfiguration::Change::TOUCHPAD_SETTINGS);
+}
+
void NativeInputManager::setInputDeviceEnabled(uint32_t deviceId, bool enabled) {
bool refresh = false;
@@ -2644,6 +2665,13 @@
im->setTouchpadSystemGesturesEnabled(enabled);
}
+static void nativeSetTouchpadAccelerationEnabled(JNIEnv* env, jobject nativeImplObj,
+ jboolean enabled) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
+
+ im->setTouchpadAccelerationEnabled(enabled);
+}
+
static void nativeSetShowTouches(JNIEnv* env, jobject nativeImplObj, jboolean enabled) {
NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
@@ -3354,6 +3382,7 @@
{"setTouchpadThreeFingerTapShortcutEnabled", "(Z)V",
(void*)nativeSetTouchpadThreeFingerTapShortcutEnabled},
{"setTouchpadSystemGesturesEnabled", "(Z)V", (void*)nativeSetTouchpadSystemGesturesEnabled},
+ {"setTouchpadAccelerationEnabled", "(Z)V", (void*)nativeSetTouchpadAccelerationEnabled},
{"setShowTouches", "(Z)V", (void*)nativeSetShowTouches},
{"setNonInteractiveDisplays", "([I)V", (void*)nativeSetNonInteractiveDisplays},
{"reloadCalibration", "()V", (void*)nativeReloadCalibration},
diff --git a/services/credentials/java/com/android/server/credentials/RequestSession.java b/services/credentials/java/com/android/server/credentials/RequestSession.java
index c0bc8e0..2aa0c6b 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 @@
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 @@
* @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 @@
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 @@
* @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 @@
finishSession(/*propagateCancellation=*/true, ApiStatus.CLIENT_CANCELED.getMetricCode());
return;
}
-
try {
invokeClientCallbackError(errorType, errorMsg);
} catch (RemoteException e) {
@@ -393,7 +419,9 @@
}
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 @@
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/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java b/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
index 76d16e1..a81a0b3 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
@@ -73,9 +73,6 @@
class ActiveAdmin {
- private final int userId;
- public final boolean isPermissionBased;
-
private static final String TAG_DISABLE_KEYGUARD_FEATURES = "disable-keyguard-features";
private static final String TAG_TEST_ONLY_ADMIN = "test-only-admin";
private static final String TAG_DISABLE_CAMERA = "disable-camera";
@@ -364,23 +361,8 @@
private static final int PROVISIONING_CONTEXT_LENGTH_LIMIT = 1000;
ActiveAdmin(DeviceAdminInfo info, boolean isParent) {
- this.userId = -1;
this.info = info;
this.isParent = isParent;
- this.isPermissionBased = false;
- }
-
- ActiveAdmin(int userId, boolean permissionBased) {
- if (Flags.activeAdminCleanup()) {
- throw new UnsupportedOperationException("permission based admin no longer supported");
- }
- if (permissionBased == false) {
- throw new IllegalArgumentException("Can only pass true for permissionBased admin");
- }
- this.userId = userId;
- this.isPermissionBased = permissionBased;
- this.isParent = false;
- this.info = null;
}
ActiveAdmin getParentActiveAdmin() {
@@ -397,16 +379,10 @@
}
int getUid() {
- if (isPermissionBased) {
- return -1;
- }
return info.getActivityInfo().applicationInfo.uid;
}
public UserHandle getUserHandle() {
- if (isPermissionBased) {
- return UserHandle.of(userId);
- }
return UserHandle.of(UserHandle.getUserId(info.getActivityInfo().applicationInfo.uid));
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyData.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyData.java
index c937e10..89c8b56 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyData.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyData.java
@@ -21,7 +21,6 @@
import android.annotation.UserIdInt;
import android.app.admin.DeviceAdminInfo;
import android.app.admin.DevicePolicyManager;
-import android.app.admin.flags.Flags;
import android.content.ComponentName;
import android.os.FileUtils;
import android.os.PersistableBundle;
@@ -125,24 +124,6 @@
final ArrayList<ActiveAdmin> mAdminList = new ArrayList<>();
final ArrayList<ComponentName> mRemovingAdmins = new ArrayList<>();
- /**
- * @deprecated Do not use. Policies set by permission holders must go into DevicePolicyEngine.
- */
- @Deprecated
- ActiveAdmin mPermissionBasedAdmin;
-
- // Create or get the permission-based admin. The permission-based admin will not have a
- // DeviceAdminInfo or ComponentName.
- ActiveAdmin createOrGetPermissionBasedAdmin(int userId) {
- if (Flags.activeAdminCleanup()) {
- throw new UnsupportedOperationException("permission based admin no longer supported");
- }
- if (mPermissionBasedAdmin == null) {
- mPermissionBasedAdmin = new ActiveAdmin(userId, /* permissionBased= */ true);
- }
- return mPermissionBasedAdmin;
- }
-
// TODO(b/35385311): Keep track of metadata in TrustedCertificateStore instead.
final ArraySet<String> mAcceptedCaCertificates = new ArraySet<>();
@@ -282,12 +263,6 @@
}
}
- if (!Flags.activeAdminCleanup() && policyData.mPermissionBasedAdmin != null) {
- out.startTag(null, "permission-based-admin");
- policyData.mPermissionBasedAdmin.writeToXml(out);
- out.endTag(null, "permission-based-admin");
- }
-
if (policyData.mPasswordOwner >= 0) {
out.startTag(null, "password-owner");
out.attributeInt(null, "value", policyData.mPasswordOwner);
@@ -495,7 +470,6 @@
policy.mLockTaskPackages.clear();
policy.mAdminList.clear();
policy.mAdminMap.clear();
- policy.mPermissionBasedAdmin = null;
policy.mAffiliationIds.clear();
policy.mOwnerInstalledCaCerts.clear();
policy.mUserControlDisabledPackages = null;
@@ -523,11 +497,6 @@
} catch (RuntimeException e) {
Slogf.w(TAG, e, "Failed loading admin %s", name);
}
- } else if (!Flags.activeAdminCleanup() && "permission-based-admin".equals(tag)) {
-
- ActiveAdmin ap = new ActiveAdmin(policy.mUserId, /* permissionBased= */ true);
- ap.readFromXml(parser, /* overwritePolicies= */ false);
- policy.mPermissionBasedAdmin = ap;
} else if ("delegation".equals(tag)) {
// Parse delegation info.
final String delegatePackage = parser.getAttributeValue(null,
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index e960abd..4c2c858 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -3448,8 +3448,8 @@
EnforcingAdmin enforcingAdmin =
EnforcingAdmin.createEnterpriseEnforcingAdmin(
admin.info.getComponent(),
- admin.getUserHandle().getIdentifier(),
- admin);
+ admin.getUserHandle().getIdentifier()
+ );
mDevicePolicyEngine.setGlobalPolicy(
PolicyDefinition.SECURITY_LOGGING,
enforcingAdmin,
@@ -3692,8 +3692,8 @@
int userId = admin.getUserHandle().getIdentifier();
EnforcingAdmin enforcingAdmin = EnforcingAdmin.createEnterpriseEnforcingAdmin(
admin.info.getComponent(),
- userId,
- admin);
+ userId
+ );
Integer passwordComplexity = mDevicePolicyEngine.getLocalPolicySetByAdmin(
PolicyDefinition.PASSWORD_COMPLEXITY,
@@ -3985,8 +3985,7 @@
final int N = admins.size();
for (int i = 0; i < N; i++) {
ActiveAdmin admin = admins.get(i);
- if (((!Flags.activeAdminCleanup() && admin.isPermissionBased)
- || admin.info.usesPolicy(DeviceAdminInfo.USES_POLICY_EXPIRE_PASSWORD))
+ if ((admin.info.usesPolicy(DeviceAdminInfo.USES_POLICY_EXPIRE_PASSWORD))
&& admin.passwordExpirationTimeout > 0L
&& now >= admin.passwordExpirationDate - EXPIRATION_GRACE_PERIOD_MS
&& admin.passwordExpirationDate > 0L) {
@@ -4167,10 +4166,10 @@
EnforcingAdmin oldAdmin =
EnforcingAdmin.createEnterpriseEnforcingAdmin(
- outgoingReceiver, userHandle, adminToTransfer);
+ outgoingReceiver, userHandle);
EnforcingAdmin newAdmin =
EnforcingAdmin.createEnterpriseEnforcingAdmin(
- incomingReceiver, userHandle, adminToTransfer);
+ incomingReceiver, userHandle);
mDevicePolicyEngine.transferPolicies(oldAdmin, newAdmin);
@@ -4470,7 +4469,7 @@
}
mDevicePolicyEngine.removePoliciesForAdmin(
EnforcingAdmin.createEnterpriseEnforcingAdmin(
- adminReceiver, userHandle, admin));
+ adminReceiver, userHandle));
}
private boolean canSetPasswordQualityOnParent(String packageName, final CallerIdentity caller) {
@@ -4525,10 +4524,8 @@
who, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD, parent);
if (Flags.unmanagedModeMigration()) {
- enforcingAdmin = EnforcingAdmin.createEnterpriseEnforcingAdmin(who,
- userId,
- getActiveAdminForCallerLocked(who,
- DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD));
+ getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD);
+ enforcingAdmin = EnforcingAdmin.createEnterpriseEnforcingAdmin(who, userId);
}
// If setPasswordQuality is called on the parent, ensure that
// the primary admin does not have password complexity state (this is an
@@ -5584,17 +5581,13 @@
Preconditions.checkArgument(!calledOnParent || isProfileOwner(caller));
final ActiveAdmin activeAdmin;
- if (Flags.activeAdminCleanup()) {
- if (admin.hasAuthority(EnforcingAdmin.DPC_AUTHORITY)) {
- synchronized (getLockObject()) {
- activeAdmin = getActiveAdminUncheckedLocked(
- admin.getComponentName(), admin.getUserId());
- }
- } else {
- activeAdmin = null;
+ if (admin.hasAuthority(EnforcingAdmin.DPC_AUTHORITY)) {
+ synchronized (getLockObject()) {
+ activeAdmin = getActiveAdminUncheckedLocked(
+ admin.getComponentName(), admin.getUserId());
}
} else {
- activeAdmin = admin.getActiveAdmin();
+ activeAdmin = null;
}
// We require the caller to explicitly clear any password quality requirements set
@@ -6331,12 +6324,7 @@
caller.getPackageName(),
getAffectedUser(parent)
);
- if (Flags.activeAdminCleanup()) {
- adminComponent = enforcingAdmin.getComponentName();
- } else {
- ActiveAdmin admin = enforcingAdmin.getActiveAdmin();
- adminComponent = admin == null ? null : admin.info.getComponent();
- }
+ adminComponent = enforcingAdmin.getComponentName();
} else {
ActiveAdmin admin = getActiveAdminOrCheckPermissionForCallerLocked(
null,
@@ -7824,19 +7812,9 @@
calledByProfileOwnerOnOrgOwnedDevice, calledOnParentInstance);
}
- int userId;
- ActiveAdmin admin = null;
- if (Flags.activeAdminCleanup()) {
- userId = enforcingAdmin.getUserId();
- Slogf.i(LOG_TAG, "wipeDataWithReason(%s): admin=%s, user=%d", wipeReasonForUser,
- enforcingAdmin, userId);
- } else {
- admin = enforcingAdmin.getActiveAdmin();
- userId = admin != null ? admin.getUserHandle().getIdentifier()
- : caller.getUserId();
- Slogf.i(LOG_TAG, "wipeDataWithReason(%s): admin=%s, user=%d", wipeReasonForUser, admin,
- userId);
- }
+ int userId = enforcingAdmin.getUserId();
+ Slogf.i(LOG_TAG, "wipeDataWithReason(%s): admin=%s, user=%d", wipeReasonForUser,
+ enforcingAdmin, userId);
if (calledByProfileOwnerOnOrgOwnedDevice) {
// When wipeData is called on the parent instance, it implies wiping the entire device.
@@ -7858,38 +7836,14 @@
final String adminName;
final ComponentName adminComp;
- if (Flags.activeAdminCleanup()) {
- adminComp = enforcingAdmin.getComponentName();
- adminName = adminComp != null
- ? adminComp.flattenToShortString()
- : enforcingAdmin.getPackageName();
- event.setAdmin(enforcingAdmin.getPackageName());
- // Not including any HSUM handling here because the "else" branch in the "flag off"
- // case below is unreachable under normal circumstances and for permission-based
- // callers admin won't be null.
- } else {
- if (admin != null) {
- if (admin.isPermissionBased) {
- adminComp = null;
- adminName = caller.getPackageName();
- event.setAdmin(adminName);
- } else {
- adminComp = admin.info.getComponent();
- adminName = adminComp.flattenToShortString();
- event.setAdmin(adminComp);
- }
- } else {
- adminComp = null;
- adminName = mInjector.getPackageManager().getPackagesForUid(caller.getUid())[0];
- Slogf.i(LOG_TAG, "Logging wipeData() event admin as " + adminName);
- event.setAdmin(adminName);
- if (mInjector.userManagerIsHeadlessSystemUserMode()) {
- // On headless system user mode, the call is meant to factory reset the whole
- // device, otherwise the caller could simply remove the current user.
- userId = UserHandle.USER_SYSTEM;
- }
- }
- }
+ adminComp = enforcingAdmin.getComponentName();
+ adminName = adminComp != null
+ ? adminComp.flattenToShortString()
+ : enforcingAdmin.getPackageName();
+ event.setAdmin(enforcingAdmin.getPackageName());
+ // Not including any HSUM handling here because the "else" branch in the "flag off"
+ // case below is unreachable under normal circumstances and for permission-based
+ // callers admin won't be null.
event.write();
String internalReason = String.format(
@@ -8375,8 +8329,7 @@
List<ActiveAdmin> admins = getActiveAdminsForLockscreenPoliciesLocked(userHandle);
for (int i = 0; i < admins.size(); i++) {
ActiveAdmin admin = admins.get(i);
- if ((!Flags.activeAdminCleanup() && admin.isPermissionBased)
- || admin.info.usesPolicy(DeviceAdminInfo.USES_POLICY_EXPIRE_PASSWORD)) {
+ if (admin.info.usesPolicy(DeviceAdminInfo.USES_POLICY_EXPIRE_PASSWORD)) {
affectedUserIds.add(admin.getUserHandle().getIdentifier());
long timeout = admin.passwordExpirationTimeout;
admin.passwordExpirationDate =
@@ -8470,9 +8423,6 @@
*/
private int getUserIdToWipeForFailedPasswords(ActiveAdmin admin) {
final int userId = admin.getUserHandle().getIdentifier();
- if (!Flags.activeAdminCleanup() && admin.isPermissionBased) {
- return userId;
- }
final ComponentName component = admin.info.getComponent();
return isProfileOwnerOfOrganizationOwnedDevice(component, userId)
? getProfileParentId(userId) : userId;
@@ -10282,8 +10232,7 @@
setGlobalSettingDeviceOwnerType(DEVICE_OWNER_TYPE_DEFAULT);
mDevicePolicyEngine.removePoliciesForAdmin(
- EnforcingAdmin.createEnterpriseEnforcingAdmin(
- admin.info.getComponent(), userId, admin));
+ EnforcingAdmin.createEnterpriseEnforcingAdmin(admin.info.getComponent(), userId));
}
private void clearApplicationRestrictions(int userId) {
@@ -10433,8 +10382,7 @@
setNetworkLoggingActiveInternal(false);
mDevicePolicyEngine.removePoliciesForAdmin(
- EnforcingAdmin.createEnterpriseEnforcingAdmin(
- admin.info.getComponent(), userId, admin));
+ EnforcingAdmin.createEnterpriseEnforcingAdmin(admin.info.getComponent(), userId));
}
@Override
@@ -16449,8 +16397,7 @@
if (admin.mPasswordPolicy.quality < minPasswordQuality) {
return false;
}
- return (!Flags.activeAdminCleanup() && admin.isPermissionBased)
- || admin.info.usesPolicy(DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD);
+ return admin.info.usesPolicy(DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD);
}
@Override
@@ -20918,8 +20865,7 @@
if (profileOwner != null) {
EnforcingAdmin admin = EnforcingAdmin.createEnterpriseEnforcingAdmin(
profileOwner.info.getComponent(),
- profileUserId,
- profileOwner);
+ profileUserId);
mDevicePolicyEngine.setLocalPolicy(
PolicyDefinition.PERSONAL_APPS_SUSPENDED,
admin,
@@ -23517,27 +23463,6 @@
*
* @param callerPackageName The package name of the calling application.
* @param adminPolicy The admin policy that should grant holders permission.
- * @param permission The name of the permission being checked.
- * @param targetUserId The userId of the user which the caller needs permission to act on.
- * @throws SecurityException if the caller has not been granted the given permission,
- * the associated cross-user permission if the caller's user is different to the target user.
- */
- private void enforcePermission(String permission, int adminPolicy,
- String callerPackageName, int targetUserId) throws SecurityException {
- if (hasAdminPolicy(adminPolicy, callerPackageName)) {
- return;
- }
- enforcePermission(permission, callerPackageName, targetUserId);
- }
-
- /**
- * Checks if the calling process has been granted permission to apply a device policy on a
- * specific user.
- * The given permission will be checked along with its associated cross-user permission if it
- * exists and the target user is different to the calling user.
- *
- * @param callerPackageName The package name of the calling application.
- * @param adminPolicy The admin policy that should grant holders permission.
* @param permissions The names of the permissions being checked.
* @param targetUserId The userId of the user which the caller needs permission to act on.
* @throws SecurityException if the caller has not been granted the given permission,
@@ -23670,24 +23595,21 @@
ComponentName component;
synchronized (getLockObject()) {
if (who != null) {
- admin = getActiveAdminUncheckedLocked(who, userId);
component = who;
} else {
admin = getDeviceOrProfileOwnerAdminLocked(userId);
component = admin.info.getComponent();
}
}
- return EnforcingAdmin.createEnterpriseEnforcingAdmin(component, userId, admin);
+ return EnforcingAdmin.createEnterpriseEnforcingAdmin(component, userId);
}
- // Check for non-DPC active admins.
+ // Check for DA active admins.
admin = getActiveAdminForCaller(who, caller);
if (admin != null) {
- return EnforcingAdmin.createDeviceAdminEnforcingAdmin(admin.info.getComponent(), userId,
- admin);
+ return EnforcingAdmin.createDeviceAdminEnforcingAdmin(
+ admin.info.getComponent(), userId);
}
- admin = Flags.activeAdminCleanup()
- ? null : getUserData(userId).createOrGetPermissionBasedAdmin(userId);
- return EnforcingAdmin.createEnforcingAdmin(caller.getPackageName(), userId, admin);
+ return EnforcingAdmin.createEnforcingAdmin(caller.getPackageName(), userId);
}
private EnforcingAdmin getEnforcingAdminForPackage(@Nullable ComponentName who,
@@ -23699,19 +23621,17 @@
admin = getActiveAdminUncheckedLocked(who, userId);
}
if (admin != null) {
- return EnforcingAdmin.createEnterpriseEnforcingAdmin(who, userId, admin);
+ return EnforcingAdmin.createEnterpriseEnforcingAdmin(who, userId);
}
} else {
- // Check for non-DPC active admins.
+ // Check for DA active admins.
admin = getActiveAdminUncheckedLocked(who, userId);
if (admin != null) {
- return EnforcingAdmin.createDeviceAdminEnforcingAdmin(who, userId, admin);
+ return EnforcingAdmin.createDeviceAdminEnforcingAdmin(who, userId);
}
}
}
- admin = Flags.activeAdminCleanup()
- ? null : getUserData(userId).createOrGetPermissionBasedAdmin(userId);
- return EnforcingAdmin.createEnforcingAdmin(packageName, userId, admin);
+ return EnforcingAdmin.createEnforcingAdmin(packageName, userId);
}
private int getAffectedUser(boolean calledOnParent) {
@@ -24427,9 +24347,7 @@
&& admin.getParentActiveAdmin().disableScreenCapture))) {
EnforcingAdmin enforcingAdmin = EnforcingAdmin.createEnterpriseEnforcingAdmin(
- admin.info.getComponent(),
- admin.getUserHandle().getIdentifier(),
- admin);
+ admin.info.getComponent(), admin.getUserHandle().getIdentifier());
mDevicePolicyEngine.setGlobalPolicy(
PolicyDefinition.SCREEN_CAPTURE_DISABLED,
enforcingAdmin,
@@ -24442,8 +24360,7 @@
if (profileOwner != null && profileOwner.disableScreenCapture) {
EnforcingAdmin enforcingAdmin = EnforcingAdmin.createEnterpriseEnforcingAdmin(
profileOwner.info.getComponent(),
- profileOwner.getUserHandle().getIdentifier(),
- profileOwner);
+ profileOwner.getUserHandle().getIdentifier());
mDevicePolicyEngine.setLocalPolicy(
PolicyDefinition.SCREEN_CAPTURE_DISABLED,
enforcingAdmin,
@@ -24485,10 +24402,7 @@
private void setLockTaskPolicyInPolicyEngine(
ActiveAdmin admin, int userId, List<String> packages, int features) {
EnforcingAdmin enforcingAdmin =
- EnforcingAdmin.createEnterpriseEnforcingAdmin(
- admin.info.getComponent(),
- userId,
- admin);
+ EnforcingAdmin.createEnterpriseEnforcingAdmin(admin.info.getComponent(), userId);
mDevicePolicyEngine.setLocalPolicy(
PolicyDefinition.LOCK_TASK,
enforcingAdmin,
@@ -24503,9 +24417,7 @@
ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(userInfo.id);
if (admin != null) {
EnforcingAdmin enforcingAdmin = EnforcingAdmin.createEnterpriseEnforcingAdmin(
- admin.info.getComponent(),
- admin.getUserHandle().getIdentifier(),
- admin);
+ admin.info.getComponent(), admin.getUserHandle().getIdentifier());
if (admin.permittedInputMethods != null) {
mDevicePolicyEngine.setLocalPolicy(
PolicyDefinition.PERMITTED_INPUT_METHODS,
@@ -24536,9 +24448,7 @@
ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(userInfo.id);
if (admin != null) {
EnforcingAdmin enforcingAdmin = EnforcingAdmin.createEnterpriseEnforcingAdmin(
- admin.info.getComponent(),
- admin.getUserHandle().getIdentifier(),
- admin);
+ admin.info.getComponent(), admin.getUserHandle().getIdentifier());
for (String accountType : admin.accountTypesWithManagementDisabled) {
mDevicePolicyEngine.setLocalPolicy(
PolicyDefinition.ACCOUNT_MANAGEMENT_DISABLED(accountType),
@@ -24569,9 +24479,7 @@
ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(userInfo.id);
if (admin != null && admin.protectedPackages != null) {
EnforcingAdmin enforcingAdmin = EnforcingAdmin.createEnterpriseEnforcingAdmin(
- admin.info.getComponent(),
- admin.getUserHandle().getIdentifier(),
- admin);
+ admin.info.getComponent(), admin.getUserHandle().getIdentifier());
if (isDeviceOwner(admin)) {
mDevicePolicyEngine.setGlobalPolicy(
PolicyDefinition.USER_CONTROLLED_DISABLED_PACKAGES,
@@ -24599,10 +24507,8 @@
if (admin == null) continue;
ComponentName adminComponent = admin.info.getComponent();
int userId = userInfo.id;
- EnforcingAdmin enforcingAdmin = EnforcingAdmin.createEnterpriseEnforcingAdmin(
- adminComponent,
- userId,
- admin);
+ EnforcingAdmin enforcingAdmin =
+ EnforcingAdmin.createEnterpriseEnforcingAdmin(adminComponent, userId);
int ownerType;
if (isDeviceOwner(admin)) {
ownerType = OWNER_TYPE_DEVICE_OWNER;
@@ -24635,9 +24541,7 @@
ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(userInfo.id);
if (admin == null) continue;
EnforcingAdmin enforcingAdmin = EnforcingAdmin.createEnterpriseEnforcingAdmin(
- admin.info.getComponent(),
- userInfo.id,
- admin);
+ admin.info.getComponent(), userInfo.id);
runner.accept(admin, enforcingAdmin);
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java b/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java
index 5a0b079..aca3315 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java
@@ -23,7 +23,6 @@
import android.app.admin.DpcAuthority;
import android.app.admin.RoleAuthority;
import android.app.admin.UnknownAuthority;
-import android.app.admin.flags.Flags;
import android.content.ComponentName;
import android.os.UserHandle;
@@ -80,36 +79,24 @@
private final int mUserId;
private final boolean mIsRoleAuthority;
private final boolean mIsSystemAuthority;
- private final ActiveAdmin mActiveAdmin;
- static EnforcingAdmin createEnforcingAdmin(@NonNull String packageName, int userId,
- ActiveAdmin admin) {
+ static EnforcingAdmin createEnforcingAdmin(@NonNull String packageName, int userId) {
Objects.requireNonNull(packageName);
- return new EnforcingAdmin(packageName, userId, admin);
+ return new EnforcingAdmin(packageName, userId);
}
static EnforcingAdmin createEnterpriseEnforcingAdmin(
@NonNull ComponentName componentName, int userId) {
Objects.requireNonNull(componentName);
return new EnforcingAdmin(
- componentName.getPackageName(), componentName, Set.of(DPC_AUTHORITY), userId,
- /* activeAdmin=*/ null);
+ componentName.getPackageName(), componentName, Set.of(DPC_AUTHORITY), userId);
}
- static EnforcingAdmin createEnterpriseEnforcingAdmin(
- @NonNull ComponentName componentName, int userId, ActiveAdmin activeAdmin) {
- Objects.requireNonNull(componentName);
- return new EnforcingAdmin(
- componentName.getPackageName(), componentName, Set.of(DPC_AUTHORITY), userId,
- activeAdmin);
- }
-
- static EnforcingAdmin createDeviceAdminEnforcingAdmin(ComponentName componentName, int userId,
- ActiveAdmin activeAdmin) {
+ static EnforcingAdmin createDeviceAdminEnforcingAdmin(ComponentName componentName, int userId) {
Objects.requireNonNull(componentName);
return new EnforcingAdmin(
componentName.getPackageName(), componentName, Set.of(DEVICE_ADMIN_AUTHORITY),
- userId, activeAdmin);
+ userId);
}
static EnforcingAdmin createSystemEnforcingAdmin(@NonNull String systemEntity) {
@@ -124,24 +111,20 @@
if (DpcAuthority.DPC_AUTHORITY.equals(authority)) {
return new EnforcingAdmin(
admin.getPackageName(), admin.getComponentName(),
- Set.of(DPC_AUTHORITY), admin.getUserHandle().getIdentifier(),
- /* activeAdmin = */ null);
+ Set.of(DPC_AUTHORITY), admin.getUserHandle().getIdentifier());
} else if (DeviceAdminAuthority.DEVICE_ADMIN_AUTHORITY.equals(authority)) {
return new EnforcingAdmin(
admin.getPackageName(), admin.getComponentName(),
- Set.of(DEVICE_ADMIN_AUTHORITY), admin.getUserHandle().getIdentifier(),
- /* activeAdmin = */ null);
+ Set.of(DEVICE_ADMIN_AUTHORITY), admin.getUserHandle().getIdentifier());
} else if (authority instanceof RoleAuthority roleAuthority) {
return new EnforcingAdmin(
admin.getPackageName(), admin.getComponentName(),
Set.of(DEVICE_ADMIN_AUTHORITY), admin.getUserHandle().getIdentifier(),
- /* activeAdmin = */ null,
/* isRoleAuthority = */ true);
}
// TODO(b/324899199): Consider supporting android.app.admin.SystemAuthority.
return new EnforcingAdmin(admin.getPackageName(), admin.getComponentName(),
- Set.of(), admin.getUserHandle().getIdentifier(),
- /* activeAdmin = */ null);
+ Set.of(), admin.getUserHandle().getIdentifier());
}
static String getRoleAuthorityOf(String roleName) {
@@ -167,7 +150,7 @@
private EnforcingAdmin(
String packageName, @Nullable ComponentName componentName, Set<String> authorities,
- int userId, @Nullable ActiveAdmin activeAdmin) {
+ int userId) {
Objects.requireNonNull(packageName);
Objects.requireNonNull(authorities);
@@ -179,10 +162,9 @@
mComponentName = componentName;
mAuthorities = new HashSet<>(authorities);
mUserId = userId;
- mActiveAdmin = activeAdmin;
}
- private EnforcingAdmin(String packageName, int userId, ActiveAdmin activeAdmin) {
+ private EnforcingAdmin(String packageName, int userId) {
Objects.requireNonNull(packageName);
// Only role authorities use this constructor.
@@ -194,7 +176,6 @@
mComponentName = null;
// authorities will be loaded when needed
mAuthorities = null;
- mActiveAdmin = activeAdmin;
}
/** Constructor for System authorities. */
@@ -210,12 +191,11 @@
mUserId = UserHandle.USER_SYSTEM;
mComponentName = null;
mAuthorities = getSystemAuthority(systemEntity);
- mActiveAdmin = null;
}
private EnforcingAdmin(
String packageName, @Nullable ComponentName componentName, Set<String> authorities,
- int userId, @Nullable ActiveAdmin activeAdmin, boolean isRoleAuthority) {
+ int userId, boolean isRoleAuthority) {
Objects.requireNonNull(packageName);
Objects.requireNonNull(authorities);
@@ -226,7 +206,6 @@
mComponentName = componentName;
mAuthorities = new HashSet<>(authorities);
mUserId = userId;
- mActiveAdmin = activeAdmin;
}
private static Set<String> getRoleAuthoritiesOrDefault(String packageName, int userId) {
@@ -295,14 +274,6 @@
}
@Nullable
- public ActiveAdmin getActiveAdmin() {
- if (Flags.activeAdminCleanup()) {
- throw new UnsupportedOperationException("getActiveAdmin() no longer supported");
- }
- return mActiveAdmin;
- }
-
- @Nullable
ComponentName getComponentName() {
return mComponentName;
}
@@ -419,7 +390,7 @@
return null;
}
// TODO(b/281697976): load active admin
- return new EnforcingAdmin(packageName, userId, null);
+ return new EnforcingAdmin(packageName, userId);
} else if (isSystemAuthority) {
if (systemEntity == null) {
Slogf.wtf(TAG, "Error parsing EnforcingAdmin with SystemAuthority, "
@@ -439,7 +410,7 @@
? null : new ComponentName(packageName, className);
Set<String> authorities = Set.of(authoritiesStr.split(ATTR_AUTHORITIES_SEPARATOR));
// TODO(b/281697976): load active admin
- return new EnforcingAdmin(packageName, componentName, authorities, userId, null);
+ return new EnforcingAdmin(packageName, componentName, authorities, userId);
}
}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index fadab1f..25e9f8a 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -1527,6 +1527,8 @@
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 @@
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/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
index 02e5470..e8b28ac 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -23,6 +23,7 @@
import static android.Manifest.permission.CONTROL_DISPLAY_BRIGHTNESS;
import static android.Manifest.permission.MANAGE_DISPLAYS;
import static android.Manifest.permission.MODIFY_USER_PREFERRED_DISPLAY_MODE;
+import static android.app.ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND;
import static android.hardware.display.DisplayManager.SWITCHING_TYPE_NONE;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR;
@@ -2123,16 +2124,14 @@
}
}
- /**
- * Tests that there is a display change notification if the frame rate override
- * list is updated.
- */
@Test
- public void testShouldNotifyChangeWhenDisplayInfoFrameRateOverrideChanged() {
+ public void test_displayChangedNotified_displayInfoFramerateOverridden() {
DisplayManagerService displayManager =
new DisplayManagerService(mContext, mShortMockedInjector);
DisplayManagerService.BinderService displayManagerBinderService =
displayManager.new BinderService();
+ when(mMockFlags.isFramerateOverrideTriggersRrCallbacksEnabled()).thenReturn(false);
+
registerDefaultDisplays(displayManager);
displayManager.onBootPhase(SystemService.PHASE_WAIT_FOR_DEFAULT_DISPLAY);
@@ -2148,6 +2147,35 @@
waitForIdleHandler(displayManager.getDisplayHandler());
assertThat(callback.receivedEvents()).contains(EVENT_DISPLAY_BASIC_CHANGED);
callback.clear();
+ }
+
+ /**
+ * Tests that there is a display change notification if the frame rate override
+ * list is updated.
+ */
+ @Test
+ public void test_refreshRateChangedNotified_displayInfoFramerateOverridden() {
+ when(mMockFlags.isFramerateOverrideTriggersRrCallbacksEnabled()).thenReturn(true);
+
+ DisplayManagerService displayManager =
+ new DisplayManagerService(mContext, mBasicInjector);
+ DisplayManagerService.BinderService displayManagerBinderService =
+ displayManager.new BinderService();
+ registerDefaultDisplays(displayManager);
+ displayManager.onBootPhase(SystemService.PHASE_WAIT_FOR_DEFAULT_DISPLAY);
+
+ FakeDisplayDevice displayDevice = createFakeDisplayDevice(displayManager, new float[]{60f});
+ FakeDisplayManagerCallback callback = registerDisplayListenerCallback(displayManager,
+ displayManagerBinderService, displayDevice);
+
+ int myUid = Process.myUid();
+ updateFrameRateOverride(displayManager, displayDevice,
+ new DisplayEventReceiver.FrameRateOverride[]{
+ new DisplayEventReceiver.FrameRateOverride(myUid, 30f),
+ });
+ waitForIdleHandler(displayManager.getDisplayHandler());
+ assertThat(callback.receivedEvents()).contains(EVENT_DISPLAY_REFRESH_RATE_CHANGED);
+ callback.clear();
updateFrameRateOverride(displayManager, displayDevice,
new DisplayEventReceiver.FrameRateOverride[]{
@@ -2155,7 +2183,7 @@
new DisplayEventReceiver.FrameRateOverride(1234, 30f),
});
waitForIdleHandler(displayManager.getDisplayHandler());
- assertThat(callback.receivedEvents()).doesNotContain(EVENT_DISPLAY_BASIC_CHANGED);
+ assertThat(callback.receivedEvents()).doesNotContain(EVENT_DISPLAY_REFRESH_RATE_CHANGED);
updateFrameRateOverride(displayManager, displayDevice,
new DisplayEventReceiver.FrameRateOverride[]{
@@ -2164,7 +2192,7 @@
new DisplayEventReceiver.FrameRateOverride(5678, 30f),
});
waitForIdleHandler(displayManager.getDisplayHandler());
- assertThat(callback.receivedEvents()).contains(EVENT_DISPLAY_BASIC_CHANGED);
+ assertThat(callback.receivedEvents()).contains(EVENT_DISPLAY_REFRESH_RATE_CHANGED);
callback.clear();
updateFrameRateOverride(displayManager, displayDevice,
@@ -2173,7 +2201,7 @@
new DisplayEventReceiver.FrameRateOverride(5678, 30f),
});
waitForIdleHandler(displayManager.getDisplayHandler());
- assertThat(callback.receivedEvents()).contains(EVENT_DISPLAY_BASIC_CHANGED);
+ assertThat(callback.receivedEvents()).contains(EVENT_DISPLAY_REFRESH_RATE_CHANGED);
callback.clear();
updateFrameRateOverride(displayManager, displayDevice,
@@ -2181,7 +2209,7 @@
new DisplayEventReceiver.FrameRateOverride(5678, 30f),
});
waitForIdleHandler(displayManager.getDisplayHandler());
- assertThat(callback.receivedEvents()).doesNotContain(EVENT_DISPLAY_BASIC_CHANGED);
+ assertThat(callback.receivedEvents()).doesNotContain(EVENT_DISPLAY_REFRESH_RATE_CHANGED);
}
/**
@@ -2317,6 +2345,29 @@
callback.clear();
}
+ @Test
+ public void test_doesNotNotifyRefreshRateChanged_whenAppInBackground() {
+ when(mMockFlags.isRefreshRateEventForForegroundAppsEnabled()).thenReturn(true);
+ DisplayManagerService displayManager =
+ new DisplayManagerService(mContext, mBasicInjector);
+ DisplayManagerService.BinderService displayManagerBinderService =
+ displayManager.new BinderService();
+ displayManager.windowManagerAndInputReady();
+ registerDefaultDisplays(displayManager);
+ displayManager.onBootPhase(SystemService.PHASE_WAIT_FOR_DEFAULT_DISPLAY);
+
+ FakeDisplayDevice displayDevice = createFakeDisplayDevice(displayManager, new float[]{60f});
+ FakeDisplayManagerCallback callback = registerDisplayListenerCallback(displayManager,
+ displayManagerBinderService, displayDevice);
+
+ when(mMockActivityManagerInternal.getUidProcessState(Process.myUid()))
+ .thenReturn(PROCESS_STATE_TRANSIENT_BACKGROUND);
+ updateRenderFrameRate(displayManager, displayDevice, 30f);
+ waitForIdleHandler(displayManager.getDisplayHandler());
+ assertEquals(0, callback.receivedEvents().size());
+ callback.clear();
+ }
+
/**
* Tests that the DisplayInfo is updated correctly with a render frame rate
*/
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 f5bed99..5393e20 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 @@
* 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 @@
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/mockingservicestests/src/com/android/server/power/ScreenUndimDetectorTest.java b/services/tests/mockingservicestests/src/com/android/server/power/ScreenUndimDetectorTest.java
index e8e1dac..8ce05e2 100644
--- a/services/tests/mockingservicestests/src/com/android/server/power/ScreenUndimDetectorTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/power/ScreenUndimDetectorTest.java
@@ -40,6 +40,7 @@
import org.junit.After;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
@@ -329,6 +330,7 @@
}
}
+ @Ignore("b/387389929")
@Test
public void recordScreenPolicy_otherTransitions_doesNotReset() {
DeviceConfig.setProperty(NAMESPACE_ATTENTION_MANAGER_SERVICE,
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 d925624..6b138b9 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_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.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.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 @@
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 @@
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 cc5be7e..1522954c 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.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 @@
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 @@
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 464fee2..fb31cfe 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.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.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.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 @@
| 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 @@
}
@Test
+ @RequiresFlagsEnabled(FLAG_ENABLE_TALKBACK_AND_MAGNIFIER_KEY_GESTURES)
public void testEventHandler_shouldIncreaseAndHaveCorrectOrderAfterOnDisplayAdded() {
prepareLooper();
@@ -191,9 +207,9 @@
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 @@
}
@Test
+ @RequiresFlagsEnabled(FLAG_ENABLE_TALKBACK_AND_MAGNIFIER_KEY_GESTURES)
public void testEventHandler_shouldHaveCorrectOrderForEventStreamTransformation() {
prepareLooper();
@@ -248,10 +265,36 @@
}
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 @@
assertNotNull(handler);
assertEquals(WindowMagnificationGestureHandler.class, handler.getClass());
assertEquals(nextEventStream.getClass(), handler.getNext().getClass());
-
}
@Test public void
@@ -412,6 +454,32 @@
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 @@
}
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/AutoclickControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AutoclickControllerTest.java
new file mode 100644
index 0000000..acce813
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AutoclickControllerTest.java
@@ -0,0 +1,242 @@
+/*
+ * 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.accessibility;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static com.android.server.testutils.MockitoUtilsKt.eq;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.verify;
+import static org.testng.AssertJUnit.assertEquals;
+import static org.testng.AssertJUnit.assertNotNull;
+import static org.testng.AssertJUnit.assertNull;
+
+import android.content.Context;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
+import android.provider.Settings;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableContext;
+import android.testing.TestableLooper;
+import android.view.InputDevice;
+import android.view.MotionEvent;
+import android.view.WindowManager;
+import android.view.accessibility.AccessibilityManager;
+
+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;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+/** Test cases for {@link AutoclickController}. */
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+public class AutoclickControllerTest {
+
+ @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+ @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+
+ @Rule
+ public TestableContext mTestableContext =
+ new TestableContext(getInstrumentation().getContext());
+
+ private TestableLooper mTestableLooper;
+ @Mock private AccessibilityTraceManager mMockTrace;
+ @Mock private WindowManager mMockWindowManager;
+ private AutoclickController mController;
+
+ @Before
+ public void setUp() {
+ mTestableLooper = TestableLooper.get(this);
+ mTestableContext.addMockSystemService(Context.WINDOW_SERVICE, mMockWindowManager);
+ mController =
+ new AutoclickController(mTestableContext, mTestableContext.getUserId(), mMockTrace);
+ }
+
+ @After
+ public void tearDown() {
+ mTestableLooper.processAllMessages();
+ }
+
+ @Test
+ public void onMotionEvent_lazyInitClickScheduler() {
+ assertNull(mController.mClickScheduler);
+
+ injectFakeMouseActionDownEvent();
+
+ assertNotNull(mController.mClickScheduler);
+ }
+
+ @Test
+ public void onMotionEvent_nonMouseSource_notInitClickScheduler() {
+ assertNull(mController.mClickScheduler);
+
+ injectFakeNonMouseActionDownEvent();
+
+ assertNull(mController.mClickScheduler);
+ }
+
+ @Test
+ public void onMotionEvent_lazyInitAutoclickSettingsObserver() {
+ assertNull(mController.mAutoclickSettingsObserver);
+
+ injectFakeMouseActionDownEvent();
+
+ assertNotNull(mController.mAutoclickSettingsObserver);
+ }
+
+ @Test
+ @EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR)
+ public void onMotionEvent_flagOn_lazyInitAutoclickIndicatorScheduler() {
+ assertNull(mController.mAutoclickIndicatorScheduler);
+
+ injectFakeMouseActionDownEvent();
+
+ assertNotNull(mController.mAutoclickIndicatorScheduler);
+ }
+
+ @Test
+ @DisableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR)
+ public void onMotionEvent_flagOff_notInitAutoclickIndicatorScheduler() {
+ assertNull(mController.mAutoclickIndicatorScheduler);
+
+ injectFakeMouseActionDownEvent();
+
+ assertNull(mController.mAutoclickIndicatorScheduler);
+ }
+
+ @Test
+ @EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR)
+ public void onMotionEvent_flagOn_lazyInitAutoclickIndicatorView() {
+ assertNull(mController.mAutoclickIndicatorView);
+
+ injectFakeMouseActionDownEvent();
+
+ assertNotNull(mController.mAutoclickIndicatorView);
+ }
+
+ @Test
+ @DisableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR)
+ public void onMotionEvent_flagOff_notInitAutoclickIndicatorView() {
+ assertNull(mController.mAutoclickIndicatorView);
+
+ injectFakeMouseActionDownEvent();
+
+ assertNull(mController.mAutoclickIndicatorView);
+ }
+
+ @Test
+ @EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR)
+ public void onMotionEvent_flagOn_addAutoclickIndicatorViewToWindowManager() {
+ injectFakeMouseActionDownEvent();
+
+ verify(mMockWindowManager).addView(eq(mController.mAutoclickIndicatorView), any());
+ }
+
+ @Test
+ @EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR)
+ public void onDestroy_flagOn_removeAutoclickIndicatorViewToWindowManager() {
+ injectFakeMouseActionDownEvent();
+
+ mController.onDestroy();
+
+ verify(mMockWindowManager).removeView(mController.mAutoclickIndicatorView);
+ }
+
+ @Test
+ public void onMotionEvent_initClickSchedulerDelayFromSetting() {
+ injectFakeMouseActionDownEvent();
+
+ int delay =
+ Settings.Secure.getIntForUser(
+ mTestableContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_AUTOCLICK_DELAY,
+ AccessibilityManager.AUTOCLICK_DELAY_DEFAULT,
+ mTestableContext.getUserId());
+ assertEquals(delay, mController.mClickScheduler.getDelayForTesting());
+ }
+
+ @Test
+ @EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR)
+ public void onMotionEvent_flagOn_initCursorAreaSizeFromSetting() {
+ injectFakeMouseActionDownEvent();
+
+ int size =
+ Settings.Secure.getIntForUser(
+ mTestableContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_AUTOCLICK_CURSOR_AREA_SIZE,
+ AccessibilityManager.AUTOCLICK_CURSOR_AREA_SIZE_DEFAULT,
+ mTestableContext.getUserId());
+ assertEquals(size, mController.mAutoclickIndicatorView.getRadiusForTesting());
+ }
+
+ @Test
+ public void onDestroy_clearClickScheduler() {
+ injectFakeMouseActionDownEvent();
+
+ mController.onDestroy();
+
+ assertNull(mController.mClickScheduler);
+ }
+
+ @Test
+ public void onDestroy_clearAutoclickSettingsObserver() {
+ injectFakeMouseActionDownEvent();
+
+ mController.onDestroy();
+
+ assertNull(mController.mAutoclickSettingsObserver);
+ }
+
+ @Test
+ @EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR)
+ public void onDestroy_flagOn_clearAutoclickIndicatorScheduler() {
+ injectFakeMouseActionDownEvent();
+
+ mController.onDestroy();
+
+ assertNull(mController.mAutoclickIndicatorScheduler);
+ }
+
+ private void injectFakeMouseActionDownEvent() {
+ MotionEvent event = getFakeMotionDownEvent();
+ event.setSource(InputDevice.SOURCE_MOUSE);
+ mController.onMotionEvent(event, event, /* policyFlags= */ 0);
+ }
+
+ private void injectFakeNonMouseActionDownEvent() {
+ MotionEvent event = getFakeMotionDownEvent();
+ event.setSource(InputDevice.SOURCE_KEYBOARD);
+ mController.onMotionEvent(event, event, /* policyFlags= */ 0);
+ }
+
+ private MotionEvent getFakeMotionDownEvent() {
+ return MotionEvent.obtain(
+ /* downTime= */ 0,
+ /* eventTime= */ 0,
+ /* action= */ MotionEvent.ACTION_DOWN,
+ /* x= */ 0,
+ /* y= */ 0,
+ /* metaState= */ 0);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/ProxyManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/ProxyManagerTest.java
index f371823..1a97445 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/ProxyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/ProxyManagerTest.java
@@ -425,7 +425,6 @@
@Test
public void testRegisterProxy_registersVirtualDeviceListener() throws RemoteException {
- mSetFlagsRule.enableFlags(android.companion.virtual.flags.Flags.FLAG_VDM_PUBLIC_APIS);
registerProxy(DISPLAY_ID);
verify(mMockIVirtualDeviceManager, times(1)).registerVirtualDeviceListener(any());
@@ -434,7 +433,6 @@
@Test
public void testRegisterMultipleProxies_registersOneVirtualDeviceListener()
throws RemoteException {
- mSetFlagsRule.enableFlags(android.companion.virtual.flags.Flags.FLAG_VDM_PUBLIC_APIS);
registerProxy(DISPLAY_ID);
registerProxy(DISPLAY_2_ID);
@@ -443,7 +441,6 @@
@Test
public void testUnregisterProxy_unregistersVirtualDeviceListener() throws RemoteException {
- mSetFlagsRule.enableFlags(android.companion.virtual.flags.Flags.FLAG_VDM_PUBLIC_APIS);
registerProxy(DISPLAY_ID);
mProxyManager.unregisterProxy(DISPLAY_ID);
@@ -454,7 +451,6 @@
@Test
public void testUnregisterProxy_onlyUnregistersVirtualDeviceListenerOnLastProxyRemoval()
throws RemoteException {
- mSetFlagsRule.enableFlags(android.companion.virtual.flags.Flags.FLAG_VDM_PUBLIC_APIS);
registerProxy(DISPLAY_ID);
registerProxy(DISPLAY_2_ID);
@@ -468,7 +464,6 @@
@Test
public void testRegisteredProxy_virtualDeviceClosed_proxyClosed()
throws RemoteException {
- mSetFlagsRule.enableFlags(android.companion.virtual.flags.Flags.FLAG_VDM_PUBLIC_APIS);
registerProxy(DISPLAY_ID);
assertThat(mProxyManager.isProxyedDeviceId(DEVICE_ID)).isTrue();
@@ -490,7 +485,6 @@
@Test
public void testRegisteredProxy_unrelatedVirtualDeviceClosed_proxyNotClosed()
throws RemoteException {
- mSetFlagsRule.enableFlags(android.companion.virtual.flags.Flags.FLAG_VDM_PUBLIC_APIS);
registerProxy(DISPLAY_ID);
assertThat(mProxyManager.isProxyedDeviceId(DEVICE_ID)).isTrue();
@@ -507,17 +501,6 @@
assertThat(mProxyManager.isProxyedDisplay(DISPLAY_ID)).isTrue();
}
- @Test
- public void testRegisterProxy_doesNotRegisterVirtualDeviceListener_flagDisabled()
- throws RemoteException {
- mSetFlagsRule.disableFlags(android.companion.virtual.flags.Flags.FLAG_VDM_PUBLIC_APIS);
- registerProxy(DISPLAY_ID);
- mProxyManager.unregisterProxy(DISPLAY_ID);
-
- verify(mMockIVirtualDeviceManager, never()).registerVirtualDeviceListener(any());
- verify(mMockIVirtualDeviceManager, never()).unregisterVirtualDeviceListener(any());
- }
-
private void registerProxy(int displayId) {
try {
mProxyManager.registerProxy(mMockAccessibilityServiceClient, displayId, anyInt(),
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 0000000..d1ef33d
--- /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/companion/virtual/VirtualDeviceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceTest.java
index 4d1d17f..77c2447 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceTest.java
@@ -31,7 +31,6 @@
import android.companion.virtual.IVirtualDevice;
import android.companion.virtual.VirtualDevice;
-import android.companion.virtual.flags.Flags;
import android.os.Parcel;
import android.platform.test.annotations.Presubmit;
import android.platform.test.flag.junit.SetFlagsRule;
@@ -109,8 +108,6 @@
@Test
public void virtualDevice_getDisplayIds() throws Exception {
- mSetFlagsRule.enableFlags(Flags.FLAG_VDM_PUBLIC_APIS);
-
VirtualDevice virtualDevice =
new VirtualDevice(
mVirtualDevice, VIRTUAL_DEVICE_ID, /*persistentId=*/null, /*name=*/null);
@@ -125,8 +122,6 @@
@Test
public void virtualDevice_hasCustomSensorSupport() throws Exception {
- mSetFlagsRule.enableFlags(Flags.FLAG_VDM_PUBLIC_APIS);
-
VirtualDevice virtualDevice =
new VirtualDevice(
mVirtualDevice, VIRTUAL_DEVICE_ID, /*persistentId=*/null, /*name=*/null);
@@ -140,7 +135,6 @@
@Test
public void virtualDevice_hasCustomAudioInputSupport() throws Exception {
- mSetFlagsRule.enableFlags(Flags.FLAG_VDM_PUBLIC_APIS);
mSetFlagsRule.enableFlags(android.media.audiopolicy.Flags.FLAG_AUDIO_MIX_TEST_API);
VirtualDevice virtualDevice =
@@ -160,8 +154,6 @@
@Test
public void virtualDevice_hasCustomCameraSupport() throws Exception {
- mSetFlagsRule.enableFlags(Flags.FLAG_VDM_PUBLIC_APIS);
-
VirtualDevice virtualDevice =
new VirtualDevice(
mVirtualDevice, VIRTUAL_DEVICE_ID, /*persistentId=*/null, /*name=*/null);
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 eb4a628..792faab 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_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 @@
"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 @@
/*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 @@
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 @@
/*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 @@
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 @@
/*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 @@
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 @@
"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 @@
"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 @@
/*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 @@
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/pm/ShortcutManagerTest2.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java
index 39206dc..3b32701 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java
@@ -1612,7 +1612,7 @@
);
}
- public void testThrottling() {
+ public void disabled_testThrottling() {
final ShortcutInfo si1 = makeShortcut("shortcut1");
assertTrue(mManager.setDynamicShortcuts(list(si1)));
@@ -1685,7 +1685,7 @@
assertEquals(START_TIME + INTERVAL * 9, mManager.getRateLimitResetTime());
}
- public void testThrottling_rewind() {
+ public void disabled_testThrottling_rewind() {
final ShortcutInfo si1 = makeShortcut("shortcut1");
assertTrue(mManager.setDynamicShortcuts(list(si1)));
@@ -1715,7 +1715,7 @@
assertEquals(3, mManager.getRemainingCallCount());
}
- public void testThrottling_perPackage() {
+ public void disabled_testThrottling_perPackage() {
final ShortcutInfo si1 = makeShortcut("shortcut1");
assertTrue(mManager.setDynamicShortcuts(list(si1)));
@@ -1847,7 +1847,7 @@
});
}
- public void testThrottling_foreground() throws Exception {
+ public void disabled_testThrottling_foreground() throws Exception {
prepareCrossProfileDataSet();
dumpsysOnLogcat("Before save & load");
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest3.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest3.java
index aad06c6..81ebf86 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest3.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest3.java
@@ -171,11 +171,12 @@
.haveRanksInOrder("ms1");
}
- public void testSetDynamicShortcuts_withManifestShortcuts() {
- runTestWithManifestShortcuts(() -> testSetDynamicShortcuts_noManifestShortcuts());
+ public void disabled_testSetDynamicShortcuts_withManifestShortcuts() {
+ runTestWithManifestShortcuts(() ->
+ disabled_testAddDynamicShortcuts_noManifestShortcuts());
}
- public void testAddDynamicShortcuts_noManifestShortcuts() {
+ public void disabled_testAddDynamicShortcuts_noManifestShortcuts() {
mManager.addDynamicShortcuts(list(
shortcut("s1", A1)
));
@@ -264,8 +265,8 @@
.haveIds("s1", "s2", "s4", "s5", "s10");
}
- public void testAddDynamicShortcuts_withManifestShortcuts() {
- runTestWithManifestShortcuts(() -> testAddDynamicShortcuts_noManifestShortcuts());
+ public void disabled_testAddDynamicShortcuts_withManifestShortcuts() {
+ runTestWithManifestShortcuts(() -> disabled_testAddDynamicShortcuts_noManifestShortcuts());
}
public void testUpdateShortcuts_noManifestShortcuts() {
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 ee8eb9b..b76e0bc 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.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 @@
when(mSecureLockDeviceService.disableSecureLockDevice(any()))
.thenReturn(ERROR_UNSUPPORTED);
}
+
+ toggleAdaptiveAuthSettingsOverride(PRIMARY_USER_ID, false /* disable */);
}
@After
@@ -252,8 +256,24 @@
}
@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 @@
}
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 @@
}
@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 @@
}
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 @@
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/servicestests/src/com/android/server/statusbar/StatusBarManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/statusbar/StatusBarManagerServiceTest.java
index 148c968..263ada8 100644
--- a/services/tests/servicestests/src/com/android/server/statusbar/StatusBarManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/statusbar/StatusBarManagerServiceTest.java
@@ -69,6 +69,7 @@
import android.os.Looper;
import android.os.RemoteException;
import android.os.UserHandle;
+import android.platform.test.annotations.EnableFlags;
import android.service.quicksettings.TileService;
import android.testing.TestableContext;
@@ -79,6 +80,7 @@
import com.android.server.LocalServices;
import com.android.server.policy.GlobalActionsProvider;
import com.android.server.wm.ActivityTaskManagerInternal;
+import com.android.systemui.shared.Flags;
import libcore.junit.util.compat.CoreCompatChangeRule;
@@ -105,6 +107,7 @@
TEST_SERVICE);
private static final CharSequence APP_NAME = "AppName";
private static final CharSequence TILE_LABEL = "Tile label";
+ private static final int SECONDARY_DISPLAY_ID = 2;
@Rule
public final TestableContext mContext =
@@ -749,6 +752,29 @@
}
@Test
+ @EnableFlags(Flags.FLAG_STATUS_BAR_CONNECTED_DISPLAYS)
+ public void testDisableForAllDisplays() throws Exception {
+ int user1Id = 0;
+ mockUidCheck();
+ mockCurrentUserCheck(user1Id);
+
+ mStatusBarManagerService.onDisplayAdded(SECONDARY_DISPLAY_ID);
+
+ int expectedFlags = DISABLE_MASK & DISABLE_BACK;
+ String pkg = mContext.getPackageName();
+
+ // before disabling
+ assertEquals(DISABLE_NONE,
+ mStatusBarManagerService.getDisableFlags(mMockStatusBar, user1Id)[0]);
+
+ // disable
+ mStatusBarManagerService.disable(expectedFlags, mMockStatusBar, pkg);
+
+ verify(mMockStatusBar).disable(0, expectedFlags, 0);
+ verify(mMockStatusBar).disable(SECONDARY_DISPLAY_ID, expectedFlags, 0);
+ }
+
+ @Test
public void testSetHomeDisabled() throws Exception {
int expectedFlags = DISABLE_MASK & DISABLE_HOME;
String pkg = mContext.getPackageName();
@@ -851,6 +877,29 @@
}
@Test
+ @EnableFlags(Flags.FLAG_STATUS_BAR_CONNECTED_DISPLAYS)
+ public void testDisable2ForAllDisplays() throws Exception {
+ int user1Id = 0;
+ mockUidCheck();
+ mockCurrentUserCheck(user1Id);
+
+ mStatusBarManagerService.onDisplayAdded(SECONDARY_DISPLAY_ID);
+
+ int expectedFlags = DISABLE2_MASK & DISABLE2_NOTIFICATION_SHADE;
+ String pkg = mContext.getPackageName();
+
+ // before disabling
+ assertEquals(DISABLE_NONE,
+ mStatusBarManagerService.getDisableFlags(mMockStatusBar, user1Id)[0]);
+
+ // disable
+ mStatusBarManagerService.disable2(expectedFlags, mMockStatusBar, pkg);
+
+ verify(mMockStatusBar).disable(0, 0, expectedFlags);
+ verify(mMockStatusBar).disable(SECONDARY_DISPLAY_ID, 0, expectedFlags);
+ }
+
+ @Test
public void testSetQuickSettingsDisabled2() throws Exception {
int expectedFlags = DISABLE2_MASK & DISABLE2_QUICK_SETTINGS;
String pkg = mContext.getPackageName();
@@ -1092,6 +1141,7 @@
// disable
mStatusBarManagerService.disableForUser(expectedUser1Flags, mMockStatusBar, pkg, user1Id);
mStatusBarManagerService.disableForUser(expectedUser2Flags, mMockStatusBar, pkg, user2Id);
+
// check that right flag is disabled
assertEquals(expectedUser1Flags,
mStatusBarManagerService.getDisableFlags(mMockStatusBar, user1Id)[0]);
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 1df8e3d..d1afa38 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java
@@ -17,15 +17,12 @@
import static android.os.UserHandle.USER_ALL;
import static android.service.notification.Adjustment.KEY_IMPORTANCE;
+import static android.service.notification.Adjustment.KEY_TYPE;
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 +157,6 @@
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 {
@@ -180,10 +166,8 @@
mContext.getOrCreateTestableResources().addOverride(
com.android.internal.R.string.config_defaultAssistantAccessComponent,
mCn.flattenToString());
+ mNm.mDefaultUnsupportedAdjustments = new String[] {};
mAssistants = spy(mNm.new NotificationAssistants(mContext, mLock, mUserProfiles, miPm));
- if (notificationClassification()) {
- setDefaultAllowedAdjustmentKeyTypes(mAssistants);
- }
when(mNm.getBinderService()).thenReturn(mINm);
mContext.ensureTestableResources();
@@ -678,7 +662,7 @@
mAssistants.disallowAdjustmentType(Adjustment.KEY_RANKING_SCORE);
assertThat(mAssistants.getAllowedAssistantAdjustments())
.doesNotContain(Adjustment.KEY_RANKING_SCORE);
- assertThat(mAssistants.getAllowedAssistantAdjustments()).contains(Adjustment.KEY_TYPE);
+ assertThat(mAssistants.getAllowedAssistantAdjustments()).contains(KEY_TYPE);
}
@Test
@@ -727,7 +711,7 @@
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 +732,7 @@
writeXmlAndReload(USER_ALL);
assertThat(mAssistants.getAllowedAdjustmentKeyTypes()).asList()
- .containsExactly(TYPE_NEWS, TYPE_CONTENT_RECOMMENDATION);
+ .containsExactlyElementsIn(List.of(TYPE_NEWS, TYPE_CONTENT_RECOMMENDATION));
}
@Test
@@ -765,168 +749,98 @@
@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);
- }
+ 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);
- @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);
-
- // 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);
+ 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);
- // 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();
+ // 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 deny some of them,
- mAssistants.setAssistantAdjustmentKeyTypeStateForPackage(allowedPkg2, TYPE_NEWS, false);
- mAssistants.setAssistantAdjustmentKeyTypeStateForPackage(allowedPkg2, TYPE_PROMOTION,
- false);
- mAssistants.setAssistantAdjustmentKeyTypeStateForPackage(allowedPkg3, TYPE_PROMOTION,
- false);
+ // And when we re-allow one of them,
+ mAssistants.setTypeAdjustmentForPackageState(deniedPkg2, true);
- // 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();
+ // 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
@@ -944,7 +858,7 @@
mAssistants.new ManagedServiceInfo(null, mCn, userId, false, null, 35, 2345256);
// Ensure bundling is enabled
- mAssistants.setAdjustmentTypeSupportedState(info, Adjustment.KEY_TYPE, true);
+ mAssistants.setAdjustmentTypeSupportedState(info, KEY_TYPE, true);
// Enable these specific bundle types
mAssistants.setAssistantAdjustmentKeyTypeState(TYPE_PROMOTION, false);
mAssistants.setAssistantAdjustmentKeyTypeState(TYPE_NEWS, true);
@@ -978,7 +892,7 @@
.isEqualTo(NotificationProtoEnums.TYPE_CONTENT_RECOMMENDATION);
// Disable the top-level bundling setting
- mAssistants.setAdjustmentTypeSupportedState(info, Adjustment.KEY_TYPE, false);
+ mAssistants.setAdjustmentTypeSupportedState(info, KEY_TYPE, false);
// Enable these specific bundle types
mAssistants.setAssistantAdjustmentKeyTypeState(TYPE_PROMOTION, true);
mAssistants.setAssistantAdjustmentKeyTypeState(TYPE_NEWS, false);
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 fdb6a68..4330226 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.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.concurrent.CountDownLatch;
import java.util.function.Consumer;
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
+import platform.test.runner.parameterized.Parameters;
+
@SmallTest
@RunWith(ParameterizedAndroidJunit4.class)
@RunWithLooper
@@ -790,6 +790,8 @@
TestableResources tr = mContext.getOrCreateTestableResources();
tr.addOverride(com.android.internal.R.string.config_defaultSearchSelectorPackageName,
SEARCH_SELECTOR_PKG);
+ tr.addOverride(R.array.config_notificationDefaultUnsupportedAdjustments,
+ new String[] {KEY_TYPE});
doAnswer(invocation -> {
mOnPermissionChangeListener = invocation.getArgument(2);
@@ -7662,7 +7664,7 @@
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 +17514,7 @@
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 +17558,7 @@
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 +17573,8 @@
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 +17904,7 @@
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 +17944,7 @@
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 +17993,7 @@
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 +18044,7 @@
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 6ef078b..31b9cf72 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -17,8 +17,10 @@
package com.android.server.notification;
import static android.app.AutomaticZenRule.TYPE_BEDTIME;
+import static android.app.AutomaticZenRule.TYPE_DRIVING;
import static android.app.AutomaticZenRule.TYPE_IMMERSIVE;
import static android.app.AutomaticZenRule.TYPE_SCHEDULE_TIME;
+import static android.app.AutomaticZenRule.TYPE_THEATER;
import static android.app.AutomaticZenRule.TYPE_UNKNOWN;
import static android.app.Flags.FLAG_BACKUP_RESTORE_LOGGING;
import static android.app.Flags.FLAG_MODES_API;
@@ -87,6 +89,7 @@
import static com.android.os.dnd.DNDProtoEnums.ROOT_CONFIG;
import static com.android.os.dnd.DNDProtoEnums.STATE_ALLOW;
import static com.android.os.dnd.DNDProtoEnums.STATE_DISALLOW;
+import static com.android.server.notification.Flags.FLAG_PREVENT_ZEN_DEVICE_EFFECTS_WHILE_DRIVING;
import static com.android.server.notification.ZenModeEventLogger.ACTIVE_RULE_TYPE_MANUAL;
import static com.android.server.notification.ZenModeHelper.RULE_LIMIT_PER_PACKAGE;
@@ -211,7 +214,9 @@
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;
@@ -5516,8 +5521,72 @@
eq(ORIGIN_INIT_USER));
}
+ @Test
+ @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI, FLAG_PREVENT_ZEN_DEVICE_EFFECTS_WHILE_DRIVING})
+ public void testDeviceEffects_allowsGrayscale() {
+ mZenModeHelper.setDeviceEffectsApplier(mDeviceEffectsApplier);
+ reset(mDeviceEffectsApplier);
+ ZenDeviceEffects grayscale = new ZenDeviceEffects.Builder()
+ .setShouldDisplayGrayscale(true)
+ .build();
+ String ruleId = addRuleWithEffects(TYPE_THEATER, grayscale);
+
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleId, CONDITION_TRUE,
+ ORIGIN_APP, CUSTOM_PKG_UID);
+ mTestableLooper.processAllMessages();
+
+ verify(mDeviceEffectsApplier).apply(eq(grayscale), anyInt());
+ }
+
+ @Test
+ @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI, FLAG_PREVENT_ZEN_DEVICE_EFFECTS_WHILE_DRIVING})
+ public void testDeviceEffects_whileDriving_avoidsGrayscale() {
+ mZenModeHelper.setDeviceEffectsApplier(mDeviceEffectsApplier);
+ reset(mDeviceEffectsApplier);
+ ZenDeviceEffects grayscale = new ZenDeviceEffects.Builder()
+ .setShouldDisplayGrayscale(true)
+ .build();
+ String ruleWithGrayscale = addRuleWithEffects(TYPE_THEATER, grayscale);
+ String drivingRule = addRuleWithEffects(TYPE_DRIVING, null);
+
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleWithGrayscale,
+ CONDITION_TRUE, ORIGIN_APP, CUSTOM_PKG_UID);
+ mTestableLooper.processAllMessages();
+
+ verify(mDeviceEffectsApplier).apply(eq(grayscale), anyInt());
+
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, drivingRule, CONDITION_TRUE,
+ ORIGIN_APP, CUSTOM_PKG_UID);
+ mTestableLooper.processAllMessages();
+
+ verify(mDeviceEffectsApplier).apply(eq(NO_EFFECTS), anyInt());
+ }
+
+ @Test
+ @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI, FLAG_PREVENT_ZEN_DEVICE_EFFECTS_WHILE_DRIVING})
+ public void testDeviceEffects_whileDrivingWithGrayscale_allowsGrayscale() {
+ mZenModeHelper.setDeviceEffectsApplier(mDeviceEffectsApplier);
+ reset(mDeviceEffectsApplier);
+ ZenDeviceEffects grayscale = new ZenDeviceEffects.Builder()
+ .setShouldDisplayGrayscale(true)
+ .build();
+ String weirdoDrivingWithGrayscale = addRuleWithEffects(TYPE_DRIVING, grayscale);
+
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, weirdoDrivingWithGrayscale,
+ CONDITION_TRUE, ORIGIN_APP, CUSTOM_PKG_UID);
+ mTestableLooper.processAllMessages();
+
+ verify(mDeviceEffectsApplier).apply(eq(grayscale), anyInt());
+ }
+
private String addRuleWithEffects(ZenDeviceEffects effects) {
+ return addRuleWithEffects(TYPE_UNKNOWN, effects);
+ }
+
+ private String addRuleWithEffects(int type, @Nullable ZenDeviceEffects effects) {
AutomaticZenRule rule = new AutomaticZenRule.Builder("Test", CONDITION_ID)
+ .setPackage(mContext.getPackageName())
+ .setType(type)
.setDeviceEffects(effects)
.build();
return mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, mContext.getPackageName(),
@@ -7406,6 +7475,43 @@
.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 +7636,12 @@
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/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index c88d515..65150e7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -2006,7 +2006,6 @@
display.continueUpdateOrientationForDiffOrienLaunchingApp();
assertTrue(display.isFixedRotationLaunchingApp(activity));
- activity.stopFreezingScreen(true /* unfreezeSurfaceNow */, true /* force */);
// Simulate the rotation has been updated to previous one, e.g. sensor updates before the
// remote rotation is completed.
doReturn(originalRotation).when(displayRotation).rotationForOrientation(
@@ -2015,14 +2014,10 @@
final DisplayInfo rotatedInfo = activity.getFixedRotationTransformDisplayInfo();
activity.finishFixedRotationTransform();
- final ScreenRotationAnimation rotationAnim = display.getRotationAnimation();
- assertNotNull(rotationAnim);
// Because the display doesn't rotate, the rotated activity needs to cancel the fixed
// rotation. There should be a rotation animation to cover the change of activity.
verify(activity).onCancelFixedRotationTransform(rotatedInfo.rotation);
- assertTrue(activity.isFreezingScreen());
- assertFalse(displayRotation.isRotatingSeamlessly());
// Simulate the remote rotation has completed and the configuration doesn't change, then
// the rotated activity should also be restored by clearing the transform.
@@ -2041,8 +2036,6 @@
activity.setVisibleRequested(false);
clearInvocations(activity);
activity.onCancelFixedRotationTransform(originalRotation);
- // The implementation of cancellation must be executed.
- verify(activity).startFreezingScreen(originalRotation);
}
@Test
@@ -2599,19 +2592,16 @@
final TestWindowState appWindow = createWindowState(attrs, activity);
activity.addWindow(appWindow);
spyOn(appWindow);
- doNothing().when(appWindow).onStartFreezingScreen();
// Set initial orientation and update.
activity.setOrientation(SCREEN_ORIENTATION_LANDSCAPE);
- mDisplayContent.updateOrientation(null /* freezeThisOneIfNeeded */,
- false /* forceUpdate */);
+ mDisplayContent.updateOrientationAndComputeConfig(false /* forceUpdate */);
assertEquals(SCREEN_ORIENTATION_LANDSCAPE, mDisplayContent.getLastOrientation());
appWindow.mResizeReported = false;
// Update the orientation to perform 180 degree rotation and check that resize was reported.
activity.setOrientation(SCREEN_ORIENTATION_REVERSE_LANDSCAPE);
- mDisplayContent.updateOrientation(null /* freezeThisOneIfNeeded */,
- false /* forceUpdate */);
+ mDisplayContent.updateOrientationAndComputeConfig(false /* forceUpdate */);
// In this test, DC will not get config update. Set the waiting flag to false.
mDisplayContent.mWaitingForConfig = false;
mWm.mRoot.performSurfacePlacement();
@@ -2635,8 +2625,6 @@
final TestWindowState appWindow = createWindowState(attrs, activity);
activity.addWindow(appWindow);
spyOn(appWindow);
- doNothing().when(appWindow).onStartFreezingScreen();
- doNothing().when(mWm).startFreezingDisplay(anyInt(), anyInt(), any(), anyInt());
// Set initial orientation and update.
performRotation(displayRotation, Surface.ROTATION_90);
@@ -2795,9 +2783,6 @@
assertEquals(Configuration.ORIENTATION_PORTRAIT, displayConfig.orientation);
assertEquals(Configuration.ORIENTATION_PORTRAIT, activityConfig.orientation);
- // Unblock the rotation animation, so the further orientation updates won't be ignored.
- unblockDisplayRotation(activity.mDisplayContent);
-
final ActivityRecord topActivity = createActivityRecord(activity.getTask());
topActivity.setOrientation(SCREEN_ORIENTATION_LANDSCAPE);
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRefresherTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRefresherTests.java
index 63dafcd..c667d76 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRefresherTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRefresherTests.java
@@ -103,7 +103,7 @@
@Test
public void testShouldRefreshActivity_refreshDisabledForActivity() throws Exception {
configureActivityAndDisplay();
- when(mActivity.mAppCompatController.getAppCompatCameraOverrides()
+ when(mActivity.mAppCompatController.getCameraOverrides()
.shouldRefreshActivityForCameraCompat()).thenReturn(false);
mActivityRefresher.addEvaluator(mEvaluatorTrue);
@@ -161,7 +161,7 @@
configureActivityAndDisplay();
mActivityRefresher.addEvaluator(mEvaluatorTrue);
doReturn(true)
- .when(mActivity.mAppCompatController.getAppCompatCameraOverrides())
+ .when(mActivity.mAppCompatController.getCameraOverrides())
.shouldRefreshActivityViaPauseForCameraCompat();
mActivityRefresher.onActivityConfigurationChanging(mActivity, mNewConfig, mOldConfig);
@@ -174,7 +174,7 @@
configureActivityAndDisplay();
mActivityRefresher.addEvaluator(mEvaluatorTrue);
doReturn(true)
- .when(mActivity.mAppCompatController.getAppCompatCameraOverrides())
+ .when(mActivity.mAppCompatController.getCameraOverrides())
.shouldRefreshActivityViaPauseForCameraCompat();
mActivityRefresher.onActivityRefreshed(mActivity);
@@ -188,7 +188,7 @@
private void assertActivityRefreshRequested(boolean refreshRequested,
boolean cycleThroughStop) throws Exception {
- verify(mActivity.mAppCompatController.getAppCompatCameraOverrides(),
+ verify(mActivity.mAppCompatController.getCameraOverrides(),
times(refreshRequested ? 1 : 0)).setIsRefreshRequested(true);
final RefreshCallbackItem refreshCallbackItem =
@@ -212,9 +212,9 @@
.build()
.getTopMostActivity();
- spyOn(mActivity.mAppCompatController.getAppCompatCameraOverrides());
+ spyOn(mActivity.mAppCompatController.getCameraOverrides());
doReturn(true).when(mActivity).inFreeformWindowingMode();
doReturn(true).when(mActivity.mAppCompatController
- .getAppCompatCameraOverrides()).shouldRefreshActivityForCameraCompat();
+ .getCameraOverrides()).shouldRefreshActivityForCameraCompat();
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraOverridesTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraOverridesTest.java
index 1d138e4..4ad1cd1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraOverridesTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraOverridesTest.java
@@ -393,7 +393,7 @@
}
private AppCompatCameraOverrides getAppCompatCameraOverrides() {
- return activity().top().mAppCompatController.getAppCompatCameraOverrides();
+ return activity().top().mAppCompatController.getCameraOverrides();
}
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java
index 576d17a..716f864 100644
--- a/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java
@@ -401,7 +401,7 @@
throws Exception {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
- doReturn(false).when(mActivity.mAppCompatController.getAppCompatCameraOverrides())
+ doReturn(false).when(mActivity.mAppCompatController.getCameraOverrides())
.shouldRefreshActivityForCameraCompat();
mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
@@ -431,7 +431,7 @@
public void testOnActivityConfigurationChanging_cycleThroughStopDisabledForApp()
throws Exception {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
- doReturn(true).when(mActivity.mAppCompatController.getAppCompatCameraOverrides())
+ doReturn(true).when(mActivity.mAppCompatController.getCameraOverrides())
.shouldRefreshActivityViaPauseForCameraCompat();
mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
@@ -477,7 +477,7 @@
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
final float configAspectRatio = 1.5f;
mWm.mAppCompatConfiguration.setCameraCompatAspectRatio(configAspectRatio);
- doReturn(true).when(mActivity.mAppCompatController.getAppCompatCameraOverrides())
+ doReturn(true).when(mActivity.mAppCompatController.getCameraOverrides())
.isOverrideMinAspectRatioForCameraEnabled();
mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
@@ -608,7 +608,7 @@
.build();
mActivity.mAppCompatController.getSizeCompatModePolicy().clearSizeCompatMode();
- spyOn(mActivity.mAppCompatController.getAppCompatCameraOverrides());
+ spyOn(mActivity.mAppCompatController.getCameraOverrides());
spyOn(mActivity.info);
doReturn(mActivity).when(mDisplayContent).topRunningActivity(anyBoolean());
@@ -630,7 +630,7 @@
private void assertActivityRefreshRequested(boolean refreshRequested,
boolean cycleThroughStop) throws Exception {
- verify(mActivity.mAppCompatController.getAppCompatCameraOverrides(),
+ verify(mActivity.mAppCompatController.getCameraOverrides(),
times(refreshRequested ? 1 : 0)).setIsRefreshRequested(true);
final RefreshCallbackItem refreshCallbackItem =
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 22e411e..3f3b24a 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 @@
}
@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 @@
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/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index d76a907..0964ebe 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -1714,7 +1714,6 @@
@Test
public void testFinishFixedRotationNoAppTransitioningTask() {
- unblockDisplayRotation(mDisplayContent);
final ActivityRecord app = createActivityRecord(mDisplayContent);
final Task task = app.getTask();
final ActivityRecord app2 = new ActivityBuilder(mWm.mAtmService).setTask(task).build();
@@ -1742,7 +1741,6 @@
public void testFixedRotationWithPip() {
final DisplayContent displayContent = mDefaultDisplay;
displayContent.setIgnoreOrientationRequest(false);
- unblockDisplayRotation(displayContent);
// Unblock the condition in PinnedTaskController#continueOrientationChangeIfNeeded.
doNothing().when(displayContent).prepareAppTransition(anyInt());
// Make resume-top really update the activity state.
@@ -1762,7 +1760,6 @@
assertTrue(displayContent.hasTopFixedRotationLaunchingApp());
assertTrue(displayContent.mPinnedTaskController.shouldDeferOrientationChange());
- verify(mWm, never()).startFreezingDisplay(anyInt(), anyInt(), any(), anyInt());
clearInvocations(pinnedTask);
// Assume that the PiP enter animation is done then the new bounds are set. Expect the
@@ -1799,7 +1796,6 @@
@Test
public void testNoFixedRotationOnResumedScheduledApp() {
- unblockDisplayRotation(mDisplayContent);
final ActivityRecord app = new ActivityBuilder(mAtm).setCreateTask(true).build();
app.setVisible(false);
app.setState(ActivityRecord.State.RESUMED, "test");
@@ -1811,7 +1807,7 @@
// The condition should reject using fixed rotation because the resumed client in real case
// might get display info immediately. And the fixed rotation adjustments haven't arrived
// client side so the info may be inconsistent with the requested orientation.
- verify(mDisplayContent).updateOrientation(eq(app), anyBoolean());
+ verify(mDisplayContent).updateOrientationAndComputeConfig(anyBoolean());
assertFalse(app.isFixedRotationTransforming());
assertFalse(mDisplayContent.hasTopFixedRotationLaunchingApp());
}
@@ -1863,9 +1859,6 @@
@Test
public void testSecondaryInternalDisplayRotationFollowsDefaultDisplay() {
- // Skip freezing so the unrelated conditions in updateRotationUnchecked won't disturb.
- doNothing().when(mWm).startFreezingDisplay(anyInt(), anyInt(), any(), anyInt());
-
final DisplayRotationCoordinator coordinator =
mRootWindowContainer.getDisplayRotationCoordinator();
final DisplayContent defaultDisplayContent = mDisplayContent;
@@ -1921,9 +1914,6 @@
@Test
public void testSecondaryNonInternalDisplayDoesNotFollowDefaultDisplay() {
- // Skip freezing so the unrelated conditions in updateRotationUnchecked won't disturb.
- doNothing().when(mWm).startFreezingDisplay(anyInt(), anyInt(), any(), anyInt());
-
final DisplayRotationCoordinator coordinator =
mRootWindowContainer.getDisplayRotationCoordinator();
@@ -1994,20 +1984,11 @@
}
};
- // kill any existing rotation animation (vestigial from test setup).
- dc.setRotationAnimation(null);
-
mWm.updateRotation(true /* alwaysSendConfiguration */, false /* forceRelayout */);
- // If remote rotation is not finished, the display should not be able to unfreeze.
- mWm.stopFreezingDisplayLocked();
- assertTrue(mWm.mDisplayFrozen);
assertTrue(called[0]);
waitUntilHandlersIdle();
assertTrue(continued[0]);
-
- mWm.stopFreezingDisplayLocked();
- assertFalse(mWm.mDisplayFrozen);
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
index 23c767c..02b796f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
@@ -267,7 +267,7 @@
public void testTreatmentDisabledPerApp_noForceRotationOrRefresh()
throws Exception {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
- doReturn(false).when(mActivity.mAppCompatController.getAppCompatCameraOverrides())
+ doReturn(false).when(mActivity.mAppCompatController.getCameraOverrides())
.shouldForceRotateForCameraCompat();
mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
@@ -469,7 +469,7 @@
throws Exception {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
- doReturn(false).when(mActivity.mAppCompatController.getAppCompatCameraOverrides())
+ doReturn(false).when(mActivity.mAppCompatController.getCameraOverrides())
.shouldRefreshActivityForCameraCompat();
mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
@@ -496,7 +496,7 @@
throws Exception {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
doReturn(false).when(mActivity
- .mAppCompatController.getAppCompatCameraOverrides())
+ .mAppCompatController.getCameraOverrides())
.isCameraCompatSplitScreenAspectRatioAllowed();
mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
@@ -509,7 +509,7 @@
public void testOnActivityConfigurationChanging_splitScreenAspectRatioAllowed_refresh()
throws Exception {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
- doReturn(true).when(mActivity.mAppCompatController.getAppCompatCameraOverrides())
+ doReturn(true).when(mActivity.mAppCompatController.getCameraOverrides())
.isCameraCompatSplitScreenAspectRatioAllowed();
mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
@@ -535,7 +535,7 @@
public void testOnActivityConfigurationChanging_cycleThroughStopDisabledForApp()
throws Exception {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
- doReturn(true).when(mActivity.mAppCompatController.getAppCompatCameraOverrides())
+ doReturn(true).when(mActivity.mAppCompatController.getCameraOverrides())
.shouldRefreshActivityViaPauseForCameraCompat();
mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
@@ -599,7 +599,7 @@
ActivityInfo.UNIVERSAL_RESIZABLE_BY_DEFAULT);
spyOn(mActivity.mAtmService.getLifecycleManager());
- spyOn(mActivity.mAppCompatController.getAppCompatCameraOverrides());
+ spyOn(mActivity.mAppCompatController.getCameraOverrides());
doReturn(mActivity).when(mDisplayContent).topRunningActivity(anyBoolean());
doReturn(naturalOrientation).when(mDisplayContent).getNaturalOrientation();
@@ -611,7 +611,7 @@
private void assertActivityRefreshRequested(boolean refreshRequested,
boolean cycleThroughStop) throws Exception {
- verify(mActivity.mAppCompatController.getAppCompatCameraOverrides(),
+ verify(mActivity.mAppCompatController.getCameraOverrides(),
times(refreshRequested ? 1 : 0)).setIsRefreshRequested(true);
final RefreshCallbackItem refreshCallbackItem =
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 f509706..523b723 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 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 @@
// 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 @@
}
@Test
- public void testDragEventCoordinates() {
+ public void testDragEventCoordinatesOverlappingWindows() {
int dragStartX = mWindow.getBounds().centerX();
int dragStartY = mWindow.getBounds().centerY();
int startOffsetPx = 10;
@@ -429,7 +431,7 @@
// 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 @@
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 @@
// 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 @@
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 @@
});
}
- 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 25b9f4b..8a7e743 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.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 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 @@
}
@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 @@
}
@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 @@
}
@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/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index a84b374..2630565 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -4683,7 +4683,8 @@
prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);
- assertFalse(mActivity.isEligibleForLetterboxEducation());
+ assertFalse(mActivity.mAppCompatController.getAppCompatLetterboxPolicy()
+ .isEligibleForLetterboxEducation());
}
@Test
@@ -4694,7 +4695,8 @@
prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);
- assertFalse(mActivity.isEligibleForLetterboxEducation());
+ assertFalse(mActivity.mAppCompatController.getAppCompatLetterboxPolicy()
+ .isEligibleForLetterboxEducation());
}
@Test
@@ -4716,7 +4718,8 @@
false /*moveParents*/, "test");
organizer.mPrimary.setBounds(0, 0, 1000, 600);
- assertFalse(mActivity.isEligibleForLetterboxEducation());
+ assertFalse(mActivity.mAppCompatController.getAppCompatLetterboxPolicy()
+ .isEligibleForLetterboxEducation());
assertEquals(WINDOWING_MODE_MULTI_WINDOW, mActivity.getWindowingMode());
}
@@ -4728,7 +4731,8 @@
prepareUnresizable(mActivity, SCREEN_ORIENTATION_LANDSCAPE);
- assertFalse(mActivity.isEligibleForLetterboxEducation());
+ assertFalse(mActivity.mAppCompatController.getAppCompatLetterboxPolicy()
+ .isEligibleForLetterboxEducation());
assertTrue(mActivity.mAppCompatController.getAspectRatioPolicy()
.isLetterboxedForFixedOrientationAndAspectRatio());
}
@@ -4745,14 +4749,16 @@
createWindowState(new WindowManager.LayoutParams(TYPE_APPLICATION_STARTING),
mActivity));
- assertFalse(mActivity.isEligibleForLetterboxEducation());
+ assertFalse(mActivity.mAppCompatController.getAppCompatLetterboxPolicy()
+ .isEligibleForLetterboxEducation());
// Verify that after removing the starting window isEligibleForLetterboxEducation returns
// true and mTask.dispatchTaskInfoChangedIfNeeded is called.
spyOn(mTask);
mActivity.removeStartingWindow();
- assertTrue(mActivity.isEligibleForLetterboxEducation());
+ assertTrue(mActivity.mAppCompatController.getAppCompatLetterboxPolicy()
+ .isEligibleForLetterboxEducation());
verify(mTask).dispatchTaskInfoChangedIfNeeded(true);
}
@@ -4768,14 +4774,16 @@
createWindowState(new WindowManager.LayoutParams(TYPE_APPLICATION_STARTING),
mActivity));
- assertFalse(mActivity.isEligibleForLetterboxEducation());
+ assertFalse(mActivity.mAppCompatController.getAppCompatLetterboxPolicy()
+ .isEligibleForLetterboxEducation());
// Verify that after removing the starting window isEligibleForLetterboxEducation still
// returns false and mTask.dispatchTaskInfoChangedIfNeeded isn't called.
spyOn(mTask);
mActivity.removeStartingWindow();
- assertFalse(mActivity.isEligibleForLetterboxEducation());
+ assertFalse(mActivity.mAppCompatController.getAppCompatLetterboxPolicy()
+ .isEligibleForLetterboxEducation());
verify(mTask, never()).dispatchTaskInfoChangedIfNeeded(true);
}
@@ -4787,7 +4795,8 @@
prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);
- assertTrue(mActivity.isEligibleForLetterboxEducation());
+ assertTrue(mActivity.mAppCompatController.getAppCompatLetterboxPolicy()
+ .isEligibleForLetterboxEducation());
assertTrue(mActivity.mAppCompatController.getAspectRatioPolicy()
.isLetterboxedForFixedOrientationAndAspectRatio());
}
@@ -4802,7 +4811,8 @@
rotateDisplay(mActivity.mDisplayContent, ROTATION_90);
- assertTrue(mActivity.isEligibleForLetterboxEducation());
+ assertTrue(mActivity.mAppCompatController.getAppCompatLetterboxPolicy()
+ .isEligibleForLetterboxEducation());
assertFalse(mActivity.mAppCompatController.getAspectRatioPolicy()
.isLetterboxedForFixedOrientationAndAspectRatio());
assertTrue(mActivity.inSizeCompatMode());
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 a95093d..59335d3 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 @@
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 c876663..8a068cc 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.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 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 @@
@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 @@
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 @@
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/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
index 38d3d2f..724d7e7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
@@ -92,6 +92,7 @@
import android.view.Display;
import android.view.DisplayInfo;
import android.view.SurfaceControl;
+import android.view.WindowInsetsController;
import android.window.TaskFragmentOrganizer;
import androidx.test.filters.MediumTest;
@@ -2109,6 +2110,43 @@
assertEquals(Color.RED, task.getTaskDescription().getBackgroundColor());
}
+ @Test
+ public void testUpdateTopOpaqueSystemBarsAppearanceWhenActivityBecomesTransparent() {
+ final Task task = createTask(mDisplayContent);
+ final ActivityRecord activity = createActivityRecord(task);
+ final ActivityManager.TaskDescription td = new ActivityManager.TaskDescription();
+ td.setSystemBarsAppearance(
+ WindowInsetsController.APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND);
+ activity.setTaskDescription(td);
+
+ assertEquals(WindowInsetsController.APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND,
+ task.getTaskDescription().getTopOpaqueSystemBarsAppearance());
+
+ activity.setOccludesParent(false);
+
+ assertEquals(0, task.getTaskDescription().getTopOpaqueSystemBarsAppearance());
+ }
+
+ @Test
+ public void testUpdateTopOpaqueSystemBarsAppearanceWhenActivityBecomesOpaque() {
+ final Task task = createTask(mDisplayContent);
+ final ActivityRecord activity = createActivityRecord(task);
+ activity.setOccludesParent(false);
+
+ final ActivityManager.TaskDescription td = new ActivityManager.TaskDescription();
+ td.setSystemBarsAppearance(
+ WindowInsetsController.APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND);
+ activity.setTaskDescription(td);
+
+ assertEquals(0, task.getTaskDescription().getTopOpaqueSystemBarsAppearance());
+
+ activity.setOccludesParent(true);
+
+ assertEquals(WindowInsetsController.APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND,
+ task.getTaskDescription().getTopOpaqueSystemBarsAppearance());
+
+ }
+
private Task getTestTask() {
return new TaskBuilder(mSupervisor).setCreateActivity(true).build();
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTransactionTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTransactionTests.java
index e9ece5d..369600c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTransactionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTransactionTests.java
@@ -79,6 +79,34 @@
}
@Test
+ public void testRemoveRootTask() {
+ final Task rootTask = createTask(mDisplayContent);
+ final Task task = createTaskInRootTask(rootTask, 0 /* userId */);
+ final ActivityRecord activity = createActivityRecord(mDisplayContent, task);
+ final TaskDisplayArea taskDisplayArea = mDisplayContent.getDefaultTaskDisplayArea();
+
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ WindowContainerToken token = rootTask.getTaskInfo().token;
+ wct.removeTask(token);
+ applyTransaction(wct);
+
+ // There is still an activity to be destroyed, so the task is not removed immediately.
+ assertNotNull(task.getParent());
+ assertTrue(rootTask.hasChild());
+ assertTrue(task.hasChild());
+ assertTrue(activity.finishing);
+
+ activity.destroyed("testRemoveRootTask");
+ // Assert that the container was removed after the activity is destroyed.
+ assertNull(task.getParent());
+ assertEquals(0, task.getChildCount());
+ assertNull(activity.getParent());
+ assertNull(taskDisplayArea.getTask(task1 -> task1.mTaskId == rootTask.mTaskId));
+ verify(mAtm.getLockTaskController(), atLeast(1)).clearLockedTask(task);
+ verify(mAtm.getLockTaskController(), atLeast(1)).clearLockedTask(rootTask);
+ }
+
+ @Test
public void testDesktopMode_tasksAreBroughtToFront() {
final TestDesktopOrganizer desktopOrganizer = new TestDesktopOrganizer(mAtm);
TaskDisplayArea tda = desktopOrganizer.mDefaultTDA;
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index f6f473b..cff172f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -855,12 +855,6 @@
startingApp.updateResizingWindowIfNeeded();
assertTrue(mWm.mResizingWindows.contains(startingApp));
assertTrue(startingApp.isDrawn());
-
- // Even if the display is frozen, invisible requested window should not be affected.
- mWm.startFreezingDisplay(0, 0, mDisplayContent);
- startingApp.getWindowFrames().setInsetsChanged(true);
- startingApp.updateResizingWindowIfNeeded();
- assertTrue(startingApp.isDrawn());
}
@SetupWindows(addWindows = W_ABOVE_ACTIVITY)
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index 37d2a75..2c390c5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -1010,20 +1010,6 @@
waitUntilWindowAnimatorIdle();
}
- /**
- * Avoids rotating screen disturbed by some conditions. It is usually used for the default
- * display that is not the instance of {@link TestDisplayContent} (it bypasses the conditions).
- *
- * @see DisplayRotation#updateRotationUnchecked
- */
- void unblockDisplayRotation(DisplayContent dc) {
- dc.mOpeningApps.clear();
- mWm.mAppsFreezingScreen = 0;
- mWm.stopFreezingDisplayLocked();
- // The rotation animation won't actually play, it needs to be cleared manually.
- dc.setRotationAnimation(null);
- }
-
static void resizeDisplay(DisplayContent displayContent, int width, int height) {
displayContent.updateBaseDisplayMetrics(width, height, displayContent.mBaseDisplayDensity,
displayContent.mBaseDisplayPhysicalXDpi, displayContent.mBaseDisplayPhysicalYDpi);
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 0000000..e92e684
--- /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 819e73d..6dda7ea 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 @@
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 ba17884..bfa4ecd 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 @@
* @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 @@
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 @@
* 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 @@
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/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java
index a52614d..531f516 100644
--- a/telecomm/java/android/telecom/Call.java
+++ b/telecomm/java/android/telecom/Call.java
@@ -52,6 +52,8 @@
* Represents an ongoing phone call that the in-call app should present to the user.
*/
public final class Call {
+ private static final String LOG_TAG = "TelecomCall";
+
/**
* The state of a {@code Call} when newly created.
*/
@@ -2912,6 +2914,11 @@
}
} catch (BadParcelableException e) {
return false;
+ } catch (ClassCastException e) {
+ Log.e(LOG_TAG, e, "areBundlesEqual: failure comparing bundle key %s", key);
+ // until we know what is causing this, we should rethrow -- this is still not
+ // expected.
+ throw e;
}
}
}
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 24fb8c5..da41655 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -180,7 +180,7 @@
* permission-protected. Your application cannot access the protected
* information unless it has the appropriate permissions declared in
* its manifest file. Where permissions apply, they are noted in the
- * the methods through which you access the protected information.
+ * methods through which you access the protected information.
*
* <p>TelephonyManager is intended for use on devices that implement
* {@link android.content.pm.PackageManager#FEATURE_TELEPHONY FEATURE_TELEPHONY}. On devices
@@ -633,11 +633,14 @@
}
/**
- * Returns the multi SIM variant
- * Returns DSDS for Dual SIM Dual Standby
- * Returns DSDA for Dual SIM Dual Active
- * Returns TSTS for Triple SIM Triple Standby
- * Returns UNKNOWN for others
+ * Returns the multi SIM variant.
+ *
+ * <ul>
+ * <li>Returns DSDS for Dual SIM Dual Standby.</li>
+ * <li>Returns DSDA for Dual SIM Dual Active.</li>
+ * <li>Returns TSTS for Triple SIM Triple Standby.</li>
+ * <li>Returns UNKNOWN for others.</li>
+ * </ul>
*/
/** {@hide} */
@UnsupportedAppUsage
@@ -657,10 +660,14 @@
/**
* Returns the number of phones available.
- * Returns 0 if none of voice, sms, data is not supported
- * Returns 1 for Single standby mode (Single SIM functionality).
- * Returns 2 for Dual standby mode (Dual SIM functionality).
- * Returns 3 for Tri standby mode (Tri SIM functionality).
+ *
+ * <ul>
+ * <li>Returns 0 if none of voice, sms, data is supported.</li>
+ * <li>Returns 1 for Single standby mode (Single SIM functionality).</li>
+ * <li>Returns 2 for Dual standby mode (Dual SIM functionality).</li>
+ * <li>Returns 3 for Tri standby mode (Tri SIM functionality).</li>
+ * </ul>
+ *
* @deprecated Use {@link #getActiveModemCount} instead.
*/
@Deprecated
@@ -671,10 +678,12 @@
/**
* Returns the number of logical modems currently configured to be activated.
*
- * Returns 0 if none of voice, sms, data is not supported
- * Returns 1 for Single standby mode (Single SIM functionality).
- * Returns 2 for Dual standby mode (Dual SIM functionality).
- * Returns 3 for Tri standby mode (Tri SIM functionality).
+ * <ul>
+ * <li>Returns 0 if none of voice, sms, data is supported.</li>
+ * <li>Returns 1 for Single standby mode (Single SIM functionality).</li>
+ * <li>Returns 2 for Dual standby mode (Dual SIM functionality).</li>
+ * <li>Returns 3 for Tri standby mode (Tri SIM functionality).</li>
+ * </ul>
*/
public int getActiveModemCount() {
int modemCount = 1;
diff --git a/tests/CompanionDeviceMultiDeviceTests/client/Android.bp b/tests/CompanionDeviceMultiDeviceTests/client/Android.bp
index ce63fe8..02b6391 100644
--- a/tests/CompanionDeviceMultiDeviceTests/client/Android.bp
+++ b/tests/CompanionDeviceMultiDeviceTests/client/Android.bp
@@ -45,7 +45,6 @@
],
optimize: {
- proguard_compatibility: true,
proguard_flags_files: ["proguard.flags"],
},
}
diff --git a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt
index 0824874..9e9d014 100644
--- a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt
+++ b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt
@@ -280,11 +280,20 @@
.waitForAndVerify()
}
- /** Click close button on the app header for the given app. */
- fun closeDesktopApp(wmHelper: WindowManagerStateHelper, device: UiDevice) {
- val caption = getCaptionForTheApp(wmHelper, device)
- val closeButton = caption?.children?.find { it.resourceName.endsWith(CLOSE_BUTTON) }
- closeButton?.click()
+ /** Close a desktop app by clicking the close button on the app header for the given app or by
+ * pressing back. */
+ fun closeDesktopApp(
+ wmHelper: WindowManagerStateHelper,
+ device: UiDevice,
+ usingBackNavigation: Boolean = false
+ ) {
+ if (usingBackNavigation) {
+ device.pressBack()
+ } else {
+ val caption = getCaptionForTheApp(wmHelper, device)
+ val closeButton = caption?.children?.find { it.resourceName.endsWith(CLOSE_BUTTON) }
+ closeButton?.click()
+ }
wmHelper
.StateSyncBuilder()
.withAppTransitionIdle()
diff --git a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
index 8c04f647..e053263 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 @@
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 @@
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 4c48eaa..6a68f2b 100644
--- a/tests/NetworkSecurityConfigTest/Android.bp
+++ b/tests/NetworkSecurityConfigTest/Android.bp
@@ -11,11 +11,12 @@
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 0000000..d1b9aa1
--- /dev/null
+++ b/tests/NetworkSecurityConfigTest/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "postsubmit": [
+ {
+ "name": "NetworkSecurityConfigTests"
+ }
+ ]
+}
diff --git a/tests/NetworkSecurityConfigTest/res/xml/domain_whitespace.xml b/tests/NetworkSecurityConfigTest/res/xml/domain_whitespace.xml
index 99106ad..5d48841 100644
--- a/tests/NetworkSecurityConfigTest/res/xml/domain_whitespace.xml
+++ b/tests/NetworkSecurityConfigTest/res/xml/domain_whitespace.xml
@@ -5,7 +5,7 @@
</domain>
<domain> developer.android.com </domain>
<pin-set>
- <pin digest="SHA-256"> zCTnfLwLKbS9S2sbp+uFz4KZOocFvXxkV06Ce9O5M2w= </pin>
+ <pin digest="SHA-256"> YPtHaftLw6/0vnc2BnNKGF54xiCA28WFcccjkA4ypCM= </pin>
</pin-set>
</domain-config>
</network-security-config>
diff --git a/tests/NetworkSecurityConfigTest/res/xml/nested_domains.xml b/tests/NetworkSecurityConfigTest/res/xml/nested_domains.xml
index 232f88f..731f0f0 100644
--- a/tests/NetworkSecurityConfigTest/res/xml/nested_domains.xml
+++ b/tests/NetworkSecurityConfigTest/res/xml/nested_domains.xml
@@ -9,7 +9,7 @@
<domain-config>
<domain>developer.android.com</domain>
<pin-set>
- <pin digest="SHA-256">zCTnfLwLKbS9S2sbp+uFz4KZOocFvXxkV06Ce9O5M2w=</pin>
+ <pin digest="SHA-256">YPtHaftLw6/0vnc2BnNKGF54xiCA28WFcccjkA4ypCM=</pin>
</pin-set>
</domain-config>
</domain-config>
diff --git a/tests/NetworkSecurityConfigTest/res/xml/pins1.xml b/tests/NetworkSecurityConfigTest/res/xml/pins1.xml
index 7cc81b0..2e49188 100644
--- a/tests/NetworkSecurityConfigTest/res/xml/pins1.xml
+++ b/tests/NetworkSecurityConfigTest/res/xml/pins1.xml
@@ -3,7 +3,7 @@
<domain-config>
<domain>android.com</domain>
<pin-set>
- <pin digest="SHA-256">zCTnfLwLKbS9S2sbp+uFz4KZOocFvXxkV06Ce9O5M2w=</pin>
+ <pin digest="SHA-256">YPtHaftLw6/0vnc2BnNKGF54xiCA28WFcccjkA4ypCM=</pin>
</pin-set>
</domain-config>
</network-security-config>
diff --git a/tests/NetworkSecurityConfigTest/src/android/security/net/config/NetworkSecurityConfigTests.java b/tests/NetworkSecurityConfigTest/src/android/security/net/config/NetworkSecurityConfigTests.java
index c6fe068..6207a62 100644
--- a/tests/NetworkSecurityConfigTest/src/android/security/net/config/NetworkSecurityConfigTests.java
+++ b/tests/NetworkSecurityConfigTest/src/android/security/net/config/NetworkSecurityConfigTests.java
@@ -40,9 +40,9 @@
super(Activity.class);
}
- // SHA-256 of the GTS intermediate CA (CN = GTS CA 1C3) for android.com (as of 09/2023).
+ // SHA-256 of the GTS intermediate CA (CN = WR2) for android.com (as of 01/2025).
private static final byte[] GTS_INTERMEDIATE_SPKI_SHA256 =
- hexToBytes("cc24e77cbc0b29b4bd4b6b1ba7eb85cf82993a8705bd7c64574e827bd3b9336c");
+ hexToBytes("60fb4769fb4bc3aff4be773606734a185e78c62080dbc58571c723900e32a423");
private static final byte[] TEST_CA_BYTES
= hexToBytes(
diff --git a/tests/NetworkSecurityConfigTest/src/android/security/net/config/TestUtils.java b/tests/NetworkSecurityConfigTest/src/android/security/net/config/TestUtils.java
index 39b5cb4c..e140d1a 100644
--- a/tests/NetworkSecurityConfigTest/src/android/security/net/config/TestUtils.java
+++ b/tests/NetworkSecurityConfigTest/src/android/security/net/config/TestUtils.java
@@ -55,6 +55,7 @@
throws Exception {
URL url = new URL("https://" + host + ":" + port);
HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
+ connection.setInstanceFollowRedirects(false);
connection.setSSLSocketFactory(context.getSocketFactory());
try {
connection.getInputStream();
@@ -68,6 +69,7 @@
throws Exception {
URL url = new URL("https://" + host + ":" + port);
HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
+ connection.setInstanceFollowRedirects(false);
connection.setSSLSocketFactory(context.getSocketFactory());
connection.getInputStream();
}
diff --git a/tools/aapt2/dump/DumpManifest.cpp b/tools/aapt2/dump/DumpManifest.cpp
index a596229..82ad9fa 100644
--- a/tools/aapt2/dump/DumpManifest.cpp
+++ b/tools/aapt2/dump/DumpManifest.cpp
@@ -2778,7 +2778,7 @@
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));
}