Merge "Move the Tile composable in InfiniteGridLayout." into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index fa11278..a42adad 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -173,6 +173,11 @@
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
+cc_aconfig_library {
+ name: "com.android.window.flags.window-aconfig_flags_c_lib",
+ aconfig_declarations: "com.android.window.flags.window-aconfig",
+}
+
// DeviceStateManager
aconfig_declarations {
name: "android.hardware.devicestate.feature.flags-aconfig",
diff --git a/Ravenwood.bp b/Ravenwood.bp
index 74382a6..3ab0934 100644
--- a/Ravenwood.bp
+++ b/Ravenwood.bp
@@ -270,6 +270,7 @@
],
jni_libs: [
"libandroid_runtime",
+ "libravenwood_runtime",
],
}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/TEST_MAPPING b/apex/jobscheduler/service/java/com/android/server/job/TEST_MAPPING
index e649485..e82df12 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/TEST_MAPPING
+++ b/apex/jobscheduler/service/java/com/android/server/job/TEST_MAPPING
@@ -41,10 +41,10 @@
]
},
{
- "name": "CtsHostsideNetworkTests",
+ "name": "CtsHostsideNetworkPolicyTests",
"options": [
- {"include-filter": "com.android.cts.net.HostsideRestrictBackgroundNetworkTests#testMeteredNetworkAccess_expeditedJob"},
- {"include-filter": "com.android.cts.net.HostsideRestrictBackgroundNetworkTests#testNonMeteredNetworkAccess_expeditedJob"}
+ {"include-filter": "com.android.cts.netpolicy.HostsideRestrictBackgroundNetworkTests#testMeteredNetworkAccess_expeditedJob"},
+ {"include-filter": "com.android.cts.netpolicy.HostsideRestrictBackgroundNetworkTests#testNonMeteredNetworkAccess_expeditedJob"}
]
},
{
diff --git a/api/StubLibraries.bp b/api/StubLibraries.bp
index f56a950..28efa1e 100644
--- a/api/StubLibraries.bp
+++ b/api/StubLibraries.bp
@@ -1144,31 +1144,47 @@
],
}
-droidstubs {
- name: "api_versions_public",
- srcs: [":android_stubs_current_with_test_libs{.jar}"],
+// Defaults for `droidstubs` modules that generate `api-versions.xml` files for
+// the various API surfaces.
+stubs_defaults {
+ name: "api_versions_base_defaults",
+ defaults_visibility: ["//visibility:private"],
generate_stubs: false,
api_levels_annotations_enabled: true,
api_levels_annotations_dirs: [
"sdk-dir",
"api-versions-jars-dir",
],
- api_levels_sdk_type: "public",
+}
+
+// Defaults for `droidstubs` modules that generate complete `api-versions.xml`
+// files, i.e. include SDK extensions.
+stubs_defaults {
+ name: "api_versions_complete_defaults",
+ defaults_visibility: ["//visibility:private"],
+ defaults: ["api_versions_base_defaults"],
extensions_info_file: ":sdk-extensions-info",
+}
+
+// Produces an `api-versions.xml` file that includes up-to-date information
+// about all the public APIs, both updatable and non-updatable and historic
+// information about all previous dessert and SDK extension releases.
+droidstubs {
+ name: "api_versions_public",
+ defaults: ["api_versions_complete_defaults"],
+ srcs: [":android_stubs_current_with_test_libs{.jar}"],
+ api_levels_sdk_type: "public",
visibility: ["//frameworks/base"],
}
+// Produces an `api-versions.xml` file that includes up-to-date information
+// about all the system APIs, both updatable and non-updatable and historic
+// information about all previous dessert and SDK extension releases.
droidstubs {
name: "api_versions_system",
+ defaults: ["api_versions_complete_defaults"],
srcs: [":android_system_stubs_current_with_test_libs{.jar}"],
- generate_stubs: false,
- api_levels_annotations_enabled: true,
- api_levels_annotations_dirs: [
- "sdk-dir",
- "api-versions-jars-dir",
- ],
api_levels_sdk_type: "system",
- extensions_info_file: ":sdk-extensions-info",
dists: [
// Make the api-versions.xml file for the system API available in the
// sdk build target.
@@ -1180,42 +1196,43 @@
],
}
-// This module can be built with:
-// m out/soong/.intermediates/frameworks/base/api/api_versions_module_lib/android_common/metalava/api-versions.xml
-droidstubs {
- name: "api_versions_module_lib",
- srcs: [":android_module_stubs_current_with_test_libs{.jar}"],
- generate_stubs: false,
- api_levels_annotations_enabled: true,
+// Defaults for `droidstubs` modules that generate `api-versions.xml` files that
+// only include non-updatable code, i.e. for platform API only, not SDK
+// extensions.
+stubs_defaults {
+ name: "api_versions_non_updatable_defaults",
+ defaults_visibility: ["//visibility:private"],
+ defaults: ["api_versions_base_defaults"],
// this only has the non-updatable portions of the module lib sdk,
// which can reference classes from updatable apexes, so remove references to them
// from this api_versions file.
flags: ["--remove-missing-class-references-in-api-levels"],
- api_levels_annotations_dirs: [
- "sdk-dir",
- "api-versions-jars-dir",
- ],
- api_levels_sdk_type: "module-lib",
// extensions_info_file is purposefully omitted, because this module should just be
// the non-updatable portions of the sdk, and extension sdks are updatable.
}
+// Produces an `api-versions.xml` file that includes up-to-date information
+// about only the non-updatable module-lib APIs and historic information about
+// all previous dessert and SDK extension releases. That historic information
+// may include information about APIs that were previously not-updatable which
+// have since become updatable.
+droidstubs {
+ name: "api_versions_module_lib",
+ defaults: ["api_versions_non_updatable_defaults"],
+ srcs: [":android_module_stubs_current_with_test_libs{.jar}"],
+ api_levels_sdk_type: "module-lib",
+}
+
+// Produces an `api-versions.xml` file that includes up-to-date information
+// about only the non-updatable system-server APIs and historic information
+// about all previous dessert and SDK extension releases. That historic
+// information may include information about APIs that were previously
+// not-updatable which have since become updatable.
droidstubs {
name: "api_versions_system_server",
+ defaults: ["api_versions_non_updatable_defaults"],
srcs: [":android_system_server_stubs_current_with_test_libs{.jar}"],
- generate_stubs: false,
- api_levels_annotations_enabled: true,
- // this only has the non-updatable portions of the system server sdk,
- // which can reference classes from updatable apexes, so remove references to them
- // from this api_versions file.
- flags: ["--remove-missing-class-references-in-api-levels"],
- api_levels_annotations_dirs: [
- "sdk-dir",
- "api-versions-jars-dir",
- ],
api_levels_sdk_type: "system-server",
- // extensions_info_file is purposefully omitted, because this module should just be
- // the non-updatable portions of the sdk, and extension sdks are updatable.
}
/////////////////////////////////////////////////////////////////////
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index f10c0fc..eabe1f1 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -872,6 +872,7 @@
public static final class AppOpsManager.OpEventProxyInfo implements android.os.Parcelable {
method public int describeContents();
method @Nullable public String getAttributionTag();
+ method @FlaggedApi("android.permission.flags.device_id_in_op_proxy_info_enabled") @Nullable public String getDeviceId();
method @Nullable public String getPackageName();
method @IntRange(from=0) public int getUid();
method public void writeToParcel(@NonNull android.os.Parcel, int);
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 0372b7b..2437be8 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1529,9 +1529,9 @@
package android.hardware {
@Deprecated public class Camera {
- method @Deprecated public static void getCameraInfo(int, @NonNull android.content.Context, boolean, android.hardware.Camera.CameraInfo);
+ method @Deprecated public static void getCameraInfo(int, @NonNull android.content.Context, int, android.hardware.Camera.CameraInfo);
method @Deprecated public static int getNumberOfCameras(@NonNull android.content.Context);
- method @Deprecated public static android.hardware.Camera open(int, @NonNull android.content.Context, boolean);
+ method @Deprecated public static android.hardware.Camera open(int, @NonNull android.content.Context, int);
method @Deprecated public final void setPreviewSurface(android.view.Surface) throws java.io.IOException;
}
@@ -1606,11 +1606,15 @@
public final class CameraManager {
method @NonNull public android.hardware.camera2.CameraCharacteristics getCameraCharacteristics(@NonNull String, boolean) throws android.hardware.camera2.CameraAccessException;
method public String[] getCameraIdListNoLazy() throws android.hardware.camera2.CameraAccessException;
+ method @FlaggedApi("com.android.window.flags.camera_compat_for_freeform") public static int getRotationOverrideInternal(@Nullable android.content.Context, @Nullable android.content.pm.PackageManager, @Nullable String);
method @RequiresPermission(android.Manifest.permission.CAMERA) public void openCamera(@NonNull String, boolean, @Nullable android.os.Handler, @NonNull android.hardware.camera2.CameraDevice.StateCallback) throws android.hardware.camera2.CameraAccessException;
method @RequiresPermission(allOf={android.Manifest.permission.SYSTEM_CAMERA, android.Manifest.permission.CAMERA}) public void openCamera(@NonNull String, int, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.camera2.CameraDevice.StateCallback) throws android.hardware.camera2.CameraAccessException;
method public static boolean shouldOverrideToPortrait(@Nullable android.content.pm.PackageManager, @Nullable String);
field public static final String LANDSCAPE_TO_PORTRAIT_PROP = "camera.enable_landscape_to_portrait";
field public static final long OVERRIDE_CAMERA_LANDSCAPE_TO_PORTRAIT = 250678880L; // 0xef10e60L
+ field @FlaggedApi("com.android.window.flags.camera_compat_for_freeform") public static final int ROTATION_OVERRIDE_NONE = 0; // 0x0
+ field @FlaggedApi("com.android.window.flags.camera_compat_for_freeform") public static final int ROTATION_OVERRIDE_OVERRIDE_TO_PORTRAIT = 1; // 0x1
+ field @FlaggedApi("com.android.window.flags.camera_compat_for_freeform") public static final int ROTATION_OVERRIDE_ROTATION_ONLY = 2; // 0x2
}
public abstract static class CameraManager.AvailabilityCallback {
@@ -3989,15 +3993,15 @@
method @RequiresPermission(android.Manifest.permission.TEST_INPUT_METHOD) public void addVirtualStylusIdForTestSession();
method @RequiresPermission(android.Manifest.permission.TEST_INPUT_METHOD) public void finishTrackingPendingImeVisibilityRequests();
method public int getDisplayId();
- method @FlaggedApi("android.view.inputmethod.imm_userhandle_hostsidetests") @NonNull @RequiresPermission(value=android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional=true) public java.util.List<android.view.inputmethod.InputMethodInfo> getEnabledInputMethodListAsUser(@NonNull android.os.UserHandle);
- method @FlaggedApi("android.view.inputmethod.imm_userhandle_hostsidetests") @NonNull @RequiresPermission(value=android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional=true) public java.util.List<android.view.inputmethod.InputMethodSubtype> getEnabledInputMethodSubtypeListAsUser(@NonNull String, boolean, @NonNull android.os.UserHandle);
+ method @NonNull @RequiresPermission(value=android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional=true) public java.util.List<android.view.inputmethod.InputMethodInfo> getEnabledInputMethodListAsUser(@NonNull android.os.UserHandle);
+ method @NonNull @RequiresPermission(value=android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional=true) public java.util.List<android.view.inputmethod.InputMethodSubtype> getEnabledInputMethodSubtypeListAsUser(@NonNull String, boolean, @NonNull android.os.UserHandle);
method @NonNull @RequiresPermission(value=android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional=true) public java.util.List<android.view.inputmethod.InputMethodInfo> getInputMethodListAsUser(int);
method public boolean hasActiveInputConnection(@Nullable android.view.View);
method @RequiresPermission(android.Manifest.permission.TEST_INPUT_METHOD) public boolean hasPendingImeVisibilityRequests();
method @RequiresPermission(android.Manifest.permission.TEST_INPUT_METHOD) public void hideSoftInputFromServerForTest();
method @RequiresPermission(android.Manifest.permission.TEST_INPUT_METHOD) public boolean isCurrentRootView(@NonNull android.view.View);
method @RequiresPermission(android.Manifest.permission.TEST_INPUT_METHOD) public boolean isInputMethodPickerShown();
- method @FlaggedApi("android.view.inputmethod.imm_userhandle_hostsidetests") @NonNull @RequiresPermission(value=android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional=true) public boolean isStylusHandwritingAvailableAsUser(@NonNull android.os.UserHandle);
+ method @NonNull @RequiresPermission(value=android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional=true) public boolean isStylusHandwritingAvailableAsUser(@NonNull android.os.UserHandle);
method @RequiresPermission(android.Manifest.permission.TEST_INPUT_METHOD) public void setStylusWindowIdleTimeoutForTest(long);
field public static final long CLEAR_SHOW_FORCED_FLAG_WHEN_LEAVING = 214016041L; // 0xcc1a029L
}
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 68cc17b..caaaf51 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -7438,6 +7438,7 @@
}
mDdmSyncStageUpdater.next(Stage.Running);
+ long timestampApplicationOnCreateNs = 0;
try {
// If the app is being launched for full backup or restore, bring it up in
// a restricted environment with the base application class.
@@ -7480,8 +7481,10 @@
+ data.instrumentationName + ": " + e.toString(), e);
}
try {
+ timestampApplicationOnCreateNs = SystemClock.elapsedRealtimeNanos();
mInstrumentation.callApplicationOnCreate(app);
} catch (Exception e) {
+ timestampApplicationOnCreateNs = 0;
if (!mInstrumentation.onException(app, e)) {
throw new RuntimeException(
"Unable to create application " + app.getClass().getName()
@@ -7519,7 +7522,7 @@
}
try {
- mgr.finishAttachApplication(mStartSeq);
+ mgr.finishAttachApplication(mStartSeq, timestampApplicationOnCreateNs);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
diff --git a/core/java/android/app/AppOpInfo.java b/core/java/android/app/AppOpInfo.java
index 5268ec4..a0f0cca 100644
--- a/core/java/android/app/AppOpInfo.java
+++ b/core/java/android/app/AppOpInfo.java
@@ -88,7 +88,7 @@
/**
* This specifies whether each option is only allowed to be read
- * by apps with manage appops permission.
+ * by apps with privileged appops permission.
*/
public final boolean restrictRead;
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 8e766c9..2d0f6fc 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -3265,7 +3265,7 @@
}
/**
- * Retrieve whether the op can be read by apps with manage appops permission.
+ * Retrieve whether the op can be read by apps with privileged appops permission.
* @hide
*/
public static boolean opRestrictsRead(int op) {
@@ -3457,6 +3457,8 @@
private @Nullable String mPackageName;
/** Attribution tag of the proxy that noted the op */
private @Nullable String mAttributionTag;
+ /** Persistent device Id of the proxy that noted the op */
+ private @Nullable String mDeviceId;
/**
* Reinit existing object with new state.
@@ -3464,14 +3466,16 @@
* @param uid UID of the proxy app that noted the op
* @param packageName Package of the proxy that noted the op
* @param attributionTag attribution tag of the proxy that noted the op
+ * @param deviceId Persistent device Id of the proxy that noted the op
*
* @hide
*/
public void reinit(@IntRange(from = 0) int uid, @Nullable String packageName,
- @Nullable String attributionTag) {
+ @Nullable String attributionTag, @Nullable String deviceId) {
mUid = Preconditions.checkArgumentNonnegative(uid);
mPackageName = packageName;
mAttributionTag = attributionTag;
+ mDeviceId = deviceId;
}
@@ -3505,16 +3509,33 @@
@IntRange(from = 0) int uid,
@Nullable String packageName,
@Nullable String attributionTag) {
+ this(uid, packageName, attributionTag,
+ VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT);
+ }
+
+ /**
+ * Creates a new OpEventProxyInfo.
+ *
+ * @param uid UID of the proxy app that noted the op
+ * @param packageName Package of the proxy that noted the op
+ * @param attributionTag Attribution tag of the proxy that noted the op
+ * @param deviceId Persistent device Id of the proxy that noted the op
+ *
+ * @hide
+ */
+ public OpEventProxyInfo(
+ @IntRange(from = 0) int uid,
+ @Nullable String packageName,
+ @Nullable String attributionTag,
+ @Nullable String deviceId) {
this.mUid = uid;
com.android.internal.util.AnnotationValidations.validate(
IntRange.class, null, mUid,
"from", 0);
this.mPackageName = packageName;
this.mAttributionTag = attributionTag;
-
- // onConstructed(); // You can define this method to get a callback
+ this.mDeviceId = deviceId;
}
-
/**
* Copy constructor
*
@@ -3525,6 +3546,7 @@
mUid = orig.mUid;
mPackageName = orig.mPackageName;
mAttributionTag = orig.mAttributionTag;
+ mDeviceId = orig.mDeviceId;
}
/**
@@ -3551,6 +3573,9 @@
return mAttributionTag;
}
+ @FlaggedApi(Flags.FLAG_DEVICE_ID_IN_OP_PROXY_INFO_ENABLED)
+ public @Nullable String getDeviceId() { return mDeviceId; }
+
@Override
@DataClass.Generated.Member
public void writeToParcel(@NonNull Parcel dest, int flags) {
@@ -3560,10 +3585,12 @@
byte flg = 0;
if (mPackageName != null) flg |= 0x2;
if (mAttributionTag != null) flg |= 0x4;
+ if (mDeviceId != null) flg |= 0x8;
dest.writeByte(flg);
dest.writeInt(mUid);
if (mPackageName != null) dest.writeString(mPackageName);
if (mAttributionTag != null) dest.writeString(mAttributionTag);
+ if (mDeviceId != null) dest.writeString(mDeviceId);
}
@Override
@@ -3581,14 +3608,14 @@
int uid = in.readInt();
String packageName = (flg & 0x2) == 0 ? null : in.readString();
String attributionTag = (flg & 0x4) == 0 ? null : in.readString();
-
+ String deviceId = (flg & 0x8) == 0 ? null : in.readString();
this.mUid = uid;
com.android.internal.util.AnnotationValidations.validate(
IntRange.class, null, mUid,
"from", 0);
this.mPackageName = packageName;
this.mAttributionTag = attributionTag;
-
+ this.mDeviceId = deviceId;
// onConstructed(); // You can define this method to get a callback
}
diff --git a/core/java/android/app/GrammaticalInflectionManager.java b/core/java/android/app/GrammaticalInflectionManager.java
index f0bc3e2..37e51f8 100644
--- a/core/java/android/app/GrammaticalInflectionManager.java
+++ b/core/java/android/app/GrammaticalInflectionManager.java
@@ -104,7 +104,7 @@
* Sets the current grammatical gender for all privileged applications. The value will be
* stored in an encrypted file at {@link android.os.Environment#getDataSystemCeDirectory(int)}
*
- * @param grammaticalGender the terms of address the user preferred in system.
+ * @param grammaticalGender the grammatical gender set by the user for the system.
*
* @see Configuration#getGrammaticalGender
* @hide
@@ -123,12 +123,12 @@
}
/**
- * Get the current grammatical gender of privileged application from the encrypted file.
+ * Allows privileged preloaded applications to get the system grammatical gender when set.
*
- * @return the value of system grammatical gender only if the calling app has the permission,
- * otherwise throwing an exception.
+ * @return The value of system grammatical gender only if the calling app has the
+ * permission, otherwise throwing an exception.
*
- * @throws SecurityException if the caller does not have the required permission.
+ * @throws SecurityException If the caller does not have the required permission.
*
* @see Configuration#getGrammaticalGender
*/
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index 3765c81..e8b57f2 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -196,7 +196,7 @@
oneway void finishReceiver(in IBinder who, int resultCode, in String resultData, in Bundle map,
boolean abortBroadcast, int flags);
void attachApplication(in IApplicationThread app, long startSeq);
- void finishAttachApplication(long startSeq);
+ void finishAttachApplication(long startSeq, long timestampApplicationOnCreateNs);
List<ActivityManager.RunningTaskInfo> getTasks(int maxNum);
@UnsupportedAppUsage
void moveTaskToFront(in IApplicationThread caller, in String callingPackage, int task,
diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java
index f1e44cc..1df8f63 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -1104,6 +1104,10 @@
return true;
}
+ if (mDataDir == null) {
+ return false;
+ }
+
// Temporarily disable logging of disk reads on the Looper thread as this is necessary -
// and the loader will access the directory anyway if we don't check it.
StrictMode.ThreadPolicy oldThreadPolicy = allowThreadDiskReads();
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 4d7e29b..c3bac71 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -18,7 +18,6 @@
import static android.annotation.Dimension.DP;
import static android.app.Flags.evenlyDividedCallStyleActionLayout;
-import static android.app.Flags.updateRankingTime;
import static android.app.admin.DevicePolicyResources.Drawables.Source.NOTIFICATION;
import static android.app.admin.DevicePolicyResources.Drawables.Style.SOLID_COLORED;
import static android.app.admin.DevicePolicyResources.Drawables.WORK_PROFILE_ICON;
@@ -897,15 +896,16 @@
/**
* Sphere of visibility of this notification, which affects how and when the SystemUI reveals
* the notification's presence and contents in untrusted situations (namely, on the secure
- * lockscreen).
+ * lockscreen and during screen sharing).
*
* The default level, {@link #VISIBILITY_PRIVATE}, behaves exactly as notifications have always
* done on Android: The notification's {@link #icon} and {@link #tickerText} (if available) are
* shown in all situations, but the contents are only available if the device is unlocked for
- * the appropriate user.
+ * the appropriate user and there is no active screen sharing session.
*
* A more permissive policy can be expressed by {@link #VISIBILITY_PUBLIC}; such a notification
- * can be read even in an "insecure" context (that is, above a secure lockscreen).
+ * can be read even in an "insecure" context (that is, above a secure lockscreen or while
+ * screen sharing with a remote viewer).
* To modify the public version of this notification—for example, to redact some portions—see
* {@link Builder#setPublicVersion(Notification)}.
*
@@ -924,7 +924,8 @@
public @interface Visibility {}
/**
- * Notification visibility: Show this notification in its entirety on all lockscreens.
+ * Notification visibility: Show this notification in its entirety on all lockscreens and while
+ * screen sharing.
*
* {@see #visibility}
*/
@@ -932,14 +933,16 @@
/**
* Notification visibility: Show this notification on all lockscreens, but conceal sensitive or
- * private information on secure lockscreens.
+ * private information on secure lockscreens. Conceal sensitive or private information while
+ * screen sharing.
*
* {@see #visibility}
*/
public static final int VISIBILITY_PRIVATE = 0;
/**
- * Notification visibility: Do not reveal any part of this notification on a secure lockscreen.
+ * Notification visibility: Do not reveal any part of this notification on a secure lockscreen
+ * or while screen sharing.
*
* {@see #visibility}
*/
@@ -2596,7 +2599,7 @@
public Notification()
{
this.when = System.currentTimeMillis();
- if (updateRankingTime()) {
+ if (Flags.sortSectionByTime()) {
creationTime = when;
extras.putBoolean(EXTRA_SHOW_WHEN, true);
} else {
@@ -2612,7 +2615,7 @@
public Notification(Context context, int icon, CharSequence tickerText, long when,
CharSequence contentTitle, CharSequence contentText, Intent contentIntent)
{
- if (updateRankingTime()) {
+ if (Flags.sortSectionByTime()) {
creationTime = when;
extras.putBoolean(EXTRA_SHOW_WHEN, true);
}
@@ -2645,7 +2648,7 @@
this.icon = icon;
this.tickerText = tickerText;
this.when = when;
- if (updateRankingTime()) {
+ if (Flags.sortSectionByTime()) {
creationTime = when;
extras.putBoolean(EXTRA_SHOW_WHEN, true);
} else {
@@ -3256,8 +3259,9 @@
boolean mustClearCookie = false;
if (!parcel.hasClassCookie(Notification.class)) {
// This is the "root" notification, and not an "inner" notification (including
- // publicVersion or anything else that might be embedded in extras).
- parcel.setClassCookie(Notification.class, this);
+ // publicVersion or anything else that might be embedded in extras). So we want
+ // to use its token for every inner notification (might be null).
+ parcel.setClassCookie(Notification.class, mAllowlistToken);
mustClearCookie = true;
}
try {
@@ -3266,7 +3270,7 @@
writeToParcelImpl(parcel, flags);
} finally {
if (mustClearCookie) {
- parcel.removeClassCookie(Notification.class, this);
+ parcel.removeClassCookie(Notification.class, mAllowlistToken);
}
}
} else {
@@ -3290,14 +3294,9 @@
parcel.writeInt(1);
if (Flags.secureAllowlistToken()) {
- Notification rootNotification = (Notification) parcel.getClassCookie(
- Notification.class);
- if (rootNotification != null && rootNotification != this) {
- // Always use the same token as the root notification
- parcel.writeStrongBinder(rootNotification.mAllowlistToken);
- } else {
- parcel.writeStrongBinder(mAllowlistToken);
- }
+ // Always use the same token as the root notification (might be null).
+ IBinder rootNotificationToken = (IBinder) parcel.getClassCookie(Notification.class);
+ parcel.writeStrongBinder(rootNotificationToken);
} else {
parcel.writeStrongBinder(mAllowlistToken);
}
@@ -5981,21 +5980,22 @@
}
if (mN.extras.getBoolean(EXTRA_SHOW_CHRONOMETER)) {
contentView.setViewVisibility(R.id.chronometer, View.VISIBLE);
- contentView.setLong(R.id.chronometer, "setBase",
- mN.when + (SystemClock.elapsedRealtime() - System.currentTimeMillis()));
+ contentView.setLong(R.id.chronometer, "setBase", mN.getWhen()
+ + (SystemClock.elapsedRealtime() - System.currentTimeMillis()));
contentView.setBoolean(R.id.chronometer, "setStarted", true);
boolean countsDown = mN.extras.getBoolean(EXTRA_CHRONOMETER_COUNT_DOWN);
contentView.setChronometerCountDown(R.id.chronometer, countsDown);
setTextViewColorSecondary(contentView, R.id.chronometer, p);
} else {
contentView.setViewVisibility(R.id.time, View.VISIBLE);
- contentView.setLong(R.id.time, "setTime", mN.when);
+ contentView.setLong(R.id.time, "setTime", mN.getWhen());
setTextViewColorSecondary(contentView, R.id.time, p);
}
} else {
// We still want a time to be set but gone, such that we can show and hide it
// on demand in case it's a child notification without anything in the header
- contentView.setLong(R.id.time, "setTime", mN.when != 0 ? mN.when : mN.creationTime);
+ contentView.setLong(R.id.time, "setTime", mN.getWhen() != 0 ? mN.getWhen() :
+ mN.creationTime);
setTextViewColorSecondary(contentView, R.id.time, p);
}
}
@@ -7162,7 +7162,7 @@
}
}
- if (!updateRankingTime()) {
+ if (!Flags.sortSectionByTime()) {
mN.creationTime = System.currentTimeMillis();
}
@@ -7615,10 +7615,29 @@
}
/**
+ * Returns #when, unless it's set to 0, which should be shown as/treated as a 'current'
+ * notification. 0 is treated as a special value because it was special in an old version of
+ * android, and some apps are still (incorrectly) using it.
+ *
+ * @hide
+ */
+ public long getWhen() {
+ if (Flags.sortSectionByTime()) {
+ if (when == 0) {
+ return creationTime;
+ }
+ }
+ return when;
+ }
+
+ /**
* @return true if the notification will show the time; false otherwise
* @hide
*/
public boolean showsTime() {
+ if (Flags.sortSectionByTime()) {
+ return extras.getBoolean(EXTRA_SHOW_WHEN);
+ }
return when != 0 && extras.getBoolean(EXTRA_SHOW_WHEN);
}
@@ -7627,6 +7646,9 @@
* @hide
*/
public boolean showsChronometer() {
+ if (Flags.sortSectionByTime()) {
+ return extras.getBoolean(EXTRA_SHOW_CHRONOMETER);
+ }
return when != 0 && extras.getBoolean(EXTRA_SHOW_CHRONOMETER);
}
@@ -9770,6 +9792,12 @@
* You can opt-out of this behavior by using {@link Notification.Builder#setColorized(boolean)}.
* <p>
*
+ * <p>
+ * Starting at {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM Android V} the
+ * {@link Notification#FLAG_NO_CLEAR NO_CLEAR flag} will be set for valid MediaStyle
+ * notifications.
+ * <p>
+ *
* To use this style with your Notification, feed it to
* {@link Notification.Builder#setStyle(android.app.Notification.Style)} like so:
* <pre class="prettyprint">
diff --git a/core/java/android/app/Service.java b/core/java/android/app/Service.java
index f092945..726064e 100644
--- a/core/java/android/app/Service.java
+++ b/core/java/android/app/Service.java
@@ -1197,6 +1197,25 @@
/**
* Callback called when a particular foreground service type has timed out.
*
+ * <p>This callback is meant to give the app a small grace period of a few seconds to finish
+ * the foreground service of the associated type - if it fails to do so, the app will be
+ * declared an ANR.
+ *
+ * <p>The foreground service of the associated type can be stopped within the time limit by
+ * {@link android.app.Service#stopSelf()},
+ * {@link android.content.Context#stopService(android.content.Intent)} or their overloads.
+ * {@link android.app.Service#stopForeground(int)} can be used as well, which demotes the
+ * service to a "background" service, which will soon be stopped by the system.
+ *
+ * <p>The specific time limit for each type (if one exists) is mentioned in the documentation
+ * for that foreground service type. See
+ * {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_DATA_SYNC dataSync} for example.
+ *
+ * <p>Note: time limits are restricted to a rolling 24-hour window - for example, if a
+ * foreground service type has a time limit of 6 hours, that time counter begins as soon as the
+ * foreground service starts. This time limit will only be reset once every 24 hours or if the
+ * app comes into the foreground state.
+ *
* @param startId the startId passed to {@link #onStartCommand(Intent, int, int)} when
* the service started.
* @param fgsType the {@link ServiceInfo.ForegroundServiceType foreground service type} which
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 8171723..9437c74 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -300,6 +300,16 @@
@EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
static final long ENABLE_CHECKING_TELEPHONY_FEATURES_FOR_VCN = 330902016;
+ /**
+ * The corresponding vendor API for Android V
+ *
+ * <p>Starting with Android V, the vendor API format has switched to YYYYMM.
+ *
+ * @see <a href="https://preview.source.android.com/docs/core/architecture/api-flags">Vendor API
+ * level</a>
+ */
+ private static final int VENDOR_API_FOR_ANDROID_V = 202404;
+
// Service registry information.
// This information is never changed once static initialization has completed.
private static final Map<Class<?>, String> SYSTEM_SERVICE_NAMES =
@@ -465,9 +475,10 @@
new CachedServiceFetcher<VcnManager>() {
@Override
public VcnManager createService(ContextImpl ctx) throws ServiceNotFoundException {
- if (shouldCheckTelephonyFeatures()
- && !ctx.getPackageManager().hasSystemFeature(
- PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)) {
+ final String telephonyFeatureToCheck = getVcnFeatureDependency();
+
+ if (telephonyFeatureToCheck != null
+ && !ctx.getPackageManager().hasSystemFeature(telephonyFeatureToCheck)) {
return null;
}
@@ -1768,16 +1779,24 @@
// partition SDK level, not application's target SDK version (which BTW we
// also check through Compatibility framework a few lines below).
@SuppressWarnings("AndroidFrameworkCompatChange")
- private static boolean shouldCheckTelephonyFeatures() {
+ @Nullable
+ private static String getVcnFeatureDependency() {
+ // Check SDK version of the client app. Apps targeting pre-V SDK might
+ // have not checked for existence of these features.
+ if (!Compatibility.isChangeEnabled(ENABLE_CHECKING_TELEPHONY_FEATURES_FOR_VCN)) {
+ return null;
+ }
+
// Check SDK version of the vendor partition. Pre-V devices might have
// incorrectly under-declared telephony features.
final int vendorApiLevel = SystemProperties.getInt(
"ro.vendor.api_level", Build.VERSION.DEVICE_INITIAL_SDK_INT);
- if (vendorApiLevel < Build.VERSION_CODES.VANILLA_ICE_CREAM) return false;
+ if (vendorApiLevel < VENDOR_API_FOR_ANDROID_V) {
+ return PackageManager.FEATURE_TELEPHONY;
+ } else {
+ return PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION;
+ }
- // Check SDK version of the client app. Apps targeting pre-V SDK might
- // have not checked for existence of these features.
- return Compatibility.isChangeEnabled(ENABLE_CHECKING_TELEPHONY_FEATURES_FOR_VCN);
}
/**
diff --git a/core/java/android/app/activity_manager.aconfig b/core/java/android/app/activity_manager.aconfig
index e4425ca..8c4667f 100644
--- a/core/java/android/app/activity_manager.aconfig
+++ b/core/java/android/app/activity_manager.aconfig
@@ -40,3 +40,13 @@
description: "Add a new callback in Service to indicate a FGS has reached its timeout."
bug: "317799821"
}
+
+flag {
+ namespace: "system_performance"
+ name: "app_start_info_timestamps"
+ description: "Additional timestamps."
+ bug: "287153617"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/core/java/android/app/trust/TrustManager.java b/core/java/android/app/trust/TrustManager.java
index e5f2976..23b2ea4 100644
--- a/core/java/android/app/trust/TrustManager.java
+++ b/core/java/android/app/trust/TrustManager.java
@@ -18,6 +18,7 @@
import android.Manifest;
import android.annotation.RequiresPermission;
+import android.annotation.SdkConstant;
import android.annotation.SystemService;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
@@ -40,6 +41,15 @@
@SystemService(Context.TRUST_SERVICE)
public class TrustManager {
+ /**
+ * Intent action used to identify services that can serve as significant providers.
+ *
+ * @hide
+ */
+ @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
+ public static final String ACTION_BIND_SIGNIFICANT_PLACE_PROVIDER =
+ "com.android.trust.provider.SignificantPlaceProvider.BIND";
+
private static final int MSG_TRUST_CHANGED = 1;
private static final int MSG_TRUST_MANAGED_CHANGED = 2;
private static final int MSG_TRUST_ERROR = 3;
diff --git a/core/java/android/appwidget/AppWidgetHost.java b/core/java/android/appwidget/AppWidgetHost.java
index 2dced96..72f992a 100644
--- a/core/java/android/appwidget/AppWidgetHost.java
+++ b/core/java/android/appwidget/AppWidgetHost.java
@@ -35,6 +35,7 @@
import android.os.RemoteException;
import android.os.ServiceManager;
import android.util.DisplayMetrics;
+import android.util.Log;
import android.util.SparseArray;
import android.widget.RemoteViews;
import android.widget.RemoteViews.InteractionHandler;
@@ -52,12 +53,15 @@
*/
public class AppWidgetHost {
+ private static final String TAG = "AppWidgetHost";
+
static final int HANDLE_UPDATE = 1;
static final int HANDLE_PROVIDER_CHANGED = 2;
static final int HANDLE_PROVIDERS_CHANGED = 3;
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
static final int HANDLE_VIEW_DATA_CHANGED = 4;
static final int HANDLE_APP_WIDGET_REMOVED = 5;
+ static final int HANDLE_VIEW_UPDATE_DEFERRED = 6;
final static Object sServiceLock = new Object();
@UnsupportedAppUsage
@@ -131,6 +135,15 @@
msg.sendToTarget();
}
+ public void updateAppWidgetDeferred(int appWidgetId) {
+ Handler handler = mWeakHandler.get();
+ if (handler == null) {
+ return;
+ }
+ Message msg = handler.obtainMessage(HANDLE_VIEW_UPDATE_DEFERRED, appWidgetId, 0, null);
+ msg.sendToTarget();
+ }
+
private static boolean isLocalBinder() {
return Process.myPid() == Binder.getCallingPid();
}
@@ -163,6 +176,10 @@
viewDataChanged(msg.arg1, msg.arg2);
break;
}
+ case HANDLE_VIEW_UPDATE_DEFERRED: {
+ updateAppWidgetDeferred(msg.arg1);
+ break;
+ }
}
}
}
@@ -480,14 +497,32 @@
void onUpdateProviderInfo(@Nullable AppWidgetProviderInfo appWidget);
/**
- * This function is called when the RemoteViews of the app widget is updated
- * @param views The new RemoteViews to be set for the app widget
+ * This function is called when the {@code RemoteViews} of the app widget is updated
+ * @param views The new {@code RemoteViews} to be set for the app widget
*
* @hide
*/
void updateAppWidget(@Nullable RemoteViews views);
/**
+ * Called for the listener to handle deferred {@code RemoteViews} updates. Default
+ * implementation is to update the widget directly.
+ * @param packageName The package name used for uid verification on the service side
+ * @param appWidgetId The widget id of the listener
+ *
+ * @hide
+ */
+ default void updateAppWidgetDeferred(String packageName, int appWidgetId) {
+ RemoteViews latestViews = null;
+ try {
+ latestViews = sService.getAppWidgetViews(packageName, appWidgetId);
+ } catch (Exception e) {
+ Log.e(TAG, "updateAppWidgetDeferred: ", e);
+ }
+ updateAppWidget(latestViews);
+ }
+
+ /**
* This function is called when the view ID is changed for the app widget
* @param viewId The new view ID to be be set for the widget
*
@@ -563,6 +598,15 @@
}
}
+ private void updateAppWidgetDeferred(int appWidgetId) {
+ AppWidgetHostListener v = getListener(appWidgetId);
+ if (v == null) {
+ Log.e(TAG, "updateAppWidgetDeferred: null listener for id: " + appWidgetId);
+ return;
+ }
+ v.updateAppWidgetDeferred(mContextOpPackageName, appWidgetId);
+ }
+
/**
* Clear the list of Views that have been created by this AppWidgetHost.
*/
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index 7ac9547..4f06209 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -1348,6 +1348,17 @@
public static final long OVERRIDE_MIN_ASPECT_RATIO = 174042980L; // buganizer id
/**
+ * This change id restricts treatments that force a given min aspect ratio to
+ * only when an app is connected to the camera
+ *
+ * @hide
+ */
+ @ChangeId
+ @Overridable
+ @Disabled
+ public static final long OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA = 325586858L; // buganizer id
+
+ /**
* This change id restricts treatments that force a given min aspect ratio to activities
* whose orientation is fixed to portrait.
*
diff --git a/core/java/android/content/res/AssetFileDescriptor.java b/core/java/android/content/res/AssetFileDescriptor.java
index 3486d5e..afddc77 100644
--- a/core/java/android/content/res/AssetFileDescriptor.java
+++ b/core/java/android/content/res/AssetFileDescriptor.java
@@ -419,6 +419,10 @@
if (available <= 0) {
return -1;
}
+ if (count == 0) {
+ // Java's InputStream explicitly specifies that this returns zero.
+ return 0;
+ }
if (count > available) count = available;
try {
diff --git a/core/java/android/hardware/Camera.java b/core/java/android/hardware/Camera.java
index a9f70c9..32d2a6f 100644
--- a/core/java/android/hardware/Camera.java
+++ b/core/java/android/hardware/Camera.java
@@ -308,8 +308,8 @@
*/
public static void getCameraInfo(int cameraId, CameraInfo cameraInfo) {
Context context = ActivityThread.currentApplication().getApplicationContext();
- boolean overrideToPortrait = CameraManager.shouldOverrideToPortrait(context);
- getCameraInfo(cameraId, context, overrideToPortrait, cameraInfo);
+ final int rotationOverride = CameraManager.getRotationOverride(context);
+ getCameraInfo(cameraId, context, rotationOverride, cameraInfo);
}
/**
@@ -320,8 +320,8 @@
@SuppressLint("UnflaggedApi") // @TestApi without associated feature.
@TestApi
public static void getCameraInfo(int cameraId, @NonNull Context context,
- boolean overrideToPortrait, CameraInfo cameraInfo) {
- _getCameraInfo(cameraId, overrideToPortrait, context.getDeviceId(),
+ int rotationOverride, CameraInfo cameraInfo) {
+ _getCameraInfo(cameraId, rotationOverride, context.getDeviceId(),
getDevicePolicyFromContext(context), cameraInfo);
IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE);
IAudioService audioService = IAudioService.Stub.asInterface(b);
@@ -336,7 +336,7 @@
}
}
- private native static void _getCameraInfo(int cameraId, boolean overrideToPortrait,
+ private native static void _getCameraInfo(int cameraId, int rotationOverride,
int deviceId, int devicePolicy, CameraInfo cameraInfo);
private static int getDevicePolicyFromContext(Context context) {
@@ -441,8 +441,8 @@
*/
public static Camera open(int cameraId) {
Context context = ActivityThread.currentApplication().getApplicationContext();
- boolean overrideToPortrait = CameraManager.shouldOverrideToPortrait(context);
- return open(cameraId, context, overrideToPortrait);
+ final int rotationOverride = CameraManager.getRotationOverride(context);
+ return open(cameraId, context, rotationOverride);
}
/**
@@ -452,8 +452,8 @@
*/
@SuppressLint("UnflaggedApi") // @TestApi without associated feature.
@TestApi
- public static Camera open(int cameraId, @NonNull Context context, boolean overrideToPortrait) {
- return new Camera(cameraId, context, overrideToPortrait);
+ public static Camera open(int cameraId, @NonNull Context context, int rotationOverride) {
+ return new Camera(cameraId, context, rotationOverride);
}
/**
@@ -524,7 +524,7 @@
return open(cameraId);
}
- private int cameraInit(int cameraId, Context context, boolean overrideToPortrait) {
+ private int cameraInit(int cameraId, Context context, int rotationOverride) {
mShutterCallback = null;
mRawImageCallback = null;
mJpegCallback = null;
@@ -544,7 +544,7 @@
boolean forceSlowJpegMode = shouldForceSlowJpegMode();
return native_setup(new WeakReference<>(this), cameraId,
- ActivityThread.currentOpPackageName(), overrideToPortrait, forceSlowJpegMode,
+ ActivityThread.currentOpPackageName(), rotationOverride, forceSlowJpegMode,
context.getDeviceId(), getDevicePolicyFromContext(context));
}
@@ -562,9 +562,9 @@
}
/** used by Camera#open, Camera#open(int) */
- Camera(int cameraId, @NonNull Context context, boolean overrideToPortrait) {
+ Camera(int cameraId, @NonNull Context context, int rotationOverride) {
Objects.requireNonNull(context);
- int err = cameraInit(cameraId, context, overrideToPortrait);
+ final int err = cameraInit(cameraId, context, rotationOverride);
if (checkInitErrors(err)) {
if (err == -EACCES) {
throw new RuntimeException("Fail to connect to camera service");
@@ -629,7 +629,7 @@
@UnsupportedAppUsage
private native int native_setup(Object cameraThis, int cameraId, String packageName,
- boolean overrideToPortrait, boolean forceSlowJpegMode, int deviceId, int devicePolicy);
+ int rotationOverride, boolean forceSlowJpegMode, int deviceId, int devicePolicy);
private native final void native_release();
diff --git a/core/java/android/hardware/biometrics/BiometricTestSession.java b/core/java/android/hardware/biometrics/BiometricTestSession.java
index c62680f..027d101 100644
--- a/core/java/android/hardware/biometrics/BiometricTestSession.java
+++ b/core/java/android/hardware/biometrics/BiometricTestSession.java
@@ -92,6 +92,7 @@
mTestedUsers = new ArraySet<>();
mUsersCleaningUp = new ArraySet<>();
setTestHalEnabled(true);
+ Log.d(getTag(), "Opening BiometricTestSession");
}
/**
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index c6a8762..342479b 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -5062,21 +5062,29 @@
/**
* <p>The version of the session configuration query
* {@link android.hardware.camera2.CameraDevice.CameraDeviceSetup#isSessionConfigurationSupported }
- * API</p>
+ * and {@link android.hardware.camera2.CameraDevice.CameraDeviceSetup#getSessionCharacteristics }
+ * APIs.</p>
* <p>The possible values in this key correspond to the values defined in
* android.os.Build.VERSION_CODES. Each version defines a set of feature combinations the
* camera device must reliably report whether they are supported via
- * {@link android.hardware.camera2.CameraDevice.CameraDeviceSetup#isSessionConfigurationSupported }
- * API. And the version is always less or equal to android.os.Build.VERSION.SDK_INT.</p>
- * <p>If set to UPSIDE_DOWN_CAKE, this camera device doesn't support
* {@link android.hardware.camera2.CameraDevice.CameraDeviceSetup#isSessionConfigurationSupported }.
- * Calling the method for this camera ID throws an UnsupportedOperationException.</p>
- * <p>If set to VANILLA_ICE_CREAM, the application can call
+ * It also defines the set of session specific keys in CameraCharacteristics when returned from
+ * {@link android.hardware.camera2.CameraDevice.CameraDeviceSetup#getSessionCharacteristics }.
+ * The version is always less or equal to android.os.Build.VERSION.SDK_INT.</p>
+ * <p>If set to UPSIDE_DOWN_CAKE, this camera device doesn't support the
+ * {@link android.hardware.camera2.CameraDevice.CameraDeviceSetup } API.
+ * Trying to create a CameraDeviceSetup instance throws an UnsupportedOperationException.</p>
+ * <p>From VANILLA_ICE_CREAM onwards, the camera compliance tests verify a set of
+ * commonly used SessionConfigurations to ensure that the outputs of
* {@link android.hardware.camera2.CameraDevice.CameraDeviceSetup#isSessionConfigurationSupported }
- * to check if the combinations of below features are supported.</p>
+ * and {@link android.hardware.camera2.CameraDevice.CameraDeviceSetup#getSessionCharacteristics }
+ * are accurate. The application is encouraged to use these SessionConfigurations when turning on
+ * multiple features at the same time.</p>
+ * <p>When set to VANILLA_ICE_CREAM, the combinations of the following configurations are verified
+ * by the compliance tests:</p>
* <ul>
- * <li>A subset of LIMITED-level device stream combinations.</li>
- * </ul>
+ * <li>
+ * <p>A set of commonly used stream combinations:</p>
* <table>
* <thead>
* <tr>
@@ -5084,257 +5092,108 @@
* <th style="text-align: center;">Size</th>
* <th style="text-align: center;">Target 2</th>
* <th style="text-align: center;">Size</th>
- * <th style="text-align: center;">Sample use case(s)</th>
* </tr>
* </thead>
* <tbody>
* <tr>
* <td style="text-align: center;">PRIV</td>
- * <td style="text-align: center;">MAXIMUM</td>
- * <td style="text-align: center;"></td>
- * <td style="text-align: center;"></td>
- * <td style="text-align: center;">Simple preview, GPU video processing, or no-preview video recording.</td>
- * </tr>
- * <tr>
- * <td style="text-align: center;">PRIV</td>
- * <td style="text-align: center;">PREVIEW</td>
- * <td style="text-align: center;"></td>
- * <td style="text-align: center;"></td>
- * <td style="text-align: center;"></td>
- * </tr>
- * <tr>
- * <td style="text-align: center;">PRIV</td>
- * <td style="text-align: center;">S1440P</td>
- * <td style="text-align: center;"></td>
- * <td style="text-align: center;"></td>
- * <td style="text-align: center;"></td>
- * </tr>
- * <tr>
- * <td style="text-align: center;">PRIV</td>
* <td style="text-align: center;">S1080P</td>
* <td style="text-align: center;"></td>
* <td style="text-align: center;"></td>
- * <td style="text-align: center;"></td>
* </tr>
* <tr>
* <td style="text-align: center;">PRIV</td>
* <td style="text-align: center;">S720P</td>
* <td style="text-align: center;"></td>
* <td style="text-align: center;"></td>
- * <td style="text-align: center;"></td>
- * </tr>
- * <tr>
- * <td style="text-align: center;">YUV</td>
- * <td style="text-align: center;">MAXIMUM</td>
- * <td style="text-align: center;"></td>
- * <td style="text-align: center;"></td>
- * <td style="text-align: center;">In-application video/image processing.</td>
- * </tr>
- * <tr>
- * <td style="text-align: center;">YUV</td>
- * <td style="text-align: center;">PREVIEW</td>
- * <td style="text-align: center;"></td>
- * <td style="text-align: center;"></td>
- * <td style="text-align: center;"></td>
- * </tr>
- * <tr>
- * <td style="text-align: center;">YUV</td>
- * <td style="text-align: center;">S1440P</td>
- * <td style="text-align: center;"></td>
- * <td style="text-align: center;"></td>
- * <td style="text-align: center;"></td>
- * </tr>
- * <tr>
- * <td style="text-align: center;">YUV</td>
- * <td style="text-align: center;">S1080P</td>
- * <td style="text-align: center;"></td>
- * <td style="text-align: center;"></td>
- * <td style="text-align: center;"></td>
- * </tr>
- * <tr>
- * <td style="text-align: center;">YUV</td>
- * <td style="text-align: center;">S720P</td>
- * <td style="text-align: center;"></td>
- * <td style="text-align: center;"></td>
- * <td style="text-align: center;"></td>
- * </tr>
- * <tr>
- * <td style="text-align: center;">PRIV</td>
- * <td style="text-align: center;">PREVIEW</td>
- * <td style="text-align: center;">JPEG</td>
- * <td style="text-align: center;">MAXIMUM</td>
- * <td style="text-align: center;">Standard still imaging.</td>
- * </tr>
- * <tr>
- * <td style="text-align: center;">PRIV</td>
- * <td style="text-align: center;">S1440P</td>
- * <td style="text-align: center;">JPEG</td>
- * <td style="text-align: center;">MAXIMUM</td>
- * <td style="text-align: center;"></td>
* </tr>
* <tr>
* <td style="text-align: center;">PRIV</td>
* <td style="text-align: center;">S1080P</td>
- * <td style="text-align: center;">JPEG</td>
- * <td style="text-align: center;">MAXIMUM</td>
- * <td style="text-align: center;"></td>
+ * <td style="text-align: center;">JPEG/JPEG_R</td>
+ * <td style="text-align: center;">MAXIMUM_16_9</td>
+ * </tr>
+ * <tr>
+ * <td style="text-align: center;">PRIV</td>
+ * <td style="text-align: center;">S1080P</td>
+ * <td style="text-align: center;">JPEG/JPEG_R</td>
+ * <td style="text-align: center;">UHD</td>
+ * </tr>
+ * <tr>
+ * <td style="text-align: center;">PRIV</td>
+ * <td style="text-align: center;">S1080P</td>
+ * <td style="text-align: center;">JPEG/JPEG_R</td>
+ * <td style="text-align: center;">S1440P</td>
+ * </tr>
+ * <tr>
+ * <td style="text-align: center;">PRIV</td>
+ * <td style="text-align: center;">S1080P</td>
+ * <td style="text-align: center;">JPEG/JPEG_R</td>
+ * <td style="text-align: center;">S1080P</td>
+ * </tr>
+ * <tr>
+ * <td style="text-align: center;">PRIV</td>
+ * <td style="text-align: center;">S1080P</td>
+ * <td style="text-align: center;">PRIV</td>
+ * <td style="text-align: center;">UHD</td>
* </tr>
* <tr>
* <td style="text-align: center;">PRIV</td>
* <td style="text-align: center;">S720P</td>
- * <td style="text-align: center;">JPEG</td>
- * <td style="text-align: center;">MAXIMUM</td>
- * <td style="text-align: center;"></td>
- * </tr>
- * <tr>
- * <td style="text-align: center;">PRIV</td>
- * <td style="text-align: center;">S1440P</td>
- * <td style="text-align: center;">JPEG</td>
- * <td style="text-align: center;">S1440P</td>
- * <td style="text-align: center;"></td>
- * </tr>
- * <tr>
- * <td style="text-align: center;">PRIV</td>
- * <td style="text-align: center;">S1080P</td>
- * <td style="text-align: center;">JPEG</td>
- * <td style="text-align: center;">S1080P</td>
- * <td style="text-align: center;"></td>
+ * <td style="text-align: center;">JPEG/JPEG_R</td>
+ * <td style="text-align: center;">MAXIMUM_16_9</td>
* </tr>
* <tr>
* <td style="text-align: center;">PRIV</td>
* <td style="text-align: center;">S720P</td>
- * <td style="text-align: center;">JPEG</td>
- * <td style="text-align: center;">S1080P</td>
- * <td style="text-align: center;"></td>
- * </tr>
- * <tr>
- * <td style="text-align: center;">YUV</td>
- * <td style="text-align: center;">PREVIEW</td>
- * <td style="text-align: center;">JPEG</td>
- * <td style="text-align: center;">MAXIMUM</td>
- * <td style="text-align: center;">In-app processing plus still capture.</td>
- * </tr>
- * <tr>
- * <td style="text-align: center;">YUV</td>
- * <td style="text-align: center;">S1440P</td>
- * <td style="text-align: center;">JPEG</td>
- * <td style="text-align: center;">MAXIMUM</td>
- * <td style="text-align: center;"></td>
- * </tr>
- * <tr>
- * <td style="text-align: center;">YUV</td>
- * <td style="text-align: center;">S1080P</td>
- * <td style="text-align: center;">JPEG</td>
- * <td style="text-align: center;">MAXIMUM</td>
- * <td style="text-align: center;"></td>
- * </tr>
- * <tr>
- * <td style="text-align: center;">YUV</td>
- * <td style="text-align: center;">S720P</td>
- * <td style="text-align: center;">JPEG</td>
- * <td style="text-align: center;">MAXIMUM</td>
- * <td style="text-align: center;"></td>
- * </tr>
- * <tr>
- * <td style="text-align: center;">YUV</td>
- * <td style="text-align: center;">S1440P</td>
- * <td style="text-align: center;">JPEG</td>
- * <td style="text-align: center;">S1440P</td>
- * <td style="text-align: center;"></td>
- * </tr>
- * <tr>
- * <td style="text-align: center;">YUV</td>
- * <td style="text-align: center;">S1080P</td>
- * <td style="text-align: center;">JPEG</td>
- * <td style="text-align: center;">S1080P</td>
- * <td style="text-align: center;"></td>
- * </tr>
- * <tr>
- * <td style="text-align: center;">YUV</td>
- * <td style="text-align: center;">S720P</td>
- * <td style="text-align: center;">JPEG</td>
- * <td style="text-align: center;">S1080P</td>
- * <td style="text-align: center;"></td>
- * </tr>
- * <tr>
- * <td style="text-align: center;">PRIV</td>
- * <td style="text-align: center;">PREVIEW</td>
- * <td style="text-align: center;">PRIV</td>
- * <td style="text-align: center;">PREVIEW</td>
- * <td style="text-align: center;">Standard recording.</td>
- * </tr>
- * <tr>
- * <td style="text-align: center;">PRIV</td>
- * <td style="text-align: center;">S1440P</td>
- * <td style="text-align: center;">PRIV</td>
- * <td style="text-align: center;">S1440P</td>
- * <td style="text-align: center;"></td>
- * </tr>
- * <tr>
- * <td style="text-align: center;">PRIV</td>
- * <td style="text-align: center;">S1080P</td>
- * <td style="text-align: center;">PRIV</td>
- * <td style="text-align: center;">S1080P</td>
- * <td style="text-align: center;"></td>
+ * <td style="text-align: center;">JPEG/JPEG_R</td>
+ * <td style="text-align: center;">UHD</td>
* </tr>
* <tr>
* <td style="text-align: center;">PRIV</td>
* <td style="text-align: center;">S720P</td>
- * <td style="text-align: center;">PRIV</td>
- * <td style="text-align: center;">S720P</td>
- * <td style="text-align: center;"></td>
- * </tr>
- * <tr>
- * <td style="text-align: center;">PRIV</td>
- * <td style="text-align: center;">PREVIEW</td>
- * <td style="text-align: center;">YUV</td>
- * <td style="text-align: center;">PREVIEW</td>
- * <td style="text-align: center;">Preview plus in-app processing.</td>
- * </tr>
- * <tr>
- * <td style="text-align: center;">PRIV</td>
- * <td style="text-align: center;">S1440P</td>
- * <td style="text-align: center;">YUV</td>
- * <td style="text-align: center;">S1440P</td>
- * <td style="text-align: center;"></td>
- * </tr>
- * <tr>
- * <td style="text-align: center;">PRIV</td>
+ * <td style="text-align: center;">JPEG/JPEG_R</td>
* <td style="text-align: center;">S1080P</td>
- * <td style="text-align: center;">YUV</td>
- * <td style="text-align: center;">S1080P</td>
- * <td style="text-align: center;"></td>
* </tr>
* <tr>
* <td style="text-align: center;">PRIV</td>
- * <td style="text-align: center;">S720P</td>
- * <td style="text-align: center;">YUV</td>
- * <td style="text-align: center;">S720P</td>
- * <td style="text-align: center;"></td>
+ * <td style="text-align: center;">XVGA</td>
+ * <td style="text-align: center;">JPEG/JPEG_R</td>
+ * <td style="text-align: center;">MAXIMUM_4_3</td>
+ * </tr>
+ * <tr>
+ * <td style="text-align: center;">PRIV</td>
+ * <td style="text-align: center;">S1080P_4_3</td>
+ * <td style="text-align: center;">JPEG/JPEG_R</td>
+ * <td style="text-align: center;">MAXIMUM_4_3</td>
* </tr>
* </tbody>
* </table>
- * <pre><code>- {@code MAXIMUM} size refers to the camera device's maximum output resolution for
- * that format from {@code StreamConfigurationMap#getOutputSizes}. {@code PREVIEW} size
- * refers to the best size match to the device's screen resolution, or to 1080p
- * (@code 1920x1080}, whichever is smaller. Both sizes are guaranteed to be supported.
- *
- * - {@code S1440P} refers to {@code 1920x1440 (4:3)} and {@code 2560x1440 (16:9)}.
- * {@code S1080P} refers to {@code 1440x1080 (4:3)} and {@code 1920x1080 (16:9)}.
- * And {@code S720P} refers to {@code 960x720 (4:3)} and {@code 1280x720 (16:9)}.
- *
- * - If a combination contains a S1440P, S1080P, or S720P stream,
- * both 4:3 and 16:9 aspect ratio sizes can be queried. For example, for the
- * stream combination of {PRIV, S1440P, JPEG, MAXIMUM}, and if MAXIMUM ==
- * 4032 x 3024, the application will be able to query both
- * {PRIV, 1920 x 1440, JPEG, 4032 x 3024} and {PRIV, 2560 x 1440, JPEG, 4032 x 2268}
- * without an exception being thrown.
- * </code></pre>
* <ul>
- * <li>VIDEO_STABILIZATION_MODES: {OFF, PREVIEW}</li>
- * <li>AE_TARGET_FPS_RANGE: { {<em>, 30}, {</em>, 60} }</li>
- * <li>DYNAMIC_RANGE_PROFILE: {STANDARD, HLG10}</li>
+ * <li>{@code MAXIMUM_4_3} refers to the camera device's maximum output resolution with
+ * 4:3 aspect ratio for that format from {@code StreamConfigurationMap#getOutputSizes}.</li>
+ * <li>{@code MAXIMUM_16_9} is the maximum output resolution with 16:9 aspect ratio.</li>
+ * <li>{@code S1440P} refers to {@code 2560x1440 (16:9)}.</li>
+ * <li>{@code S1080P} refers to {@code 1920x1080 (16:9)}.</li>
+ * <li>{@code S720P} refers to {@code 1280x720 (16:9)}.</li>
+ * <li>{@code UHD} refers to {@code 3840x2160 (16:9)}.</li>
+ * <li>{@code XVGA} refers to {@code 1024x768 (4:3)}.</li>
+ * <li>{@code S1080P_43} refers to {@code 1440x1080 (4:3)}.</li>
* </ul>
+ * </li>
+ * <li>
+ * <p>VIDEO_STABILIZATION_MODE: {OFF, PREVIEW}</p>
+ * </li>
+ * <li>
+ * <p>AE_TARGET_FPS_RANGE: { {*, 30}, {*, 60} }</p>
+ * </li>
+ * <li>
+ * <p>DYNAMIC_RANGE_PROFILE: {STANDARD, HLG10}</p>
+ * </li>
+ * </ul>
+ * <p>All of the above configurations can be set up with a SessionConfiguration. The list of
+ * OutputConfiguration contains the stream configurations and DYNAMIC_RANGE_PROFILE, and
+ * the AE_TARGET_FPS_RANGE and VIDEO_STABILIZATION_MODE are set as session parameters.</p>
* <p>This key is available on all devices.</p>
*/
@PublicKey
diff --git a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
index eb644e8..dfbf06b 100644
--- a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
@@ -917,8 +917,11 @@
* image. For example, it can be used as a temporary placeholder for the requested capture
* while the final image is being processed. The supported sizes for a still capture's postview
* can be retrieved using
- * {@link CameraExtensionCharacteristics#getPostviewSupportedSizes(int, Size, int)}.
- * The formats of the still capture and postview should be equivalent upon capture request.</p>
+ * {@link CameraExtensionCharacteristics#getPostviewSupportedSizes(int, Size, int)}.</p>
+ *
+ * <p>Starting with Android {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM},
+ * the formats of the still capture and postview are not required to be equivalent upon capture
+ * request.</p>
*
* @param extension the extension type
* @return {@code true} in case postview is supported, {@code false} otherwise
@@ -976,8 +979,7 @@
*
* @param extension the extension type
* @param captureSize size of the still capture for which the postview is requested
- * @param format device-specific extension output format of the still capture and
- * postview
+ * @param format device-specific extension output format of the postview
* @return non-modifiable list of available sizes or an empty list if the format and
* size is not supported.
* @throws IllegalArgumentException in case of unsupported extension or if postview
@@ -1018,8 +1020,8 @@
}
IAdvancedExtenderImpl extender = initializeAdvancedExtension(extension);
extender.init(mCameraId, mCharacteristicsMapNative);
- return generateSupportedSizes(extender.getSupportedPostviewResolutions(
- sz), format, streamMap);
+ return getSupportedSizes(extender.getSupportedPostviewResolutions(sz),
+ format);
} else {
Pair<IPreviewExtenderImpl, IImageCaptureExtenderImpl> extenders =
initializeExtension(extension);
@@ -1034,15 +1036,13 @@
}
if (format == ImageFormat.YUV_420_888) {
- return generateSupportedSizes(
- extenders.second.getSupportedPostviewResolutions(sz),
- format, streamMap);
+ return getSupportedSizes(
+ extenders.second.getSupportedPostviewResolutions(sz), format);
} else if (format == ImageFormat.JPEG) {
// The framework will perform the additional encoding pass on the
// processed YUV_420 buffers.
- return generateJpegSupportedSizes(
- extenders.second.getSupportedPostviewResolutions(sz),
- streamMap);
+ return getSupportedSizes(
+ extenders.second.getSupportedPostviewResolutions(sz), format);
} else if (format == ImageFormat.JPEG_R || format == ImageFormat.YCBCR_P010) {
// Jpeg_R/UltraHDR + YCBCR_P010 is currently not supported in the basic
// extension case
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index 90a2cf0..a019612 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -28,6 +28,8 @@
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.annotation.TestApi;
+import android.app.ActivityManager;
+import android.app.TaskInfo;
import android.app.compat.CompatChanges;
import android.companion.virtual.VirtualDeviceManager;
import android.compat.annotation.ChangeId;
@@ -67,6 +69,7 @@
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
+import android.util.Pair;
import android.util.Size;
import android.view.Display;
@@ -169,6 +172,36 @@
"camera.enable_landscape_to_portrait";
/**
+ * Does not override landscape feed to portrait.
+ *
+ * @hide
+ */
+ @TestApi
+ @FlaggedApi(com.android.window.flags.Flags.FLAG_CAMERA_COMPAT_FOR_FREEFORM)
+ public static final int ROTATION_OVERRIDE_NONE = ICameraService.ROTATION_OVERRIDE_NONE;
+
+ /**
+ * Crops and rotates landscape camera feed to portrait, and changes sensor orientation to
+ * portrait.
+ *
+ * @hide
+ */
+ @TestApi
+ @FlaggedApi(com.android.window.flags.Flags.FLAG_CAMERA_COMPAT_FOR_FREEFORM)
+ public static final int ROTATION_OVERRIDE_OVERRIDE_TO_PORTRAIT =
+ ICameraService.ROTATION_OVERRIDE_OVERRIDE_TO_PORTRAIT;
+
+ /**
+ * Crops and rotates landscape camera feed to portrait, but doesn't change sensor orientation.
+ *
+ * @hide
+ */
+ @TestApi
+ @FlaggedApi(com.android.window.flags.Flags.FLAG_CAMERA_COMPAT_FOR_FREEFORM)
+ public static final int ROTATION_OVERRIDE_ROTATION_ONLY =
+ ICameraService.ROTATION_OVERRIDE_ROTATION_ONLY;
+
+ /**
* Enable physical camera availability callbacks when the logical camera is unavailable
*
* <p>Previously once a logical camera becomes unavailable, no
@@ -347,7 +380,8 @@
*/
@NonNull
public Set<Set<String>> getConcurrentCameraIds() throws CameraAccessException {
- return CameraManagerGlobal.get().getConcurrentCameraIds();
+ return CameraManagerGlobal.get().getConcurrentCameraIds(mContext.getDeviceId(),
+ getDevicePolicyFromContext(mContext));
}
/**
@@ -386,7 +420,8 @@
@NonNull Map<String, SessionConfiguration> cameraIdAndSessionConfig)
throws CameraAccessException {
return CameraManagerGlobal.get().isConcurrentSessionConfigurationSupported(
- cameraIdAndSessionConfig, mContext.getApplicationInfo().targetSdkVersion);
+ cameraIdAndSessionConfig, mContext.getApplicationInfo().targetSdkVersion,
+ mContext.getDeviceId(), getDevicePolicyFromContext(mContext));
}
/**
@@ -627,7 +662,8 @@
CameraMetadataNative physicalCameraInfo =
cameraService.getCameraCharacteristics(physicalCameraId,
mContext.getApplicationInfo().targetSdkVersion,
- /*overrideToPortrait*/ false, DEVICE_ID_DEFAULT,
+ /*rotationOverride*/ ICameraService.ROTATION_OVERRIDE_NONE,
+ DEVICE_ID_DEFAULT,
DEVICE_POLICY_DEFAULT);
StreamConfiguration[] configs = physicalCameraInfo.get(
CameraCharacteristics.
@@ -674,7 +710,7 @@
@NonNull
public CameraCharacteristics getCameraCharacteristics(@NonNull String cameraId)
throws CameraAccessException {
- return getCameraCharacteristics(cameraId, shouldOverrideToPortrait(mContext));
+ return getCameraCharacteristics(cameraId, getRotationOverride(mContext));
}
/**
@@ -699,7 +735,16 @@
@NonNull
public CameraCharacteristics getCameraCharacteristics(@NonNull String cameraId,
boolean overrideToPortrait) throws CameraAccessException {
- CameraCharacteristics characteristics;
+ return getCameraCharacteristics(cameraId,
+ overrideToPortrait
+ ? ICameraService.ROTATION_OVERRIDE_OVERRIDE_TO_PORTRAIT
+ : ICameraService.ROTATION_OVERRIDE_NONE);
+ }
+
+ @NonNull
+ private CameraCharacteristics getCameraCharacteristics(@NonNull String cameraId,
+ int rotationOverride) throws CameraAccessException {
+ CameraCharacteristics characteristics = null;
if (CameraManagerGlobal.sCameraServiceDisabled) {
throw new IllegalArgumentException("No cameras available on device");
}
@@ -711,7 +756,7 @@
}
try {
CameraMetadataNative info = cameraService.getCameraCharacteristics(cameraId,
- mContext.getApplicationInfo().targetSdkVersion, overrideToPortrait,
+ mContext.getApplicationInfo().targetSdkVersion, rotationOverride,
mContext.getDeviceId(), getDevicePolicyFromContext(mContext));
characteristics = prepareCameraCharacteristics(cameraId, info, cameraService);
} catch (ServiceSpecificException e) {
@@ -752,7 +797,7 @@
boolean hasConcurrentStreams =
CameraManagerGlobal.get().cameraIdHasConcurrentStreamsLocked(cameraId,
- mContext.getDeviceId());
+ mContext.getDeviceId(), getDevicePolicyFromContext(mContext));
metadata.setHasMandatoryConcurrentStreams(hasConcurrentStreams);
Size displaySize = getDisplaySize();
@@ -926,7 +971,7 @@
*/
private CameraDevice openCameraDeviceUserAsync(String cameraId,
CameraDevice.StateCallback callback, Executor executor, final int uid,
- final int oomScoreOffset, boolean overrideToPortrait) throws CameraAccessException {
+ final int oomScoreOffset, int rotationOverride) throws CameraAccessException {
CameraCharacteristics characteristics = getCameraCharacteristics(cameraId);
CameraDevice device = null;
Map<String, CameraCharacteristics> physicalIdsToChars =
@@ -961,7 +1006,7 @@
cameraUser = cameraService.connectDevice(callbacks, cameraId,
mContext.getOpPackageName(), mContext.getAttributionTag(), uid,
oomScoreOffset, mContext.getApplicationInfo().targetSdkVersion,
- overrideToPortrait, mContext.getDeviceId(),
+ rotationOverride, mContext.getDeviceId(),
getDevicePolicyFromContext(mContext));
} catch (ServiceSpecificException e) {
if (e.errorCode == ICameraService.ERROR_DEPRECATED_HAL) {
@@ -1126,7 +1171,10 @@
@Nullable Handler handler,
@NonNull final CameraDevice.StateCallback callback) throws CameraAccessException {
openCameraForUid(cameraId, callback, CameraDeviceImpl.checkAndWrapHandler(handler),
- USE_CALLING_UID, /*oomScoreOffset*/0, overrideToPortrait);
+ USE_CALLING_UID, /*oomScoreOffset*/0,
+ overrideToPortrait
+ ? ICameraService.ROTATION_OVERRIDE_OVERRIDE_TO_PORTRAIT
+ : ICameraService.ROTATION_OVERRIDE_NONE);
}
/**
@@ -1240,7 +1288,7 @@
"oomScoreOffset < 0, cannot increase priority of camera client");
}
openCameraForUid(cameraId, callback, executor, USE_CALLING_UID, oomScoreOffset,
- shouldOverrideToPortrait(mContext));
+ getRotationOverride(mContext));
}
/**
@@ -1258,11 +1306,14 @@
* Must be USE_CALLING_UID unless the caller is a trusted service.
* @param oomScoreOffset
* The minimum oom score that cameraservice must see for this client.
+ * @param rotationOverride
+ * The type of rotation override (none, override_to_portrait, rotation_only)
+ * that should be followed for this camera id connection
* @hide
*/
public void openCameraForUid(@NonNull String cameraId,
@NonNull final CameraDevice.StateCallback callback, @NonNull Executor executor,
- int clientUid, int oomScoreOffset, boolean overrideToPortrait)
+ int clientUid, int oomScoreOffset, int rotationOverride)
throws CameraAccessException {
if (cameraId == null) {
@@ -1275,7 +1326,7 @@
}
openCameraDeviceUserAsync(cameraId, callback, executor, clientUid, oomScoreOffset,
- overrideToPortrait);
+ rotationOverride);
}
/**
@@ -1297,7 +1348,7 @@
@NonNull final CameraDevice.StateCallback callback, @NonNull Executor executor,
int clientUid) throws CameraAccessException {
openCameraForUid(cameraId, callback, executor, clientUid, /*oomScoreOffset*/0,
- shouldOverrideToPortrait(mContext));
+ getRotationOverride(mContext));
}
/**
@@ -1442,7 +1493,7 @@
/**
* @hide
*/
- public static boolean shouldOverrideToPortrait(@Nullable Context context) {
+ public static int getRotationOverride(@Nullable Context context) {
PackageManager packageManager = null;
String packageName = null;
@@ -1451,7 +1502,64 @@
packageName = context.getOpPackageName();
}
- return shouldOverrideToPortrait(packageManager, packageName);
+ return getRotationOverride(context, packageManager, packageName);
+ }
+
+ /**
+ * @hide
+ */
+ public static int getRotationOverride(@Nullable Context context,
+ @Nullable PackageManager packageManager, @Nullable String packageName) {
+ if (com.android.window.flags.Flags.cameraCompatForFreeform()) {
+ return getRotationOverrideInternal(context, packageManager, packageName);
+ } else {
+ return shouldOverrideToPortrait(packageManager, packageName)
+ ? ICameraService.ROTATION_OVERRIDE_OVERRIDE_TO_PORTRAIT
+ : ICameraService.ROTATION_OVERRIDE_NONE;
+ }
+ }
+
+ /**
+ * @hide
+ */
+ @FlaggedApi(com.android.window.flags.Flags.FLAG_CAMERA_COMPAT_FOR_FREEFORM)
+ @TestApi
+ public static int getRotationOverrideInternal(@Nullable Context context,
+ @Nullable PackageManager packageManager, @Nullable String packageName) {
+ if (!CameraManagerGlobal.sLandscapeToPortrait) {
+ return ICameraService.ROTATION_OVERRIDE_NONE;
+ }
+
+ if (context != null) {
+ final ActivityManager activityManager =
+ context.getSystemService(ActivityManager.class);
+ for (ActivityManager.AppTask appTask : activityManager.getAppTasks()) {
+ final TaskInfo taskInfo = appTask.getTaskInfo();
+ if (taskInfo.appCompatTaskInfo.cameraCompatTaskInfo.freeformCameraCompatMode
+ != 0
+ && taskInfo.topActivity != null
+ && taskInfo.topActivity.getPackageName().equals(packageName)) {
+ // WindowManager has requested rotation override.
+ return ICameraService.ROTATION_OVERRIDE_ROTATION_ONLY;
+ }
+ }
+ }
+
+ if (packageManager != null && packageName != null) {
+ try {
+ return packageManager.getProperty(
+ PackageManager.PROPERTY_COMPAT_OVERRIDE_LANDSCAPE_TO_PORTRAIT,
+ packageName).getBoolean()
+ ? ICameraService.ROTATION_OVERRIDE_OVERRIDE_TO_PORTRAIT
+ : ICameraService.ROTATION_OVERRIDE_NONE;
+ } catch (PackageManager.NameNotFoundException e) {
+ // No such property
+ }
+ }
+
+ return CompatChanges.isChangeEnabled(OVERRIDE_CAMERA_LANDSCAPE_TO_PORTRAIT)
+ ? ICameraService.ROTATION_OVERRIDE_OVERRIDE_TO_PORTRAIT
+ : ICameraService.ROTATION_OVERRIDE_NONE;
}
/**
@@ -1459,7 +1567,7 @@
*/
@TestApi
public static boolean shouldOverrideToPortrait(@Nullable PackageManager packageManager,
- @Nullable String packageName) {
+ @Nullable String packageName) {
if (!CameraManagerGlobal.sLandscapeToPortrait) {
return false;
}
@@ -1477,6 +1585,7 @@
return CompatChanges.isChangeEnabled(OVERRIDE_CAMERA_LANDSCAPE_TO_PORTRAIT);
}
+
/**
* @hide
*/
@@ -1991,7 +2100,7 @@
// Opened Camera ID -> apk name map
private final ArrayMap<DeviceCameraInfo, String> mOpenedDevices = new ArrayMap<>();
- private final Set<Set<String>> mConcurrentCameraIdCombinations = new ArraySet<>();
+ private final Set<Set<DeviceCameraInfo>> mConcurrentCameraIdCombinations = new ArraySet<>();
// Registered availability callbacks and their executors
private final ArrayMap<AvailabilityCallback, Executor> mCallbackMap = new ArrayMap<>();
@@ -2150,7 +2259,13 @@
ConcurrentCameraIdCombination[] cameraIdCombinations =
cameraService.getConcurrentCameraIds();
for (ConcurrentCameraIdCombination comb : cameraIdCombinations) {
- mConcurrentCameraIdCombinations.add(comb.getConcurrentCameraIdCombination());
+ Set<Pair<String, Integer>> combination =
+ comb.getConcurrentCameraIdCombination();
+ Set<DeviceCameraInfo> deviceCameraInfoSet = new ArraySet<>();
+ for (Pair<String, Integer> entry : combination) {
+ deviceCameraInfoSet.add(new DeviceCameraInfo(entry.first, entry.second));
+ }
+ mConcurrentCameraIdCombinations.add(deviceCameraInfoSet);
}
} catch (ServiceSpecificException e) {
// Unexpected failure
@@ -2235,13 +2350,12 @@
return cameraIds.toArray(new String[0]);
}
- private Set<Set<String>> extractConcurrentCameraIdListLocked() {
+ private Set<Set<String>> extractConcurrentCameraIdListLocked(int deviceId,
+ int devicePolicy) {
Set<Set<String>> concurrentCameraIds = new ArraySet<>();
- for (Set<String> cameraIds : mConcurrentCameraIdCombinations) {
+ for (Set<DeviceCameraInfo> deviceCameraInfos : mConcurrentCameraIdCombinations) {
Set<String> extractedCameraIds = new ArraySet<>();
- for (String cameraId : cameraIds) {
- // TODO(b/291736219): This to be made device-aware.
- DeviceCameraInfo info = new DeviceCameraInfo(cameraId, DEVICE_ID_DEFAULT);
+ for (DeviceCameraInfo info : deviceCameraInfos) {
// if the camera id status is NOT_PRESENT or ENUMERATING; skip the device.
// TODO: Would a device status NOT_PRESENT ever be in the map ? it gets removed
// in the callback anyway.
@@ -2254,9 +2368,14 @@
|| status == ICameraServiceListener.STATUS_NOT_PRESENT) {
continue;
}
- extractedCameraIds.add(cameraId);
+ if (shouldHideCamera(deviceId, devicePolicy, info)) {
+ continue;
+ }
+ extractedCameraIds.add(info.mCameraId);
}
- concurrentCameraIds.add(extractedCameraIds);
+ if (!extractedCameraIds.isEmpty()) {
+ concurrentCameraIds.add(extractedCameraIds);
+ }
}
return concurrentCameraIds;
}
@@ -2417,12 +2536,13 @@
return cameraIds;
}
- public @NonNull Set<Set<String>> getConcurrentCameraIds() {
+ public @NonNull Set<Set<String>> getConcurrentCameraIds(int deviceId, int devicePolicy) {
Set<Set<String>> concurrentStreamingCameraIds;
synchronized (mLock) {
// Try to make sure we have an up-to-date list of concurrent camera devices.
connectCameraServiceLocked();
- concurrentStreamingCameraIds = extractConcurrentCameraIdListLocked();
+ concurrentStreamingCameraIds = extractConcurrentCameraIdListLocked(deviceId,
+ devicePolicy);
}
// TODO: Some sort of sorting ?
return concurrentStreamingCameraIds;
@@ -2430,13 +2550,12 @@
public boolean isConcurrentSessionConfigurationSupported(
@NonNull Map<String, SessionConfiguration> cameraIdsAndSessionConfigurations,
- int targetSdkVersion) throws CameraAccessException {
+ int targetSdkVersion, int deviceId, int devicePolicy)
+ throws CameraAccessException {
if (cameraIdsAndSessionConfigurations == null) {
throw new IllegalArgumentException("cameraIdsAndSessionConfigurations was null");
}
- // TODO(b/291736219): Check if this API needs to be made device-aware.
-
int size = cameraIdsAndSessionConfigurations.size();
if (size == 0) {
throw new IllegalArgumentException("camera id and session combination is empty");
@@ -2446,14 +2565,20 @@
// Go through all the elements and check if the camera ids are valid at least /
// belong to one of the combinations returned by getConcurrentCameraIds()
boolean subsetFound = false;
- for (Set<String> combination : mConcurrentCameraIdCombinations) {
- if (combination.containsAll(cameraIdsAndSessionConfigurations.keySet())) {
+ for (Set<DeviceCameraInfo> combination : mConcurrentCameraIdCombinations) {
+ Set<DeviceCameraInfo> infos = new ArraySet<>();
+ for (String cameraId : cameraIdsAndSessionConfigurations.keySet()) {
+ infos.add(new DeviceCameraInfo(cameraId,
+ devicePolicy == DEVICE_POLICY_DEFAULT
+ ? DEVICE_ID_DEFAULT : deviceId));
+ }
+ if (combination.containsAll(infos)) {
subsetFound = true;
}
}
if (!subsetFound) {
Log.v(TAG, "isConcurrentSessionConfigurationSupported called with a subset of"
- + "camera ids not returned by getConcurrentCameraIds");
+ + " camera ids not returned by getConcurrentCameraIds");
return false;
}
CameraIdAndSessionConfiguration [] cameraIdsAndConfigs =
@@ -2467,7 +2592,7 @@
}
try {
return mCameraService.isConcurrentSessionConfigurationSupported(
- cameraIdsAndConfigs, targetSdkVersion);
+ cameraIdsAndConfigs, targetSdkVersion, deviceId, devicePolicy);
} catch (ServiceSpecificException e) {
throw ExceptionUtils.throwAsPublicException(e);
} catch (RemoteException e) {
@@ -2486,8 +2611,10 @@
* @return Whether the camera device was found in the set of combinations returned by
* getConcurrentCameraIds
*/
- public boolean cameraIdHasConcurrentStreamsLocked(String cameraId, int deviceId) {
- DeviceCameraInfo info = new DeviceCameraInfo(cameraId, deviceId);
+ public boolean cameraIdHasConcurrentStreamsLocked(String cameraId, int deviceId,
+ int devicePolicy) {
+ DeviceCameraInfo info = new DeviceCameraInfo(cameraId,
+ devicePolicy == DEVICE_POLICY_DEFAULT ? DEVICE_ID_DEFAULT : deviceId);
if (!mDeviceStatus.containsKey(info)) {
// physical camera ids aren't advertised in concurrent camera id combinations.
if (DEBUG) {
@@ -2496,8 +2623,8 @@
}
return false;
}
- for (Set<String> comb : mConcurrentCameraIdCombinations) {
- if (comb.contains(cameraId)) {
+ for (Set<DeviceCameraInfo> comb : mConcurrentCameraIdCombinations) {
+ if (comb.contains(info)) {
return true;
}
}
diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceSetupImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceSetupImpl.java
index 372839d..8898a4c 100644
--- a/core/java/android/hardware/camera2/impl/CameraDeviceSetupImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraDeviceSetupImpl.java
@@ -134,7 +134,8 @@
try {
CameraMetadataNative metadata = cameraService.getSessionCharacteristics(
mCameraId, mTargetSdkVersion,
- CameraManager.shouldOverrideToPortrait(mContext), sessionConfig,
+ CameraManager.getRotationOverride(mContext),
+ sessionConfig,
mContext.getDeviceId(),
mCameraManager.getDevicePolicyFromContext(mContext));
diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionJpegProcessor.java b/core/java/android/hardware/camera2/impl/CameraExtensionJpegProcessor.java
index 875550a..a10e250 100644
--- a/core/java/android/hardware/camera2/impl/CameraExtensionJpegProcessor.java
+++ b/core/java/android/hardware/camera2/impl/CameraExtensionJpegProcessor.java
@@ -36,6 +36,8 @@
import android.util.Log;
import android.view.Surface;
+import com.android.internal.camera.flags.Flags;
+
import java.nio.ByteBuffer;
import java.util.HashSet;
import java.util.Iterator;
@@ -57,6 +59,8 @@
private android.hardware.camera2.extension.Size mResolution = null;
private android.hardware.camera2.extension.Size mPostviewResolution = null;
private int mFormat = -1;
+ private int mPostviewFormat = -1;
+ private int mCaptureFormat = -1;
private Surface mOutputSurface = null;
private ImageWriter mOutputWriter = null;
private Surface mPostviewOutputSurface = null;
@@ -204,10 +208,12 @@
}
public void onOutputSurface(Surface surface, int format) throws RemoteException {
- if (format != ImageFormat.JPEG) {
+ if (!Flags.extension10Bit() && format != ImageFormat.JPEG) {
Log.e(TAG, "Unsupported output format: " + format);
return;
}
+ CameraExtensionUtils.SurfaceInfo surfaceInfo = CameraExtensionUtils.querySurface(surface);
+ mCaptureFormat = surfaceInfo.mFormat;
mOutputSurface = surface;
initializePipeline();
}
@@ -215,10 +221,11 @@
public void onPostviewOutputSurface(Surface surface) throws RemoteException {
CameraExtensionUtils.SurfaceInfo postviewSurfaceInfo =
CameraExtensionUtils.querySurface(surface);
- if (postviewSurfaceInfo.mFormat != ImageFormat.JPEG) {
+ if (!Flags.extension10Bit() && postviewSurfaceInfo.mFormat != ImageFormat.JPEG) {
Log.e(TAG, "Unsupported output format: " + postviewSurfaceInfo.mFormat);
return;
}
+ mPostviewFormat = postviewSurfaceInfo.mFormat;
mPostviewOutputSurface = surface;
initializePostviewPipeline();
}
@@ -233,7 +240,7 @@
}
public void onImageFormatUpdate(int format) throws RemoteException {
- if (format != ImageFormat.YUV_420_888) {
+ if (!Flags.extension10Bit() && format != ImageFormat.YUV_420_888) {
Log.e(TAG, "Unsupported input format: " + format);
return;
}
@@ -244,33 +251,45 @@
private void initializePipeline() throws RemoteException {
if ((mFormat != -1) && (mOutputSurface != null) && (mResolution != null) &&
(mYuvReader == null)) {
- // Jpeg/blobs are expected to be configured with (w*h)x1.5 + 64k Jpeg APP1 segment
- mOutputWriter = ImageWriter.newInstance(mOutputSurface, 1 /*maxImages*/,
- ImageFormat.JPEG,
- (mResolution.width * mResolution.height * 3)/2 + JPEG_APP_SEGMENT_SIZE, 1);
- mYuvReader = ImageReader.newInstance(mResolution.width, mResolution.height, mFormat,
- JPEG_QUEUE_SIZE);
- mYuvReader.setOnImageAvailableListener(
- new YuvCallback(mYuvReader, mOutputWriter), mHandler);
- mProcessor.onOutputSurface(mYuvReader.getSurface(), mFormat);
+ if (Flags.extension10Bit() && mCaptureFormat == ImageFormat.YUV_420_888) {
+ // For the case when postview is JPEG and capture is YUV
+ mProcessor.onOutputSurface(mOutputSurface, mCaptureFormat);
+ } else {
+ // Jpeg/blobs are expected to be configured with (w*h)x1.5 + 64k Jpeg APP1 segment
+ mOutputWriter = ImageWriter.newInstance(mOutputSurface, 1 /*maxImages*/,
+ ImageFormat.JPEG,
+ (mResolution.width * mResolution.height * 3) / 2
+ + JPEG_APP_SEGMENT_SIZE, 1);
+ mYuvReader = ImageReader.newInstance(mResolution.width, mResolution.height,
+ mFormat, JPEG_QUEUE_SIZE);
+ mYuvReader.setOnImageAvailableListener(
+ new YuvCallback(mYuvReader, mOutputWriter), mHandler);
+ mProcessor.onOutputSurface(mYuvReader.getSurface(), mFormat);
+ }
mProcessor.onResolutionUpdate(mResolution, mPostviewResolution);
- mProcessor.onImageFormatUpdate(mFormat);
+ mProcessor.onImageFormatUpdate(ImageFormat.YUV_420_888);
}
}
private void initializePostviewPipeline() throws RemoteException {
if ((mFormat != -1) && (mPostviewOutputSurface != null) && (mPostviewResolution != null)
&& (mPostviewYuvReader == null)) {
- // Jpeg/blobs are expected to be configured with (w*h)x1
- mPostviewOutputWriter = ImageWriter.newInstance(mPostviewOutputSurface, 1/*maxImages*/,
- ImageFormat.JPEG, mPostviewResolution.width * mPostviewResolution.height, 1);
- mPostviewYuvReader = ImageReader.newInstance(mPostviewResolution.width,
- mPostviewResolution.height, mFormat, JPEG_QUEUE_SIZE);
- mPostviewYuvReader.setOnImageAvailableListener(
- new YuvCallback(mPostviewYuvReader, mPostviewOutputWriter), mHandler);
- mProcessor.onPostviewOutputSurface(mPostviewYuvReader.getSurface());
+ if (Flags.extension10Bit() && mPostviewFormat == ImageFormat.YUV_420_888) {
+ // For the case when postview is YUV and capture is JPEG
+ mProcessor.onPostviewOutputSurface(mPostviewOutputSurface);
+ } else {
+ // Jpeg/blobs are expected to be configured with (w*h)x1
+ mPostviewOutputWriter = ImageWriter.newInstance(mPostviewOutputSurface,
+ 1/*maxImages*/, ImageFormat.JPEG,
+ mPostviewResolution.width * mPostviewResolution.height, 1);
+ mPostviewYuvReader = ImageReader.newInstance(mPostviewResolution.width,
+ mPostviewResolution.height, mFormat, JPEG_QUEUE_SIZE);
+ mPostviewYuvReader.setOnImageAvailableListener(
+ new YuvCallback(mPostviewYuvReader, mPostviewOutputWriter), mHandler);
+ mProcessor.onPostviewOutputSurface(mPostviewYuvReader.getSurface());
+ }
mProcessor.onResolutionUpdate(mResolution, mPostviewResolution);
- mProcessor.onImageFormatUpdate(mFormat);
+ mProcessor.onImageFormatUpdate(ImageFormat.YUV_420_888);
}
}
diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
index c00e610..3ae3199 100644
--- a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
@@ -390,7 +390,16 @@
if (surfaceInfo.mFormat == ImageFormat.JPEG) {
mImageJpegProcessor = new CameraExtensionJpegProcessor(mImageProcessor);
mImageProcessor = mImageJpegProcessor;
+ } else if (Flags.extension10Bit() && mClientPostviewSurface != null) {
+ // Handles case when postview is JPEG and capture is YUV
+ CameraExtensionUtils.SurfaceInfo postviewSurfaceInfo =
+ CameraExtensionUtils.querySurface(mClientPostviewSurface);
+ if (postviewSurfaceInfo.mFormat == ImageFormat.JPEG) {
+ mImageJpegProcessor = new CameraExtensionJpegProcessor(mImageProcessor);
+ mImageProcessor = mImageJpegProcessor;
+ }
}
+
mBurstCaptureImageReader = ImageReader.newInstance(surfaceInfo.mWidth,
surfaceInfo.mHeight, CameraExtensionCharacteristics.PROCESSING_INPUT_FORMAT,
mImageExtender.getMaxCaptureStage());
diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionUtils.java b/core/java/android/hardware/camera2/impl/CameraExtensionUtils.java
index f0c6e2e..40f0477 100644
--- a/core/java/android/hardware/camera2/impl/CameraExtensionUtils.java
+++ b/core/java/android/hardware/camera2/impl/CameraExtensionUtils.java
@@ -112,19 +112,30 @@
if (outputConfig == null) return null;
SurfaceInfo surfaceInfo = querySurface(outputConfig.getSurface());
- if (surfaceInfo.mFormat == captureFormat) {
- if (supportedPostviewSizes.containsKey(captureFormat)) {
- Size postviewSize = new Size(surfaceInfo.mWidth, surfaceInfo.mHeight);
- if (supportedPostviewSizes.get(surfaceInfo.mFormat)
- .contains(postviewSize)) {
- return outputConfig.getSurface();
- } else {
- throw new IllegalArgumentException("Postview size not supported!");
- }
+
+ if (Flags.extension10Bit()) {
+ Size postviewSize = new Size(surfaceInfo.mWidth, surfaceInfo.mHeight);
+ if (supportedPostviewSizes.get(surfaceInfo.mFormat)
+ .contains(postviewSize)) {
+ return outputConfig.getSurface();
+ } else {
+ throw new IllegalArgumentException("Postview size not supported!");
}
} else {
- throw new IllegalArgumentException("Postview format should be equivalent to " +
- " the capture format!");
+ if (surfaceInfo.mFormat == captureFormat) {
+ if (supportedPostviewSizes.containsKey(captureFormat)) {
+ Size postviewSize = new Size(surfaceInfo.mWidth, surfaceInfo.mHeight);
+ if (supportedPostviewSizes.get(surfaceInfo.mFormat)
+ .contains(postviewSize)) {
+ return outputConfig.getSurface();
+ } else {
+ throw new IllegalArgumentException("Postview size not supported!");
+ }
+ }
+ } else {
+ throw new IllegalArgumentException("Postview format should be equivalent to "
+ + " the capture format!");
+ }
}
return null;
diff --git a/core/java/android/hardware/camera2/utils/ConcurrentCameraIdCombination.java b/core/java/android/hardware/camera2/utils/ConcurrentCameraIdCombination.java
index 8f4d636..1a8bf1d 100644
--- a/core/java/android/hardware/camera2/utils/ConcurrentCameraIdCombination.java
+++ b/core/java/android/hardware/camera2/utils/ConcurrentCameraIdCombination.java
@@ -13,13 +13,15 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
package android.hardware.camera2.utils;
import android.annotation.NonNull;
import android.os.Parcel;
import android.os.Parcelable;
+import android.util.ArraySet;
+import android.util.Pair;
-import java.util.HashSet;
import java.util.Set;
/**
@@ -30,21 +32,21 @@
*/
public class ConcurrentCameraIdCombination implements Parcelable {
- private Set<String> mConcurrentCameraIds = new HashSet<>();
+ private final Set<Pair<String, Integer>> mConcurrentCameraIdDeviceIdPairs = new ArraySet<>();
public static final @NonNull
Parcelable.Creator<ConcurrentCameraIdCombination> CREATOR =
- new Parcelable.Creator<ConcurrentCameraIdCombination>() {
- @Override
- public ConcurrentCameraIdCombination createFromParcel(Parcel in) {
- return new ConcurrentCameraIdCombination(in);
- }
+ new Parcelable.Creator<>() {
+ @Override
+ public ConcurrentCameraIdCombination createFromParcel(Parcel in) {
+ return new ConcurrentCameraIdCombination(in);
+ }
- @Override
- public ConcurrentCameraIdCombination[] newArray(int size) {
- return new ConcurrentCameraIdCombination[size];
- }
- };
+ @Override
+ public ConcurrentCameraIdCombination[] newArray(int size) {
+ return new ConcurrentCameraIdCombination[size];
+ }
+ };
private ConcurrentCameraIdCombination(Parcel in) {
readFromParcel(in);
@@ -57,9 +59,10 @@
@Override
public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(mConcurrentCameraIds.size());
- for (String cameraId : mConcurrentCameraIds) {
- dest.writeString(cameraId);
+ dest.writeInt(mConcurrentCameraIdDeviceIdPairs.size());
+ for (Pair<String, Integer> cameraIdDeviceIdPair : mConcurrentCameraIdDeviceIdPairs) {
+ dest.writeString(cameraIdDeviceIdPair.first);
+ dest.writeInt(cameraIdDeviceIdPair.second);
}
}
@@ -67,7 +70,7 @@
* helper for CREATOR
*/
public void readFromParcel(Parcel in) {
- mConcurrentCameraIds.clear();
+ mConcurrentCameraIdDeviceIdPairs.clear();
int cameraCombinationSize = in.readInt();
if (cameraCombinationSize < 0) {
throw new RuntimeException("cameraCombinationSize " + cameraCombinationSize
@@ -78,14 +81,15 @@
if (cameraId == null) {
throw new RuntimeException("Failed to read camera id from Parcel");
}
- mConcurrentCameraIds.add(cameraId);
+ int deviceId = in.readInt();
+ mConcurrentCameraIdDeviceIdPairs.add(new Pair<>(cameraId, deviceId));
}
}
/**
* Get this concurrent camera id combination.
*/
- public Set<String> getConcurrentCameraIdCombination() {
- return mConcurrentCameraIds;
+ public Set<Pair<String, Integer>> getConcurrentCameraIdCombination() {
+ return mConcurrentCameraIdDeviceIdPairs;
}
}
diff --git a/core/java/android/hardware/hdmi/OWNERS b/core/java/android/hardware/hdmi/OWNERS
index 6952e5d..f7a22f5 100644
--- a/core/java/android/hardware/hdmi/OWNERS
+++ b/core/java/android/hardware/hdmi/OWNERS
@@ -3,3 +3,4 @@
include /services/core/java/com/android/server/display/OWNERS
quxiangfang@google.com
+donpaul@google.com
\ No newline at end of file
diff --git a/core/java/android/hardware/location/ISignificantPlaceProvider.aidl b/core/java/android/hardware/location/ISignificantPlaceProvider.aidl
new file mode 100644
index 0000000..e02169e
--- /dev/null
+++ b/core/java/android/hardware/location/ISignificantPlaceProvider.aidl
@@ -0,0 +1,10 @@
+package android.hardware.location;
+
+import android.hardware.location.ISignificantPlaceProviderManager;
+
+/**
+ * @hide
+ */
+oneway interface ISignificantPlaceProvider {
+ void setSignificantPlaceProviderManager(in ISignificantPlaceProviderManager manager);
+}
diff --git a/core/java/android/hardware/location/ISignificantPlaceProviderManager.aidl b/core/java/android/hardware/location/ISignificantPlaceProviderManager.aidl
new file mode 100644
index 0000000..76eefe7
--- /dev/null
+++ b/core/java/android/hardware/location/ISignificantPlaceProviderManager.aidl
@@ -0,0 +1,8 @@
+package android.hardware.location;
+
+/**
+ * @hide
+ */
+interface ISignificantPlaceProviderManager {
+ void setInSignificantPlace(in boolean inSignificantPlace);
+}
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index d38354d..c6a9203 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -1989,85 +1989,86 @@
* @hide
*/
@StringDef(value = {
- DISALLOW_MODIFY_ACCOUNTS,
- DISALLOW_CONFIG_WIFI,
- DISALLOW_CONFIG_LOCALE,
- DISALLOW_INSTALL_APPS,
- DISALLOW_UNINSTALL_APPS,
- DISALLOW_SHARE_LOCATION,
+ ALLOW_PARENT_PROFILE_APP_LINKING,
+ DISALLOW_ADD_CLONE_PROFILE,
+ DISALLOW_ADD_MANAGED_PROFILE,
+ DISALLOW_ADD_PRIVATE_PROFILE,
+ DISALLOW_ADD_USER,
+ DISALLOW_ADD_WIFI_CONFIG,
+ DISALLOW_ADJUST_VOLUME,
DISALLOW_AIRPLANE_MODE,
- DISALLOW_CONFIG_BRIGHTNESS,
DISALLOW_AMBIENT_DISPLAY,
- DISALLOW_CONFIG_SCREEN_TIMEOUT,
- DISALLOW_INSTALL_UNKNOWN_SOURCES,
- DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY,
- DISALLOW_CONFIG_BLUETOOTH,
+ DISALLOW_APPS_CONTROL,
+ DISALLOW_ASSIST_CONTENT,
+ DISALLOW_AUTOFILL,
+ DISALLOW_BIOMETRIC,
DISALLOW_BLUETOOTH,
DISALLOW_BLUETOOTH_SHARING,
- DISALLOW_USB_FILE_TRANSFER,
- DISALLOW_CONFIG_CREDENTIALS,
- DISALLOW_REMOVE_USER,
- DISALLOW_REMOVE_MANAGED_PROFILE,
- DISALLOW_DEBUGGING_FEATURES,
- DISALLOW_CONFIG_VPN,
- DISALLOW_CONFIG_LOCATION,
- DISALLOW_CONFIG_DATE_TIME,
- DISALLOW_CONFIG_TETHERING,
- DISALLOW_NETWORK_RESET,
- DISALLOW_FACTORY_RESET,
- DISALLOW_ADD_USER,
- DISALLOW_ADD_MANAGED_PROFILE,
- DISALLOW_ADD_CLONE_PROFILE,
- DISALLOW_ADD_PRIVATE_PROFILE,
- ENSURE_VERIFY_APPS,
- DISALLOW_CONFIG_CELL_BROADCASTS,
- DISALLOW_CONFIG_MOBILE_NETWORKS,
- DISALLOW_APPS_CONTROL,
- DISALLOW_MOUNT_PHYSICAL_MEDIA,
- DISALLOW_UNMUTE_MICROPHONE,
- DISALLOW_ADJUST_VOLUME,
- DISALLOW_OUTGOING_CALLS,
- DISALLOW_SMS,
- DISALLOW_FUN,
- DISALLOW_CREATE_WINDOWS,
- DISALLOW_SYSTEM_ERROR_DIALOGS,
- DISALLOW_CROSS_PROFILE_COPY_PASTE,
- DISALLOW_OUTGOING_BEAM,
- DISALLOW_WALLPAPER,
- DISALLOW_SET_WALLPAPER,
- DISALLOW_SAFE_BOOT,
- DISALLOW_RECORD_AUDIO,
- DISALLOW_RUN_IN_BACKGROUND,
DISALLOW_CAMERA,
- DISALLOW_UNMUTE_DEVICE,
- DISALLOW_DATA_ROAMING,
- DISALLOW_SET_USER_ICON,
- DISALLOW_OEM_UNLOCK,
- DISALLOW_UNIFIED_PASSWORD,
- ALLOW_PARENT_PROFILE_APP_LINKING,
- DISALLOW_AUTOFILL,
+ DISALLOW_CAMERA_TOGGLE,
+ DISALLOW_CELLULAR_2G,
+ DISALLOW_CHANGE_WIFI_STATE,
+ DISALLOW_CONFIG_BLUETOOTH,
+ DISALLOW_CONFIG_BRIGHTNESS,
+ DISALLOW_CONFIG_CELL_BROADCASTS,
+ DISALLOW_CONFIG_CREDENTIALS,
+ DISALLOW_CONFIG_DATE_TIME,
+ DISALLOW_CONFIG_DEFAULT_APPS,
+ DISALLOW_CONFIG_LOCALE,
+ DISALLOW_CONFIG_LOCATION,
+ DISALLOW_CONFIG_MOBILE_NETWORKS,
+ DISALLOW_CONFIG_PRIVATE_DNS,
+ DISALLOW_CONFIG_SCREEN_TIMEOUT,
+ DISALLOW_CONFIG_TETHERING,
+ DISALLOW_CONFIG_VPN,
+ DISALLOW_CONFIG_WIFI,
DISALLOW_CONTENT_CAPTURE,
DISALLOW_CONTENT_SUGGESTIONS,
- DISALLOW_USER_SWITCH,
- DISALLOW_SHARE_INTO_MANAGED_PROFILE,
- DISALLOW_PRINTING,
- DISALLOW_CONFIG_PRIVATE_DNS,
- DISALLOW_MICROPHONE_TOGGLE,
- DISALLOW_CAMERA_TOGGLE,
- KEY_RESTRICTIONS_PENDING,
- DISALLOW_BIOMETRIC,
- DISALLOW_CHANGE_WIFI_STATE,
- DISALLOW_WIFI_TETHERING,
- DISALLOW_SHARING_ADMIN_CONFIGURED_WIFI,
- DISALLOW_WIFI_DIRECT,
- DISALLOW_ADD_WIFI_CONFIG,
- DISALLOW_CELLULAR_2G,
- DISALLOW_ULTRA_WIDEBAND_RADIO,
+ DISALLOW_CREATE_WINDOWS,
+ DISALLOW_CROSS_PROFILE_COPY_PASTE,
+ DISALLOW_DATA_ROAMING,
+ DISALLOW_DEBUGGING_FEATURES,
+ DISALLOW_FACTORY_RESET,
+ DISALLOW_FUN,
DISALLOW_GRANT_ADMIN,
+ DISALLOW_INSTALL_APPS,
+ DISALLOW_INSTALL_UNKNOWN_SOURCES,
+ DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY,
+ DISALLOW_MICROPHONE_TOGGLE,
+ DISALLOW_MODIFY_ACCOUNTS,
+ DISALLOW_MOUNT_PHYSICAL_MEDIA,
DISALLOW_NEAR_FIELD_COMMUNICATION_RADIO,
- DISALLOW_THREAD_NETWORK,
+ DISALLOW_NETWORK_RESET,
+ DISALLOW_OEM_UNLOCK,
+ DISALLOW_OUTGOING_BEAM,
+ DISALLOW_OUTGOING_CALLS,
+ DISALLOW_PRINTING,
+ DISALLOW_RECORD_AUDIO,
+ DISALLOW_REMOVE_MANAGED_PROFILE,
+ DISALLOW_REMOVE_USER,
+ DISALLOW_RUN_IN_BACKGROUND,
+ DISALLOW_SAFE_BOOT,
+ DISALLOW_SET_USER_ICON,
+ DISALLOW_SET_WALLPAPER,
+ DISALLOW_SHARE_INTO_MANAGED_PROFILE,
+ DISALLOW_SHARE_LOCATION,
+ DISALLOW_SHARING_ADMIN_CONFIGURED_WIFI,
DISALLOW_SIM_GLOBALLY,
- DISALLOW_ASSIST_CONTENT,
+ DISALLOW_SMS,
+ DISALLOW_SYSTEM_ERROR_DIALOGS,
+ DISALLOW_THREAD_NETWORK,
+ DISALLOW_ULTRA_WIDEBAND_RADIO,
+ DISALLOW_UNIFIED_PASSWORD,
+ DISALLOW_UNINSTALL_APPS,
+ DISALLOW_UNMUTE_DEVICE,
+ DISALLOW_UNMUTE_MICROPHONE,
+ DISALLOW_USB_FILE_TRANSFER,
+ DISALLOW_USER_SWITCH,
+ DISALLOW_WALLPAPER,
+ DISALLOW_WIFI_DIRECT,
+ DISALLOW_WIFI_TETHERING,
+ ENSURE_VERIFY_APPS,
+ KEY_RESTRICTIONS_PENDING,
})
@Retention(RetentionPolicy.SOURCE)
public @interface UserRestrictionKey {}
diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig
index 6b5e17d..b588308 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -180,3 +180,11 @@
description: "Use runtime permission state to determine appop state"
bug: "266164193"
}
+
+flag {
+ name: "device_id_in_op_proxy_info_enabled"
+ is_fixed_read_only: true
+ namespace: "permissions"
+ description: "Enable getDeviceId API in OpEventProxyInfo"
+ bug: "337340961"
+}
\ No newline at end of file
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 72ab970..e6ddf35 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -1736,6 +1736,24 @@
"android.settings.NETWORK_OPERATOR_SETTINGS";
/**
+ * Activity Action: Show settings for selecting the network provider.
+ * <p>
+ * In some cases, a matching Activity may not be provided, so ensure you
+ * safeguard against this.
+ * <p>
+ * Access to this preference can be customized via Settings' app.
+ * <p>
+ * Input: Nothing.
+ * <p>
+ * Output: Nothing.
+ *
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_NETWORK_PROVIDER_SETTINGS =
+ "android.settings.NETWORK_PROVIDER_SETTINGS";
+
+ /**
* Activity Action: Show settings for selection of 2G/3G.
* <p>
* In some cases, a matching Activity may not exist, so ensure you
diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java
index 997c9581..5f6bdbf 100644
--- a/core/java/android/service/dreams/DreamService.java
+++ b/core/java/android/service/dreams/DreamService.java
@@ -1383,16 +1383,22 @@
DreamService.DREAM_META_DATA, DREAM_META_DATA_ROOT_TAG,
com.android.internal.R.styleable.Dream)) {
if (rawMetadata == null) return null;
- return new DreamMetadata(
- convertToComponentName(
- rawMetadata.getString(
- com.android.internal.R.styleable.Dream_settingsActivity), serviceInfo),
- rawMetadata.getDrawable(
- com.android.internal.R.styleable.Dream_previewImage),
- rawMetadata.getBoolean(R.styleable.Dream_showClockAndComplications,
- DEFAULT_SHOW_COMPLICATIONS),
- rawMetadata.getInt(R.styleable.Dream_dreamCategory, DREAM_CATEGORY_DEFAULT)
- );
+ try {
+ return new DreamMetadata(
+ convertToComponentName(
+ rawMetadata.getString(
+ com.android.internal.R.styleable.Dream_settingsActivity),
+ serviceInfo),
+ rawMetadata.getDrawable(
+ com.android.internal.R.styleable.Dream_previewImage),
+ rawMetadata.getBoolean(R.styleable.Dream_showClockAndComplications,
+ DEFAULT_SHOW_COMPLICATIONS),
+ rawMetadata.getInt(R.styleable.Dream_dreamCategory, DREAM_CATEGORY_DEFAULT)
+ );
+ } catch (Exception exception) {
+ Log.e(TAG, "Failed to create read metadata", exception);
+ return null;
+ }
}
}
diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java
index 8dee4b1..c674968 100644
--- a/core/java/android/text/Layout.java
+++ b/core/java/android/text/Layout.java
@@ -70,6 +70,11 @@
* For text that will not change, use a {@link StaticLayout}.
*/
public abstract class Layout {
+
+ // These should match the constants in framework/base/libs/hwui/hwui/DrawTextFunctor.h
+ private static final float HIGH_CONTRAST_TEXT_BORDER_WIDTH_MIN_PX = 4f;
+ private static final float HIGH_CONTRAST_TEXT_BORDER_WIDTH_FACTOR = 0.2f;
+
/** @hide */
@IntDef(prefix = { "BREAK_STRATEGY_" }, value = {
LineBreaker.BREAK_STRATEGY_SIMPLE,
@@ -494,9 +499,9 @@
drawText(canvas, firstLine, lastLine);
- // Since high contrast text draws a solid rectangle background behind the text, it covers up
- // the highlights and selections. In this case we draw over the top of the text with a
- // blend mode that ensures the text stays high-contrast.
+ // Since high contrast text draws a thick border on the text, the highlight actually makes
+ // it harder to read. In this case we draw over the top of the text with a blend mode that
+ // ensures the text stays high-contrast.
if (shouldDrawHighlightsOnTop(canvas)) {
drawHighlights(canvas, highlightPaths, highlightPaints, selectionPath, selectionPaint,
cursorOffsetVertical, firstLine, lastLine);
@@ -922,6 +927,9 @@
public void drawBackground(
@NonNull Canvas canvas,
int firstLine, int lastLine) {
+
+ drawHighContrastBackground(canvas, firstLine, lastLine);
+
// First, draw LineBackgroundSpans.
// LineBackgroundSpans know nothing about the alignment, margins, or
// direction of the layout or line. XXX: Should they?
@@ -988,6 +996,66 @@
}
/**
+ * Draws a solid rectangle behind the text, the same color as the high contrast stroke border,
+ * to make it even easier to read.
+ *
+ * <p>We draw it here instead of in DrawTextFunctor so that multiple spans don't draw
+ * backgrounds over each other's text.
+ */
+ private void drawHighContrastBackground(@NonNull Canvas canvas, int firstLine, int lastLine) {
+ if (!shouldDrawHighlightsOnTop(canvas)) {
+ return;
+ }
+
+ var padding = Math.max(HIGH_CONTRAST_TEXT_BORDER_WIDTH_MIN_PX,
+ mPaint.getTextSize() * HIGH_CONTRAST_TEXT_BORDER_WIDTH_FACTOR);
+
+ var bgPaint = mWorkPlainPaint;
+ bgPaint.reset();
+ bgPaint.setColor(isHighContrastTextDark() ? Color.WHITE : Color.BLACK);
+ bgPaint.setStyle(Paint.Style.FILL);
+
+ int start = getLineStart(firstLine);
+ int end = getLineEnd(lastLine);
+ // Draw a separate background rectangle for each line of text, that only surrounds the
+ // characters on that line.
+ forEachCharacterBounds(
+ start,
+ end,
+ firstLine,
+ lastLine,
+ new CharacterBoundsListener() {
+ int mLastLineNum = -1;
+ final RectF mLineBackground = new RectF();
+
+ @Override
+ public void onCharacterBounds(int index, int lineNum, float left, float top,
+ float right, float bottom) {
+ if (lineNum != mLastLineNum) {
+ drawRect();
+ mLineBackground.set(left, top, right, bottom);
+ mLastLineNum = lineNum;
+ } else {
+ mLineBackground.union(left, top, right, bottom);
+ }
+ }
+
+ @Override
+ public void onEnd() {
+ drawRect();
+ }
+
+ private void drawRect() {
+ if (!mLineBackground.isEmpty()) {
+ mLineBackground.inset(-padding, -padding);
+ canvas.drawRect(mLineBackground, bgPaint);
+ }
+ }
+ }
+ );
+ }
+
+ /**
* @param canvas
* @return The range of lines that need to be drawn, possibly empty.
* @hide
@@ -1682,7 +1750,7 @@
}
if (bounds == null) {
- throw new IllegalArgumentException("bounds can't be null.");
+ throw new IllegalArgumentException("bounds can't be null.");
}
final int neededLength = 4 * (end - start);
@@ -1698,6 +1766,34 @@
final int startLine = getLineForOffset(start);
final int endLine = getLineForOffset(end - 1);
+
+ forEachCharacterBounds(start, end, startLine, endLine,
+ (index, lineNum, left, lineTop, right, lineBottom) -> {
+ final int boundsIndex = boundsStart + 4 * (index - start);
+ bounds[boundsIndex] = left;
+ bounds[boundsIndex + 1] = lineTop;
+ bounds[boundsIndex + 2] = right;
+ bounds[boundsIndex + 3] = lineBottom;
+ });
+ }
+
+ /**
+ * Return the characters' bounds in the given range. The coordinates are in local text layout.
+ *
+ * @param start the start index to compute the character bounds, inclusive.
+ * @param end the end index to compute the character bounds, exclusive.
+ * @param startLine index of the line that contains {@code start}
+ * @param endLine index of the line that contains {@code end}
+ * @param listener called for each character with its bounds
+ *
+ */
+ private void forEachCharacterBounds(
+ @IntRange(from = 0) int start,
+ @IntRange(from = 0) int end,
+ @IntRange(from = 0) int startLine,
+ @IntRange(from = 0) int endLine,
+ CharacterBoundsListener listener
+ ) {
float[] horizontalBounds = null;
for (int line = startLine; line <= endLine; ++line) {
final int lineStart = getLineStart(line);
@@ -1722,13 +1818,10 @@
final float left = horizontalBounds[offset * 2] + lineStartPos;
final float right = horizontalBounds[offset * 2 + 1] + lineStartPos;
- final int boundsIndex = boundsStart + 4 * (index - start);
- bounds[boundsIndex] = left;
- bounds[boundsIndex + 1] = lineTop;
- bounds[boundsIndex + 2] = right;
- bounds[boundsIndex + 3] = lineBottom;
+ listener.onCharacterBounds(index, line, left, lineTop, right, lineBottom);
}
}
+ listener.onEnd();
}
/**
@@ -4443,4 +4536,15 @@
public Paint.FontMetrics getMinimumFontMetrics() {
return mMinimumFontMetrics;
}
+
+ /**
+ * Callback for {@link #forEachCharacterBounds(int, int, int, int, CharacterBoundsListener)}
+ */
+ private interface CharacterBoundsListener {
+ void onCharacterBounds(int index, int lineNum, float left, float top, float right,
+ float bottom);
+
+ /** Called after the last character has been sent to {@link #onCharacterBounds}. */
+ default void onEnd() {}
+ }
}
diff --git a/core/java/android/text/format/Formatter.java b/core/java/android/text/format/Formatter.java
index b9de93c..7653bdb 100644
--- a/core/java/android/text/format/Formatter.java
+++ b/core/java/android/text/format/Formatter.java
@@ -56,11 +56,18 @@
public static class BytesResult {
public final String value;
public final String units;
+ /**
+ * Content description of the {@link #units}.
+ * See {@link View#setContentDescription(CharSequence)}
+ */
+ public final String unitsContentDescription;
public final long roundedBytes;
- public BytesResult(String value, String units, long roundedBytes) {
+ public BytesResult(String value, String units, String unitsContentDescription,
+ long roundedBytes) {
this.value = value;
this.units = units;
+ this.unitsContentDescription = unitsContentDescription;
this.roundedBytes = roundedBytes;
}
}
@@ -271,20 +278,20 @@
final Locale locale = res.getConfiguration().getLocales().get(0);
final NumberFormat numberFormatter = getNumberFormatter(locale, rounded.fractionDigits);
final String formattedNumber = numberFormatter.format(rounded.value);
- final String units;
+ // Since ICU does not give us access to the pattern, we need to extract the unit string
+ // from ICU, which we do by taking out the formatted number out of the formatted string
+ // and trimming the result of spaces and controls.
+ final String formattedMeasure = formatMeasureShort(
+ locale, numberFormatter, rounded.value, rounded.units);
+ final String numberRemoved = deleteFirstFromString(formattedMeasure, formattedNumber);
+ String units = SPACES_AND_CONTROLS.trim(numberRemoved).toString();
+ String unitsContentDescription = units;
if (rounded.units == MeasureUnit.BYTE) {
// ICU spells out "byte" instead of "B".
units = getByteSuffixOverride(res);
- } else {
- // Since ICU does not give us access to the pattern, we need to extract the unit string
- // from ICU, which we do by taking out the formatted number out of the formatted string
- // and trimming the result of spaces and controls.
- final String formattedMeasure = formatMeasureShort(
- locale, numberFormatter, rounded.value, rounded.units);
- final String numberRemoved = deleteFirstFromString(formattedMeasure, formattedNumber);
- units = SPACES_AND_CONTROLS.trim(numberRemoved).toString();
}
- return new BytesResult(formattedNumber, units, rounded.roundedBytes);
+ return new BytesResult(formattedNumber, units, unitsContentDescription,
+ rounded.roundedBytes);
}
/**
diff --git a/core/java/android/tracing/flags.aconfig b/core/java/android/tracing/flags.aconfig
index 74428aa..d7389ba 100644
--- a/core/java/android/tracing/flags.aconfig
+++ b/core/java/android/tracing/flags.aconfig
@@ -30,3 +30,11 @@
is_fixed_read_only: true
bug: "276433199"
}
+
+flag {
+ name: "perfetto_view_capture_tracing"
+ namespace: "windowing_tools"
+ description: "Migrate ViewCapture tracing to Perfetto"
+ is_fixed_read_only: true
+ bug: "323166383"
+}
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 65d9b3a..cb5a885 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -62,7 +62,6 @@
import android.view.InputDevice;
import android.view.IInputFilter;
import android.view.AppTransitionAnimationSpec;
-import android.view.TaskTransitionSpec;
import android.view.WindowContentFrameStats;
import android.view.WindowManager;
import android.view.SurfaceControl;
@@ -962,19 +961,6 @@
void setTaskSnapshotEnabled(boolean enabled);
/**
- * Customized the task transition animation with a task transition spec.
- *
- * @param spec the spec that will be used to customize the task animations
- */
- void setTaskTransitionSpec(in TaskTransitionSpec spec);
-
- /**
- * Clears any task transition spec that has been previously set and
- * reverts to using the default task transition with no spec changes.
- */
- void clearTaskTransitionSpec();
-
- /**
* Registers the frame rate per second count callback for one given task ID.
* Each callback can only register for receiving FPS callback for one task id until unregister
* is called. If there's no task associated with the given task id,
diff --git a/core/java/android/view/TaskTransitionSpec.aidl b/core/java/android/view/TaskTransitionSpec.aidl
deleted file mode 100644
index 08af15c..0000000
--- a/core/java/android/view/TaskTransitionSpec.aidl
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
-** Copyright 2021, 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.view;
-
-/** @hide */
-parcelable TaskTransitionSpec;
diff --git a/core/java/android/view/TaskTransitionSpec.java b/core/java/android/view/TaskTransitionSpec.java
deleted file mode 100644
index 9a2d3ba..0000000
--- a/core/java/android/view/TaskTransitionSpec.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright (C) 2021 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.view;
-
-import android.os.Parcel;
-import android.os.Parcelable;
-
-/**
- * Holds information about how to execute task transition animations.
- *
- * This class is intended to be used with IWindowManager.setTaskTransitionSpec methods when
- * we want more customization over the way default task transitions are executed.
- *
- * @hide
- */
-public class TaskTransitionSpec implements Parcelable {
- /**
- * The background color to use during task animations (override the default background color)
- */
- public final int backgroundColor;
-
- public TaskTransitionSpec(int backgroundColor) {
- this.backgroundColor = backgroundColor;
- }
-
- public TaskTransitionSpec(Parcel in) {
- this.backgroundColor = in.readInt();
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(backgroundColor);
- }
-
- public static final @android.annotation.NonNull Parcelable.Creator<TaskTransitionSpec>
- CREATOR = new Parcelable.Creator<TaskTransitionSpec>() {
- public TaskTransitionSpec createFromParcel(Parcel in) {
- return new TaskTransitionSpec(in);
- }
-
- public TaskTransitionSpec[] newArray(int size) {
- return new TaskTransitionSpec[size];
- }
- };
-}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index ddead88..9579614 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -40,6 +40,7 @@
import static android.view.flags.Flags.FLAG_VIEW_VELOCITY_API;
import static android.view.flags.Flags.enableUseMeasureCacheDuringForceLayout;
import static android.view.flags.Flags.sensitiveContentAppProtection;
+import static android.view.flags.Flags.sensitiveContentPrematureProtectionRemovedFix;
import static android.view.flags.Flags.toolkitFrameRateBySizeReadOnly;
import static android.view.flags.Flags.toolkitFrameRateDefaultNormalReadOnly;
import static android.view.flags.Flags.toolkitFrameRateSmallUsesPercentReadOnly;
@@ -912,12 +913,6 @@
private static final String AUTOFILL_LOG_TAG = "View.Autofill";
/**
- * The logging tag used by this class when logging verbose and chatty (high volume)
- * autofill-related messages.
- */
- private static final String AUTOFILL_CHATTY_LOG_TAG = "View.Autofill.Chatty";
-
- /**
* The logging tag used by this class when logging content capture-related messages.
*/
private static final String CONTENT_CAPTURE_LOG_TAG = "View.ContentCapture";
@@ -5779,7 +5774,7 @@
*/
private static final float FRAME_RATE_SIZE_PERCENTAGE_THRESHOLD = 0.07f;
- static final float MAX_FRAME_RATE = 140;
+ static final float MAX_FRAME_RATE = 120;
// The preferred frame rate of the view that is mainly used for
// touch boosting, view velocity handling, and TextureView.
@@ -8708,8 +8703,8 @@
@CallSuper
protected void onFocusChanged(boolean gainFocus, @FocusDirection int direction,
@Nullable Rect previouslyFocusedRect) {
- if (Log.isLoggable(AUTOFILL_CHATTY_LOG_TAG, Log.VERBOSE)) {
- Log.v(AUTOFILL_CHATTY_LOG_TAG, "onFocusChanged() entered. gainFocus: "
+ if (DBG) {
+ Log.d(VIEW_LOG_TAG, "onFocusChanged() entered. gainFocus: "
+ gainFocus);
}
if (gainFocus) {
@@ -8777,8 +8772,8 @@
if (canNotifyAutofillEnterExitEvent()) {
AutofillManager afm = getAutofillManager();
if (afm != null) {
- if (Log.isLoggable(AUTOFILL_CHATTY_LOG_TAG, Log.VERBOSE)) {
- Log.v(AUTOFILL_CHATTY_LOG_TAG, this + " afm is not null");
+ if (DBG) {
+ Log.d(VIEW_LOG_TAG, this + " afm is not null");
}
if (enter) {
// We have not been laid out yet, hence cannot evaluate
@@ -8791,8 +8786,8 @@
// animation beginning. On the time, the view is not visible
// to the user. And then as the animation progresses, the view
// becomes visible to the user.
- if (Log.isLoggable(AUTOFILL_CHATTY_LOG_TAG, Log.VERBOSE)) {
- Log.v(AUTOFILL_CHATTY_LOG_TAG,
+ if (DBG) {
+ Log.d(VIEW_LOG_TAG,
"notifyEnterOrExitForAutoFillIfNeeded:"
+ " isLaidOut(): " + isLaidOut()
+ " isVisibleToUser(): " + isVisibleToUser()
@@ -11024,28 +11019,28 @@
}
private boolean isAutofillable() {
- if (Log.isLoggable(AUTOFILL_CHATTY_LOG_TAG, Log.VERBOSE)) {
- Log.v(AUTOFILL_CHATTY_LOG_TAG, "isAutofillable() entered.");
+ if (DBG) {
+ Log.d(VIEW_LOG_TAG, "isAutofillable() entered.");
}
if (getAutofillType() == AUTOFILL_TYPE_NONE) {
- if (Log.isLoggable(AUTOFILL_CHATTY_LOG_TAG, Log.VERBOSE)) {
- Log.v(AUTOFILL_CHATTY_LOG_TAG, "getAutofillType() returns AUTOFILL_TYPE_NONE");
+ if (DBG) {
+ Log.d(VIEW_LOG_TAG, "getAutofillType() returns AUTOFILL_TYPE_NONE");
}
return false;
}
final AutofillManager afm = getAutofillManager();
if (afm == null) {
- if (Log.isLoggable(AUTOFILL_CHATTY_LOG_TAG, Log.VERBOSE)) {
- Log.v(AUTOFILL_CHATTY_LOG_TAG, "AutofillManager is null");
+ if (DBG) {
+ Log.d(VIEW_LOG_TAG, "AutofillManager is null");
}
return false;
}
// Check whether view is not part of an activity. If it's not, return false.
if (getAutofillViewId() <= LAST_APP_AUTOFILL_ID) {
- if (Log.isLoggable(AUTOFILL_CHATTY_LOG_TAG, Log.VERBOSE)) {
- Log.v(AUTOFILL_CHATTY_LOG_TAG, "getAutofillViewId()<=LAST_APP_AUTOFILL_ID");
+ if (DBG) {
+ Log.d(VIEW_LOG_TAG, "getAutofillViewId()<=LAST_APP_AUTOFILL_ID");
}
return false;
}
@@ -11056,9 +11051,8 @@
if ((isImportantForAutofill() && afm.isTriggerFillRequestOnFilteredImportantViewsEnabled())
|| (!isImportantForAutofill()
&& afm.isTriggerFillRequestOnUnimportantViewEnabled())) {
- if (Log.isLoggable(AUTOFILL_CHATTY_LOG_TAG, Log.VERBOSE)) {
- Log.v(AUTOFILL_CHATTY_LOG_TAG,
- "isImportantForAutofill(): " + isImportantForAutofill()
+ if (DBG) {
+ Log.d(VIEW_LOG_TAG, "isImportantForAutofill(): " + isImportantForAutofill()
+ "afm.isAutofillable(): " + afm.isAutofillable(this));
}
return afm.isAutofillable(this) ? true : notifyAugmentedAutofillIfNeeded(afm);
@@ -11066,8 +11060,8 @@
// If the previous condition is not met, fall back to the previous way to trigger fill
// request based on autofill importance instead.
- if (Log.isLoggable(AUTOFILL_CHATTY_LOG_TAG, Log.VERBOSE)) {
- Log.v(AUTOFILL_CHATTY_LOG_TAG, "isImportantForAutofill(): " + isImportantForAutofill());
+ if (DBG) {
+ Log.d(VIEW_LOG_TAG, "isImportantForAutofill(): " + isImportantForAutofill());
}
return isImportantForAutofill() ? true : notifyAugmentedAutofillIfNeeded(afm);
}
@@ -11083,8 +11077,8 @@
/** @hide */
public boolean canNotifyAutofillEnterExitEvent() {
- if (Log.isLoggable(AUTOFILL_CHATTY_LOG_TAG, Log.VERBOSE)) {
- Log.v(AUTOFILL_CHATTY_LOG_TAG, "canNotifyAutofillEnterExitEvent() entered. "
+ if (DBG) {
+ Log.d(VIEW_LOG_TAG, "canNotifyAutofillEnterExitEvent() entered. "
+ " isAutofillable(): " + isAutofillable()
+ " isAttachedToWindow(): " + isAttachedToWindow());
}
@@ -17160,10 +17154,12 @@
/**
* Handle a key event before it is processed by any input method
- * associated with the view hierarchy. This can be used to intercept
+ * associated with the view hierarchy. This can be used to intercept
* key events in special situations before the IME consumes them; a
* typical example would be handling the BACK key to update the application's
- * UI instead of allowing the IME to see it and close itself.
+ * UI instead of allowing the IME to see it and close itself. Due to a bug,
+ * this function is not called for BACK key events on Android T and U, when
+ * the IME is shown.
*
* @param keyCode The value in event.getKeyCode().
* @param event Description of the key event.
@@ -32242,7 +32238,11 @@
void decreaseSensitiveViewsCount() {
mSensitiveViewsCount--;
if (mSensitiveViewsCount == 0) {
- mViewRootImpl.notifySensitiveContentAppProtection(false);
+ if (sensitiveContentPrematureProtectionRemovedFix()) {
+ mViewRootImpl.removeSensitiveContentProtectionOnTransactionCommit();
+ } else {
+ mViewRootImpl.notifySensitiveContentAppProtection(false);
+ }
}
if (mSensitiveViewsCount < 0) {
Log.wtf(VIEW_LOG_TAG, "mSensitiveViewsCount is negative" + mSensitiveViewsCount);
@@ -33949,8 +33949,9 @@
protected int calculateFrameRateCategory() {
int category;
switch (getViewRootImpl().intermittentUpdateState()) {
- case ViewRootImpl.INTERMITTENT_STATE_INTERMITTENT ->
- category = FRAME_RATE_CATEGORY_NORMAL | FRAME_RATE_CATEGORY_REASON_INTERMITTENT;
+ case ViewRootImpl.INTERMITTENT_STATE_INTERMITTENT -> category =
+ (sToolkitFrameRateBySizeReadOnlyFlagValue ? FRAME_RATE_CATEGORY_LOW
+ : FRAME_RATE_CATEGORY_NORMAL) | FRAME_RATE_CATEGORY_REASON_INTERMITTENT;
case ViewRootImpl.INTERMITTENT_STATE_NOT_INTERMITTENT ->
category = mSizeBasedFrameRateCategoryAndReason;
default -> category = mLastFrameRateCategory;
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 11ad86c..ab529e6 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -2654,9 +2654,10 @@
ViewRootImpl viewRootImpl = getViewRootImpl();
if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
- final boolean isDispatchingBack = (viewRootImpl != null
- && viewRootImpl.getOnBackInvokedDispatcher().isDispatching());
- if (!disallowIntercept || isDispatchingBack) { // Allow back to intercept touch
+ final boolean isBackGestureInProgress = (viewRootImpl != null
+ && viewRootImpl.getOnBackInvokedDispatcher().isBackGestureInProgress());
+ if (!disallowIntercept || isBackGestureInProgress) {
+ // Allow back to intercept touch
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 3bc6829..1d84375 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -317,6 +317,7 @@
private static final boolean DEBUG_SCROLL_CAPTURE = false || LOCAL_LOGV;
private static final boolean DEBUG_TOUCH_NAVIGATION = false || LOCAL_LOGV;
private static final boolean DEBUG_BLAST = false || LOCAL_LOGV;
+ private static final boolean DEBUG_SENSITIVE_CONTENT = false || LOCAL_LOGV;
private static final int LOGTAG_INPUT_FOCUS = 62001;
private static final int LOGTAG_VIEWROOT_DRAW_EVENT = 60004;
@@ -4257,11 +4258,13 @@
// when the values are applicable.
if (mDrawnThisFrame) {
mDrawnThisFrame = false;
- updateInfrequentCount();
setCategoryFromCategoryCounts();
+ updateInfrequentCount();
setPreferredFrameRate(mPreferredFrameRate);
setPreferredFrameRateCategory(mPreferredFrameRateCategory);
- if (!mIsFrameRateConflicted) {
+ if (mPreferredFrameRate > 0
+ || (mLastPreferredFrameRate != 0 && mPreferredFrameRate == 0)
+ ) {
mHandler.removeMessages(MSG_FRAME_RATE_SETTING);
mHandler.sendEmptyMessageDelayed(MSG_FRAME_RATE_SETTING,
FRAME_RATE_SETTING_REEVALUATE_TIME);
@@ -4276,6 +4279,10 @@
mPreferredFrameRate = -1;
mIsFrameRateConflicted = false;
mFrameRateCategoryChangeReason = FRAME_RATE_CATEGORY_REASON_UNKNOWN;
+ } else if (mPreferredFrameRate == 0) {
+ // From MSG_FRAME_RATE_SETTING, where mPreferredFrameRate is set to 0
+ setPreferredFrameRate(0);
+ mPreferredFrameRate = -1;
}
}
@@ -4330,6 +4337,10 @@
if (mSensitiveContentProtectionService == null) {
return;
}
+ if (DEBUG_SENSITIVE_CONTENT) {
+ Log.d(TAG, "Notify sensitive content, package=" + mContext.getPackageName()
+ + ", token=" + getWindowToken() + ", flag=" + showSensitiveContent);
+ }
// The window would be blocked during screen share if it shows sensitive content.
mSensitiveContentProtectionService.setSensitiveContentProtection(
getWindowToken(), mContext.getPackageName(), showSensitiveContent);
@@ -4338,6 +4349,24 @@
}
}
+ /**
+ * Sensitive protection is removed on transaction commit to avoid prematurely removing
+ * the protection.
+ */
+ void removeSensitiveContentProtectionOnTransactionCommit() {
+ if (DEBUG_SENSITIVE_CONTENT) {
+ Log.d(TAG, "Add transaction to remove sensitive content protection, package="
+ + mContext.getPackageName() + ", token=" + getWindowToken());
+ }
+ Transaction t = new Transaction();
+ t.addTransactionCommittedListener(mExecutor, () -> {
+ if (mAttachInfo.mSensitiveViewsCount == 0) {
+ notifySensitiveContentAppProtection(false);
+ }
+ });
+ applyTransactionOnDraw(t);
+ }
+
private void notifyContentCaptureEvents() {
if (!isContentCaptureEnabled()) {
if (DEBUG_CONTENT_CAPTURE) {
@@ -7224,7 +7253,7 @@
private int doOnBackKeyEvent(KeyEvent keyEvent) {
WindowOnBackInvokedDispatcher dispatcher = getOnBackInvokedDispatcher();
OnBackInvokedCallback topCallback = dispatcher.getTopCallback();
- if (dispatcher.isDispatching()) {
+ if (dispatcher.isBackGestureInProgress()) {
return FINISH_NOT_HANDLED;
}
if (topCallback instanceof OnBackAnimationCallback) {
@@ -12565,6 +12594,13 @@
case FRAME_RATE_CATEGORY_HIGH ->
mFrameRateCategoryHighCount = FRAME_RATE_CATEGORY_COUNT;
}
+
+ // If it's currently an intermittent update,
+ // we should keep mPreferredFrameRateCategory as NORMAL
+ if (intermittentUpdateState() == INTERMITTENT_STATE_INTERMITTENT) {
+ return;
+ }
+
if (mFrameRateCategoryHighCount > 0) {
mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_HIGH;
} else if (mFrameRateCategoryHighHintCount > 0) {
diff --git a/core/java/android/view/flags/view_flags.aconfig b/core/java/android/view/flags/view_flags.aconfig
index 16e1415..12bd45a 100644
--- a/core/java/android/view/flags/view_flags.aconfig
+++ b/core/java/android/view/flags/view_flags.aconfig
@@ -46,6 +46,18 @@
}
flag {
+ name: "sensitive_content_premature_protection_removed_fix"
+ namespace: "permissions"
+ description: "Bug fix where sensitive content protection is prematurely removed."
+ bug: "336626172"
+ # Referenced in WM where WM starts before DeviceConfig
+ is_fixed_read_only: true
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "enable_arrow_icon_on_hover_when_clickable"
namespace: "toolkit"
description: "Enable default arrow icon when hovering on buttons or clickable widgets."
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 8174da6..1cdcd20 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -1671,7 +1671,6 @@
@NonNull
@RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)
@TestApi
- @FlaggedApi(Flags.FLAG_IMM_USERHANDLE_HOSTSIDETESTS)
@SuppressLint("UserHandle")
public boolean isStylusHandwritingAvailableAsUser(@NonNull UserHandle user) {
final Context fallbackContext = ActivityThread.currentApplication();
@@ -1816,7 +1815,6 @@
@NonNull
@RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)
@TestApi
- @FlaggedApi(Flags.FLAG_IMM_USERHANDLE_HOSTSIDETESTS)
@SuppressLint("UserHandle")
public List<InputMethodInfo> getEnabledInputMethodListAsUser(@NonNull UserHandle user) {
return IInputMethodManagerGlobalInvoker.getEnabledInputMethodList(user.getIdentifier());
@@ -1858,7 +1856,6 @@
@NonNull
@RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)
@TestApi
- @FlaggedApi(Flags.FLAG_IMM_USERHANDLE_HOSTSIDETESTS)
@SuppressLint("UserHandle")
public List<InputMethodSubtype> getEnabledInputMethodSubtypeListAsUser(
@NonNull String imeId, boolean allowsImplicitlyEnabledSubtypes,
diff --git a/core/java/android/view/inputmethod/flags.aconfig b/core/java/android/view/inputmethod/flags.aconfig
index d79903b..fa9458d 100644
--- a/core/java/android/view/inputmethod/flags.aconfig
+++ b/core/java/android/view/inputmethod/flags.aconfig
@@ -106,3 +106,13 @@
}
}
+flag {
+ name: "defer_show_soft_input_until_session_creation"
+ namespace: "input_method"
+ description: "Defers showSoftInput until the IME session has been created."
+ bug: "337766845"
+ is_fixed_read_only: true
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index f643bd4..5430f8f 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -382,7 +382,7 @@
/**
* Maps Intent ID to RemoteCollectionItems to avoid duplicate items
*/
- private RemoteCollectionCache mCollectionCache = new RemoteCollectionCache();
+ private @NonNull RemoteCollectionCache mCollectionCache = new RemoteCollectionCache();
/** Cache of ApplicationInfos used by collection items. */
private ApplicationInfoCache mApplicationInfoCache = new ApplicationInfoCache();
@@ -775,6 +775,16 @@
}
/**
+ * Return {@code true} only if this {@code RemoteViews} is a legacy list widget that uses
+ * {@code Intent} for inflating child entries.
+ *
+ * @hide
+ */
+ public boolean isLegacyListRemoteViews() {
+ return mCollectionCache.mIdToUriMapping.size() > 0;
+ }
+
+ /**
* Note all {@link Uri} that are referenced internally, with the expectation that Uri permission
* grants will need to be issued to ensure the recipient of this object is able to render its
* contents.
@@ -1231,8 +1241,8 @@
}
private class RemoteCollectionCache {
- private SparseArray<String> mIdToUriMapping = new SparseArray<>();
- private HashMap<String, RemoteCollectionItems> mUriToCollectionMapping = new HashMap<>();
+ private final SparseArray<String> mIdToUriMapping = new SparseArray<>();
+ private final Map<String, RemoteCollectionItems> mUriToCollectionMapping = new HashMap<>();
RemoteCollectionCache() { }
diff --git a/core/java/android/window/ImeOnBackInvokedDispatcher.java b/core/java/android/window/ImeOnBackInvokedDispatcher.java
index ee6ba24..3b9b162 100644
--- a/core/java/android/window/ImeOnBackInvokedDispatcher.java
+++ b/core/java/android/window/ImeOnBackInvokedDispatcher.java
@@ -16,6 +16,8 @@
package android.window;
+import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Bundle;
@@ -27,6 +29,8 @@
import android.util.Log;
import android.view.ViewRootImpl;
+import com.android.internal.annotations.VisibleForTesting;
+
import java.util.ArrayList;
/**
@@ -280,7 +284,8 @@
sendStopDispatching();
}
- static class ImeOnBackInvokedCallback implements OnBackInvokedCallback {
+ @VisibleForTesting(visibility = PACKAGE)
+ public static class ImeOnBackInvokedCallback implements OnBackInvokedCallback {
@NonNull
private final IOnBackInvokedCallback mIOnBackInvokedCallback;
/**
@@ -327,7 +332,8 @@
* Subclass of ImeOnBackInvokedCallback indicating that a predictive IME back animation may be
* played instead of invoking the callback.
*/
- static class DefaultImeOnBackAnimationCallback extends ImeOnBackInvokedCallback {
+ @VisibleForTesting(visibility = PACKAGE)
+ public static class DefaultImeOnBackAnimationCallback extends ImeOnBackInvokedCallback {
DefaultImeOnBackAnimationCallback(@NonNull IOnBackInvokedCallback iCallback, int id,
int priority) {
super(iCallback, id, priority);
diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java
index 7f6678e..e351d6b 100644
--- a/core/java/android/window/WindowOnBackInvokedDispatcher.java
+++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java
@@ -114,7 +114,7 @@
/** Updates the dispatcher state on a new {@link MotionEvent}. */
public void onMotionEvent(MotionEvent ev) {
- if (!isDispatching() || ev == null || ev.getAction() != MotionEvent.ACTION_MOVE) {
+ if (!isBackGestureInProgress() || ev == null || ev.getAction() != MotionEvent.ACTION_MOVE) {
return;
}
mTouchTracker.update(ev.getX(), ev.getY(), Float.NaN, Float.NaN);
@@ -176,6 +176,12 @@
mImeDispatcher.registerOnBackInvokedCallback(priority, callback);
return;
}
+ if ((callback instanceof ImeOnBackInvokedDispatcher.DefaultImeOnBackAnimationCallback
+ || callback instanceof ImeOnBackInvokedDispatcher.ImeOnBackInvokedCallback)
+ && !isOnBackInvokedCallbackEnabled()) {
+ // Fall back to compat back key injection if legacy back behaviour should be used.
+ return;
+ }
if (!mOnBackInvokedCallbacks.containsKey(priority)) {
mOnBackInvokedCallbacks.put(priority, new ArrayList<>());
}
@@ -240,9 +246,9 @@
}
/**
- * Indicates if the dispatcher is actively dispatching to a callback.
+ * Indicates if a user gesture is currently in progress.
*/
- public boolean isDispatching() {
+ public boolean isBackGestureInProgress() {
synchronized (mLock) {
return mTouchTracker.isActive() || mImeDispatchingActive;
}
@@ -469,12 +475,17 @@
@Override
public void onBackStarted(BackMotionEvent backEvent) {
mHandler.post(() -> {
+ final OnBackAnimationCallback callback = getBackAnimationCallback();
+
+ // reset progress animator before dispatching onBackStarted to callback. This
+ // ensures that onBackCancelled (of a previous gesture) is always dispatched
+ // before onBackStarted
+ if (callback != null) mProgressAnimator.reset();
mTouchTracker.setState(BackTouchTracker.TouchTrackerState.ACTIVE);
mTouchTracker.setShouldUpdateStartLocation(true);
mTouchTracker.setGestureStartLocation(
backEvent.getTouchX(), backEvent.getTouchY(), backEvent.getSwipeEdge());
- final OnBackAnimationCallback callback = getBackAnimationCallback();
if (callback != null) {
callback.onBackStarted(new BackEvent(
backEvent.getTouchX(),
@@ -493,14 +504,9 @@
public void onBackCancelled() {
mHandler.post(() -> {
final OnBackAnimationCallback callback = getBackAnimationCallback();
- if (callback == null) {
- mTouchTracker.reset();
- return;
- }
- mProgressAnimator.onBackCancelled(() -> {
- mTouchTracker.reset();
- callback.onBackCancelled();
- });
+ mTouchTracker.reset();
+ if (callback == null) return;
+ mProgressAnimator.onBackCancelled(callback::onBackCancelled);
});
}
diff --git a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig
index 98ff3c6..cd13c4a 100644
--- a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig
+++ b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig
@@ -2,6 +2,13 @@
container: "system"
flag {
+ name: "disable_thin_letterboxing_reachability"
+ namespace: "large_screen_experiences_app_compat"
+ description: "Whether reachability is disabled in case of thin letterboxing"
+ bug: "334077350"
+}
+
+flag {
name: "allows_screen_size_decoupled_from_status_bar_and_cutout"
namespace: "large_screen_experiences_app_compat"
description: "When necessary, configuration decoupled from status bar and display cutout"
diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig
index 55927cc..e8b4f0b 100644
--- a/core/java/android/window/flags/lse_desktop_experience.aconfig
+++ b/core/java/android/window/flags/lse_desktop_experience.aconfig
@@ -64,3 +64,10 @@
description: "Hides the App Handle when in fullscreen immersive mode"
bug: "336368019"
}
+
+flag {
+ name: "enable_desktop_windowing_quick_switch"
+ namespace: "lse_desktop_experience"
+ description: "Enables quick switch for desktop mode"
+ bug: "338066529"
+}
diff --git a/core/java/com/android/internal/appwidget/IAppWidgetHost.aidl b/core/java/com/android/internal/appwidget/IAppWidgetHost.aidl
index 7cbacad..8faa4cf 100644
--- a/core/java/com/android/internal/appwidget/IAppWidgetHost.aidl
+++ b/core/java/com/android/internal/appwidget/IAppWidgetHost.aidl
@@ -23,6 +23,7 @@
/** {@hide} */
oneway interface IAppWidgetHost {
+ void updateAppWidgetDeferred(int appWidgetId);
void updateAppWidget(int appWidgetId, in RemoteViews views);
void providerChanged(int appWidgetId, in AppWidgetProviderInfo info);
void providersChanged();
diff --git a/core/java/com/android/internal/inputmethod/ImeTracing.java b/core/java/com/android/internal/inputmethod/ImeTracing.java
index db95012..ee9c3aa 100644
--- a/core/java/com/android/internal/inputmethod/ImeTracing.java
+++ b/core/java/com/android/internal/inputmethod/ImeTracing.java
@@ -16,6 +16,7 @@
package com.android.internal.inputmethod;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.app.ActivityThread;
@@ -144,7 +145,7 @@
*
* @param where Place where the trace was triggered.
*/
- public abstract void triggerManagerServiceDump(String where);
+ public abstract void triggerManagerServiceDump(String where, @NonNull ServiceDumper dumper);
/**
* Being called while taking a bugreport so that tracing files can be included in the bugreport
diff --git a/core/java/com/android/internal/inputmethod/ImeTracingClientImpl.java b/core/java/com/android/internal/inputmethod/ImeTracingClientImpl.java
index 95ed4ed..8a684d3 100644
--- a/core/java/com/android/internal/inputmethod/ImeTracingClientImpl.java
+++ b/core/java/com/android/internal/inputmethod/ImeTracingClientImpl.java
@@ -83,7 +83,7 @@
}
@Override
- public void triggerManagerServiceDump(String where) {
+ public void triggerManagerServiceDump(String where, @NonNull ServiceDumper dumper) {
// Intentionally left empty, this is implemented in ImeTracingServerImpl
}
diff --git a/core/java/com/android/internal/inputmethod/ImeTracingServerImpl.java b/core/java/com/android/internal/inputmethod/ImeTracingServerImpl.java
index edd74f6..937f94c 100644
--- a/core/java/com/android/internal/inputmethod/ImeTracingServerImpl.java
+++ b/core/java/com/android/internal/inputmethod/ImeTracingServerImpl.java
@@ -18,6 +18,7 @@
import static android.os.Build.IS_USER;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.SystemClock;
import android.util.Log;
@@ -116,7 +117,7 @@
}
@Override
- public void triggerManagerServiceDump(String where) {
+ public void triggerManagerServiceDump(String where, @NonNull ServiceDumper dumper) {
if (!isEnabled() || !isAvailable()) {
return;
}
diff --git a/core/java/com/android/internal/os/PowerStats.java b/core/java/com/android/internal/os/PowerStats.java
index 7c7c7b8..9f9aae5 100644
--- a/core/java/com/android/internal/os/PowerStats.java
+++ b/core/java/com/android/internal/os/PowerStats.java
@@ -473,7 +473,14 @@
} finally {
// Unconditionally skip to the end of the written data, even if the actual parcel
// format is incompatible
- parcel.setDataPosition(endPos);
+ if (endPos > parcel.dataPosition()) {
+ if (endPos >= parcel.dataSize()) {
+ throw new IndexOutOfBoundsException(
+ "PowerStats end position: " + endPos + " is outside the parcel bounds: "
+ + parcel.dataSize());
+ }
+ parcel.setDataPosition(endPos);
+ }
}
}
diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java
index f4315e3..74c2325 100644
--- a/core/java/com/android/internal/policy/DecorView.java
+++ b/core/java/com/android/internal/policy/DecorView.java
@@ -523,8 +523,8 @@
ViewRootImpl viewRootImpl = getViewRootImpl();
if (viewRootImpl != null) {
viewRootImpl.getOnBackInvokedDispatcher().onMotionEvent(event);
- // Intercept touch if back dispatching is active.
- if (viewRootImpl.getOnBackInvokedDispatcher().isDispatching()) {
+ // Intercept touch if back gesture is in progress.
+ if (viewRootImpl.getOnBackInvokedDispatcher().isBackGestureInProgress()) {
return true;
}
}
diff --git a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
index 874cc49..ee33eb4 100644
--- a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
@@ -99,7 +99,7 @@
private final Map<LogLevel, Integer> mDefaultLogLevelCounts = new ArrayMap<>();
private final Map<IProtoLogGroup, Map<LogLevel, Integer>> mLogLevelCounts = new ArrayMap<>();
- private final ExecutorService mBackgroundLoggingService = Executors.newCachedThreadPool();
+ private final ExecutorService mBackgroundLoggingService = Executors.newSingleThreadExecutor();
public PerfettoProtoLogImpl(String viewerConfigFilePath,
TreeMap<String, IProtoLogGroup> logGroups, Runnable cacheUpdater) {
diff --git a/core/java/com/android/internal/util/ScreenshotHelper.java b/core/java/com/android/internal/util/ScreenshotHelper.java
index 69d3d6a..c21a43e 100644
--- a/core/java/com/android/internal/util/ScreenshotHelper.java
+++ b/core/java/com/android/internal/util/ScreenshotHelper.java
@@ -53,8 +53,6 @@
public ScreenshotHelper(Context context) {
mContext = context;
- IntentFilter filter = new IntentFilter(ACTION_USER_SWITCHED);
- mContext.registerReceiver(mBroadcastReceiver, filter, Context.RECEIVER_EXPORTED);
}
/**
@@ -108,6 +106,8 @@
public void takeScreenshotInternal(ScreenshotRequest request, @NonNull Handler handler,
@Nullable Consumer<Uri> completionConsumer, long timeoutMs) {
synchronized (mScreenshotLock) {
+ mContext.registerReceiver(mBroadcastReceiver,
+ new IntentFilter(ACTION_USER_SWITCHED), Context.RECEIVER_EXPORTED);
final Runnable mScreenshotTimeout = () -> {
synchronized (mScreenshotLock) {
@@ -223,6 +223,11 @@
mScreenshotConnection = null;
mScreenshotService = null;
}
+ try {
+ mContext.unregisterReceiver(mBroadcastReceiver);
+ } catch (IllegalArgumentException e) {
+ Log.w(TAG, "Attempted to remove broadcast receiver twice");
+ }
}
/**
diff --git a/core/jni/android_hardware_Camera.cpp b/core/jni/android_hardware_Camera.cpp
index 72b98a2..2316f4c 100644
--- a/core/jni/android_hardware_Camera.cpp
+++ b/core/jni/android_hardware_Camera.cpp
@@ -529,7 +529,7 @@
}
static void android_hardware_Camera_getCameraInfo(JNIEnv *env, jobject thiz, jint cameraId,
- jboolean overrideToPortrait, jint deviceId,
+ jint rotationOverride, jint deviceId,
jint devicePolicy, jobject info_obj) {
CameraInfo cameraInfo;
if (cameraId >= Camera::getNumberOfCameras(deviceId, devicePolicy) || cameraId < 0) {
@@ -538,7 +538,7 @@
return;
}
- status_t rc = Camera::getCameraInfo(cameraId, overrideToPortrait, deviceId, devicePolicy,
+ status_t rc = Camera::getCameraInfo(cameraId, rotationOverride, deviceId, devicePolicy,
&cameraInfo);
if (rc != NO_ERROR) {
jniThrowRuntimeException(env, "Fail to get camera info");
@@ -557,7 +557,7 @@
// connect to camera service
static jint android_hardware_Camera_native_setup(JNIEnv *env, jobject thiz, jobject weak_this,
jint cameraId, jstring clientPackageName,
- jboolean overrideToPortrait,
+ jint rotationOverride,
jboolean forceSlowJpegMode, jint deviceId,
jint devicePolicy) {
// Convert jstring to String16
@@ -571,7 +571,7 @@
int targetSdkVersion = android_get_application_target_sdk_version();
sp<Camera> camera =
Camera::connect(cameraId, clientName, Camera::USE_CALLING_UID, Camera::USE_CALLING_PID,
- targetSdkVersion, overrideToPortrait, forceSlowJpegMode, deviceId,
+ targetSdkVersion, rotationOverride, forceSlowJpegMode, deviceId,
devicePolicy);
if (camera == NULL) {
return -EACCES;
@@ -600,7 +600,7 @@
// Update default display orientation in case the sensor is reverse-landscape
CameraInfo cameraInfo;
- status_t rc = Camera::getCameraInfo(cameraId, overrideToPortrait, deviceId, devicePolicy,
+ status_t rc = Camera::getCameraInfo(cameraId, rotationOverride, deviceId, devicePolicy,
&cameraInfo);
if (rc != NO_ERROR) {
ALOGE("%s: getCameraInfo error: %d", __FUNCTION__, rc);
@@ -1057,9 +1057,9 @@
static const JNINativeMethod camMethods[] = {
{"_getNumberOfCameras", "(II)I", (void *)android_hardware_Camera_getNumberOfCameras},
- {"_getCameraInfo", "(IZIILandroid/hardware/Camera$CameraInfo;)V",
+ {"_getCameraInfo", "(IIIILandroid/hardware/Camera$CameraInfo;)V",
(void *)android_hardware_Camera_getCameraInfo},
- {"native_setup", "(Ljava/lang/Object;ILjava/lang/String;ZZII)I",
+ {"native_setup", "(Ljava/lang/Object;ILjava/lang/String;IZII)I",
(void *)android_hardware_Camera_native_setup},
{"native_release", "()V", (void *)android_hardware_Camera_release},
{"setPreviewSurface", "(Landroid/view/Surface;)V",
diff --git a/core/jni/android_hardware_UsbDeviceConnection.cpp b/core/jni/android_hardware_UsbDeviceConnection.cpp
index a022842..7267eb8 100644
--- a/core/jni/android_hardware_UsbDeviceConnection.cpp
+++ b/core/jni/android_hardware_UsbDeviceConnection.cpp
@@ -190,17 +190,21 @@
return -1;
}
- jbyte* bufferBytes = NULL;
- if (buffer) {
- bufferBytes = (jbyte*)env->GetPrimitiveArrayCritical(buffer, NULL);
+ bool is_dir_in = (endpoint & USB_ENDPOINT_DIR_MASK) == USB_DIR_IN;
+ jbyte *bufferBytes = (jbyte *)malloc(length);
+
+ if (!is_dir_in && buffer) {
+ env->GetByteArrayRegion(buffer, start, length, bufferBytes);
}
- jint result = usb_device_bulk_transfer(device, endpoint, bufferBytes + start, length, timeout);
+ jint result = usb_device_bulk_transfer(device, endpoint, bufferBytes, length, timeout);
- if (bufferBytes) {
- env->ReleasePrimitiveArrayCritical(buffer, bufferBytes, 0);
+ if (is_dir_in && buffer) {
+ env->SetByteArrayRegion(buffer, start, length, bufferBytes);
}
+ free(bufferBytes);
+
return result;
}
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index 3ed9f49..12d62cc 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -1828,10 +1828,10 @@
std::string source = "/dev/__properties__/appcompat_override";
std::string target = "/dev/__properties__";
if (access(source.c_str(), F_OK) != 0) {
- fail_fn(CREATE_ERROR("Error accessing %s: %s", source.c_str(), strerror(errno)));
+ return;
}
if (access(target.c_str(), F_OK) != 0) {
- fail_fn(CREATE_ERROR("Error accessing %s: %s", target.c_str(), strerror(errno)));
+ return;
}
BindMount(source, target, fail_fn);
// Reload the system properties file, to ensure new values are read into memory
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index e2106c5..77a9912 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -8233,7 +8233,7 @@
</activity>
<activity android:name="com.android.internal.accessibility.dialog.AccessibilityButtonChooserActivity"
android:exported="false"
- android:theme="@style/Theme.DeviceDefault.Resolver"
+ android:theme="@style/AccessibilityButtonChooserDialog"
android:finishOnCloseSystemDialogs="true"
android:excludeFromRecents="true"
android:documentLaunchMode="never"
diff --git a/core/res/res/drawable-nodpi/stat_sys_adb.xml b/core/res/res/drawable-nodpi/stat_sys_adb.xml
index 8ae2a9b..6ce4b9d 100644
--- a/core/res/res/drawable-nodpi/stat_sys_adb.xml
+++ b/core/res/res/drawable-nodpi/stat_sys_adb.xml
@@ -21,16 +21,102 @@
android:viewportHeight="24">
<group>
<clip-path
- android:pathData="M12,20.923C10.764,20.923 9.946,20.065 9.131,18.653C8.316,17.241 4.291,10.269 3.476,8.857C2.66,7.445 2.326,6.309 2.944,5.238C3.563,4.167 4.714,3.888 6.344,3.888C7.975,3.888 16.025,3.888 17.656,3.888C19.286,3.888 20.437,4.167 21.056,5.238C21.674,6.309 21.34,7.445 20.524,8.857C19.709,10.269 15.684,17.241 14.869,18.653C14.054,20.065 13.236,20.923 12,20.923H12Z"/>
+ android:pathData="
+ M12.6495 17.375
+ C12.3608 17.875 11.6392 17.875 11.3505 17.375
+ L3.98927 4.625
+ C3.70059 4.125 4.06143 3.5 4.63878 3.5
+ L19.3612 3.5
+ C19.9386 3.5 20.2994 4.125 20.0107 4.625
+ L12.6495 17.375
+ Z
+ "/>
<path
- android:pathData="M5,14.978h14v9.8h-14z"
+ android:pathData="M5,12 h14v9.8h-14z"
android:fillColor="#ffffff"/>
<path
- android:pathData="M18.722,15.576C18.717,15.548 18.713,15.521 18.708,15.493C18.68,15.324 18.646,15.156 18.605,14.991C18.534,14.701 18.445,14.42 18.339,14.146C18.249,13.915 18.146,13.69 18.033,13.472C17.886,13.192 17.722,12.923 17.539,12.667C17.316,12.354 17.067,12.06 16.795,11.789C16.68,11.676 16.562,11.566 16.44,11.461C16.175,11.233 15.893,11.025 15.595,10.839C15.598,10.834 15.6,10.83 15.602,10.825C15.739,10.59 15.875,10.355 16.012,10.119C16.145,9.889 16.279,9.659 16.412,9.429C16.508,9.264 16.604,9.098 16.699,8.933C16.722,8.894 16.74,8.854 16.753,8.812C16.791,8.696 16.792,8.575 16.762,8.462C16.754,8.434 16.745,8.406 16.734,8.38C16.723,8.353 16.71,8.327 16.695,8.302C16.644,8.216 16.571,8.142 16.479,8.087C16.399,8.039 16.308,8.011 16.215,8.002C16.176,7.999 16.137,7.999 16.098,8.003C16.066,8.007 16.034,8.013 16.002,8.021C15.889,8.051 15.785,8.113 15.703,8.202C15.674,8.235 15.647,8.27 15.624,8.309C15.529,8.475 15.433,8.64 15.337,8.805L14.937,9.495C14.801,9.731 14.664,9.966 14.528,10.202C14.513,10.227 14.498,10.253 14.483,10.279C14.462,10.271 14.442,10.263 14.421,10.255C13.669,9.968 12.853,9.811 12,9.811C11.977,9.811 11.954,9.811 11.931,9.811C11.172,9.819 10.444,9.951 9.764,10.188C9.686,10.215 9.608,10.244 9.531,10.274C9.517,10.25 9.503,10.226 9.489,10.202C9.353,9.966 9.216,9.731 9.08,9.495C8.946,9.265 8.813,9.035 8.679,8.805C8.584,8.64 8.488,8.475 8.392,8.31C8.37,8.271 8.343,8.235 8.314,8.203C8.232,8.113 8.127,8.051 8.014,8.021C7.983,8.013 7.951,8.007 7.919,8.004C7.88,8 7.841,7.999 7.802,8.003C7.709,8.011 7.618,8.039 7.537,8.088C7.446,8.142 7.373,8.217 7.322,8.302C7.307,8.327 7.294,8.353 7.283,8.38C7.271,8.407 7.262,8.434 7.255,8.462C7.225,8.575 7.226,8.697 7.264,8.812C7.277,8.854 7.295,8.894 7.318,8.934C7.413,9.099 7.509,9.264 7.605,9.429C7.738,9.659 7.872,9.889 8.005,10.119C8.141,10.355 8.278,10.59 8.414,10.826C8.415,10.828 8.417,10.83 8.418,10.832C8.143,11.003 7.881,11.192 7.634,11.4C7.486,11.524 7.343,11.654 7.207,11.79C6.935,12.061 6.685,12.354 6.462,12.668C6.279,12.923 6.114,13.192 5.968,13.472C5.855,13.691 5.752,13.915 5.662,14.147C5.556,14.42 5.467,14.702 5.396,14.991C5.355,15.157 5.321,15.324 5.293,15.494C5.288,15.521 5.284,15.549 5.279,15.576C5.264,15.675 5.251,15.774 5.241,15.874L18.759,15.874C18.749,15.774 18.736,15.675 18.72,15.576L18.722,15.576Z"
+ android:pathData="
+ M15.97 10.48
+ C15.97 10.46 15.97 10.45 15.96 10.43
+ C15.95 10.33 15.93 10.23 15.90 10.13
+ C15.86 9.96 15.81 9.79 15.75 9.63
+ C15.69 9.50 15.63 9.36 15.57 9.23
+ C15.48 9.07 15.38 8.91 15.27 8.76
+ C15.14 8.57 14.99 8.40 14.83 8.24
+ C14.76 8.17 14.69 8.11 14.62 8.04
+ C14.47 7.91 14.30 7.78 14.12 7.67
+ C14.12 7.67 14.13 7.67 14.13 7.67
+ C14.21 7.53 14.29 7.39 14.37 7.25
+ C14.45 7.11 14.53 6.98 14.61 6.84
+ C14.66 6.74 14.72 6.64 14.78 6.55
+ C14.79 6.52 14.80 6.50 14.81 6.48
+ C14.83 6.41 14.83 6.33 14.81 6.27
+ C14.81 6.25 14.80 6.24 14.80 6.22
+ C14.79 6.20 14.78 6.19 14.77 6.17
+ C14.74 6.12 14.70 6.08 14.65 6.05
+ C14.60 6.02 14.54 6 14.49 6
+ C14.47 5.99 14.44 5.99 14.42 6
+ C14.40 6 14.38 6 14.36 6.01
+ C14.30 6.02 14.23 6.06 14.19 6.11
+ C14.17 6.13 14.15 6.15 14.14 6.18
+ C14.08 6.28 14.03 6.37 13.97 6.47
+ L13.73 6.88
+ C13.65 7.02 13.57 7.16 13.49 7.30
+ C13.48 7.31 13.47 7.33 13.46 7.34
+ C13.45 7.34 13.44 7.33 13.43 7.33
+ C12.98 7.16 12.50 7.07 12 7.07
+ C11.98 7.07 11.97 7.07 11.95 7.07
+ C11.51 7.07 11.07 7.15 10.67 7.29
+ C10.63 7.31 10.58 7.32 10.53 7.34
+ C10.53 7.33 10.52 7.31 10.51 7.30
+ C10.43 7.16 10.35 7.02 10.27 6.88
+ C10.19 6.74 10.11 6.61 10.03 6.47
+ C9.97 6.37 9.92 6.28 9.86 6.18
+ C9.85 6.15 9.83 6.13 9.81 6.11
+ C9.77 6.06 9.70 6.03 9.64 6.01
+ C9.62 6 9.60 6 9.58 6
+ C9.56 5.99 9.53 5.99 9.51 6
+ C9.46 6 9.40 6.02 9.35 6.05
+ C9.30 6.08 9.26 6.12 9.23 6.17
+ C9.22 6.19 9.21 6.20 9.20 6.22
+ C9.20 6.24 9.19 6.25 9.19 6.27
+ C9.17 6.34 9.17 6.41 9.19 6.48
+ C9.20 6.50 9.21 6.52 9.22 6.55
+ C9.28 6.65 9.34 6.74 9.39 6.84
+ C9.47 6.98 9.55 7.11 9.63 7.25
+ C9.71 7.39 9.79 7.53 9.87 7.67
+ C9.87 7.67 9.87 7.67 9.88 7.67
+ C9.71 7.77 9.56 7.88 9.41 8.01
+ C9.32 8.08 9.24 8.16 9.16 8.24
+ C9 8.40 8.85 8.57 8.72 8.76
+ C8.61 8.91 8.51 9.07 8.43 9.23
+ C8.36 9.36 8.30 9.50 8.24 9.63
+ C8.18 9.79 8.13 9.96 8.09 10.13
+ C8.06 10.23 8.04 10.33 8.03 10.43
+ C8.02 10.45 8.02 10.46 8.02 10.48
+ C8.01 10.54 8 10.60 8 10.65
+ L16 10.65
+ C15.99 10.60 15.98 10.54 15.97 10.48
+ L15.97 10.48
+ Z
+ "
android:fillColor="#ffffff"/>
</group>
<path
- android:pathData="M12,20.923C10.764,20.923 9.946,20.065 9.131,18.653C8.316,17.241 4.291,10.269 3.476,8.857C2.66,7.445 2.326,6.309 2.944,5.238C3.563,4.167 4.714,3.888 6.344,3.888C7.975,3.888 16.025,3.888 17.656,3.888C19.286,3.888 20.437,4.167 21.056,5.238C21.674,6.309 21.34,7.445 20.524,8.857C19.709,10.269 15.684,17.241 14.869,18.653C14.054,20.065 13.236,20.923 12,20.923H12Z"
+ android:pathData="
+ M12,20.923
+ C10.764,20.923 9.946,20.065 9.131,18.653
+ C8.316,17.241 4.291,10.269 3.476,8.857
+ C2.66,7.445 2.326,6.309 2.944,5.238
+ C3.563,4.167 4.714,3.888 6.344,3.888
+ C7.975,3.888 16.025,3.888 17.656,3.888
+ C19.286,3.888 20.437,4.167 21.056,5.238
+ C21.674,6.309 21.34,7.445 20.524,8.857
+ C19.709,10.269 15.684,17.241 14.869,18.653
+ C14.054,20.065 13.236,20.923 12,20.923
+ H12
+ Z
+ "
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#ffffff"/>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 91ef324..877d11e 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -764,6 +764,9 @@
<!-- Indicates whether to enable hinge angle sensor when using unfold animation -->
<bool name="config_unfoldTransitionHingeAngle">false</bool>
+ <!-- Indicates whether to enable haptics during unfold animation -->
+ <bool name="config_unfoldTransitionHapticsEnabled">false</bool>
+
<!-- Indicates the time needed to time out the fold animation if the device stops in half folded
mode. -->
<integer name="config_unfoldTransitionHalfFoldedTimeout">1000</integer>
@@ -3656,6 +3659,7 @@
"emergency" = Launch emergency dialer
"lockdown" = Lock down device until the user authenticates
"logout" = Logout the current user
+ "system_update" = Launch System Update screen
-->
<string-array translatable="false" name="config_globalActionsList">
<item>emergency</item>
@@ -7034,4 +7038,7 @@
If the gesture is completed faster than this, we assume it's not performed by human and the
event gets ignored. -->
<integer name="config_defaultMinEmergencyGestureTapDurationMillis">200</integer>
+
+ <!-- Whether the system uses auto-suspend mode. -->
+ <bool name="config_useAutoSuspend">true</bool>
</resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 54c9479..1fca4f8 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -5859,6 +5859,10 @@
<string name="dynamic_mode_notification_title">Battery Saver turned on</string>
<!-- Summary of notification letting users know why battery saver was turned on automatically [CHAR_LIMIT=NONE]-->
<string name="dynamic_mode_notification_summary">Reducing battery usage to extend battery life</string>
+ <!-- Title of notification letting users know why battery saver was turned on automatically [CHAR_LIMIT=NONE]-->
+ <string name="dynamic_mode_notification_title_v2">Battery Saver is on</string>
+ <!-- Summary of notification letting users know why battery saver was turned on automatically [CHAR_LIMIT=NONE]-->
+ <string name="dynamic_mode_notification_summary_v2">Battery Saver is turned on to extend battery life</string>
<!-- Battery saver strings -->
<!-- The user visible name of the notification channel for battery saver notifications [CHAR_LIMIT=80] -->
diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml
index 5945f81..a46dc04 100644
--- a/core/res/res/values/styles.xml
+++ b/core/res/res/values/styles.xml
@@ -1685,4 +1685,9 @@
<item name="android:paddingStart">8dp</item>
<item name="android:background">?android:attr/selectableItemBackground</item>
</style>
+
+ <style name="AccessibilityButtonChooserDialog"
+ parent="@style/Theme.DeviceDefault.Resolver">
+ <item name="android:windowOptOutEdgeToEdgeEnforcement">true</item>
+ </style>
</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index daf1230..b3d8f39 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -4219,6 +4219,7 @@
<java-symbol type="bool" name="config_supportsConcurrentInternalDisplays" />
<java-symbol type="bool" name="config_unfoldTransitionEnabled" />
<java-symbol type="bool" name="config_unfoldTransitionHingeAngle" />
+ <java-symbol type="bool" name="config_unfoldTransitionHapticsEnabled" />
<java-symbol type="integer" name="config_unfoldTransitionHalfFoldedTimeout" />
<java-symbol type="array" name="config_perDeviceStateRotationLockDefaults" />
@@ -4260,7 +4261,9 @@
<java-symbol type="string" name="battery_saver_charged_notification_summary" />
<java-symbol type="string" name="dynamic_mode_notification_channel_name" />
<java-symbol type="string" name="dynamic_mode_notification_title" />
+ <java-symbol type="string" name="dynamic_mode_notification_title_v2" />
<java-symbol type="string" name="dynamic_mode_notification_summary" />
+ <java-symbol type="string" name="dynamic_mode_notification_summary_v2" />
<java-symbol type="drawable" name="ic_battery" />
<java-symbol type="bool" name="config_skipSensorAvailable" />
@@ -5423,4 +5426,7 @@
<!-- Back swipe thresholds -->
<java-symbol type="dimen" name="navigation_edge_action_progress_threshold" />
<java-symbol type="dimen" name="back_progress_non_linear_factor" />
+
+ <!-- For PowerManagerService to determine whether to use auto-suspend mode -->
+ <java-symbol type="bool" name="config_useAutoSuspend" />
</resources>
diff --git a/core/tests/coretests/src/android/app/NotificationTest.java b/core/tests/coretests/src/android/app/NotificationTest.java
index 5cc1ee4..7fb894a 100644
--- a/core/tests/coretests/src/android/app/NotificationTest.java
+++ b/core/tests/coretests/src/android/app/NotificationTest.java
@@ -86,6 +86,7 @@
import android.os.Parcelable;
import android.os.SystemProperties;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.SpannableStringBuilder;
@@ -126,6 +127,7 @@
@Rule
public TestRule compatChangeRule = new PlatformCompatChangeRule();
+ @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@Before
public void setUp() {
@@ -1835,6 +1837,36 @@
Assert.assertEquals(bitmap, resultBitmap);
}
+ @Test
+ public void testGetWhen_zero() {
+ Notification n = new Notification.Builder(mContext, "test")
+ .setWhen(0)
+ .build();
+
+ mSetFlagsRule.disableFlags(Flags.FLAG_SORT_SECTION_BY_TIME);
+
+ assertThat(n.getWhen()).isEqualTo(0);
+
+ mSetFlagsRule.enableFlags(Flags.FLAG_SORT_SECTION_BY_TIME);
+
+ assertThat(n.getWhen()).isEqualTo(n.creationTime);
+ }
+
+ @Test
+ public void testGetWhen_devProvidedNonZero() {
+ Notification n = new Notification.Builder(mContext, "test")
+ .setWhen(9)
+ .build();
+
+ mSetFlagsRule.disableFlags(Flags.FLAG_SORT_SECTION_BY_TIME);
+
+ assertThat(n.getWhen()).isEqualTo(9);
+
+ mSetFlagsRule.enableFlags(Flags.FLAG_SORT_SECTION_BY_TIME);
+
+ assertThat(n.getWhen()).isEqualTo(9);
+ }
+
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/text/LayoutTest.java b/core/tests/coretests/src/android/text/LayoutTest.java
index f60eff6..1c12362 100644
--- a/core/tests/coretests/src/android/text/LayoutTest.java
+++ b/core/tests/coretests/src/android/text/LayoutTest.java
@@ -18,9 +18,6 @@
import static com.android.graphics.hwui.flags.Flags.FLAG_HIGH_CONTRAST_TEXT_SMALL_TEXT_RECT;
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
-
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -47,6 +44,8 @@
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.google.common.truth.Expect;
+
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -63,7 +62,13 @@
@Rule
public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+ @Rule
+ public final Expect expect = Expect.create();
+
+ // Line count when using MockLayout
private static final int LINE_COUNT = 5;
+ // Actual line count when using StaticLayout
+ private static final int STATIC_LINE_COUNT = 9;
private static final int LINE_HEIGHT = 12;
private static final int LINE_DESCENT = 4;
private static final CharSequence LAYOUT_TEXT = "alwei\t;sdfs\ndf @";
@@ -655,8 +660,8 @@
@Test
@RequiresFlagsEnabled(FLAG_HIGH_CONTRAST_TEXT_SMALL_TEXT_RECT)
public void highContrastTextEnabled_testDrawSelectionAndHighlight_drawsHighContrastSelectionAndHighlight() {
- Layout layout = new MockLayout(LAYOUT_TEXT, mTextPaint, mWidth,
- mAlign, mSpacingMult, mSpacingAdd);
+ Layout layout = new StaticLayout(LAYOUT_TEXT, mTextPaint, mWidth,
+ mAlign, mSpacingMult, mSpacingAdd, /* includePad= */ false);
List<Path> highlightPaths = new ArrayList<>();
List<Paint> highlightPaints = new ArrayList<>();
@@ -677,9 +682,12 @@
layout.draw(c, highlightPaths, highlightPaints, selectionPath, selectionPaint,
/* cursorOffsetVertical= */ 0);
List<MockCanvas.DrawCommand> drawCommands = c.getDrawCommands();
- var textsDrawn = LINE_COUNT;
+ var textsDrawn = STATIC_LINE_COUNT;
var highlightsDrawn = 2;
- assertThat(drawCommands.size()).isEqualTo(textsDrawn + highlightsDrawn);
+ var backgroundRectsDrawn = STATIC_LINE_COUNT;
+ expect.withMessage("wrong number of drawCommands: " + drawCommands)
+ .that(drawCommands.size())
+ .isEqualTo(textsDrawn + backgroundRectsDrawn + highlightsDrawn);
var highlightsFound = 0;
var curLineIndex = 0;
@@ -687,29 +695,26 @@
MockCanvas.DrawCommand drawCommand = drawCommands.get(i);
if (drawCommand.path != null) {
- assertThat(drawCommand.path).isEqualTo(selectionPath);
- assertThat(drawCommand.paint.getColor()).isEqualTo(Color.YELLOW);
- assertThat(drawCommand.paint.getBlendMode()).isNotNull();
+ expect.that(drawCommand.path).isEqualTo(selectionPath);
+ expect.that(drawCommand.paint.getColor()).isEqualTo(Color.YELLOW);
+ expect.that(drawCommand.paint.getBlendMode()).isNotNull();
highlightsFound++;
} else if (drawCommand.text != null) {
- int start = layout.getLineStart(curLineIndex);
- int end = layout.getLineEnd(curLineIndex);
- assertEquals(LAYOUT_TEXT.toString().substring(start, end), drawCommand.text);
curLineIndex++;
- assertWithMessage("highlight is drawn on top of text")
+ expect.withMessage("highlight is drawn on top of text")
.that(highlightsFound).isEqualTo(0);
}
}
- assertThat(highlightsFound).isEqualTo(2);
+ expect.that(highlightsFound).isEqualTo(2);
}
@Test
@RequiresFlagsEnabled(FLAG_HIGH_CONTRAST_TEXT_SMALL_TEXT_RECT)
public void highContrastTextEnabled_testDrawHighlight_drawsHighContrastHighlight() {
- Layout layout = new MockLayout(LAYOUT_TEXT, mTextPaint, mWidth,
- mAlign, mSpacingMult, mSpacingAdd);
+ Layout layout = new StaticLayout(LAYOUT_TEXT, mTextPaint, mWidth,
+ mAlign, mSpacingMult, mSpacingAdd, /* includePad= */ false);
List<Path> highlightPaths = new ArrayList<>();
List<Paint> highlightPaints = new ArrayList<>();
@@ -730,9 +735,12 @@
layout.draw(c, highlightPaths, highlightPaints, /* selectionPath= */ null,
/* selectionPaint= */ null, /* cursorOffsetVertical= */ 0);
List<MockCanvas.DrawCommand> drawCommands = c.getDrawCommands();
- var textsDrawn = LINE_COUNT;
+ var textsDrawn = STATIC_LINE_COUNT;
var highlightsDrawn = 1;
- assertThat(drawCommands.size()).isEqualTo(textsDrawn + highlightsDrawn);
+ var backgroundRectsDrawn = STATIC_LINE_COUNT;
+ expect.withMessage("wrong number of drawCommands: " + drawCommands)
+ .that(drawCommands.size())
+ .isEqualTo(textsDrawn + backgroundRectsDrawn + highlightsDrawn);
var highlightsFound = 0;
var curLineIndex = 0;
@@ -740,29 +748,26 @@
MockCanvas.DrawCommand drawCommand = drawCommands.get(i);
if (drawCommand.path != null) {
- assertThat(drawCommand.path).isEqualTo(selectionPath);
- assertThat(drawCommand.paint.getColor()).isEqualTo(Color.YELLOW);
- assertThat(drawCommand.paint.getBlendMode()).isNotNull();
+ expect.that(drawCommand.path).isEqualTo(selectionPath);
+ expect.that(drawCommand.paint.getColor()).isEqualTo(Color.YELLOW);
+ expect.that(drawCommand.paint.getBlendMode()).isNotNull();
highlightsFound++;
} else if (drawCommand.text != null) {
- int start = layout.getLineStart(curLineIndex);
- int end = layout.getLineEnd(curLineIndex);
- assertEquals(LAYOUT_TEXT.toString().substring(start, end), drawCommand.text);
curLineIndex++;
- assertWithMessage("highlight is drawn on top of text")
+ expect.withMessage("highlight is drawn on top of text")
.that(highlightsFound).isEqualTo(0);
}
}
- assertThat(highlightsFound).isEqualTo(1);
+ expect.that(highlightsFound).isEqualTo(1);
}
@Test
@RequiresFlagsEnabled(FLAG_HIGH_CONTRAST_TEXT_SMALL_TEXT_RECT)
public void highContrastTextDisabledByDefault_testDrawHighlight_drawsNormalHighlightBehind() {
- Layout layout = new MockLayout(LAYOUT_TEXT, mTextPaint, mWidth,
- mAlign, mSpacingMult, mSpacingAdd);
+ Layout layout = new StaticLayout(LAYOUT_TEXT, mTextPaint, mWidth,
+ mAlign, mSpacingMult, mSpacingAdd, /* includePad= */ false);
List<Path> highlightPaths = new ArrayList<>();
List<Paint> highlightPaints = new ArrayList<>();
@@ -782,9 +787,12 @@
layout.draw(c, highlightPaths, highlightPaints, /* selectionPath= */ null,
/* selectionPaint= */ null, /* cursorOffsetVertical= */ 0);
List<MockCanvas.DrawCommand> drawCommands = c.getDrawCommands();
- var textsDrawn = LINE_COUNT;
+ var textsDrawn = STATIC_LINE_COUNT;
var highlightsDrawn = 1;
- assertThat(drawCommands.size()).isEqualTo(textsDrawn + highlightsDrawn);
+ var backgroundRectsDrawn = 0;
+ expect.withMessage("wrong number of drawCommands: " + drawCommands)
+ .that(drawCommands.size())
+ .isEqualTo(textsDrawn + backgroundRectsDrawn + highlightsDrawn);
var highlightsFound = 0;
var curLineIndex = 0;
@@ -792,29 +800,26 @@
MockCanvas.DrawCommand drawCommand = drawCommands.get(i);
if (drawCommand.path != null) {
- assertThat(drawCommand.path).isEqualTo(selectionPath);
- assertThat(drawCommand.paint.getColor()).isEqualTo(Color.CYAN);
- assertThat(drawCommand.paint.getBlendMode()).isNull();
+ expect.that(drawCommand.path).isEqualTo(selectionPath);
+ expect.that(drawCommand.paint.getColor()).isEqualTo(Color.CYAN);
+ expect.that(drawCommand.paint.getBlendMode()).isNull();
highlightsFound++;
} else if (drawCommand.text != null) {
- int start = layout.getLineStart(curLineIndex);
- int end = layout.getLineEnd(curLineIndex);
- assertEquals(LAYOUT_TEXT.toString().substring(start, end), drawCommand.text);
curLineIndex++;
- assertWithMessage("highlight is drawn behind text")
+ expect.withMessage("highlight is drawn behind text")
.that(highlightsFound).isGreaterThan(0);
}
}
- assertThat(highlightsFound).isEqualTo(1);
+ expect.that(highlightsFound).isEqualTo(1);
}
@Test
@RequiresFlagsDisabled(FLAG_HIGH_CONTRAST_TEXT_SMALL_TEXT_RECT)
public void highContrastTextEnabledButFlagOff_testDrawHighlight_drawsNormalHighlightBehind() {
- Layout layout = new MockLayout(LAYOUT_TEXT, mTextPaint, mWidth,
- mAlign, mSpacingMult, mSpacingAdd);
+ Layout layout = new StaticLayout(LAYOUT_TEXT, mTextPaint, mWidth,
+ mAlign, mSpacingMult, mSpacingAdd, /* includePad= */ false);
List<Path> highlightPaths = new ArrayList<>();
List<Paint> highlightPaints = new ArrayList<>();
@@ -835,9 +840,12 @@
layout.draw(c, highlightPaths, highlightPaints, /* selectionPath= */ null,
/* selectionPaint= */ null, /* cursorOffsetVertical= */ 0);
List<MockCanvas.DrawCommand> drawCommands = c.getDrawCommands();
- var textsDrawn = LINE_COUNT;
+ var textsDrawn = STATIC_LINE_COUNT;
var highlightsDrawn = 1;
- assertThat(drawCommands.size()).isEqualTo(textsDrawn + highlightsDrawn);
+ var backgroundRectsDrawn = 0;
+ expect.withMessage("wrong number of drawCommands: " + drawCommands)
+ .that(drawCommands.size())
+ .isEqualTo(textsDrawn + backgroundRectsDrawn + highlightsDrawn);
var highlightsFound = 0;
var curLineIndex = 0;
@@ -845,33 +853,84 @@
MockCanvas.DrawCommand drawCommand = drawCommands.get(i);
if (drawCommand.path != null) {
- assertThat(drawCommand.path).isEqualTo(selectionPath);
- assertThat(drawCommand.paint.getColor()).isEqualTo(Color.CYAN);
- assertThat(drawCommand.paint.getBlendMode()).isNull();
+ expect.that(drawCommand.path).isEqualTo(selectionPath);
+ expect.that(drawCommand.paint.getColor()).isEqualTo(Color.CYAN);
+ expect.that(drawCommand.paint.getBlendMode()).isNull();
highlightsFound++;
} else if (drawCommand.text != null) {
- int start = layout.getLineStart(curLineIndex);
- int end = layout.getLineEnd(curLineIndex);
- assertEquals(LAYOUT_TEXT.toString().substring(start, end), drawCommand.text);
curLineIndex++;
- assertWithMessage("highlight is drawn behind text")
+ expect.withMessage("highlight is drawn behind text")
.that(highlightsFound).isGreaterThan(0);
}
}
- assertThat(highlightsFound).isEqualTo(1);
+ expect.that(highlightsFound).isEqualTo(1);
}
@Test
public void mockCanvasHighContrastOverridesCorrectly() {
var canvas = new MockCanvas(100, 100);
- assertThat(canvas.isHighContrastTextEnabled()).isFalse();
+ expect.that(canvas.isHighContrastTextEnabled()).isFalse();
canvas.setHighContrastTextEnabled(true);
- assertThat(canvas.isHighContrastTextEnabled()).isTrue();
+ expect.that(canvas.isHighContrastTextEnabled()).isTrue();
canvas.setHighContrastTextEnabled(false);
- assertThat(canvas.isHighContrastTextEnabled()).isFalse();
+ expect.that(canvas.isHighContrastTextEnabled()).isFalse();
+ }
+
+ @Test
+ @RequiresFlagsEnabled(FLAG_HIGH_CONTRAST_TEXT_SMALL_TEXT_RECT)
+ public void highContrastTextEnabled_testDrawLightText_drawsBlackBackgroundRects() {
+ mTextPaint.setColor(Color.parseColor("#CCAA33"));
+ Layout layout = new StaticLayout(LAYOUT_TEXT, mTextPaint, mWidth,
+ mAlign, mSpacingMult, mSpacingAdd, /* includePad= */ false);
+
+ final int width = 256;
+ final int height = 256;
+ MockCanvas c = new MockCanvas(width, height);
+ c.setHighContrastTextEnabled(true);
+ layout.draw(
+ c,
+ /* highlightPaths= */ null,
+ /* highlightPaints= */ null,
+ /* selectionPath= */ null,
+ /* selectionPaint= */ null,
+ /* cursorOffsetVertical= */ 0
+ );
+ List<MockCanvas.DrawCommand> drawCommands = c.getDrawCommands();
+ var textsDrawn = STATIC_LINE_COUNT;
+ var highlightsDrawn = 0;
+ var backgroundRectsDrawn = STATIC_LINE_COUNT;
+ expect.withMessage("wrong number of drawCommands: " + drawCommands)
+ .that(drawCommands.size())
+ .isEqualTo(textsDrawn + backgroundRectsDrawn + highlightsDrawn);
+
+ int numBackgroundsFound = 0;
+ var curLineIndex = 0;
+ for (int i = 0; i < drawCommands.size(); i++) {
+ MockCanvas.DrawCommand drawCommand = drawCommands.get(i);
+
+ if (drawCommand.rect != null) {
+ numBackgroundsFound++;
+ expect.that(drawCommand.paint.getColor()).isEqualTo(Color.BLACK);
+ expect.that(drawCommand.rect.height()).isAtLeast(LINE_HEIGHT);
+ expect.that(drawCommand.rect.width()).isGreaterThan(0);
+ float expectedY = (numBackgroundsFound) * (LINE_HEIGHT + LINE_DESCENT);
+ expect.that(drawCommand.rect.bottom).isAtLeast(expectedY);
+ } else if (drawCommand.text != null) {
+ // draw text
+ curLineIndex++;
+
+ expect.withMessage("background is drawn on top of text")
+ .that(numBackgroundsFound).isEqualTo(backgroundRectsDrawn);
+ } else {
+ fail("unexpected path drawn");
+ }
+ }
+
+ // One for each line
+ expect.that(numBackgroundsFound).isEqualTo(backgroundRectsDrawn);
}
private static final class MockCanvas extends Canvas {
@@ -881,22 +940,46 @@
public final float x;
public final float y;
public final Path path;
+ public final RectF rect;
public final Paint paint;
DrawCommand(String text, float x, float y, Paint paint) {
this.text = text;
this.x = x;
this.y = y;
- this.paint = paint;
+ this.paint = new Paint(paint);
path = null;
+ rect = null;
}
DrawCommand(Path path, Paint paint) {
this.path = path;
- this.paint = paint;
+ this.paint = new Paint(paint);
y = 0;
x = 0;
text = null;
+ rect = null;
+ }
+
+ DrawCommand(RectF rect, Paint paint) {
+ this.rect = new RectF(rect);
+ this.paint = new Paint(paint);
+ path = null;
+ y = 0;
+ x = 0;
+ text = null;
+ }
+
+ @Override
+ public String toString() {
+ return "DrawCommand{"
+ + "text='" + text + '\''
+ + ", x=" + x
+ + ", y=" + y
+ + ", path=" + path
+ + ", rect=" + rect
+ + ", paint=" + paint
+ + '}';
}
}
@@ -956,6 +1039,11 @@
mDrawCommands.add(new DrawCommand(path, p));
}
+ @Override
+ public void drawRect(RectF rect, Paint p) {
+ mDrawCommands.add(new DrawCommand(rect, p));
+ }
+
List<DrawCommand> getDrawCommands() {
return mDrawCommands;
}
diff --git a/core/tests/coretests/src/android/view/ViewFrameRateTest.java b/core/tests/coretests/src/android/view/ViewFrameRateTest.java
index f885e31..72f1119 100644
--- a/core/tests/coretests/src/android/view/ViewFrameRateTest.java
+++ b/core/tests/coretests/src/android/view/ViewFrameRateTest.java
@@ -36,6 +36,7 @@
import android.annotation.NonNull;
import android.app.Activity;
+import android.app.Instrumentation;
import android.os.Handler;
import android.os.Looper;
import android.os.SystemClock;
@@ -47,6 +48,7 @@
import androidx.test.annotation.UiThreadTest;
import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.rule.ActivityTestRule;
import androidx.test.runner.AndroidJUnit4;
@@ -194,7 +196,7 @@
@RequiresFlagsEnabled({FLAG_VIEW_VELOCITY_API,
FLAG_TOOLKIT_FRAME_RATE_VELOCITY_MAPPING_READ_ONLY,
FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY})
- public void highVelocity140() throws Throwable {
+ public void highVelocity120() throws Throwable {
mActivityRule.runOnUiThread(() -> {
ViewGroup.LayoutParams layoutParams = mMovingView.getLayoutParams();
layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT;
@@ -206,7 +208,7 @@
mMovingView.setFrameContentVelocity(1_000_000_000f);
mMovingView.invalidate();
runAfterDraw(() -> {
- assertEquals(140f, mViewRoot.getLastPreferredFrameRate(), 0f);
+ assertEquals(120f, mViewRoot.getLastPreferredFrameRate(), 0f);
});
});
waitForAfterDraw();
@@ -486,16 +488,19 @@
waitForFrameRateCategoryToSettle();
for (int i = 0; i < 5; i++) {
int expectedCategory;
- if (i < 4) {
+ if (i < 2) {
// not intermittent yet.
// It takes 2 frames of intermittency before Views vote as intermittent.
- // It takes 4 more frames for the category to drop to the next category.
expectedCategory =
toolkitFrameRateDefaultNormalReadOnly() ? FRAME_RATE_CATEGORY_NORMAL
: FRAME_RATE_CATEGORY_HIGH;
} else {
// intermittent
- expectedCategory = FRAME_RATE_CATEGORY_NORMAL;
+ // Even though this is not a small View, step 3 is triggered by this flag, which
+ // brings intermittent to LOW
+ expectedCategory = toolkitFrameRateBySizeReadOnly()
+ ? FRAME_RATE_CATEGORY_LOW
+ : FRAME_RATE_CATEGORY_NORMAL;
}
mActivityRule.runOnUiThread(() -> {
mMovingView.invalidate();
@@ -537,6 +542,48 @@
});
waitForAfterDraw();
}
+
+ @Test
+ @RequiresFlagsEnabled({FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY,
+ FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY
+ })
+ public void frameRateReset() throws Throwable {
+ mMovingView.setRequestedFrameRate(120f);
+ waitForFrameRateCategoryToSettle();
+ mActivityRule.runOnUiThread(() -> mMovingView.setVisibility(View.INVISIBLE));
+
+ final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+
+ for (int i = 0; i < 120; i++) {
+ mActivityRule.runOnUiThread(() -> {
+ mMovingView.getParent().onDescendantInvalidated(mMovingView, mMovingView);
+ });
+ instrumentation.waitForIdleSync();
+ }
+
+ assertEquals(0f, mViewRoot.getLastPreferredFrameRate(), 0f);
+ }
+
+ @Test
+ @RequiresFlagsEnabled({FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY,
+ FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY
+ })
+ public void frameRateResetWithInvalidations() throws Throwable {
+ mMovingView.setRequestedFrameRate(120f);
+ waitForFrameRateCategoryToSettle();
+ mMovingView.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_NORMAL);
+
+ for (int i = 0; i < 120; i++) {
+ mActivityRule.runOnUiThread(() -> {
+ mMovingView.invalidate();
+ runAfterDraw(() -> {});
+ });
+ waitForAfterDraw();
+ }
+
+ assertEquals(0f, mViewRoot.getLastPreferredFrameRate(), 0f);
+ }
+
private void runAfterDraw(@NonNull Runnable runnable) {
Handler handler = new Handler(Looper.getMainLooper());
mAfterDrawLatch = new CountDownLatch(1);
diff --git a/core/tests/coretests/src/android/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java
index 9afc4be..a7f8176 100644
--- a/core/tests/coretests/src/android/view/ViewRootImplTest.java
+++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java
@@ -28,7 +28,6 @@
import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH_HINT;
import static android.view.Surface.FRAME_RATE_CATEGORY_LOW;
import static android.view.Surface.FRAME_RATE_CATEGORY_NORMAL;
-import static android.view.Surface.FRAME_RATE_CATEGORY_NO_PREFERENCE;
import static android.view.Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE;
import static android.view.Surface.FRAME_RATE_COMPATIBILITY_GTE;
import static android.view.View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
@@ -1139,6 +1138,8 @@
mView = new View(sContext);
WindowManager.LayoutParams wmlp = new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY);
wmlp.token = new Binder(); // Set a fake token to bypass 'is your activity running' check
+ int expected = toolkitFrameRateDefaultNormalReadOnly()
+ ? FRAME_RATE_CATEGORY_NORMAL : FRAME_RATE_CATEGORY_HIGH;
sInstrumentation.runOnMainSync(() -> {
WindowManager wm = sContext.getSystemService(WindowManager.class);
@@ -1158,51 +1159,49 @@
Thread.sleep(delay);
sInstrumentation.runOnMainSync(() -> {
mView.invalidate();
- int expected = toolkitFrameRateDefaultNormalReadOnly()
- ? FRAME_RATE_CATEGORY_NORMAL : FRAME_RATE_CATEGORY_HIGH;
runAfterDraw(() -> assertEquals(expected,
mViewRootImpl.getLastPreferredFrameRateCategory()));
});
waitForAfterDraw();
- // reset the frame rate category counts
- for (int i = 0; i < 5; i++) {
- sInstrumentation.runOnMainSync(() -> {
- mView.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE);
- mView.invalidate();
- });
- sInstrumentation.waitForIdleSync();
- }
-
// In transition from frequent update to infrequent update
Thread.sleep(delay);
sInstrumentation.runOnMainSync(() -> {
- mView.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE);
mView.invalidate();
- runAfterDraw(() -> {
- assertEquals(FRAME_RATE_CATEGORY_NO_PREFERENCE,
- mViewRootImpl.getLastPreferredFrameRateCategory());
- });
+ runAfterDraw(() -> assertEquals(expected,
+ mViewRootImpl.getLastPreferredFrameRateCategory()));
});
- waitForAfterDraw();
+
+ // Infrequent update
Thread.sleep(delay);
+
+ // Even though this is not a small View, step 3 is triggered by this flag, which
+ // brings intermittent to LOW
+ int intermittentExpected = toolkitFrameRateBySizeReadOnly()
+ ? FRAME_RATE_CATEGORY_LOW
+ : FRAME_RATE_CATEGORY_NORMAL;
+
sInstrumentation.runOnMainSync(() -> {
- mView.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_DEFAULT);
mView.invalidate();
- runAfterDraw(() -> assertEquals(FRAME_RATE_CATEGORY_NO_PREFERENCE,
+ runAfterDraw(() -> assertEquals(intermittentExpected,
mViewRootImpl.getLastPreferredFrameRateCategory()));
});
waitForAfterDraw();
- // Infrequent update
- Thread.sleep(delay);
+ // When the View vote, it's still considered as intermittent update state
sInstrumentation.runOnMainSync(() -> {
- mView.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_DEFAULT);
mView.invalidate();
- runAfterDraw(() -> assertEquals(FRAME_RATE_CATEGORY_NORMAL,
+ runAfterDraw(() -> assertEquals(intermittentExpected,
mViewRootImpl.getLastPreferredFrameRateCategory()));
});
waitForAfterDraw();
+
+ // Becomes frequent update state
+ sInstrumentation.runOnMainSync(() -> {
+ mView.invalidate();
+ runAfterDraw(() -> assertEquals(expected,
+ mViewRootImpl.getLastPreferredFrameRateCategory()));
+ });
}
/**
diff --git a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
index 852d696..50d7f59 100644
--- a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
+++ b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
@@ -40,6 +40,7 @@
import android.platform.test.annotations.Presubmit;
import android.view.IWindow;
import android.view.IWindowSession;
+import android.view.ImeBackAnimationController;
import android.view.MotionEvent;
import androidx.test.filters.SmallTest;
@@ -50,6 +51,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
+import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
@@ -77,6 +79,12 @@
@Mock
private OnBackAnimationCallback mCallback2;
@Mock
+ private ImeOnBackInvokedDispatcher.ImeOnBackInvokedCallback mImeCallback;
+ @Mock
+ private ImeOnBackInvokedDispatcher.DefaultImeOnBackAnimationCallback mDefaultImeCallback;
+ @Mock
+ private ImeBackAnimationController mImeBackAnimationController;
+ @Mock
private Context mContext;
@Mock
private ApplicationInfo mApplicationInfo;
@@ -103,7 +111,7 @@
doReturn(mApplicationInfo).when(mContext).getApplicationInfo();
mDispatcher = new WindowOnBackInvokedDispatcher(mContext, Looper.getMainLooper());
- mDispatcher.attachToWindow(mWindowSession, mWindow, null);
+ mDispatcher.attachToWindow(mWindowSession, mWindow, mImeBackAnimationController);
}
private void waitForIdle() {
@@ -365,6 +373,30 @@
}
@Test
+ public void onBackCancelled_calledBeforeOnBackStartedOfNewGesture() throws RemoteException {
+ mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback1);
+ OnBackInvokedCallbackInfo callbackInfo = assertSetCallbackInfo();
+
+ callbackInfo.getCallback().onBackStarted(mBackEvent);
+
+ waitForIdle();
+ verify(mCallback1).onBackStarted(any(BackEvent.class));
+ clearInvocations(mCallback1);
+
+ callbackInfo.getCallback().onBackCancelled();
+ waitForIdle();
+
+ // simulate start of new gesture while cancel animation is still running
+ callbackInfo.getCallback().onBackStarted(mBackEvent);
+ waitForIdle();
+
+ // verify that onBackCancelled is called before onBackStarted
+ InOrder orderVerifier = Mockito.inOrder(mCallback1);
+ orderVerifier.verify(mCallback1).onBackCancelled();
+ orderVerifier.verify(mCallback1).onBackStarted(any(BackEvent.class));
+ }
+
+ @Test
public void onDetachFromWindow_cancelCallbackAndIgnoreOnBackInvoked() throws RemoteException {
mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback1);
@@ -392,11 +424,11 @@
callbackInfo.getCallback().onBackStarted(mBackEvent);
waitForIdle();
- assertTrue(mDispatcher.isDispatching());
+ assertTrue(mDispatcher.isBackGestureInProgress());
callbackInfo.getCallback().onBackInvoked();
waitForIdle();
- assertFalse(mDispatcher.isDispatching());
+ assertFalse(mDispatcher.isBackGestureInProgress());
}
@Test
@@ -411,7 +443,7 @@
callbackInfo.getCallback().onBackStarted(mBackEvent);
waitForIdle();
- assertTrue(mDispatcher.isDispatching());
+ assertTrue(mDispatcher.isBackGestureInProgress());
assertTrue(mDispatcher.mTouchTracker.isActive());
main.runWithScissors(() -> mDispatcher.onMotionEvent(mMotionEvent), 100);
@@ -419,4 +451,28 @@
// onBackPressed is called from animator, so it can happen more than once.
verify(mCallback1, atLeast(1)).onBackProgressed(any());
}
+
+ @Test
+ public void registerImeCallbacks_onBackInvokedCallbackEnabled() throws RemoteException {
+ mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mDefaultImeCallback);
+ assertCallbacksSize(/* default */ 1, /* overlay */ 0);
+ assertSetCallbackInfo();
+ assertTopCallback(mImeBackAnimationController);
+
+ mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mImeCallback);
+ assertCallbacksSize(/* default */ 2, /* overlay */ 0);
+ assertSetCallbackInfo();
+ assertTopCallback(mImeCallback);
+ }
+
+ @Test
+ public void registerImeCallbacks_legacyBack() throws RemoteException {
+ doReturn(false).when(mApplicationInfo).isOnBackInvokedCallbackEnabled();
+
+ mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mDefaultImeCallback);
+ assertNoSetCallbackInfo();
+
+ mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mImeCallback);
+ assertNoSetCallbackInfo();
+ }
}
diff --git a/core/tests/coretests/src/com/android/internal/os/PowerStatsTest.java b/core/tests/coretests/src/com/android/internal/os/PowerStatsTest.java
index 6402206..baab3b2 100644
--- a/core/tests/coretests/src/com/android/internal/os/PowerStatsTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/PowerStatsTest.java
@@ -168,6 +168,20 @@
assertThat(end).isEqualTo("END");
}
+ @Test
+ public void parceling_corruptParcel() {
+ PowerStats stats = new PowerStats(mDescriptor);
+ Parcel parcel = Parcel.obtain();
+ stats.writeToParcel(parcel);
+
+ Parcel newParcel = marshallAndUnmarshall(parcel);
+ newParcel.writeInt(-42); // Negative section length
+ newParcel.setDataPosition(0);
+
+ PowerStats newStats = PowerStats.readFromParcel(newParcel, mRegistry);
+ assertThat(newStats).isNull();
+ }
+
private static Parcel marshallAndUnmarshall(Parcel parcel) {
byte[] bytes = parcel.marshall();
parcel.recycle();
diff --git a/core/tests/packagemonitortests/src/com/android/internal/content/PackageMonitorTest.java b/core/tests/packagemonitortests/src/com/android/internal/content/PackageMonitorTest.java
index 03cb17e..1d91af5 100644
--- a/core/tests/packagemonitortests/src/com/android/internal/content/PackageMonitorTest.java
+++ b/core/tests/packagemonitortests/src/com/android/internal/content/PackageMonitorTest.java
@@ -71,7 +71,7 @@
spyPackageMonitor.register(mMockContext, UserHandle.ALL, mMockHandler);
assertThat(spyPackageMonitor.getRegisteredHandler()).isEqualTo(mMockHandler);
- verify(mMockContext, times(1)).registerReceiverAsUser(any(), eq(UserHandle.ALL), any(),
+ verify(mMockContext, never()).registerReceiverAsUser(any(), eq(UserHandle.ALL), any(),
eq(null), eq(mMockHandler));
assertThrows(IllegalStateException.class,
@@ -97,7 +97,7 @@
@Test
public void testPackageMonitorNotRegisterWithoutSupportPackageRestartQuery() throws Exception {
- PackageMonitor spyPackageMonitor = spy(new TestPackageMonitor(false));
+ PackageMonitor spyPackageMonitor = spy(new TestPackageMonitor());
spyPackageMonitor.register(mMockContext, UserHandle.ALL, mMockHandler);
@@ -106,6 +106,16 @@
}
@Test
+ public void testPackageMonitorRegisterWithSupportPackageRestartQuery() throws Exception {
+ PackageMonitor spyPackageMonitor = spy(new TestPackageMonitor(true));
+
+ spyPackageMonitor.register(mMockContext, UserHandle.ALL, mMockHandler);
+
+ verify(mMockContext, times(1)).registerReceiverAsUser(any(), eq(UserHandle.ALL), any(),
+ eq(null), eq(mMockHandler));
+ }
+
+ @Test
public void testPackageMonitorDoHandlePackageEventUidRemoved() throws Exception {
PackageMonitor spyPackageMonitor = spy(new TestPackageMonitor());
@@ -487,7 +497,7 @@
}
public TestPackageMonitor() {
- super();
+ super(false);
}
}
}
diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java
index 319f115..6d31578 100644
--- a/graphics/java/android/graphics/Bitmap.java
+++ b/graphics/java/android/graphics/Bitmap.java
@@ -465,6 +465,11 @@
* how pixels are stored. This affects the quality (color depth) as
* well as the ability to display transparent/translucent colors.
*/
+ // It's touched by Graphics.cpp, so we need to make this enum usable on Ravenwood.
+ // Otherwise, all the ctors would throw, which would make the class unloadable
+ // because the static initializer needs the enum members because of `sConfigs`.
+ // TODO: Remove it once we expose the outer class.
+ @android.ravenwood.annotation.RavenwoodKeepWholeClass
public enum Config {
// these native values must match up with the enum in SkBitmap.h
diff --git a/graphics/java/android/graphics/Interpolator.java b/graphics/java/android/graphics/Interpolator.java
index 1045464..994fb2d 100644
--- a/graphics/java/android/graphics/Interpolator.java
+++ b/graphics/java/android/graphics/Interpolator.java
@@ -18,6 +18,9 @@
import android.os.SystemClock;
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
+@android.ravenwood.annotation.RavenwoodClassLoadHook(
+ android.ravenwood.annotation.RavenwoodClassLoadHook.LIBANDROID_LOADING_HOOK)
public class Interpolator {
public Interpolator(int valueCount) {
diff --git a/graphics/java/android/graphics/Matrix.java b/graphics/java/android/graphics/Matrix.java
index bc58feb..fbb690c 100644
--- a/graphics/java/android/graphics/Matrix.java
+++ b/graphics/java/android/graphics/Matrix.java
@@ -28,6 +28,9 @@
/**
* The Matrix class holds a 3x3 matrix for transforming coordinates.
*/
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
+@android.ravenwood.annotation.RavenwoodClassLoadHook(
+ android.ravenwood.annotation.RavenwoodClassLoadHook.LIBANDROID_LOADING_HOOK)
public class Matrix {
public static final int MSCALE_X = 0; //!< use with getValues/setValues
@@ -229,7 +232,7 @@
private static class NoImagePreloadHolder {
public static final NativeAllocationRegistry sRegistry =
NativeAllocationRegistry.createMalloced(
- Matrix.class.getClassLoader(), nGetNativeFinalizer());
+ Matrix.class.getClassLoader(), nGetNativeFinalizerWrapper());
}
private final long native_instance;
@@ -238,7 +241,7 @@
* Create an identity matrix
*/
public Matrix() {
- native_instance = nCreate(0);
+ native_instance = nCreateWrapper(0);
NoImagePreloadHolder.sRegistry.registerNativeAllocation(this, native_instance);
}
@@ -248,7 +251,7 @@
* @param src The matrix to copy into this matrix
*/
public Matrix(Matrix src) {
- native_instance = nCreate(src != null ? src.native_instance : 0);
+ native_instance = nCreateWrapper(src != null ? src.native_instance : 0);
NoImagePreloadHolder.sRegistry.registerNativeAllocation(this, native_instance);
}
@@ -846,6 +849,34 @@
return native_instance;
}
+ /**
+ * Wrapper method we use to switch to ExtraNatives.nCreate(src) only on Ravenwood.
+ *
+ * @see ExtraNatives
+ */
+ @android.ravenwood.annotation.RavenwoodReplace
+ private static long nCreateWrapper(long src) {
+ return nCreate(src);
+ }
+
+ private static long nCreateWrapper$ravenwood(long src) {
+ return ExtraNatives.nCreate(src);
+ }
+
+ /**
+ * Wrapper method we use to switch to ExtraNatives.nGetNativeFinalizer(src) only on Ravenwood.
+ *
+ * @see ExtraNatives
+ */
+ @android.ravenwood.annotation.RavenwoodReplace
+ private static long nGetNativeFinalizerWrapper() {
+ return nGetNativeFinalizer();
+ }
+
+ private static long nGetNativeFinalizerWrapper$ravenwood() {
+ return ExtraNatives.nGetNativeFinalizer();
+ }
+
// ------------------ Regular JNI ------------------------
private static native long nCreate(long nSrc_or_zero);
@@ -943,4 +974,25 @@
private static native float nMapRadius(long nObject, float radius);
@CriticalNative
private static native boolean nEquals(long nA, long nB);
+
+ /**
+ * Due to b/337329128, native methods that are called by the static initializers cannot be
+ * in the same class when running on a host side JVM (such as on Ravenwood and Android Studio).
+ *
+ * There are two methods that are called by the static initializers (either directly or
+ * indirectly) in this class, namely nCreate() and nGetNativeFinalizer(). On Ravenwood
+ * these methods can't be on the Matrix class itself, so we use a nested class to host them.
+ *
+ * We still keep the original nCreate() method and call it on non-ravenwood environment,
+ * in order to avoid problems in downstream (such as Android Studio).
+ *
+ * @see #nCreateWrapper(long)
+ * @see #nGetNativeFinalizerWrapper()
+ *
+ * TODO(b/337110712) Clean it up somehow. (remove the original nCreate() and unify the code?)
+ */
+ private static class ExtraNatives {
+ static native long nCreate(long nSrc_or_zero);
+ static native long nGetNativeFinalizer();
+ }
}
diff --git a/graphics/java/android/graphics/Path.java b/graphics/java/android/graphics/Path.java
index deb89fa..073307c 100644
--- a/graphics/java/android/graphics/Path.java
+++ b/graphics/java/android/graphics/Path.java
@@ -36,11 +36,16 @@
* (based on the paint's Style), or it can be used for clipping or to draw
* text on a path.
*/
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
+@android.ravenwood.annotation.RavenwoodClassLoadHook(
+ android.ravenwood.annotation.RavenwoodClassLoadHook.LIBANDROID_LOADING_HOOK)
public class Path {
-
- private static final NativeAllocationRegistry sRegistry =
- NativeAllocationRegistry.createMalloced(
- Path.class.getClassLoader(), nGetFinalizer());
+ // See b/337329128 for why we need an inner class here.
+ private static class NoImagePreloadHolder {
+ static final NativeAllocationRegistry sRegistry =
+ NativeAllocationRegistry.createMalloced(
+ Path.class.getClassLoader(), nGetFinalizer());
+ }
/**
* @hide
@@ -52,7 +57,7 @@
*/
public Path() {
mNativePath = nInit();
- sRegistry.registerNativeAllocation(this, mNativePath);
+ NoImagePreloadHolder.sRegistry.registerNativeAllocation(this, mNativePath);
}
/**
@@ -62,7 +67,7 @@
*/
public Path(@Nullable Path src) {
mNativePath = nInit(src != null ? src.mNativePath : 0);
- sRegistry.registerNativeAllocation(this, mNativePath);
+ NoImagePreloadHolder.sRegistry.registerNativeAllocation(this, mNativePath);
}
/**
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 89d3058..e38038e 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -1520,12 +1520,16 @@
@GuardedBy("mLock")
TaskFragmentContainer resolveStartActivityIntent(@NonNull WindowContainerTransaction wct,
int taskId, @NonNull Intent intent, @Nullable Activity launchingActivity) {
- // Skip resolving if started from an isolated navigated TaskFragmentContainer.
if (launchingActivity != null) {
final TaskFragmentContainer taskFragmentContainer = getContainerWithActivity(
launchingActivity);
if (taskFragmentContainer != null
&& taskFragmentContainer.isIsolatedNavigationEnabled()) {
+ // Skip resolving if started from an isolated navigated TaskFragmentContainer.
+ return null;
+ }
+ if (isAssociatedWithOverlay(launchingActivity)) {
+ // Skip resolving if the launching activity associated with an overlay.
return null;
}
}
@@ -1659,9 +1663,8 @@
// is the first embedded TF in the task.
final TaskContainer taskContainer = container.getTaskContainer();
// TODO(b/265271880): remove redundant logic after all TF operations take fragmentToken.
- final Rect taskBounds = taskContainer.getBounds();
final Rect sanitizedBounds = sanitizeBounds(activityStackAttributes.getRelativeBounds(),
- getMinDimensions(intent), taskBounds);
+ getMinDimensions(intent), container);
final int windowingMode = taskContainer
.getWindowingModeForTaskFragment(sanitizedBounds);
mPresenter.createTaskFragment(wct, taskFragmentToken, activityInTask.getActivityToken(),
@@ -1722,17 +1725,14 @@
@GuardedBy("mLock")
@Nullable
TaskFragmentContainer getContainerWithActivity(@NonNull IBinder activityToken) {
- // Check pending appeared activity first because there can be a delay for the server
- // update.
- TaskFragmentContainer taskFragmentContainer =
- getContainer(container -> container.hasPendingAppearedActivity(activityToken));
- if (taskFragmentContainer != null) {
- return taskFragmentContainer;
+ for (int i = mTaskContainers.size() - 1; i >= 0; --i) {
+ final TaskFragmentContainer container = mTaskContainers.valueAt(i)
+ .getContainerWithActivity(activityToken);
+ if (container != null) {
+ return container;
+ }
}
-
-
- // Check appeared activity if there is no such pending appeared activity.
- return getContainer(container -> container.hasAppearedActivity(activityToken));
+ return null;
}
@GuardedBy("mLock")
@@ -2096,19 +2096,7 @@
if (container == null) {
return null;
}
- final List<SplitContainer> splitContainers =
- container.getTaskContainer().getSplitContainers();
- if (splitContainers.isEmpty()) {
- return null;
- }
- for (int i = splitContainers.size() - 1; i >= 0; i--) {
- final SplitContainer splitContainer = splitContainers.get(i);
- if (container.equals(splitContainer.getSecondaryContainer())
- || container.equals(splitContainer.getPrimaryContainer())) {
- return splitContainer;
- }
- }
- return null;
+ return container.getTaskContainer().getActiveSplitForContainer(container);
}
/**
@@ -2158,6 +2146,11 @@
return false;
}
+ if (isAssociatedWithOverlay(activity)) {
+ // Can't launch the placeholder if the activity associates an overlay.
+ return false;
+ }
+
final TaskFragmentContainer container = getContainerWithActivity(activity);
if (container != null && !allowLaunchPlaceholder(container)) {
// We don't allow activity in this TaskFragment to launch placeholder.
@@ -2197,6 +2190,11 @@
*/
@GuardedBy("mLock")
private boolean allowLaunchPlaceholder(@NonNull TaskFragmentContainer container) {
+ if (container.isOverlay()) {
+ // Don't launch placeholder if the container is an overlay.
+ return false;
+ }
+
final TaskFragmentContainer topContainer = container.getTaskContainer()
.getTopNonFinishingTaskFragmentContainer();
if (container != topContainer) {
@@ -2470,13 +2468,10 @@
@GuardedBy("mLock")
TaskFragmentContainer getContainer(@NonNull Predicate<TaskFragmentContainer> predicate) {
for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
- final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i)
- .getTaskFragmentContainers();
- for (int j = containers.size() - 1; j >= 0; j--) {
- final TaskFragmentContainer container = containers.get(j);
- if (predicate.test(container)) {
- return container;
- }
+ final TaskFragmentContainer container = mTaskContainers.valueAt(i)
+ .getContainer(predicate);
+ if (container != null) {
+ return container;
}
}
return null;
@@ -2641,6 +2636,16 @@
return overlayContainers;
}
+ @GuardedBy("mLock")
+ private boolean isAssociatedWithOverlay(@NonNull Activity activity) {
+ final TaskContainer taskContainer = getTaskContainer(getTaskId(activity));
+ if (taskContainer == null) {
+ return false;
+ }
+ return taskContainer.getContainer(c -> c.isOverlay() && !c.isFinished()
+ && c.getAssociatedActivityToken() == activity.getActivityToken()) != null;
+ }
+
/**
* Creates an overlay container or updates a visible overlay container if its
* {@link TaskFragmentContainer#getTaskId()}, {@link TaskFragmentContainer#getOverlayTag()}
@@ -2734,7 +2739,7 @@
}
// Requesting an always-on-top overlay.
if (!associateLaunchingActivity) {
- if (overlayContainer.isAssociatedWithActivity()) {
+ if (overlayContainer.isOverlayWithActivityAssociation()) {
// Dismiss the overlay container since it has associated with an activity.
Log.w(TAG, "The overlay container with tag:"
+ overlayContainer.getOverlayTag() + " is dismissed because"
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
index 6231ea0..0e4fb30 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -591,16 +591,14 @@
@NonNull TaskFragmentContainer container,
@NonNull ActivityStackAttributes attributes,
@Nullable Size minDimensions) {
- final Rect taskBounds = container.getTaskContainer().getBounds();
final Rect relativeBounds = sanitizeBounds(attributes.getRelativeBounds(), minDimensions,
- taskBounds);
+ container);
final boolean isFillParent = relativeBounds.isEmpty();
// Note that we only set isolated navigation for overlay container without activity
// association. Activity will be launched to an expanded container on top of the overlay
// if the overlay is associated with an activity. Thus, an overlay with activity association
// will never be isolated navigated.
- final boolean isIsolatedNavigated = container.isOverlay()
- && !container.isAssociatedWithActivity() && !isFillParent;
+ final boolean isIsolatedNavigated = container.isAlwaysOnTopOverlay() && !isFillParent;
final boolean dimOnTask = !isFillParent
&& attributes.getWindowAttributes().getDimAreaBehavior() == DIM_AREA_ON_TASK
&& Flags.fullscreenDimFlag();
@@ -624,7 +622,7 @@
*/
@NonNull
static Rect sanitizeBounds(@NonNull Rect bounds, @Nullable Size minDimension,
- @NonNull Rect taskBounds) {
+ @NonNull TaskFragmentContainer container) {
if (bounds.isEmpty()) {
// Don't need to check if the bounds follows the task bounds.
return bounds;
@@ -633,10 +631,33 @@
// Expand the bounds if the bounds are smaller than minimum dimensions.
return new Rect();
}
+ final TaskContainer taskContainer = container.getTaskContainer();
+ final Rect taskBounds = taskContainer.getBounds();
if (!taskBounds.contains(bounds)) {
// Expand the bounds if the bounds exceed the task bounds.
return new Rect();
}
+
+ if (!container.isOverlay()) {
+ // Stop here if the container is not an overlay.
+ return bounds;
+ }
+
+ final IBinder associatedActivityToken = container.getAssociatedActivityToken();
+
+ if (associatedActivityToken == null) {
+ // Stop here if the container is an always-on-top overlay.
+ return bounds;
+ }
+
+ // Expand the overlay with activity association if the associated activity is part of a
+ // split, or we may need to handle three change transition together.
+ final TaskFragmentContainer associatedContainer = taskContainer
+ .getContainerWithActivity(associatedActivityToken);
+ if (taskContainer.getActiveSplitForContainer(associatedContainer) != null) {
+ return new Rect();
+ }
+
return bounds;
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
index fdf0910..67d34c7 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
@@ -43,6 +43,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.window.extensions.core.util.function.Predicate;
import androidx.window.extensions.embedding.SplitAttributes.SplitType;
import androidx.window.extensions.embedding.SplitAttributes.SplitType.ExpandContainersSplitType;
import androidx.window.extensions.embedding.SplitAttributes.SplitType.RatioSplitType;
@@ -318,6 +319,38 @@
return null;
}
+ @Nullable
+ TaskFragmentContainer getContainerWithActivity(@NonNull IBinder activityToken) {
+ return getContainer(container -> container.hasAppearedActivity(activityToken)
+ || container.hasPendingAppearedActivity(activityToken));
+ }
+
+ @Nullable
+ TaskFragmentContainer getContainer(@NonNull Predicate<TaskFragmentContainer> predicate) {
+ for (int i = mContainers.size() - 1; i >= 0; i--) {
+ final TaskFragmentContainer container = mContainers.get(i);
+ if (predicate.test(container)) {
+ return container;
+ }
+ }
+ return null;
+ }
+
+ @Nullable
+ SplitContainer getActiveSplitForContainer(@Nullable TaskFragmentContainer container) {
+ if (container == null) {
+ return null;
+ }
+ for (int i = mSplitContainers.size() - 1; i >= 0; i--) {
+ final SplitContainer splitContainer = mSplitContainers.get(i);
+ if (container.equals(splitContainer.getSecondaryContainer())
+ || container.equals(splitContainer.getPrimaryContainer())) {
+ return splitContainer;
+ }
+ }
+ return null;
+ }
+
/**
* Returns the always-on-top overlay container in the task, or {@code null} if it doesn't exist.
*/
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
index 094ebcb..c952dfe 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
@@ -465,7 +465,7 @@
return;
}
// Early return if this container is not an overlay with activity association.
- if (!isOverlay() || !isAssociatedWithActivity()) {
+ if (!isOverlayWithActivityAssociation()) {
return;
}
if (mAssociatedActivityToken == activityToken) {
@@ -500,7 +500,7 @@
// sure the controller considers this container as the one containing the activity.
// This is needed when the activity is added as pending appeared activity to one
// TaskFragment while it is also an appeared activity in another.
- return mController.getContainerWithActivity(activityToken) == this;
+ return mTaskContainer.getContainerWithActivity(activityToken) == this;
}
/** Whether this activity has appeared in the TaskFragment on the server side. */
@@ -1019,16 +1019,16 @@
return mAssociatedActivityToken;
}
- boolean isAssociatedWithActivity() {
- return mAssociatedActivityToken != null;
- }
-
/**
* Returns {@code true} if the overlay container should be always on top, which should be
* a non-fill-parent overlay without activity association.
*/
boolean isAlwaysOnTopOverlay() {
- return isOverlay() && !isAssociatedWithActivity();
+ return isOverlay() && mAssociatedActivityToken == null;
+ }
+
+ boolean isOverlayWithActivityAssociation() {
+ return isOverlay() && mAssociatedActivityToken != null;
}
@Override
@@ -1050,7 +1050,7 @@
+ " runningActivityCount=" + getRunningActivityCount()
+ " isFinished=" + mIsFinished
+ " overlayTag=" + mOverlayTag
- + " associatedActivity" + mAssociatedActivityToken
+ + " associatedActivityToken=" + mAssociatedActivityToken
+ " lastRequestedBounds=" + mLastRequestedBounds
+ " pendingAppearedActivities=" + mPendingAppearedActivities
+ (includeContainersToFinishOnExit ? " containersToFinishOnExit="
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java
index 50abdfd..fab298d 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java
@@ -21,11 +21,15 @@
import static android.view.Display.DEFAULT_DISPLAY;
import static androidx.window.extensions.embedding.ActivityEmbeddingOptionsProperties.KEY_OVERLAY_TAG;
+import static androidx.window.extensions.embedding.EmbeddingTestUtils.SPLIT_ATTRIBUTES;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_BOUNDS;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_ID;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.TEST_TAG;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.createMockTaskFragmentInfo;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.createSplitPairRuleBuilder;
+import static androidx.window.extensions.embedding.EmbeddingTestUtils.createSplitPlaceholderRuleBuilder;
+import static androidx.window.extensions.embedding.EmbeddingTestUtils.createSplitRule;
+import static androidx.window.extensions.embedding.SplitPresenter.sanitizeBounds;
import static androidx.window.extensions.embedding.WindowAttributes.DIM_AREA_ON_TASK;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
@@ -85,6 +89,7 @@
import org.mockito.junit.MockitoRule;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
/**
@@ -102,6 +107,9 @@
@Rule
public MockitoRule rule = MockitoJUnit.rule();
+ private static final Intent PLACEHOLDER_INTENT = new Intent().setComponent(
+ new ComponentName("test", "placeholder"));
+
@Rule
public final SetFlagsRule mSetFlagRule = new SetFlagsRule();
@@ -259,8 +267,7 @@
mSplitController.setActivityStackAttributesCalculator(params ->
new ActivityStackAttributes.Builder().setRelativeBounds(bounds).build());
final TaskFragmentContainer overlayContainer = createOrUpdateOverlayTaskFragmentIfNeeded(
- mOverlayContainer1.getOverlayTag(),
- mOverlayContainer1.getTopNonFinishingActivity());
+ mOverlayContainer1.getOverlayTag());
assertWithMessage("overlayContainer1 must be updated since the new overlay container"
+ " is launched with the same tag and task")
@@ -280,12 +287,13 @@
mSplitController.setActivityStackAttributesCalculator(params ->
new ActivityStackAttributes.Builder().setRelativeBounds(bounds).build());
final TaskFragmentContainer overlayContainer = createOrUpdateOverlayTaskFragmentIfNeeded(
- mOverlayContainer1.getOverlayTag(), mActivity);
+ mOverlayContainer1.getOverlayTag(), createMockActivity());
assertWithMessage("overlayContainer1 must be dismissed since the new overlay container"
+ " is associated with different launching activity")
.that(mSplitController.getAllNonFinishingOverlayContainers())
.containsExactly(mOverlayContainer2, overlayContainer);
+ assertThat(overlayContainer).isNotEqualTo(mOverlayContainer1);
}
@Test
@@ -323,7 +331,8 @@
}
private void createExistingOverlayContainers(boolean visible) {
- mOverlayContainer1 = createTestOverlayContainer(TASK_ID, "test1", visible);
+ mOverlayContainer1 = createTestOverlayContainer(TASK_ID, "test1", visible,
+ true /* associatedLaunchingActivity */, mActivity);
mOverlayContainer2 = createTestOverlayContainer(TASK_ID + 1, "test2", visible);
List<TaskFragmentContainer> overlayContainers = mSplitController
.getAllNonFinishingOverlayContainers();
@@ -335,17 +344,49 @@
mIntent.setComponent(new ComponentName(ApplicationProvider.getApplicationContext(),
MinimumDimensionActivity.class));
final Rect bounds = new Rect(0, 0, 100, 100);
+ final TaskFragmentContainer overlayContainer =
+ createTestOverlayContainer(TASK_ID, "test1");
- SplitPresenter.sanitizeBounds(bounds, SplitPresenter.getMinDimensions(mIntent),
- TASK_BOUNDS);
+ assertThat(sanitizeBounds(bounds, SplitPresenter.getMinDimensions(mIntent),
+ overlayContainer).isEmpty()).isTrue();
}
@Test
public void testSanitizeBounds_notInTaskBounds_expandOverlay() {
final Rect bounds = new Rect(TASK_BOUNDS);
bounds.offset(10, 10);
+ final TaskFragmentContainer overlayContainer =
+ createTestOverlayContainer(TASK_ID, "test1");
- SplitPresenter.sanitizeBounds(bounds, null, TASK_BOUNDS);
+ assertThat(sanitizeBounds(bounds, null, overlayContainer)
+ .isEmpty()).isTrue();
+ }
+
+ @Test
+ public void testSanitizeBounds_visibleSplit_expandOverlay() {
+ // Launch a visible split
+ final Activity primaryActivity = createMockActivity();
+ final Activity secondaryActivity = createMockActivity();
+ final TaskFragmentContainer primaryContainer =
+ createMockTaskFragmentContainer(primaryActivity, true /* isVisible */);
+ final TaskFragmentContainer secondaryContainer =
+ createMockTaskFragmentContainer(secondaryActivity, true /* isVisible */);
+
+ final SplitPairRule splitPairRule = createSplitPairRuleBuilder(
+ activityActivityPair -> true /* activityPairPredicate */,
+ activityIntentPair -> true /* activityIntentPairPredicate */,
+ parentWindowMetrics -> true /* parentWindowMetricsPredicate */)
+ .build();
+ mSplitController.registerSplit(mTransaction, primaryContainer, primaryActivity,
+ secondaryContainer, splitPairRule, splitPairRule.getDefaultSplitAttributes());
+
+ final Rect bounds = new Rect(0, 0, 100, 100);
+ final TaskFragmentContainer overlayContainer =
+ createTestOverlayContainer(TASK_ID, "test1", true /* isVisible */,
+ true /* associatedLaunchingActivity */, secondaryActivity);
+
+ assertThat(sanitizeBounds(bounds, null, overlayContainer)
+ .isEmpty()).isTrue();
}
@Test
@@ -701,6 +742,70 @@
.doesNotContain(overlayWithAssociation);
}
+ @Test
+ public void testLaunchPlaceholderIfNecessary_skipIfActivityAssociateOverlay() {
+ setupPlaceholderRule(mActivity);
+ createTestOverlayContainer(TASK_ID, "test", true /* isVisible */,
+ true /* associateLaunchingActivity */, mActivity);
+
+
+ mSplitController.mTransactionManager.startNewTransaction();
+ assertThat(mSplitController.launchPlaceholderIfNecessary(mTransaction, mActivity,
+ false /* isOnCreated */)).isFalse();
+
+ verify(mTransaction, never()).startActivityInTaskFragment(any(), any(), any(), any());
+ }
+
+ @Test
+ public void testLaunchPlaceholderIfNecessary_skipIfActivityInOverlay() {
+ setupPlaceholderRule(mActivity);
+ createOrUpdateOverlayTaskFragmentIfNeeded("test1", mActivity);
+
+ mSplitController.mTransactionManager.startNewTransaction();
+ assertThat(mSplitController.launchPlaceholderIfNecessary(mTransaction, mActivity,
+ false /* isOnCreated */)).isFalse();
+
+ verify(mTransaction, never()).startActivityInTaskFragment(any(), any(), any(), any());
+ }
+
+ /** Setups a rule to launch placeholder for the given activity. */
+ private void setupPlaceholderRule(@NonNull Activity primaryActivity) {
+ final SplitRule placeholderRule = createSplitPlaceholderRuleBuilder(PLACEHOLDER_INTENT,
+ primaryActivity::equals, i -> false, w -> true)
+ .setDefaultSplitAttributes(SPLIT_ATTRIBUTES)
+ .build();
+ mSplitController.setEmbeddingRules(Collections.singleton(placeholderRule));
+ }
+
+ @Test
+ public void testResolveStartActivityIntent_skipIfAssociateOverlay() {
+ final Intent intent = new Intent();
+ mSplitController.setEmbeddingRules(Collections.singleton(
+ createSplitRule(mActivity, intent)));
+ createTestOverlayContainer(TASK_ID, "test", true /* isVisible */,
+ true /* associateLaunchingActivity */, mActivity);
+ final TaskFragmentContainer container = mSplitController.resolveStartActivityIntent(
+ mTransaction, TASK_ID, intent, mActivity);
+
+ assertThat(container).isNull();
+ verify(mSplitController, never()).resolveStartActivityIntentByRule(any(), anyInt(), any(),
+ any());
+ }
+
+ @Test
+ public void testResolveStartActivityIntent_skipIfLaunchingActivityInOverlay() {
+ final Intent intent = new Intent();
+ mSplitController.setEmbeddingRules(Collections.singleton(
+ createSplitRule(mActivity, intent)));
+ createOrUpdateOverlayTaskFragmentIfNeeded("test1", mActivity);
+ final TaskFragmentContainer container = mSplitController.resolveStartActivityIntent(
+ mTransaction, TASK_ID, intent, mActivity);
+
+ assertThat(container).isNull();
+ verify(mSplitController, never()).resolveStartActivityIntentByRule(any(), anyInt(), any(),
+ any());
+ }
+
/**
* A simplified version of {@link SplitController#createOrUpdateOverlayTaskFragmentIfNeeded}
*/
@@ -726,9 +831,16 @@
/** Creates a mock TaskFragment that has been registered and appeared in the organizer. */
@NonNull
private TaskFragmentContainer createMockTaskFragmentContainer(@NonNull Activity activity) {
+ return createMockTaskFragmentContainer(activity, false /* isVisible */);
+ }
+
+ /** Creates a mock TaskFragment that has been registered and appeared in the organizer. */
+ @NonNull
+ private TaskFragmentContainer createMockTaskFragmentContainer(
+ @NonNull Activity activity, boolean isVisible) {
final TaskFragmentContainer container = mSplitController.newContainer(activity,
activity.getTaskId());
- setupTaskFragmentInfo(container, activity, false /* isVisible */);
+ setupTaskFragmentInfo(container, activity, isVisible);
return container;
}
@@ -745,17 +857,26 @@
true /* associateLaunchingActivity */);
}
+ @NonNull
+ private TaskFragmentContainer createTestOverlayContainer(int taskId, @NonNull String tag,
+ boolean isVisible, boolean associateLaunchingActivity) {
+ return createTestOverlayContainer(taskId, tag, isVisible, associateLaunchingActivity,
+ null /* launchingActivity */);
+ }
+
// TODO(b/243518738): add more test coverage on overlay container without activity association
// once we have use cases.
@NonNull
private TaskFragmentContainer createTestOverlayContainer(int taskId, @NonNull String tag,
- boolean isVisible, boolean associateLaunchingActivity) {
- Activity activity = createMockActivity();
+ boolean isVisible, boolean associateLaunchingActivity,
+ @Nullable Activity launchingActivity) {
+ final Activity activity = launchingActivity != null
+ ? launchingActivity : createMockActivity();
TaskFragmentContainer overlayContainer = mSplitController.newContainer(
null /* pendingAppearedActivity */, mIntent, activity, taskId,
null /* pairedPrimaryContainer */, tag, Bundle.EMPTY,
associateLaunchingActivity);
- setupTaskFragmentInfo(overlayContainer, activity, isVisible);
+ setupTaskFragmentInfo(overlayContainer, createMockActivity(), isVisible);
return overlayContainer;
}
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java
index abfc9c8..44ab2c4 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java
@@ -538,9 +538,7 @@
// container1.
container2.setInfo(mTransaction, mInfo);
- assertTrue(container1.hasActivity(mActivity.getActivityToken()));
- assertFalse(container2.hasActivity(mActivity.getActivityToken()));
-
+ assertTrue(container2.hasActivity(mActivity.getActivityToken()));
// When the pending appeared record is removed from container1, we respect the appeared
// record in container2.
container1.removePendingAppearedActivity(mActivity.getActivityToken());
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt
index e1bf40c..6110133 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt
@@ -105,18 +105,21 @@
}
@Test
- fun onDragUpdate_stayOnSameSide() {
+ fun drag_stayOnSameSide() {
runOnMainSync {
controller.onDragStart(initialLocationOnLeft = false)
controller.onDragUpdate(pointOnRight.x, pointOnRight.y)
+ controller.onDragEnd()
}
waitForAnimateIn()
assertThat(dropTargetView).isNull()
assertThat(testListener.locationChanges).isEmpty()
+ assertThat(testListener.locationReleases).containsExactly(BubbleBarLocation.RIGHT)
}
@Test
- fun onDragUpdate_toLeft() {
+ fun drag_toLeft() {
+ // Drag to left, but don't finish
runOnMainSync {
controller.onDragStart(initialLocationOnLeft = false)
controller.onDragUpdate(pointOnLeft.x, pointOnLeft.y)
@@ -132,10 +135,16 @@
.isEqualTo(expectedDropTargetBounds.height())
assertThat(testListener.locationChanges).containsExactly(BubbleBarLocation.LEFT)
+ assertThat(testListener.locationReleases).isEmpty()
+
+ // Finish the drag
+ runOnMainSync { controller.onDragEnd() }
+ assertThat(testListener.locationReleases).containsExactly(BubbleBarLocation.LEFT)
}
@Test
- fun onDragUpdate_toLeftAndBackToRight() {
+ fun drag_toLeftAndBackToRight() {
+ // Drag to left
runOnMainSync {
controller.onDragStart(initialLocationOnLeft = false)
controller.onDragUpdate(pointOnLeft.x, pointOnLeft.y)
@@ -143,6 +152,7 @@
waitForAnimateIn()
assertThat(dropTargetView).isNotNull()
+ // Drag to right
runOnMainSync { controller.onDragUpdate(pointOnRight.x, pointOnRight.y) }
// We have to wait for existing drop target to animate out and new to animate in
waitForAnimateOut()
@@ -158,10 +168,15 @@
assertThat(testListener.locationChanges)
.containsExactly(BubbleBarLocation.LEFT, BubbleBarLocation.RIGHT)
+ assertThat(testListener.locationReleases).isEmpty()
+
+ // Release the view
+ runOnMainSync { controller.onDragEnd() }
+ assertThat(testListener.locationReleases).containsExactly(BubbleBarLocation.RIGHT)
}
@Test
- fun onDragUpdate_toLeftInExclusionRect() {
+ fun drag_toLeftInExclusionRect() {
runOnMainSync {
controller.onDragStart(initialLocationOnLeft = false)
// Exclusion rect is around the bottom center area of the screen
@@ -170,6 +185,10 @@
waitForAnimateIn()
assertThat(dropTargetView).isNull()
assertThat(testListener.locationChanges).isEmpty()
+ assertThat(testListener.locationReleases).isEmpty()
+
+ runOnMainSync { controller.onDragEnd() }
+ assertThat(testListener.locationReleases).containsExactly(BubbleBarLocation.RIGHT)
}
@Test
@@ -256,8 +275,13 @@
internal class TestLocationChangeListener : BaseBubblePinController.LocationChangeListener {
val locationChanges = mutableListOf<BubbleBarLocation>()
+ val locationReleases = mutableListOf<BubbleBarLocation>()
override fun onChange(location: BubbleBarLocation) {
locationChanges.add(location)
}
+
+ override fun onRelease(location: BubbleBarLocation) {
+ locationReleases.add(location)
+ }
}
}
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index c2c90c8..c8bfe7a4 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -420,7 +420,7 @@
<dimen name="freeform_decor_caption_height">42dp</dimen>
<!-- Height of desktop mode caption for freeform tasks. -->
- <dimen name="desktop_mode_freeform_decor_caption_height">42dp</dimen>
+ <dimen name="desktop_mode_freeform_decor_caption_height">40dp</dimen>
<!-- Height of desktop mode caption for fullscreen tasks. -->
<dimen name="desktop_mode_fullscreen_decor_caption_height">36dp</dimen>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BadgedImageView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BadgedImageView.java
index 0297901..f9a1d94 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BadgedImageView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BadgedImageView.java
@@ -82,8 +82,8 @@
private BubbleViewProvider mBubble;
private BubblePositioner mPositioner;
- private boolean mOnLeft;
-
+ private boolean mBadgeOnLeft;
+ private boolean mDotOnLeft;
private DotRenderer mDotRenderer;
private DotRenderer.DrawParams mDrawParams;
private int mDotColor;
@@ -153,7 +153,8 @@
public void hideDotAndBadge(boolean onLeft) {
addDotSuppressionFlag(BadgedImageView.SuppressionFlag.BEHIND_STACK);
- mOnLeft = onLeft;
+ mBadgeOnLeft = onLeft;
+ mDotOnLeft = onLeft;
hideBadge();
}
@@ -185,7 +186,7 @@
mDrawParams.dotColor = mDotColor;
mDrawParams.iconBounds = mTempBounds;
- mDrawParams.leftAlign = mOnLeft;
+ mDrawParams.leftAlign = mDotOnLeft;
mDrawParams.scale = mDotScale;
mDotRenderer.draw(canvas, mDrawParams);
@@ -255,7 +256,7 @@
* Whether decorations (badges or dots) are on the left.
*/
boolean getDotOnLeft() {
- return mOnLeft;
+ return mDotOnLeft;
}
/**
@@ -263,7 +264,7 @@
*/
float[] getDotCenter() {
float[] dotPosition;
- if (mOnLeft) {
+ if (mDotOnLeft) {
dotPosition = mDotRenderer.getLeftDotPosition();
} else {
dotPosition = mDotRenderer.getRightDotPosition();
@@ -291,22 +292,23 @@
if (onLeft != getDotOnLeft()) {
if (shouldDrawDot()) {
animateDotScale(0f /* showDot */, () -> {
- mOnLeft = onLeft;
+ mDotOnLeft = onLeft;
invalidate();
animateDotScale(1.0f, null /* after */);
});
} else {
- mOnLeft = onLeft;
+ mDotOnLeft = onLeft;
}
}
+ mBadgeOnLeft = onLeft;
// TODO animate badge
showBadge();
-
}
/** Sets the position of the dot and badge. */
void setDotBadgeOnLeft(boolean onLeft) {
- mOnLeft = onLeft;
+ mBadgeOnLeft = onLeft;
+ mDotOnLeft = onLeft;
invalidate();
showBadge();
}
@@ -361,7 +363,7 @@
}
int translationX;
- if (mOnLeft) {
+ if (mBadgeOnLeft) {
translationX = -(mBubble.getBubbleIcon().getWidth() - appBadgeBitmap.getWidth());
} else {
translationX = 0;
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 d295877..edd5935 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
@@ -474,11 +474,16 @@
mDisplayController.addDisplayChangingController(
(displayId, fromRotation, toRotation, newDisplayAreaInfo, t) -> {
- // This is triggered right before the rotation is applied
- if (fromRotation != toRotation) {
+ Rect newScreenBounds = new Rect();
+ if (newDisplayAreaInfo != null) {
+ newScreenBounds =
+ newDisplayAreaInfo.configuration.windowConfiguration.getBounds();
+ }
+ // This is triggered right before the rotation or new screen size is applied
+ if (fromRotation != toRotation || !newScreenBounds.equals(mScreenBounds)) {
if (mStackView != null) {
// Layout listener set on stackView will update the positioner
- // once the rotation is applied
+ // once the rotation or screen change is applied
mStackView.onOrientationChanged();
}
}
@@ -725,6 +730,17 @@
}
}
+ /**
+ * Animate bubble bar to the given location. The location change is transient. It does not
+ * update the state of the bubble bar.
+ * To update bubble bar pinned location, use {@link #setBubbleBarLocation(BubbleBarLocation)}.
+ */
+ public void animateBubbleBarLocation(BubbleBarLocation bubbleBarLocation) {
+ if (canShowAsBubbleBar()) {
+ mBubbleStateListener.animateBubbleBarLocation(bubbleBarLocation);
+ }
+ }
+
/** Whether this userId belongs to the current user. */
private boolean isCurrentProfile(int userId) {
return userId == UserHandle.USER_ALL
@@ -2250,15 +2266,19 @@
private final SingleInstanceRemoteListener<BubbleController, IBubblesListener> mListener;
private final Bubbles.BubbleStateListener mBubbleListener =
new Bubbles.BubbleStateListener() {
+ @Override
+ public void onBubbleStateChange(BubbleBarUpdate update) {
+ Bundle b = new Bundle();
+ b.setClassLoader(BubbleBarUpdate.class.getClassLoader());
+ b.putParcelable(BubbleBarUpdate.BUNDLE_KEY, update);
+ mListener.call(l -> l.onBubbleStateChange(b));
+ }
- @Override
- public void onBubbleStateChange(BubbleBarUpdate update) {
- Bundle b = new Bundle();
- b.setClassLoader(BubbleBarUpdate.class.getClassLoader());
- b.putParcelable(BubbleBarUpdate.BUNDLE_KEY, update);
- mListener.call(l -> l.onBubbleStateChange(b));
- }
- };
+ @Override
+ public void animateBubbleBarLocation(BubbleBarLocation location) {
+ mListener.call(l -> l.animateBubbleBarLocation(location));
+ }
+ };
IBubblesImpl(BubbleController controller) {
mController = controller;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
index 127a49f..322088b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
@@ -37,6 +37,7 @@
import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
+import com.android.wm.shell.common.bubbles.BubbleBarLocation;
import com.android.wm.shell.common.bubbles.BubbleBarUpdate;
import com.android.wm.shell.shared.annotations.ExternalThread;
@@ -304,6 +305,12 @@
* Called when the bubbles state changes.
*/
void onBubbleStateChange(BubbleBarUpdate update);
+
+ /**
+ * Called when bubble bar should temporarily be animated to a new location.
+ * Does not result in a state change.
+ */
+ void animateBubbleBarLocation(BubbleBarLocation location);
}
/** Listener to find out about stack expansion / collapse events. */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubblesListener.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubblesListener.aidl
index e48f8d5..14d29cd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubblesListener.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubblesListener.aidl
@@ -15,8 +15,9 @@
*/
package com.android.wm.shell.bubbles;
-import android.os.Bundle;
+import android.os.Bundle;
+import com.android.wm.shell.common.bubbles.BubbleBarLocation;
/**
* Listener interface that Launcher attaches to SystemUI to get bubbles callbacks.
*/
@@ -26,4 +27,10 @@
* Called when the bubbles state changes.
*/
void onBubbleStateChange(in Bundle update);
+
+ /**
+ * Called when bubble bar should temporarily be animated to a new location.
+ * Does not result in a state change.
+ */
+ void animateBubbleBarLocation(in BubbleBarLocation location);
}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt
index fe9c4d4..a51ac63 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt
@@ -135,9 +135,9 @@
private fun finishDrag() {
if (!isStuckToDismiss) {
- animationHelper.animateToRestPosition()
pinController.onDragEnd()
dragListener.onReleased(inDismiss = false)
+ animationHelper.animateToRestPosition()
dismissView.hide()
}
isMoving = false
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
index 62cc4da..a351cef 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
@@ -33,6 +33,8 @@
import android.view.WindowManager;
import android.widget.FrameLayout;
+import androidx.annotation.NonNull;
+
import com.android.wm.shell.bubbles.Bubble;
import com.android.wm.shell.bubbles.BubbleController;
import com.android.wm.shell.bubbles.BubbleData;
@@ -42,6 +44,8 @@
import com.android.wm.shell.bubbles.DeviceConfig;
import com.android.wm.shell.bubbles.DismissViewUtils;
import com.android.wm.shell.bubbles.bar.BubbleBarExpandedViewDragController.DragListener;
+import com.android.wm.shell.common.bubbles.BaseBubblePinController;
+import com.android.wm.shell.common.bubbles.BubbleBarLocation;
import com.android.wm.shell.common.bubbles.DismissView;
import kotlin.Unit;
@@ -115,7 +119,18 @@
mBubbleExpandedViewPinController = new BubbleExpandedViewPinController(
context, this, mPositioner);
- mBubbleExpandedViewPinController.setListener(mBubbleController::setBubbleBarLocation);
+ mBubbleExpandedViewPinController.setListener(
+ new BaseBubblePinController.LocationChangeListener() {
+ @Override
+ public void onChange(@NonNull BubbleBarLocation bubbleBarLocation) {
+ mBubbleController.animateBubbleBarLocation(bubbleBarLocation);
+ }
+
+ @Override
+ public void onRelease(@NonNull BubbleBarLocation location) {
+ mBubbleController.setBubbleBarLocation(location);
+ }
+ });
setOnClickListener(view -> hideMenuOrCollapse());
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BaseBubblePinController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BaseBubblePinController.kt
index a008045..e514f9d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BaseBubblePinController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BaseBubblePinController.kt
@@ -82,6 +82,7 @@
fun onDragEnd() {
getDropTargetView()?.let { view -> view.animateOut { removeDropTargetView(view) } }
dismissZone = null
+ listener?.onRelease(if (onLeft) LEFT else RIGHT)
}
/**
@@ -170,14 +171,22 @@
/** Receive updates on location changes */
interface LocationChangeListener {
/**
- * Bubble bar [BubbleBarLocation] has changed as a result of dragging
+ * Bubble bar has been dragged to a new [BubbleBarLocation]. And the drag is still in
+ * progress.
*
* Triggered when drag gesture passes the middle of the screen and before touch up. Can be
* triggered multiple times per gesture.
*
* @param location new location as a result of the ongoing drag operation
*/
- fun onChange(location: BubbleBarLocation)
+ fun onChange(location: BubbleBarLocation) {}
+
+ /**
+ * Bubble bar has been released in the [BubbleBarLocation].
+ *
+ * @param location final location of the bubble bar once drag is released
+ */
+ fun onRelease(location: BubbleBarLocation)
}
companion object {
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 4eff3f0..6e61f22 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
@@ -26,6 +26,7 @@
import com.android.wm.shell.common.FloatingContentCoordinator;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SystemWindows;
+import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
import com.android.wm.shell.common.pip.PipBoundsState;
import com.android.wm.shell.common.pip.PipDisplayLayoutState;
@@ -43,6 +44,7 @@
import com.android.wm.shell.pip2.phone.PipScheduler;
import com.android.wm.shell.pip2.phone.PipTouchHandler;
import com.android.wm.shell.pip2.phone.PipTransition;
+import com.android.wm.shell.pip2.phone.PipTransitionState;
import com.android.wm.shell.shared.annotations.ShellMainThread;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
@@ -69,9 +71,11 @@
PipBoundsAlgorithm pipBoundsAlgorithm,
Optional<PipController> pipController,
PipTouchHandler pipTouchHandler,
- @NonNull PipScheduler pipScheduler) {
+ @NonNull PipScheduler pipScheduler,
+ @NonNull PipTransitionState pipStackListenerController) {
return new PipTransition(context, shellInit, shellTaskOrganizer, transitions,
- pipBoundsState, null, pipBoundsAlgorithm, pipScheduler);
+ pipBoundsState, null, pipBoundsAlgorithm, pipScheduler,
+ pipStackListenerController);
}
@WMSingleton
@@ -85,6 +89,9 @@
PipBoundsAlgorithm pipBoundsAlgorithm,
PipDisplayLayoutState pipDisplayLayoutState,
PipScheduler pipScheduler,
+ TaskStackListenerImpl taskStackListener,
+ ShellTaskOrganizer shellTaskOrganizer,
+ PipTransitionState pipTransitionState,
@ShellMainThread ShellExecutor mainExecutor) {
if (!PipUtils.isPip2ExperimentEnabled()) {
return Optional.empty();
@@ -92,7 +99,7 @@
return Optional.ofNullable(PipController.create(
context, shellInit, shellController, displayController, displayInsetsController,
pipBoundsState, pipBoundsAlgorithm, pipDisplayLayoutState, pipScheduler,
- mainExecutor));
+ taskStackListener, shellTaskOrganizer, pipTransitionState, mainExecutor));
}
}
@@ -101,8 +108,8 @@
static PipScheduler providePipScheduler(Context context,
PipBoundsState pipBoundsState,
@ShellMainThread ShellExecutor mainExecutor,
- ShellTaskOrganizer shellTaskOrganizer) {
- return new PipScheduler(context, pipBoundsState, mainExecutor, shellTaskOrganizer);
+ PipTransitionState pipTransitionState) {
+ return new PipScheduler(context, pipBoundsState, mainExecutor, pipTransitionState);
}
@WMSingleton
@@ -146,4 +153,10 @@
return new PipMotionHelper(context, pipBoundsState, menuController, pipSnapAlgorithm,
floatingContentCoordinator, pipPerfHintControllerOptional);
}
+
+ @WMSingleton
+ @Provides
+ static PipTransitionState providePipStackListenerController() {
+ return new PipTransitionState();
+ }
}
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 0a9e5d0..ecfb134 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
@@ -158,6 +158,10 @@
com.android.wm.shell.R.dimen.desktop_mode_transition_area_width
)
+ /** Task id of the task currently being dragged from fullscreen/split. */
+ val draggingTaskId
+ get() = dragToDesktopTransitionHandler.draggingTaskId
+
private var recentsAnimationRunning = false
private lateinit var splitScreenController: SplitScreenController
@@ -406,6 +410,7 @@
fun onDesktopSplitSelectAnimComplete(taskInfo: RunningTaskInfo) {
val wct = WindowContainerTransaction()
wct.setBounds(taskInfo.token, Rect())
+ wct.setWindowingMode(taskInfo.token, WINDOWING_MODE_UNDEFINED)
shellTaskOrganizer.applyTransaction(wct)
}
@@ -447,7 +452,9 @@
)
val wct = WindowContainerTransaction()
wct.setBounds(task.token, Rect())
- addMoveToSplitChanges(wct, task)
+ // Rather than set windowing mode to multi-window at task level, set it to
+ // undefined and inherit from split stage.
+ wct.setWindowingMode(task.token, WINDOWING_MODE_UNDEFINED)
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
transitions.startTransition(TRANSIT_CHANGE, wct, null /* handler */)
} else {
@@ -458,10 +465,12 @@
private fun exitSplitIfApplicable(wct: WindowContainerTransaction, taskInfo: RunningTaskInfo) {
if (splitScreenController.isTaskInSplitScreen(taskInfo.taskId)) {
splitScreenController.prepareExitSplitScreen(
- wct,
- splitScreenController.getStageOfTask(taskInfo.taskId),
- EXIT_REASON_DESKTOP_MODE
+ wct,
+ splitScreenController.getStageOfTask(taskInfo.taskId),
+ EXIT_REASON_DESKTOP_MODE
)
+ splitScreenController.transitionHandler
+ ?.onSplitToDesktop()
}
}
@@ -1044,9 +1053,11 @@
wct: WindowContainerTransaction,
taskInfo: RunningTaskInfo
) {
- // Explicitly setting multi-window at task level interferes with animations.
- // Let task inherit windowing mode once transition is complete instead.
- wct.setWindowingMode(taskInfo.token, WINDOWING_MODE_UNDEFINED)
+ // This windowing mode is to get the transition animation started; once we complete
+ // split select, we will change windowing mode to undefined and inherit from split stage.
+ // Going to undefined here causes task to flicker to the top left.
+ // Cancelling the split select flow will revert it to fullscreen.
+ wct.setWindowingMode(taskInfo.token, WINDOWING_MODE_MULTI_WINDOW)
// The task's density may have been overridden in freeform; revert it here as we don't
// want it overridden in multi-window.
wct.setDensityDpi(taskInfo.token, getDefaultDensityDpi())
@@ -1237,7 +1248,7 @@
finalizeDragToDesktop(taskInfo, getDefaultDesktopTaskBounds(displayLayout))
}
DesktopModeVisualIndicator.IndicatorType.NO_INDICATOR,
- DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR -> {
+ DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR -> {
cancelDragToDesktop(taskInfo)
}
DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_LEFT_INDICATOR -> {
@@ -1504,7 +1515,7 @@
@JvmField
val DESKTOP_MODE_INITIAL_BOUNDS_SCALE = SystemProperties
- .getInt("persist.wm.debug.freeform_initial_bounds_scale", 75) / 100f
+ .getInt("persist.wm.debug.desktop_mode_initial_bounds_scale", 75) / 100f
/**
* Check if desktop density override is enabled
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
index e341f2d..e5e435d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
@@ -6,6 +6,7 @@
import android.animation.ValueAnimator
import android.app.ActivityOptions
import android.app.ActivityOptions.SourceInfo
+import android.app.ActivityTaskManager.INVALID_TASK_ID
import android.app.PendingIntent
import android.app.PendingIntent.FLAG_ALLOW_UNSAFE_IMPLICIT_INTENT
import android.app.PendingIntent.FLAG_MUTABLE
@@ -26,6 +27,9 @@
import android.window.WindowContainerToken
import android.window.WindowContainerTransaction
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
+import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT
+import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT
+import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED
import com.android.wm.shell.protolog.ShellProtoLogGroup
import com.android.wm.shell.shared.TransitionUtil
import com.android.wm.shell.splitscreen.SplitScreenController
@@ -68,7 +72,7 @@
.addCategory(Intent.CATEGORY_HOME)
private var dragToDesktopStateListener: DragToDesktopStateListener? = null
- private var splitScreenController: SplitScreenController? = null
+ private lateinit var splitScreenController: SplitScreenController
private var transitionState: TransitionState? = null
private lateinit var onTaskResizeAnimationListener: OnTaskResizeAnimationListener
@@ -76,6 +80,9 @@
val inProgress: Boolean
get() = transitionState != null
+ /** The task id of the task currently being dragged from fullscreen/split. */
+ val draggingTaskId: Int
+ get() = transitionState?.draggedTaskId ?: INVALID_TASK_ID
/** Sets a listener to receive callback about events during the transition animation. */
fun setDragToDesktopStateListener(listener: DragToDesktopStateListener) {
dragToDesktopStateListener = listener
@@ -130,10 +137,14 @@
.startTransition(TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP, wct, this)
transitionState = if (isSplitTask(taskId)) {
+ val otherTask = getOtherSplitTask(taskId) ?: throw IllegalStateException(
+ "Expected split task to have a counterpart."
+ )
TransitionState.FromSplit(
draggedTaskId = taskId,
dragAnimator = dragToDesktopAnimator,
- startTransitionToken = startTransitionToken
+ startTransitionToken = startTransitionToken,
+ otherSplitTask = otherTask
)
} else {
TransitionState.FromFullscreen(
@@ -347,6 +358,12 @@
?: error("Start transition expected to be waiting for merge but wasn't")
if (isEndTransition) {
info.changes.withIndex().forEach { (i, change) ->
+ // If we're exiting split, hide the remaining split task.
+ if (state is TransitionState.FromSplit &&
+ change.taskInfo?.taskId == state.otherSplitTask) {
+ t.hide(change.leash)
+ startTransactionFinishT.hide(change.leash)
+ }
if (change.mode == TRANSIT_CLOSE) {
t.hide(change.leash)
startTransactionFinishT.hide(change.leash)
@@ -392,7 +409,6 @@
onTaskResizeAnimationListener.onAnimationStart(state.draggedTaskId, t,
unscaledStartBounds)
finishCallback.onTransitionFinished(null /* wct */)
-
val tx: SurfaceControl.Transaction = transactionSupplier.get()
ValueAnimator.ofObject(rectEvaluator, unscaledStartBounds, endBounds)
.setDuration(DRAG_TO_DESKTOP_FINISH_ANIM_DURATION_MS)
@@ -549,7 +565,18 @@
}
private fun isSplitTask(taskId: Int): Boolean {
- return splitScreenController?.isTaskInSplitScreen(taskId) ?: false
+ return splitScreenController.isTaskInSplitScreen(taskId)
+ }
+
+ private fun getOtherSplitTask(taskId: Int): Int? {
+ val splitPos = splitScreenController.getSplitPosition(taskId)
+ if (splitPos == SPLIT_POSITION_UNDEFINED) return null
+ val otherTaskPos = if (splitPos == SPLIT_POSITION_BOTTOM_OR_RIGHT) {
+ SPLIT_POSITION_TOP_OR_LEFT
+ } else {
+ SPLIT_POSITION_BOTTOM_OR_RIGHT
+ }
+ return splitScreenController.getTaskInfo(otherTaskPos)?.taskId
}
private fun requireTransitionState(): TransitionState {
@@ -598,6 +625,7 @@
override var cancelled: Boolean = false,
override var startAborted: Boolean = false,
var splitRootChange: Change? = null,
+ var otherSplitTask: Int
) : TransitionState()
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
index 4f71a02..7730285 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
@@ -54,7 +54,6 @@
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
-import java.util.function.Consumer;
/**
* Responsible supplying PiP Transitions.
@@ -125,12 +124,8 @@
/**
* Called when the Shell wants to start resizing Pip transition/animation.
- *
- * @param onFinishResizeCallback callback guaranteed to execute when animation ends and
- * client completes any potential draws upon WM state updates.
*/
- public void startResizeTransition(WindowContainerTransaction wct,
- Consumer<Rect> onFinishResizeCallback) {
+ public void startResizeTransition(WindowContainerTransaction wct) {
// Default implementation does nothing.
}
@@ -266,9 +261,9 @@
}
/** Whether a particular package is same as current pip package. */
- public boolean isInPipPackage(String packageName) {
+ public boolean isPackageActiveInPip(String packageName) {
final TaskInfo inPipTask = mPipOrganizer.getTaskInfo();
- return packageName != null && inPipTask != null
+ return packageName != null && inPipTask != null && mPipOrganizer.isInPip()
&& packageName.equals(SplitScreenUtils.getPackageName(inPipTask.baseIntent));
}
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 1e18b8c..a12882f 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
@@ -16,25 +16,31 @@
package com.android.wm.shell.pip2.phone;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE;
import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_PIP;
+import android.app.ActivityManager;
import android.app.PictureInPictureParams;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.graphics.Rect;
+import android.os.Bundle;
import android.view.InsetsState;
import android.view.SurfaceControl;
import androidx.annotation.BinderThread;
+import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.util.Preconditions;
import com.android.wm.shell.R;
+import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.DisplayLayout;
@@ -42,6 +48,8 @@
import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SingleInstanceRemoteListener;
+import com.android.wm.shell.common.TaskStackListenerCallback;
+import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.common.pip.IPip;
import com.android.wm.shell.common.pip.IPipAnimationListener;
import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
@@ -57,8 +65,11 @@
* Manages the picture-in-picture (PIP) UI and states for Phones.
*/
public class PipController implements ConfigurationChangeListener,
+ PipTransitionState.PipTransitionStateChangedListener,
DisplayController.OnDisplaysChangedListener, RemoteCallable<PipController> {
private static final String TAG = PipController.class.getSimpleName();
+ private static final String SWIPE_TO_PIP_APP_BOUNDS = "pip_app_bounds";
+ private static final String SWIPE_TO_PIP_OVERLAY = "swipe_to_pip_overlay";
private final Context mContext;
private final ShellController mShellController;
@@ -68,6 +79,9 @@
private final PipBoundsAlgorithm mPipBoundsAlgorithm;
private final PipDisplayLayoutState mPipDisplayLayoutState;
private final PipScheduler mPipScheduler;
+ private final TaskStackListenerImpl mTaskStackListener;
+ private final ShellTaskOrganizer mShellTaskOrganizer;
+ private final PipTransitionState mPipTransitionState;
private final ShellExecutor mMainExecutor;
// Wrapper for making Binder calls into PiP animation listener hosted in launcher's Recents.
@@ -104,6 +118,9 @@
PipBoundsAlgorithm pipBoundsAlgorithm,
PipDisplayLayoutState pipDisplayLayoutState,
PipScheduler pipScheduler,
+ TaskStackListenerImpl taskStackListener,
+ ShellTaskOrganizer shellTaskOrganizer,
+ PipTransitionState pipTransitionState,
ShellExecutor mainExecutor) {
mContext = context;
mShellController = shellController;
@@ -113,6 +130,10 @@
mPipBoundsAlgorithm = pipBoundsAlgorithm;
mPipDisplayLayoutState = pipDisplayLayoutState;
mPipScheduler = pipScheduler;
+ mTaskStackListener = taskStackListener;
+ mShellTaskOrganizer = shellTaskOrganizer;
+ mPipTransitionState = pipTransitionState;
+ mPipTransitionState.addPipTransitionStateChangedListener(this);
mMainExecutor = mainExecutor;
if (PipUtils.isPip2ExperimentEnabled()) {
@@ -132,6 +153,9 @@
PipBoundsAlgorithm pipBoundsAlgorithm,
PipDisplayLayoutState pipDisplayLayoutState,
PipScheduler pipScheduler,
+ TaskStackListenerImpl taskStackListener,
+ ShellTaskOrganizer shellTaskOrganizer,
+ PipTransitionState pipTransitionState,
ShellExecutor mainExecutor) {
if (!context.getPackageManager().hasSystemFeature(FEATURE_PICTURE_IN_PICTURE)) {
ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
@@ -140,7 +164,8 @@
}
return new PipController(context, shellInit, shellController, displayController,
displayInsetsController, pipBoundsState, pipBoundsAlgorithm, pipDisplayLayoutState,
- pipScheduler, mainExecutor);
+ pipScheduler, taskStackListener, shellTaskOrganizer, pipTransitionState,
+ mainExecutor);
}
private void onInit() {
@@ -164,6 +189,17 @@
mShellController.addExternalInterface(KEY_EXTRA_SHELL_PIP,
this::createExternalInterface, this);
mShellController.addConfigurationChangeListener(this);
+
+ mTaskStackListener.addListener(new TaskStackListenerCallback() {
+ @Override
+ public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task,
+ boolean homeTaskVisible, boolean clearedTask, boolean wasVisible) {
+ if (task.getWindowingMode() != WINDOWING_MODE_PINNED) {
+ return;
+ }
+ mPipScheduler.scheduleExitPipViaExpand();
+ }
+ });
}
private ExternalInterfaceBinder createExternalInterface() {
@@ -245,11 +281,46 @@
Rect destinationBounds, SurfaceControl overlay, Rect appBounds) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"onSwipePipToHomeAnimationStart: %s", componentName);
- mPipScheduler.onSwipePipToHomeAnimationStart(taskId, componentName, destinationBounds,
- overlay, appBounds);
+ Bundle extra = new Bundle();
+ extra.putParcelable(SWIPE_TO_PIP_OVERLAY, overlay);
+ extra.putParcelable(SWIPE_TO_PIP_APP_BOUNDS, appBounds);
+ mPipTransitionState.setState(PipTransitionState.SWIPING_TO_PIP, extra);
+ if (overlay != null) {
+ // Shell transitions might use a root animation leash, which will be removed when
+ // the Recents transition is finished. Launcher attaches the overlay leash to this
+ // animation target leash; thus, we need to reparent it to the actual Task surface now.
+ // PipTransition is responsible to fade it out and cleanup when finishing the enter PIP
+ // transition.
+ SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
+ mShellTaskOrganizer.reparentChildSurfaceToTask(taskId, overlay, tx);
+ tx.setLayer(overlay, Integer.MAX_VALUE);
+ tx.apply();
+ }
mPipRecentsAnimationListener.onPipAnimationStarted();
}
+ @Override
+ public void onPipTransitionStateChanged(@PipTransitionState.TransitionState int oldState,
+ @PipTransitionState.TransitionState int newState, @Nullable Bundle extra) {
+ if (newState == PipTransitionState.SWIPING_TO_PIP) {
+ Preconditions.checkState(extra != null,
+ "No extra bundle for " + mPipTransitionState);
+
+ SurfaceControl overlay = extra.getParcelable(
+ SWIPE_TO_PIP_OVERLAY, SurfaceControl.class);
+ Rect appBounds = extra.getParcelable(
+ SWIPE_TO_PIP_APP_BOUNDS, Rect.class);
+
+ Preconditions.checkState(appBounds != null,
+ "App bounds can't be null for " + mPipTransitionState);
+ mPipTransitionState.setSwipePipToHomeState(overlay, appBounds);
+ } else if (newState == PipTransitionState.ENTERED_PIP) {
+ if (mPipTransitionState.isInSwipePipToHomeTransition()) {
+ mPipTransitionState.resetSwipePipToHomeState();
+ }
+ }
+ }
+
//
// IPipAnimationListener Binder proxy helpers
//
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 b4ca7df..72fa3ba 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
@@ -21,21 +21,16 @@
import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP;
import android.content.BroadcastReceiver;
-import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.Rect;
-import android.view.SurfaceControl;
-import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
import androidx.annotation.IntDef;
-import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
-import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.pip.PipBoundsState;
import com.android.wm.shell.common.pip.PipUtils;
@@ -43,7 +38,6 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-import java.util.function.Consumer;
/**
* Scheduler for Shell initiated PiP transitions and animations.
@@ -55,31 +49,10 @@
private final Context mContext;
private final PipBoundsState mPipBoundsState;
private final ShellExecutor mMainExecutor;
- private final ShellTaskOrganizer mShellTaskOrganizer;
+ private final PipTransitionState mPipTransitionState;
private PipSchedulerReceiver mSchedulerReceiver;
private PipTransitionController mPipTransitionController;
- // pinned PiP task's WC token
- @Nullable
- private WindowContainerToken mPipTaskToken;
-
- // pinned PiP task's leash
- @Nullable
- private SurfaceControl mPinnedTaskLeash;
-
- // true if Launcher has started swipe PiP to home animation
- private boolean mInSwipePipToHomeTransition;
-
- // Overlay leash potentially used during swipe PiP to home transition;
- // if null while mInSwipePipToHomeTransition is true, then srcRectHint was invalid.
- @Nullable
- SurfaceControl mSwipePipToHomeOverlay;
-
- // App bounds used when as a starting point to swipe PiP to home animation in Launcher;
- // these are also used to calculate the app icon overlay buffer size.
- @NonNull
- final Rect mSwipePipToHomeAppBounds = new Rect();
-
/**
* Temporary PiP CUJ codes to schedule PiP related transitions directly from Shell.
* This is used for a broadcast receiver to resolve intents. This should be removed once
@@ -118,11 +91,11 @@
public PipScheduler(Context context,
PipBoundsState pipBoundsState,
ShellExecutor mainExecutor,
- ShellTaskOrganizer shellTaskOrganizer) {
+ PipTransitionState pipTransitionState) {
mContext = context;
mPipBoundsState = pipBoundsState;
mMainExecutor = mainExecutor;
- mShellTaskOrganizer = shellTaskOrganizer;
+ mPipTransitionState = pipTransitionState;
if (PipUtils.isPip2ExperimentEnabled()) {
// temporary broadcast receiver to initiate exit PiP via expand
@@ -140,25 +113,17 @@
mPipTransitionController = pipTransitionController;
}
- void setPinnedTaskLeash(SurfaceControl pinnedTaskLeash) {
- mPinnedTaskLeash = pinnedTaskLeash;
- }
-
- void setPipTaskToken(@Nullable WindowContainerToken pipTaskToken) {
- mPipTaskToken = pipTaskToken;
- }
-
@Nullable
private WindowContainerTransaction getExitPipViaExpandTransaction() {
- if (mPipTaskToken == null) {
+ if (mPipTransitionState.mPipTaskToken == null) {
return null;
}
WindowContainerTransaction wct = new WindowContainerTransaction();
// final expanded bounds to be inherited from the parent
- wct.setBounds(mPipTaskToken, null);
+ wct.setBounds(mPipTransitionState.mPipTaskToken, null);
// if we are hitting a multi-activity case
// windowing mode change will reparent to original host task
- wct.setWindowingMode(mPipTaskToken, WINDOWING_MODE_UNDEFINED);
+ wct.setWindowingMode(mPipTransitionState.mPipTaskToken, WINDOWING_MODE_UNDEFINED);
return wct;
}
@@ -183,43 +148,12 @@
/**
* Animates resizing of the pinned stack given the duration.
*/
- public void scheduleAnimateResizePip(Rect toBounds, Consumer<Rect> onFinishResizeCallback) {
- if (mPipTaskToken == null) {
+ public void scheduleAnimateResizePip(Rect toBounds) {
+ if (mPipTransitionState.mPipTaskToken == null || !mPipTransitionState.isInPip()) {
return;
}
WindowContainerTransaction wct = new WindowContainerTransaction();
- wct.setBounds(mPipTaskToken, toBounds);
- mPipTransitionController.startResizeTransition(wct, onFinishResizeCallback);
- }
-
- void onSwipePipToHomeAnimationStart(int taskId, ComponentName componentName,
- Rect destinationBounds, SurfaceControl overlay, Rect appBounds) {
- mInSwipePipToHomeTransition = true;
- mSwipePipToHomeOverlay = overlay;
- mSwipePipToHomeAppBounds.set(appBounds);
- if (overlay != null) {
- // Shell transitions might use a root animation leash, which will be removed when
- // the Recents transition is finished. Launcher attaches the overlay leash to this
- // animation target leash; thus, we need to reparent it to the actual Task surface now.
- // PipTransition is responsible to fade it out and cleanup when finishing the enter PIP
- // transition.
- SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
- mShellTaskOrganizer.reparentChildSurfaceToTask(taskId, overlay, tx);
- tx.setLayer(overlay, Integer.MAX_VALUE);
- tx.apply();
- }
- }
-
- void setInSwipePipToHomeTransition(boolean inSwipePipToHome) {
- mInSwipePipToHomeTransition = inSwipePipToHome;
- }
-
- boolean isInSwipePipToHomeTransition() {
- return mInSwipePipToHomeTransition;
- }
-
- void onExitPip() {
- mPipTaskToken = null;
- mPinnedTaskLeash = null;
+ wct.setBounds(mPipTransitionState.mPipTaskToken, toBounds);
+ mPipTransitionController.startResizeTransition(wct);
}
}
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 e829d4e..12dce5b 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
@@ -34,6 +34,7 @@
import android.app.PictureInPictureParams;
import android.content.Context;
import android.graphics.Rect;
+import android.os.Bundle;
import android.os.IBinder;
import android.view.SurfaceControl;
import android.window.TransitionInfo;
@@ -43,6 +44,7 @@
import androidx.annotation.Nullable;
+import com.android.internal.util.Preconditions;
import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
@@ -54,23 +56,33 @@
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
-import java.util.function.Consumer;
-
/**
* Implementation of transitions for PiP on phone.
*/
-public class PipTransition extends PipTransitionController {
+public class PipTransition extends PipTransitionController implements
+ PipTransitionState.PipTransitionStateChangedListener {
private static final String TAG = PipTransition.class.getSimpleName();
+ private static final String PIP_TASK_TOKEN = "pip_task_token";
+ private static final String PIP_TASK_LEASH = "pip_task_leash";
+
/**
* The fixed start delay in ms when fading out the content overlay from bounds animation.
* The fadeout animation is guaranteed to start after the client has drawn under the new config.
*/
private static final int CONTENT_OVERLAY_FADE_OUT_DELAY_MS = 400;
+ //
+ // Dependencies
+ //
+
private final Context mContext;
private final PipScheduler mPipScheduler;
- @Nullable
- private WindowContainerToken mPipTaskToken;
+ private final PipTransitionState mPipTransitionState;
+
+ //
+ // Transition tokens
+ //
+
@Nullable
private IBinder mEnterTransition;
@Nullable
@@ -78,7 +90,14 @@
@Nullable
private IBinder mResizeTransition;
- private Consumer<Rect> mFinishResizeCallback;
+ //
+ // Internal state and relevant cached info
+ //
+
+ @Nullable
+ private WindowContainerToken mPipTaskToken;
+ @Nullable
+ private SurfaceControl mPipLeash;
public PipTransition(
Context context,
@@ -88,13 +107,16 @@
PipBoundsState pipBoundsState,
PipMenuController pipMenuController,
PipBoundsAlgorithm pipBoundsAlgorithm,
- PipScheduler pipScheduler) {
+ PipScheduler pipScheduler,
+ PipTransitionState pipTransitionState) {
super(shellInit, shellTaskOrganizer, transitions, pipBoundsState, pipMenuController,
pipBoundsAlgorithm);
mContext = context;
mPipScheduler = pipScheduler;
mPipScheduler.setPipTransitionController(this);
+ mPipTransitionState = pipTransitionState;
+ mPipTransitionState.addPipTransitionStateChangedListener(this);
}
@Override
@@ -104,6 +126,10 @@
}
}
+ //
+ // Transition collection stage lifecycle hooks
+ //
+
@Override
public void startExitTransition(int type, WindowContainerTransaction out,
@Nullable Rect destinationBounds) {
@@ -117,13 +143,11 @@
}
@Override
- public void startResizeTransition(WindowContainerTransaction wct,
- Consumer<Rect> onFinishResizeCallback) {
+ public void startResizeTransition(WindowContainerTransaction wct) {
if (wct == null) {
return;
}
mResizeTransition = mTransitions.startTransition(TRANSIT_RESIZE_PIP, wct, this);
- mFinishResizeCallback = onFinishResizeCallback;
}
@Nullable
@@ -146,6 +170,10 @@
}
}
+ //
+ // Transition playing stage lifecycle hooks
+ //
+
@Override
public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
@@ -163,7 +191,19 @@
@NonNull Transitions.TransitionFinishCallback finishCallback) {
if (transition == mEnterTransition || info.getType() == TRANSIT_PIP) {
mEnterTransition = null;
- if (mPipScheduler.isInSwipePipToHomeTransition()) {
+ // If we are in swipe PiP to Home transition we are ENTERING_PIP as a jumpcut transition
+ // is being carried out.
+ TransitionInfo.Change pipChange = getPipChange(info);
+
+ // If there is no PiP change, exit this transition handler and potentially try others.
+ if (pipChange == null) return false;
+
+ Bundle extra = new Bundle();
+ extra.putParcelable(PIP_TASK_TOKEN, pipChange.getContainer());
+ extra.putParcelable(PIP_TASK_LEASH, pipChange.getLeash());
+ mPipTransitionState.setState(PipTransitionState.ENTERING_PIP, extra);
+
+ if (mPipTransitionState.isInSwipePipToHomeTransition()) {
// If this is the second transition as a part of swipe PiP to home cuj,
// handle this transition as a special case with no-op animation.
return handleSwipePipToHomeTransition(info, startTransaction, finishTransaction,
@@ -179,9 +219,11 @@
finishCallback);
} else if (transition == mExitViaExpandTransition) {
mExitViaExpandTransition = null;
+ mPipTransitionState.setState(PipTransitionState.EXITING_PIP);
return startExpandAnimation(info, startTransaction, finishTransaction, finishCallback);
} else if (transition == mResizeTransition) {
mResizeTransition = null;
+ mPipTransitionState.setState(PipTransitionState.CHANGING_PIP_BOUNDS);
return startResizeAnimation(info, startTransaction, finishTransaction, finishCallback);
}
@@ -191,6 +233,10 @@
return false;
}
+ //
+ // Animation schedulers and entry points
+ //
+
private boolean startResizeAnimation(@NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction startTransaction,
@NonNull SurfaceControl.Transaction finishTransaction,
@@ -236,11 +282,7 @@
if (pipChange == null) {
return false;
}
- mPipScheduler.setInSwipePipToHomeTransition(false);
- mPipTaskToken = pipChange.getContainer();
-
- // cache the PiP task token and leash
- mPipScheduler.setPipTaskToken(mPipTaskToken);
+ WindowContainerToken pipTaskToken = pipChange.getContainer();
SurfaceControl pipLeash = pipChange.getLeash();
PictureInPictureParams params = pipChange.getTaskInfo().pictureInPictureParams;
@@ -264,9 +306,9 @@
} else {
final float scaleX = (float) destinationBounds.width() / startBounds.width();
final float scaleY = (float) destinationBounds.height() / startBounds.height();
- final int overlaySize = PipContentOverlay.PipAppIconOverlay
- .getOverlaySize(mPipScheduler.mSwipePipToHomeAppBounds, destinationBounds);
- SurfaceControl overlayLeash = mPipScheduler.mSwipePipToHomeOverlay;
+ final int overlaySize = PipContentOverlay.PipAppIconOverlay.getOverlaySize(
+ mPipTransitionState.getSwipePipToHomeAppBounds(), destinationBounds);
+ SurfaceControl overlayLeash = mPipTransitionState.getSwipePipToHomeOverlay();
startTransaction.setPosition(pipLeash, destinationBounds.left, destinationBounds.top)
.setScale(pipLeash, scaleX, scaleY)
@@ -274,7 +316,7 @@
.reparent(overlayLeash, pipLeash)
.setLayer(overlayLeash, Integer.MAX_VALUE);
- if (mPipTaskToken != null) {
+ if (pipTaskToken != null) {
SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
tx.addTransactionCommittedListener(mPipScheduler.getMainExecutor(),
this::onClientDrawAtTransitionEnd)
@@ -282,7 +324,7 @@
.setPosition(overlayLeash,
(destinationBounds.width() - overlaySize) / 2f,
(destinationBounds.height() - overlaySize) / 2f);
- finishWct.setBoundsChangeTransaction(mPipTaskToken, tx);
+ finishWct.setBoundsChangeTransaction(pipTaskToken, tx);
}
}
startTransaction.apply();
@@ -293,14 +335,6 @@
return true;
}
- private void onClientDrawAtTransitionEnd() {
- startOverlayFadeoutAnimation();
- }
-
- //
- // Subroutines setting up and starting transitions' animations.
- //
-
private void startOverlayFadeoutAnimation() {
ValueAnimator animator = ValueAnimator.ofFloat(1f, 0f);
animator.setDuration(CONTENT_OVERLAY_FADE_OUT_DELAY_MS);
@@ -309,15 +343,17 @@
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
- tx.remove(mPipScheduler.mSwipePipToHomeOverlay);
+ tx.remove(mPipTransitionState.getSwipePipToHomeOverlay());
tx.apply();
- mPipScheduler.mSwipePipToHomeOverlay = null;
+
+ // We have fully completed enter-PiP animation after the overlay is gone.
+ mPipTransitionState.setState(PipTransitionState.ENTERED_PIP);
}
});
animator.addUpdateListener(animation -> {
float alpha = (float) animation.getAnimatedValue();
SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
- tx.setAlpha(mPipScheduler.mSwipePipToHomeOverlay, alpha).apply();
+ tx.setAlpha(mPipTransitionState.getSwipePipToHomeOverlay(), alpha).apply();
});
animator.start();
}
@@ -330,10 +366,8 @@
if (pipChange == null) {
return false;
}
- mPipTaskToken = pipChange.getContainer();
-
// cache the PiP task token and leash
- mPipScheduler.setPipTaskToken(mPipTaskToken);
+ WindowContainerToken pipTaskToken = pipChange.getContainer();
startTransaction.apply();
// TODO: b/275910498 Use a new implementation of the PiP animator here.
@@ -349,10 +383,8 @@
if (pipChange == null) {
return false;
}
- mPipTaskToken = pipChange.getContainer();
-
// cache the PiP task token and leash
- mPipScheduler.setPipTaskToken(mPipTaskToken);
+ WindowContainerToken pipTaskToken = pipChange.getContainer();
startTransaction.apply();
finishCallback.onTransitionFinished(null);
@@ -366,7 +398,7 @@
startTransaction.apply();
// TODO: b/275910498 Use a new implementation of the PiP animator here.
finishCallback.onTransitionFinished(null);
- onExitPip();
+ mPipTransitionState.setState(PipTransitionState.EXITED_PIP);
return true;
}
@@ -376,12 +408,20 @@
@NonNull Transitions.TransitionFinishCallback finishCallback) {
startTransaction.apply();
finishCallback.onTransitionFinished(null);
- onExitPip();
+ mPipTransitionState.setState(PipTransitionState.EXITED_PIP);
return true;
}
+ /**
+ * TODO: b/275910498 Use a new implementation of the PiP animator here.
+ */
+ private void startResizeAnimation(SurfaceControl leash, Rect startBounds,
+ Rect endBounds, int duration) {
+ mPipTransitionState.setState(PipTransitionState.CHANGED_PIP_BOUNDS);
+ }
+
//
- // Utility methods for checking PiP-related transition info and requests.
+ // Various helpers to resolve transition requests and infos
//
@Nullable
@@ -442,11 +482,11 @@
}
private boolean isRemovePipTransition(@NonNull TransitionInfo info) {
- if (mPipTaskToken == null) {
+ if (mPipTransitionState.mPipTaskToken == null) {
// PiP removal makes sense if enter-PiP has cached a valid pinned task token.
return false;
}
- TransitionInfo.Change pipChange = info.getChange(mPipTaskToken);
+ TransitionInfo.Change pipChange = info.getChange(mPipTransitionState.mPipTaskToken);
if (pipChange == null) {
// Search for the PiP change by token since the windowing mode might be FULLSCREEN now.
return false;
@@ -460,14 +500,43 @@
return isPipMovedToBack || isPipClosed;
}
- /**
- * TODO: b/275910498 Use a new implementation of the PiP animator here.
- */
- private void startResizeAnimation(SurfaceControl leash, Rect startBounds,
- Rect endBounds, int duration) {}
+ //
+ // Miscellaneous callbacks and listeners
+ //
- private void onExitPip() {
- mPipTaskToken = null;
- mPipScheduler.onExitPip();
+ private void onClientDrawAtTransitionEnd() {
+ if (mPipTransitionState.getSwipePipToHomeOverlay() != null) {
+ startOverlayFadeoutAnimation();
+ } else if (mPipTransitionState.getState() == PipTransitionState.ENTERING_PIP) {
+ // If we were entering PiP (i.e. playing the animation) with a valid srcRectHint,
+ // and then we get a signal on client finishing its draw after the transition
+ // has ended, then we have fully entered PiP.
+ mPipTransitionState.setState(PipTransitionState.ENTERED_PIP);
+ }
+ }
+
+ @Override
+ public void onPipTransitionStateChanged(@PipTransitionState.TransitionState int oldState,
+ @PipTransitionState.TransitionState int newState, @Nullable Bundle extra) {
+ switch (newState) {
+ case PipTransitionState.ENTERING_PIP:
+ Preconditions.checkState(extra != null,
+ "No extra bundle for " + mPipTransitionState);
+
+ mPipTransitionState.mPipTaskToken = extra.getParcelable(
+ PIP_TASK_TOKEN, WindowContainerToken.class);
+ mPipTransitionState.mPinnedTaskLeash = extra.getParcelable(
+ PIP_TASK_LEASH, SurfaceControl.class);
+ boolean hasValidTokenAndLeash = mPipTransitionState.mPipTaskToken != null
+ && mPipTransitionState.mPinnedTaskLeash != null;
+
+ Preconditions.checkState(hasValidTokenAndLeash,
+ "Unexpected bundle for " + mPipTransitionState);
+ break;
+ case PipTransitionState.EXITED_PIP:
+ mPipTransitionState.mPipTaskToken = null;
+ mPipTransitionState.mPinnedTaskLeash = null;
+ break;
+ }
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java
new file mode 100644
index 0000000..f7bc622
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java
@@ -0,0 +1,275 @@
+/*
+ * 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.pip2.phone;
+
+import android.annotation.IntDef;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.view.SurfaceControl;
+import android.window.WindowContainerToken;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.internal.util.Preconditions;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Contains the state relevant to carry out or probe the status of PiP transitions.
+ *
+ * <p>Existing and new PiP components can subscribe to PiP transition related state changes
+ * via <code>PipTransitionStateChangedListener</code>.</p>
+ *
+ * <p><code>PipTransitionState</code> users shouldn't rely on listener execution ordering.
+ * For example, if a class <code>Foo</code> wants to change some arbitrary state A that belongs
+ * to some other class <code>Bar</code>, a special care must be given when manipulating state A in
+ * <code>Foo#onPipTransitionStateChanged()</code>, since that's the responsibility of
+ * the class <code>Bar</code>.</p>
+ *
+ * <p>Hence, the recommended usage for classes who want to subscribe to
+ * <code>PipTransitionState</code> changes is to manipulate only their own internal state or
+ * <code>PipTransitionState</code> state.</p>
+ *
+ * <p>If there is some state that must be manipulated in another class <code>Bar</code>, it should
+ * just be moved to <code>PipTransitionState</code> and become a shared state
+ * between Foo and Bar.</p>
+ *
+ * <p>Moreover, <code>onPipTransitionStateChanged(oldState, newState, extra)</code>
+ * receives a <code>Bundle</code> extra object that can be optionally set via
+ * <code>setState(state, extra)</code>. This can be used to resolve extra information to update
+ * relevant internal or <code>PipTransitionState</code> state. However, each listener
+ * needs to check for whether the extra passed is correct for a particular state,
+ * and throw an <code>IllegalStateException</code> otherwise.</p>
+ */
+public class PipTransitionState {
+ public static final int UNDEFINED = 0;
+
+ // State for Launcher animating the swipe PiP to home animation.
+ public static final int SWIPING_TO_PIP = 1;
+
+ // State for Shell animating enter PiP or jump-cutting to PiP mode after Launcher animation.
+ public static final int ENTERING_PIP = 2;
+
+ // State for app finishing drawing in PiP mode as a final step in enter PiP flow.
+ public static final int ENTERED_PIP = 3;
+
+ // State for scheduling a transition to change PiP bounds.
+ public static final int CHANGING_PIP_BOUNDS = 4;
+
+ // State for app potentially finishing drawing in new PiP bounds after resize is complete.
+ public static final int CHANGED_PIP_BOUNDS = 5;
+
+ // State for starting exiting PiP.
+ public static final int EXITING_PIP = 6;
+
+ // State for finishing exit PiP flow.
+ public static final int EXITED_PIP = 7;
+
+ private static final int FIRST_CUSTOM_STATE = 1000;
+
+ private int mPrevCustomState = FIRST_CUSTOM_STATE;
+
+ @IntDef(prefix = { "TRANSITION_STATE_" }, value = {
+ UNDEFINED,
+ SWIPING_TO_PIP,
+ ENTERING_PIP,
+ ENTERED_PIP,
+ CHANGING_PIP_BOUNDS,
+ CHANGED_PIP_BOUNDS,
+ EXITING_PIP,
+ EXITED_PIP,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface TransitionState {}
+
+ @TransitionState
+ private int mState;
+
+ //
+ // Swipe up to enter PiP related state
+ //
+
+ // true if Launcher has started swipe PiP to home animation
+ private boolean mInSwipePipToHomeTransition;
+
+ // App bounds used when as a starting point to swipe PiP to home animation in Launcher;
+ // these are also used to calculate the app icon overlay buffer size.
+ @NonNull
+ private final Rect mSwipePipToHomeAppBounds = new Rect();
+
+ //
+ // Tokens and leashes
+ //
+
+ // pinned PiP task's WC token
+ @Nullable
+ WindowContainerToken mPipTaskToken;
+
+ // pinned PiP task's leash
+ @Nullable
+ SurfaceControl mPinnedTaskLeash;
+
+ // Overlay leash potentially used during swipe PiP to home transition;
+ // if null while mInSwipePipToHomeTransition is true, then srcRectHint was invalid.
+ @Nullable
+ private SurfaceControl mSwipePipToHomeOverlay;
+
+ /**
+ * An interface to track state updates as we progress through PiP transitions.
+ */
+ public interface PipTransitionStateChangedListener {
+
+ /** Reports changes in PiP transition state. */
+ void onPipTransitionStateChanged(@TransitionState int oldState,
+ @TransitionState int newState, @Nullable Bundle extra);
+ }
+
+ private final List<PipTransitionStateChangedListener> mCallbacks = new ArrayList<>();
+
+ /**
+ * @return the state of PiP in the context of transitions.
+ */
+ @TransitionState
+ public int getState() {
+ return mState;
+ }
+
+ /**
+ * Sets the state of PiP in the context of transitions.
+ */
+ public void setState(@TransitionState int state) {
+ setState(state, null /* extra */);
+ }
+
+ /**
+ * Sets the state of PiP in the context of transitions
+ *
+ * @param extra a bundle passed to the subscribed listeners to resolve/cache extra info.
+ */
+ public void setState(@TransitionState int state, @Nullable Bundle extra) {
+ if (state == ENTERING_PIP || state == SWIPING_TO_PIP) {
+ // Whenever we are entering PiP caller must provide extra state to set as well.
+ Preconditions.checkArgument(extra != null && !extra.isEmpty(),
+ "No extra bundle for either ENTERING_PIP or SWIPING_TO_PIP state.");
+ }
+ if (mState != state) {
+ dispatchPipTransitionStateChanged(mState, state, extra);
+ mState = state;
+ }
+ }
+
+ private void dispatchPipTransitionStateChanged(@TransitionState int oldState,
+ @TransitionState int newState, @Nullable Bundle extra) {
+ mCallbacks.forEach(l -> l.onPipTransitionStateChanged(oldState, newState, extra));
+ }
+
+ /**
+ * Adds a {@link PipTransitionStateChangedListener} for future PiP transition state updates.
+ */
+ public void addPipTransitionStateChangedListener(PipTransitionStateChangedListener listener) {
+ if (mCallbacks.contains(listener)) {
+ return;
+ }
+ mCallbacks.add(listener);
+ }
+
+ /**
+ * @return true if provided {@link PipTransitionStateChangedListener}
+ * is registered before removing it.
+ */
+ public boolean removePipTransitionStateChangedListener(
+ PipTransitionStateChangedListener listener) {
+ return mCallbacks.remove(listener);
+ }
+
+ /**
+ * @return true if we have fully entered PiP.
+ */
+ public boolean isInPip() {
+ return mState > ENTERING_PIP && mState < EXITING_PIP;
+ }
+
+ void setSwipePipToHomeState(@Nullable SurfaceControl overlayLeash,
+ @NonNull Rect appBounds) {
+ mInSwipePipToHomeTransition = true;
+ if (overlayLeash != null && !appBounds.isEmpty()) {
+ mSwipePipToHomeOverlay = overlayLeash;
+ mSwipePipToHomeAppBounds.set(appBounds);
+ }
+ }
+
+ void resetSwipePipToHomeState() {
+ mInSwipePipToHomeTransition = false;
+ mSwipePipToHomeOverlay = null;
+ mSwipePipToHomeAppBounds.setEmpty();
+ }
+
+ /**
+ * @return true if in swipe PiP to home. Note that this is true until overlay fades if used too.
+ */
+ public boolean isInSwipePipToHomeTransition() {
+ return mInSwipePipToHomeTransition;
+ }
+
+ /**
+ * @return the overlay used during swipe PiP to home for invalid srcRectHints in auto-enter PiP;
+ * null if srcRectHint provided is valid.
+ */
+ @Nullable
+ public SurfaceControl getSwipePipToHomeOverlay() {
+ return mSwipePipToHomeOverlay;
+ }
+
+ /**
+ * @return app bounds used to calculate
+ */
+ @NonNull
+ public Rect getSwipePipToHomeAppBounds() {
+ return mSwipePipToHomeAppBounds;
+ }
+
+ /**
+ * @return a custom state solely for internal use by the caller.
+ */
+ @TransitionState
+ public int getCustomState() {
+ return ++mPrevCustomState;
+ }
+
+ private String stateToString() {
+ switch (mState) {
+ case UNDEFINED: return "undefined";
+ case ENTERING_PIP: return "entering-pip";
+ case ENTERED_PIP: return "entered-pip";
+ case CHANGING_PIP_BOUNDS: return "changing-bounds";
+ case CHANGED_PIP_BOUNDS: return "changed-bounds";
+ case EXITING_PIP: return "exiting-pip";
+ case EXITED_PIP: return "exited-pip";
+ }
+ throw new IllegalStateException("Unknown state: " + mState);
+ }
+
+ @Override
+ public String toString() {
+ return String.format("PipTransitionState(mState=%s, mInSwipePipToHomeTransition=%b)",
+ stateToString(), mInSwipePipToHomeTransition);
+ }
+}
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 e7d9812..c5f23a8 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
@@ -373,6 +373,10 @@
if (DesktopModeStatus.isEnabled() && mDesktopModeTaskRepository.isPresent()
&& mDesktopModeTaskRepository.get().isActiveTask(taskInfo.taskId)) {
+ if (mDesktopModeTaskRepository.get().isMinimizedTask(taskInfo.taskId)) {
+ // Minimized freeform tasks should not be shown at all.
+ continue;
+ }
// Freeform tasks will be added as a separate entry
if (mostRecentFreeformTaskIndex == Integer.MAX_VALUE) {
mostRecentFreeformTaskIndex = recentTasks.size();
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 4c68106..5e9451a 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
@@ -201,7 +201,7 @@
private final DisplayImeController mDisplayImeController;
private final DisplayInsetsController mDisplayInsetsController;
private final TransactionPool mTransactionPool;
- private final SplitScreenTransitions mSplitTransitions;
+ private SplitScreenTransitions mSplitTransitions;
private final SplitscreenEventLogger mLogger;
private final ShellExecutor mMainExecutor;
// Cache live tile tasks while entering recents, evict them from stages in finish transaction
@@ -399,6 +399,11 @@
return mSplitTransitions;
}
+ @VisibleForTesting
+ void setSplitTransitions(SplitScreenTransitions splitScreenTransitions) {
+ mSplitTransitions = splitScreenTransitions;
+ }
+
public boolean isSplitScreenVisible() {
return mSideStageListener.mVisible && mMainStageListener.mVisible;
}
@@ -583,7 +588,7 @@
options = resolveStartStage(STAGE_TYPE_UNDEFINED, position, options, null /* wct */);
wct.startTask(taskId, options);
// If this should be mixed, send the task to avoid split handle transition directly.
- if (mMixedHandler != null && mMixedHandler.shouldSplitEnterMixed(taskId, mTaskOrganizer)) {
+ if (mMixedHandler != null && mMixedHandler.isTaskInPip(taskId, mTaskOrganizer)) {
mTaskOrganizer.applyTransaction(wct);
return;
}
@@ -622,7 +627,7 @@
wct.sendPendingIntent(intent, fillInIntent, options);
// If this should be mixed, just send the intent to avoid split handle transition directly.
- if (mMixedHandler != null && mMixedHandler.shouldSplitEnterMixed(intent)) {
+ if (mMixedHandler != null && mMixedHandler.isIntentInPip(intent)) {
mTaskOrganizer.applyTransaction(wct);
return;
}
@@ -711,16 +716,7 @@
taskId1, taskId2, splitPosition, snapPosition);
final WindowContainerTransaction wct = new WindowContainerTransaction();
if (taskId2 == INVALID_TASK_ID) {
- if (mMainStage.containsTask(taskId1) || mSideStage.containsTask(taskId1)) {
- prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, wct);
- }
- if (mRecentTasks.isPresent()) {
- mRecentTasks.get().removeSplitPair(taskId1);
- }
- options1 = options1 != null ? options1 : new Bundle();
- addActivityOptions(options1, null);
- wct.startTask(taskId1, options1);
- mSplitTransitions.startFullscreenTransition(wct, remoteTransition);
+ startSingleTask(taskId1, options1, wct, remoteTransition);
return;
}
@@ -741,11 +737,15 @@
"startIntentAndTask: intent=%s task1=%d position=%d snapPosition=%d",
pendingIntent.getIntent(), taskId, splitPosition, snapPosition);
final WindowContainerTransaction wct = new WindowContainerTransaction();
- if (taskId == INVALID_TASK_ID) {
- options1 = options1 != null ? options1 : new Bundle();
- addActivityOptions(options1, null);
- wct.sendPendingIntent(pendingIntent, fillInIntent, options1);
- mSplitTransitions.startFullscreenTransition(wct, remoteTransition);
+ boolean firstIntentPipped = mMixedHandler.isIntentInPip(pendingIntent);
+ boolean secondTaskPipped = mMixedHandler.isTaskInPip(taskId, mTaskOrganizer);
+ if (taskId == INVALID_TASK_ID || secondTaskPipped) {
+ startSingleIntent(pendingIntent, fillInIntent, options1, wct, remoteTransition);
+ return;
+ }
+
+ if (firstIntentPipped) {
+ startSingleTask(taskId, options2, wct, remoteTransition);
return;
}
@@ -757,6 +757,24 @@
startWithTask(wct, taskId, options2, snapPosition, remoteTransition, instanceId);
}
+ /**
+ * @param taskId Starts this task in fullscreen, removing it from existing pairs if it was part
+ * of one.
+ */
+ private void startSingleTask(int taskId, Bundle options, WindowContainerTransaction wct,
+ RemoteTransition remoteTransition) {
+ if (mMainStage.containsTask(taskId) || mSideStage.containsTask(taskId)) {
+ prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, wct);
+ }
+ if (mRecentTasks.isPresent()) {
+ mRecentTasks.get().removeSplitPair(taskId);
+ }
+ options = options != null ? options : new Bundle();
+ addActivityOptions(options, null);
+ wct.startTask(taskId, options);
+ mSplitTransitions.startFullscreenTransition(wct, remoteTransition);
+ }
+
/** Starts a shortcut and a task to a split pair in one transition. */
void startShortcutAndTask(ShortcutInfo shortcutInfo, @Nullable Bundle options1,
int taskId, @Nullable Bundle options2, @SplitPosition int splitPosition,
@@ -844,6 +862,21 @@
return;
}
+ boolean handledForPipSplitLaunch = handlePippedSplitIntentsLaunch(
+ pendingIntent1,
+ pendingIntent2,
+ options1,
+ options2,
+ shortcutInfo1,
+ shortcutInfo2,
+ wct,
+ fillInIntent1,
+ fillInIntent2,
+ remoteTransition);
+ if (handledForPipSplitLaunch) {
+ return;
+ }
+
if (!mMainStage.isActive()) {
// Build a request WCT that will launch both apps such that task 0 is on the main stage
// while task 1 is on the side stage.
@@ -878,6 +911,46 @@
setEnterInstanceId(instanceId);
}
+ /**
+ * Checks if either of the apps in the desired split launch is currently in Pip. If so, it will
+ * launch the non-pipped app as a fullscreen app, otherwise no-op.
+ */
+ private boolean handlePippedSplitIntentsLaunch(PendingIntent pendingIntent1,
+ PendingIntent pendingIntent2, Bundle options1, Bundle options2,
+ ShortcutInfo shortcutInfo1, ShortcutInfo shortcutInfo2, WindowContainerTransaction wct,
+ Intent fillInIntent1, Intent fillInIntent2, RemoteTransition remoteTransition) {
+ // If one of the split apps to start is in Pip, only launch the non-pip app in fullscreen
+ boolean firstIntentPipped = mMixedHandler.isIntentInPip(pendingIntent1);
+ boolean secondIntentPipped = mMixedHandler.isIntentInPip(pendingIntent2);
+ if (firstIntentPipped || secondIntentPipped) {
+ Bundle options = secondIntentPipped ? options1 : options2;
+ options = options == null ? new Bundle() : options;
+ addActivityOptions(options, null);
+ if (shortcutInfo1 != null || shortcutInfo2 != null) {
+ ShortcutInfo infoToLaunch = secondIntentPipped ? shortcutInfo1 : shortcutInfo2;
+ wct.startShortcut(mContext.getPackageName(), infoToLaunch, options);
+ mSplitTransitions.startFullscreenTransition(wct, remoteTransition);
+ } else {
+ PendingIntent intentToLaunch = secondIntentPipped ? pendingIntent1 : pendingIntent2;
+ Intent fillInIntentToLaunch = secondIntentPipped ? fillInIntent1 : fillInIntent2;
+ startSingleIntent(intentToLaunch, fillInIntentToLaunch, options, wct,
+ remoteTransition);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ /** @param pendingIntent Starts this intent in fullscreen */
+ private void startSingleIntent(PendingIntent pendingIntent, Intent fillInIntent, Bundle options,
+ WindowContainerTransaction wct,
+ RemoteTransition remoteTransition) {
+ Bundle optionsToLaunch = options != null ? options : new Bundle();
+ addActivityOptions(optionsToLaunch, null);
+ wct.sendPendingIntent(pendingIntent, fillInIntent, optionsToLaunch);
+ mSplitTransitions.startFullscreenTransition(wct, remoteTransition);
+ }
+
/** Starts a pair of tasks using legacy transition. */
void startTasksWithLegacyTransition(int taskId1, @Nullable Bundle options1,
int taskId2, @Nullable Bundle options2, @SplitPosition int splitPosition,
@@ -3351,6 +3424,11 @@
true /* reparentLeafTaskIfRelaunch */);
}
+ /** Call this when the animation from split screen to desktop is started. */
+ public void onSplitToDesktop() {
+ setSplitsVisible(false);
+ }
+
/** Call this when the recents animation finishes by doing pair-to-pair switch. */
public void onRecentsPairToPairAnimationFinish(WindowContainerTransaction finishWct) {
ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onRecentsPairToPairAnimationFinish");
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
index 4bc0dc0..4d02ec2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
@@ -563,22 +563,23 @@
/** Use to when split use intent to enter, check if this enter transition should be mixed or
* not.*/
- public boolean shouldSplitEnterMixed(PendingIntent intent) {
+ public boolean isIntentInPip(PendingIntent intent) {
// Check if this intent package is same as pip one or not, if true we want let the pip
// task enter split.
if (mPipHandler != null) {
- return mPipHandler.isInPipPackage(SplitScreenUtils.getPackageName(intent.getIntent()));
+ return mPipHandler
+ .isPackageActiveInPip(SplitScreenUtils.getPackageName(intent.getIntent()));
}
return false;
}
/** Use to when split use taskId to enter, check if this enter transition should be mixed or
* not.*/
- public boolean shouldSplitEnterMixed(int taskId, ShellTaskOrganizer shellTaskOrganizer) {
+ public boolean isTaskInPip(int taskId, ShellTaskOrganizer shellTaskOrganizer) {
// Check if this intent package is same as pip one or not, if true we want let the pip
// task enter split.
if (mPipHandler != null) {
- return mPipHandler.isInPipPackage(
+ return mPipHandler.isPackageActiveInPip(
SplitScreenUtils.getPackageName(taskId, shellTaskOrganizer));
}
return false;
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 922c55f..01175f5 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
@@ -16,6 +16,7 @@
package com.android.wm.shell.windowdecor;
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
@@ -34,6 +35,7 @@
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
import static com.android.wm.shell.compatui.AppCompatUtils.isSingleTopActivityTranslucent;
import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR;
+import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
import android.annotation.NonNull;
import android.app.ActivityManager;
@@ -272,10 +274,9 @@
mSplitScreenController.registerSplitScreenListener(new SplitScreen.SplitScreenListener() {
@Override
public void onTaskStageChanged(int taskId, @StageType int stage, boolean visible) {
- if (visible) {
+ if (visible && stage != STAGE_TYPE_UNDEFINED) {
DesktopModeWindowDecoration decor = mWindowDecorByTaskId.get(taskId);
- if (decor != null && DesktopModeStatus.isEnabled()
- && decor.mTaskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
+ if (decor != null && DesktopModeStatus.isEnabled()) {
mDesktopTasksController.moveToSplit(decor.mTaskInfo);
}
}
@@ -915,6 +916,11 @@
@Nullable
private DesktopModeWindowDecoration getRelevantWindowDecor(MotionEvent ev) {
+ // If we are mid-transition, dragged task's decor is always relevant.
+ final int draggedTaskId = mDesktopTasksController.getDraggingTaskId();
+ if (draggedTaskId != INVALID_TASK_ID) {
+ return mWindowDecorByTaskId.get(draggedTaskId);
+ }
final DesktopModeWindowDecoration focusedDecor = getFocusedDecor();
if (focusedDecor == null) {
return null;
diff --git a/libs/WindowManager/Shell/tests/flicker/appcompat/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/flicker/appcompat/AndroidTestTemplate.xml
index 5b2ffec..4dd14f4 100644
--- a/libs/WindowManager/Shell/tests/flicker/appcompat/AndroidTestTemplate.xml
+++ b/libs/WindowManager/Shell/tests/flicker/appcompat/AndroidTestTemplate.xml
@@ -20,6 +20,8 @@
<option name="isolated-storage" value="false"/>
<target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
+ <!-- disable DeprecatedTargetSdk warning -->
+ <option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/>
<!-- keeps the screen on during tests -->
<option name="screen-always-on" value="on"/>
<!-- prevents the phone from restarting -->
diff --git a/libs/WindowManager/Shell/tests/flicker/bubble/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/flicker/bubble/AndroidTestTemplate.xml
index 9f7d9fc..5c86a38 100644
--- a/libs/WindowManager/Shell/tests/flicker/bubble/AndroidTestTemplate.xml
+++ b/libs/WindowManager/Shell/tests/flicker/bubble/AndroidTestTemplate.xml
@@ -20,6 +20,8 @@
<option name="isolated-storage" value="false"/>
<target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
+ <!-- disable DeprecatedTargetSdk warning -->
+ <option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/>
<!-- keeps the screen on during tests -->
<option name="screen-always-on" value="on"/>
<!-- prevents the phone from restarting -->
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/flicker/pip/AndroidTestTemplate.xml
index 882b200..aa70c09 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/AndroidTestTemplate.xml
+++ b/libs/WindowManager/Shell/tests/flicker/pip/AndroidTestTemplate.xml
@@ -20,6 +20,8 @@
<option name="isolated-storage" value="false"/>
<target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
+ <!-- disable DeprecatedTargetSdk warning -->
+ <option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/>
<!-- keeps the screen on during tests -->
<option name="screen-always-on" value="on"/>
<!-- prevents the phone from restarting -->
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/csuiteDefaultTemplate.xml b/libs/WindowManager/Shell/tests/flicker/pip/csuiteDefaultTemplate.xml
index f5a8655..bf040d2 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/csuiteDefaultTemplate.xml
+++ b/libs/WindowManager/Shell/tests/flicker/pip/csuiteDefaultTemplate.xml
@@ -20,6 +20,8 @@
<option name="isolated-storage" value="false"/>
<target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
+ <!-- disable DeprecatedTargetSdk warning -->
+ <option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/>
<!-- keeps the screen on during tests -->
<option name="screen-always-on" value="on"/>
<!-- prevents the phone from restarting -->
diff --git a/libs/WindowManager/Shell/tests/flicker/service/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/flicker/service/AndroidTestTemplate.xml
index 51a55e35..c7c804f 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/AndroidTestTemplate.xml
+++ b/libs/WindowManager/Shell/tests/flicker/service/AndroidTestTemplate.xml
@@ -20,6 +20,8 @@
<option name="isolated-storage" value="false"/>
<target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
+ <!-- disable DeprecatedTargetSdk warning -->
+ <option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/>
<!-- keeps the screen on during tests -->
<option name="screen-always-on" value="on"/>
<!-- prevents the phone from restarting -->
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/DesktopModeFlickerScenarios.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/DesktopModeFlickerScenarios.kt
index 75dfeba..17cace0 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/DesktopModeFlickerScenarios.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/DesktopModeFlickerScenarios.kt
@@ -23,6 +23,7 @@
import android.tools.flicker.assertors.assertions.AppWindowHasDesktopModeInitialBoundsAtTheEnd
import android.tools.flicker.assertors.assertions.AppWindowOnTopAtEnd
import android.tools.flicker.assertors.assertions.AppWindowOnTopAtStart
+import android.tools.flicker.assertors.assertions.AppWindowRemainInsideDisplayBounds
import android.tools.flicker.assertors.assertions.LauncherWindowMovesToTop
import android.tools.flicker.config.AssertionTemplates
import android.tools.flicker.config.FlickerConfigEntry
@@ -114,5 +115,30 @@
)
.associateBy({ it }, { AssertionInvocationGroup.BLOCKING }),
)
+
+ val CORNER_RESIZE =
+ FlickerConfigEntry(
+ scenarioId = ScenarioId("CORNER_RESIZE"),
+ extractor =
+ ShellTransitionScenarioExtractor(
+ transitionMatcher =
+ object : ITransitionMatcher {
+ override fun findAll(
+ transitions: Collection<Transition>
+ ): Collection<Transition> {
+ return transitions.filter {
+ it.type == TransitionType.CHANGE
+ }
+ }
+ }
+ ),
+ assertions =
+ AssertionTemplates.COMMON_ASSERTIONS +
+ listOf(
+ AppLayerIsVisibleAlways(Components.DESKTOP_MODE_APP),
+ AppWindowOnTopAtEnd(Components.DESKTOP_MODE_APP),
+ AppWindowRemainInsideDisplayBounds(Components.DESKTOP_MODE_APP),
+ ).associateBy({ it }, { AssertionInvocationGroup.BLOCKING }),
+ )
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/ResizeAppWithCornerResizeLandscape.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/ResizeAppWithCornerResizeLandscape.kt
new file mode 100644
index 0000000..8d1a530
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/ResizeAppWithCornerResizeLandscape.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.desktopmode.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.service.desktopmode.flicker.DesktopModeFlickerScenarios.Companion.CORNER_RESIZE
+import com.android.wm.shell.flicker.service.desktopmode.scenarios.ResizeAppWithCornerResize
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class ResizeAppWithCornerResizeLandscape : ResizeAppWithCornerResize(Rotation.ROTATION_90) {
+ @ExpectedScenarios(["CORNER_RESIZE"])
+ @Test
+ override fun resizeAppWithCornerResize() = super.resizeAppWithCornerResize()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT).use(CORNER_RESIZE)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/ResizeAppWithCornerResizePortrait.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/ResizeAppWithCornerResizePortrait.kt
new file mode 100644
index 0000000..2d81c8c
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/ResizeAppWithCornerResizePortrait.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.desktopmode.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.service.desktopmode.flicker.DesktopModeFlickerScenarios.Companion.CORNER_RESIZE
+import com.android.wm.shell.flicker.service.desktopmode.scenarios.ResizeAppWithCornerResize
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class ResizeAppWithCornerResizePortrait : ResizeAppWithCornerResize(Rotation.ROTATION_0) {
+ @ExpectedScenarios(["CORNER_RESIZE"])
+ @Test
+ override fun resizeAppWithCornerResize() = super.resizeAppWithCornerResize()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT).use(CORNER_RESIZE)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/ResizeAppWithCornerResize.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/ResizeAppWithCornerResize.kt
new file mode 100644
index 0000000..289ca9f
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/ResizeAppWithCornerResize.kt
@@ -0,0 +1,68 @@
+/*
+ * 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.service.desktopmode.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.SimpleAppHelper
+import com.android.window.flags.Flags
+import com.android.wm.shell.flicker.service.common.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 ResizeAppWithCornerResize
+@JvmOverloads
+constructor(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))
+
+ @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
+
+ @Before
+ fun setup() {
+ Assume.assumeTrue(Flags.enableDesktopWindowingMode())
+ tapl.setEnableRotation(true)
+ tapl.setExpectedRotation(rotation.value)
+ testApp.enterDesktopWithDrag(wmHelper, device)
+ }
+
+ @Test
+ open fun resizeAppWithCornerResize() {
+ testApp.cornerResize(wmHelper, device, DesktopModeAppHelper.Corners.RIGHT_TOP, 50, -50)
+ }
+
+ @After
+ fun teardown() {
+ testApp.exit(wmHelper)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/flicker/splitscreen/AndroidTestTemplate.xml
index 05f937a..214bdfa 100644
--- a/libs/WindowManager/Shell/tests/flicker/splitscreen/AndroidTestTemplate.xml
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/AndroidTestTemplate.xml
@@ -20,6 +20,8 @@
<option name="isolated-storage" value="false"/>
<target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
+ <!-- disable DeprecatedTargetSdk warning -->
+ <option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/>
<!-- keeps the screen on during tests -->
<option name="screen-always-on" value="on"/>
<!-- prevents the phone from restarting -->
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/PipTransitionStateTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/PipTransitionStateTest.java
new file mode 100644
index 0000000..bd8ac37
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/PipTransitionStateTest.java
@@ -0,0 +1,110 @@
+/*
+ * 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.pip2;
+
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.testing.AndroidTestingRunner;
+
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.common.pip.PhoneSizeSpecSource;
+import com.android.wm.shell.pip2.phone.PipTransitionState;
+
+import junit.framework.Assert;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Unit test against {@link PhoneSizeSpecSource}.
+ *
+ * This test mocks the PiP2 flag to be true.
+ */
+@RunWith(AndroidTestingRunner.class)
+public class PipTransitionStateTest extends ShellTestCase {
+ private static final String EXTRA_ENTRY_KEY = "extra_entry_key";
+ private PipTransitionState mPipTransitionState;
+ private PipTransitionState.PipTransitionStateChangedListener mStateChangedListener;
+ private Parcelable mEmptyParcelable;
+
+ @Before
+ public void setUp() {
+ mPipTransitionState = new PipTransitionState();
+ mPipTransitionState.setState(PipTransitionState.UNDEFINED);
+ mEmptyParcelable = new Bundle();
+ }
+
+ @Test
+ public void testEnteredState_withoutExtra() {
+ mStateChangedListener = (oldState, newState, extra) -> {
+ Assert.assertEquals(PipTransitionState.ENTERED_PIP, newState);
+ Assert.assertNull(extra);
+ };
+ mPipTransitionState.addPipTransitionStateChangedListener(mStateChangedListener);
+ mPipTransitionState.setState(PipTransitionState.ENTERED_PIP);
+ mPipTransitionState.removePipTransitionStateChangedListener(mStateChangedListener);
+ }
+
+ @Test
+ public void testEnteredState_withExtra() {
+ mStateChangedListener = (oldState, newState, extra) -> {
+ Assert.assertEquals(PipTransitionState.ENTERED_PIP, newState);
+ Assert.assertNotNull(extra);
+ Assert.assertEquals(mEmptyParcelable, extra.getParcelable(EXTRA_ENTRY_KEY));
+ };
+ Bundle extra = new Bundle();
+ extra.putParcelable(EXTRA_ENTRY_KEY, mEmptyParcelable);
+
+ mPipTransitionState.addPipTransitionStateChangedListener(mStateChangedListener);
+ mPipTransitionState.setState(PipTransitionState.ENTERED_PIP, extra);
+ mPipTransitionState.removePipTransitionStateChangedListener(mStateChangedListener);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testEnteringState_withoutExtra() {
+ mPipTransitionState.setState(PipTransitionState.ENTERING_PIP);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testSwipingToPipState_withoutExtra() {
+ mPipTransitionState.setState(PipTransitionState.SWIPING_TO_PIP);
+ }
+
+ @Test
+ public void testCustomState_withExtra_thenEntered_withoutExtra() {
+ final int customState = mPipTransitionState.getCustomState();
+ mStateChangedListener = (oldState, newState, extra) -> {
+ if (newState == customState) {
+ Assert.assertNotNull(extra);
+ Assert.assertEquals(mEmptyParcelable, extra.getParcelable(EXTRA_ENTRY_KEY));
+ return;
+ } else if (newState == PipTransitionState.ENTERED_PIP) {
+ Assert.assertNull(extra);
+ return;
+ }
+ Assert.fail("Neither custom not ENTERED_PIP state is received.");
+ };
+ Bundle extra = new Bundle();
+ extra.putParcelable(EXTRA_ENTRY_KEY, mEmptyParcelable);
+
+ mPipTransitionState.addPipTransitionStateChangedListener(mStateChangedListener);
+ mPipTransitionState.setState(customState, extra);
+ mPipTransitionState.setState(PipTransitionState.ENTERED_PIP);
+ mPipTransitionState.removePipTransitionStateChangedListener(mStateChangedListener);
+ }
+}
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 40b59c1..5cf9be4 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
@@ -395,6 +395,51 @@
}
@Test
+ public void testGetRecentTasks_proto2Enabled_ignoresMinimizedFreeformTasks() {
+ StaticMockitoSession mockitoSession = mockitoSession().mockStatic(
+ DesktopModeStatus.class).startMocking();
+ when(DesktopModeStatus.isEnabled()).thenReturn(true);
+
+ ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1);
+ ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2);
+ ActivityManager.RecentTaskInfo t3 = makeTaskInfo(3);
+ ActivityManager.RecentTaskInfo t4 = makeTaskInfo(4);
+ ActivityManager.RecentTaskInfo t5 = makeTaskInfo(5);
+ setRawList(t1, t2, t3, t4, t5);
+
+ when(mDesktopModeTaskRepository.isActiveTask(1)).thenReturn(true);
+ when(mDesktopModeTaskRepository.isActiveTask(3)).thenReturn(true);
+ when(mDesktopModeTaskRepository.isActiveTask(5)).thenReturn(true);
+ when(mDesktopModeTaskRepository.isMinimizedTask(3)).thenReturn(true);
+
+ ArrayList<GroupedRecentTaskInfo> recentTasks = mRecentTasksController.getRecentTasks(
+ MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0);
+
+ // 2 freeform tasks should be grouped into one, 1 task should be skipped, 3 total recents
+ // entries
+ assertEquals(3, recentTasks.size());
+ GroupedRecentTaskInfo freeformGroup = recentTasks.get(0);
+ GroupedRecentTaskInfo singleGroup1 = recentTasks.get(1);
+ GroupedRecentTaskInfo singleGroup2 = recentTasks.get(2);
+
+ // Check that groups have expected types
+ assertEquals(GroupedRecentTaskInfo.TYPE_FREEFORM, freeformGroup.getType());
+ assertEquals(GroupedRecentTaskInfo.TYPE_SINGLE, singleGroup1.getType());
+ assertEquals(GroupedRecentTaskInfo.TYPE_SINGLE, singleGroup2.getType());
+
+ // Check freeform group entries
+ assertEquals(2, freeformGroup.getTaskInfoList().size());
+ assertEquals(t1, freeformGroup.getTaskInfoList().get(0));
+ assertEquals(t5, freeformGroup.getTaskInfoList().get(1));
+
+ // Check single entries
+ assertEquals(t2, singleGroup1.getTaskInfo1());
+ assertEquals(t4, singleGroup2.getTaskInfo1());
+
+ mockitoSession.finishMocking();
+ }
+
+ @Test
public void testRemovedTaskRemovesSplit() {
ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1);
ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
index d819261..d7c3835 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
@@ -40,10 +40,12 @@
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.ActivityManager;
+import android.app.PendingIntent;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Bundle;
@@ -51,6 +53,7 @@
import android.os.Looper;
import android.view.SurfaceControl;
import android.view.SurfaceSession;
+import android.window.RemoteTransition;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
@@ -74,6 +77,7 @@
import com.android.wm.shell.splitscreen.SplitScreen.SplitScreenListener;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.transition.DefaultMixedHandler;
import com.android.wm.shell.transition.HomeTransitionObserver;
import com.android.wm.shell.transition.Transitions;
@@ -111,6 +115,8 @@
private TransactionPool mTransactionPool;
@Mock
private LaunchAdjacentController mLaunchAdjacentController;
+ @Mock
+ private DefaultMixedHandler mDefaultMixedHandler;
private final Rect mBounds1 = new Rect(10, 20, 30, 40);
private final Rect mBounds2 = new Rect(5, 10, 15, 20);
@@ -370,6 +376,96 @@
}
}
+ @Test
+ public void testSplitIntentAndTaskWithPippedApp_launchFullscreen() {
+ int taskId = 9;
+ SplitScreenTransitions splitScreenTransitions =
+ spy(mStageCoordinator.getSplitTransitions());
+ mStageCoordinator.setSplitTransitions(splitScreenTransitions);
+ mStageCoordinator.setMixedHandler(mDefaultMixedHandler);
+ PendingIntent pendingIntent = mock(PendingIntent.class);
+ RemoteTransition remoteTransition = mock(RemoteTransition.class);
+ when(remoteTransition.getDebugName()).thenReturn("");
+ // Test launching second task full screen
+ when(mDefaultMixedHandler.isIntentInPip(pendingIntent)).thenReturn(true);
+ mStageCoordinator.startIntentAndTask(
+ pendingIntent,
+ null /*fillInIntent*/,
+ null /*option1*/,
+ taskId,
+ null /*option2*/,
+ 0 /*splitPosition*/,
+ 1 /*snapPosition*/,
+ remoteTransition /*remoteTransition*/,
+ null /*instanceId*/);
+ verify(splitScreenTransitions, times(1))
+ .startFullscreenTransition(any(), any());
+
+ // Test launching first intent fullscreen
+ when(mDefaultMixedHandler.isIntentInPip(pendingIntent)).thenReturn(false);
+ when(mDefaultMixedHandler.isTaskInPip(taskId, mTaskOrganizer)).thenReturn(true);
+ mStageCoordinator.startIntentAndTask(
+ pendingIntent,
+ null /*fillInIntent*/,
+ null /*option1*/,
+ taskId,
+ null /*option2*/,
+ 0 /*splitPosition*/,
+ 1 /*snapPosition*/,
+ remoteTransition /*remoteTransition*/,
+ null /*instanceId*/);
+ verify(splitScreenTransitions, times(2))
+ .startFullscreenTransition(any(), any());
+ }
+
+ @Test
+ public void testSplitIntentsWithPippedApp_launchFullscreen() {
+ SplitScreenTransitions splitScreenTransitions =
+ spy(mStageCoordinator.getSplitTransitions());
+ mStageCoordinator.setSplitTransitions(splitScreenTransitions);
+ mStageCoordinator.setMixedHandler(mDefaultMixedHandler);
+ PendingIntent pendingIntent = mock(PendingIntent.class);
+ PendingIntent pendingIntent2 = mock(PendingIntent.class);
+ RemoteTransition remoteTransition = mock(RemoteTransition.class);
+ when(remoteTransition.getDebugName()).thenReturn("");
+ // Test launching second task full screen
+ when(mDefaultMixedHandler.isIntentInPip(pendingIntent)).thenReturn(true);
+ mStageCoordinator.startIntents(
+ pendingIntent,
+ null /*fillInIntent*/,
+ null /*shortcutInfo1*/,
+ new Bundle(),
+ pendingIntent2,
+ null /*fillInIntent2*/,
+ null /*shortcutInfo1*/,
+ new Bundle(),
+ 0 /*splitPosition*/,
+ 1 /*snapPosition*/,
+ remoteTransition /*remoteTransition*/,
+ null /*instanceId*/);
+ verify(splitScreenTransitions, times(1))
+ .startFullscreenTransition(any(), any());
+
+ // Test launching first intent fullscreen
+ when(mDefaultMixedHandler.isIntentInPip(pendingIntent)).thenReturn(false);
+ when(mDefaultMixedHandler.isIntentInPip(pendingIntent2)).thenReturn(true);
+ mStageCoordinator.startIntents(
+ pendingIntent,
+ null /*fillInIntent*/,
+ null /*shortcutInfo1*/,
+ new Bundle(),
+ pendingIntent2,
+ null /*fillInIntent2*/,
+ null /*shortcutInfo1*/,
+ new Bundle(),
+ 0 /*splitPosition*/,
+ 1 /*snapPosition*/,
+ remoteTransition /*remoteTransition*/,
+ null /*instanceId*/);
+ verify(splitScreenTransitions, times(2))
+ .startFullscreenTransition(any(), any());
+ }
+
private Transitions createTestTransitions() {
ShellInit shellInit = new ShellInit(mMainExecutor);
final Transitions t = new Transitions(mContext, shellInit, mock(ShellController.class),
diff --git a/libs/hwui/hwui/DrawTextFunctor.h b/libs/hwui/hwui/DrawTextFunctor.h
index 1fcb692..cfca480 100644
--- a/libs/hwui/hwui/DrawTextFunctor.h
+++ b/libs/hwui/hwui/DrawTextFunctor.h
@@ -34,7 +34,9 @@
namespace android {
-inline constexpr int kHighContrastTextBorderWidth = 4;
+// These should match the constants in framework/base/core/java/android/text/Layout.java
+inline constexpr float kHighContrastTextBorderWidth = 4.0f;
+inline constexpr float kHighContrastTextBorderWidthFactor = 0.2f;
static inline void drawStroke(SkScalar left, SkScalar right, SkScalar top, SkScalar thickness,
const Paint& paint, Canvas* canvas) {
@@ -48,7 +50,16 @@
paint->setShader(nullptr);
paint->setColorFilter(nullptr);
paint->setLooper(nullptr);
- paint->setStrokeWidth(kHighContrastTextBorderWidth + 0.04 * paint->getSkFont().getSize());
+
+ if (flags::high_contrast_text_small_text_rect()) {
+ paint->setStrokeWidth(
+ std::max(kHighContrastTextBorderWidth,
+ kHighContrastTextBorderWidthFactor * paint->getSkFont().getSize()));
+ } else {
+ auto borderWidthFactor = 0.04f;
+ paint->setStrokeWidth(kHighContrastTextBorderWidth +
+ borderWidthFactor * paint->getSkFont().getSize());
+ }
paint->setStrokeJoin(SkPaint::kRound_Join);
paint->setLooper(nullptr);
}
@@ -106,36 +117,7 @@
Paint outlinePaint(paint);
simplifyPaint(darken ? SK_ColorWHITE : SK_ColorBLACK, &outlinePaint);
outlinePaint.setStyle(SkPaint::kStrokeAndFill_Style);
- if (flags::high_contrast_text_small_text_rect()) {
- const SkFont& font = paint.getSkFont();
- auto padding = kHighContrastTextBorderWidth + 0.1f * font.getSize();
-
- // Draw the background only behind each glyph's bounds. We do this instead of using
- // the bounds of the entire layout, because the layout includes alignment whitespace
- // etc which can obscure other text from separate passes (e.g. emojis).
- // Merge all the glyph bounds into one rect for this line, since drawing a rect for
- // each glyph is expensive.
- SkRect glyphBounds;
- SkRect bgBounds;
- for (size_t i = start; i < end; i++) {
- auto glyph = layout.getGlyphId(i);
-
- font.getBounds(reinterpret_cast<const SkGlyphID*>(&glyph), 1, &glyphBounds,
- &paint);
- glyphBounds.offset(layout.getX(i), layout.getY(i));
-
- bgBounds.join(glyphBounds);
- }
-
- if (!bgBounds.isEmpty()) {
- bgBounds.offset(x, y);
- bgBounds.outset(padding, padding);
- canvas->drawRect(bgBounds.fLeft, bgBounds.fTop, bgBounds.fRight,
- bgBounds.fBottom, outlinePaint);
- }
- } else {
- canvas->drawGlyphs(glyphFunc, glyphCount, outlinePaint, x, y, totalAdvance);
- }
+ canvas->drawGlyphs(glyphFunc, glyphCount, outlinePaint, x, y, totalAdvance);
// inner
gDrawTextBlobMode = DrawTextBlobMode::HctInner;
diff --git a/libs/hwui/jni/Graphics.cpp b/libs/hwui/jni/Graphics.cpp
index 8315c4c..07e97f8 100644
--- a/libs/hwui/jni/Graphics.cpp
+++ b/libs/hwui/jni/Graphics.cpp
@@ -211,11 +211,7 @@
static jfieldID gRegion_nativeInstanceID;
static jmethodID gRegion_constructorMethodID;
-static jclass gByte_class;
-static jobject gVMRuntime;
-static jclass gVMRuntime_class;
-static jmethodID gVMRuntime_newNonMovableArray;
-static jmethodID gVMRuntime_addressOf;
+static jclass gByte_class;
static jclass gColorSpace_class;
static jmethodID gColorSpace_getMethodID;
@@ -789,13 +785,6 @@
gByte_class = (jclass) env->NewGlobalRef(
env->GetStaticObjectField(c, env->GetStaticFieldID(c, "TYPE", "Ljava/lang/Class;")));
- gVMRuntime_class = MakeGlobalRefOrDie(env, FindClassOrDie(env, "dalvik/system/VMRuntime"));
- m = env->GetStaticMethodID(gVMRuntime_class, "getRuntime", "()Ldalvik/system/VMRuntime;");
- gVMRuntime = env->NewGlobalRef(env->CallStaticObjectMethod(gVMRuntime_class, m));
- gVMRuntime_newNonMovableArray = GetMethodIDOrDie(env, gVMRuntime_class, "newNonMovableArray",
- "(Ljava/lang/Class;I)Ljava/lang/Object;");
- gVMRuntime_addressOf = GetMethodIDOrDie(env, gVMRuntime_class, "addressOf", "(Ljava/lang/Object;)J");
-
gColorSpace_class = MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/graphics/ColorSpace"));
gColorSpace_getMethodID = GetStaticMethodIDOrDie(env, gColorSpace_class,
"get", "(Landroid/graphics/ColorSpace$Named;)Landroid/graphics/ColorSpace;");
diff --git a/libs/hwui/jni/android_graphics_Matrix.cpp b/libs/hwui/jni/android_graphics_Matrix.cpp
index ca667b0..c0d791a 100644
--- a/libs/hwui/jni/android_graphics_Matrix.cpp
+++ b/libs/hwui/jni/android_graphics_Matrix.cpp
@@ -376,11 +376,24 @@
{"nEquals", "(JJ)Z", (void*) SkMatrixGlue::equals}
};
+static const JNINativeMethod extra_methods[] = {
+ {"nGetNativeFinalizer", "()J", (void*)SkMatrixGlue::getNativeFinalizer},
+ {"nCreate", "(J)J", (void*)SkMatrixGlue::create},
+};
+
static jclass sClazz;
static jfieldID sNativeInstanceField;
static jmethodID sCtor;
int register_android_graphics_Matrix(JNIEnv* env) {
+ // Methods only used on Ravenwood (for now). See the javadoc on Matrix$ExtraNativesx
+ // for why we need it.
+ //
+ // We don't need it on non-ravenwood, but we don't (yet) have a way to detect ravenwood
+ // environment, so we just always run it.
+ RegisterMethodsOrDie(env, "android/graphics/Matrix$ExtraNatives", extra_methods,
+ NELEM(extra_methods));
+
int result = RegisterMethodsOrDie(env, "android/graphics/Matrix", methods, NELEM(methods));
jclass clazz = FindClassOrDie(env, "android/graphics/Matrix");
diff --git a/location/lib/java/com/android/location/provider/SignificantPlaceProvider.java b/location/lib/java/com/android/location/provider/SignificantPlaceProvider.java
new file mode 100644
index 0000000..0b39a9a
--- /dev/null
+++ b/location/lib/java/com/android/location/provider/SignificantPlaceProvider.java
@@ -0,0 +1,95 @@
+/*
+ * 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.location.provider;
+
+import android.annotation.Nullable;
+import android.app.trust.TrustManager;
+import android.hardware.location.ISignificantPlaceProvider;
+import android.hardware.location.ISignificantPlaceProviderManager;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.Process;
+import android.os.RemoteException;
+
+import com.android.internal.annotations.GuardedBy;
+
+/** @hide */
+public class SignificantPlaceProvider {
+
+ public static final String ACTION = TrustManager.ACTION_BIND_SIGNIFICANT_PLACE_PROVIDER;
+
+ private final IBinder mBinder;
+
+ // write locked on mBinder, read lock is optional depending on atomicity requirements
+ @Nullable private volatile ISignificantPlaceProviderManager mManager;
+
+ @GuardedBy("mBinder")
+ private boolean mInSignificantPlace = false;
+
+ public SignificantPlaceProvider() {
+ mBinder = new Service();
+ mManager = null;
+ }
+
+ public IBinder getBinder() {
+ return mBinder;
+ }
+
+ /** Set whether the device is currently in a trusted location. */
+ public void setInSignificantPlace(boolean inSignificantPlace) {
+ synchronized (mBinder) {
+ if (inSignificantPlace == mInSignificantPlace) {
+ return;
+ }
+
+ mInSignificantPlace = inSignificantPlace;
+ }
+
+ ISignificantPlaceProviderManager manager = mManager;
+ if (manager != null) {
+ try {
+ manager.setInSignificantPlace(inSignificantPlace);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ private final class Service extends ISignificantPlaceProvider.Stub {
+
+ Service() {}
+
+ @Override
+ public void setSignificantPlaceProviderManager(ISignificantPlaceProviderManager manager) {
+ if (Binder.getCallingUid() != Process.SYSTEM_UID) {
+ return;
+ }
+
+ synchronized (mBinder) {
+ if (mInSignificantPlace) {
+ try {
+ manager.setInSignificantPlace(true);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ mManager = manager;
+ }
+ }
+ }
+}
diff --git a/media/OWNERS b/media/OWNERS
index 1e5a458..5e39195 100644
--- a/media/OWNERS
+++ b/media/OWNERS
@@ -11,6 +11,7 @@
nchalko@google.com
philburk@google.com
quxiangfang@google.com
+shuzhenwang@google.com
wonsik@google.com
# go/android-fwk-media-solutions for info on areas of ownership.
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/OWNERS b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/OWNERS
new file mode 100644
index 0000000..f4f9c08
--- /dev/null
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/OWNERS
@@ -0,0 +1,5 @@
+# Bug component: 1344
+include platform/frameworks/base:/media/OWNERS
+
+# Camera
+per-file *Camera* = file:platform/frameworks/av:/camera/OWNERS
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java
index fc96896..353366d 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java
@@ -89,7 +89,8 @@
public void testCameraInfo() throws Exception {
for (int cameraId = 0; cameraId < mUtils.getGuessedNumCameras(); ++cameraId) {
CameraInfo info = mUtils.getCameraService().getCameraInfo(cameraId,
- /*overrideToPortrait*/false, DEVICE_ID_DEFAULT, DEVICE_POLICY_DEFAULT);
+ ICameraService.ROTATION_OVERRIDE_NONE, DEVICE_ID_DEFAULT,
+ DEVICE_POLICY_DEFAULT);
assertTrue("Facing was not set for camera " + cameraId, info.info.facing != -1);
assertTrue("Orientation was not set for camera " + cameraId,
info.info.orientation != -1);
@@ -164,7 +165,7 @@
ICameraService.USE_CALLING_UID,
ICameraService.USE_CALLING_PID,
getContext().getApplicationInfo().targetSdkVersion,
- /*overrideToPortrait*/false,
+ ICameraService.ROTATION_OVERRIDE_NONE,
/*forceSlowJpegMode*/false,
DEVICE_ID_DEFAULT, DEVICE_POLICY_DEFAULT);
assertNotNull(String.format("Camera %s was null", cameraId), cameraUser);
@@ -266,7 +267,8 @@
clientPackageName, clientAttributionTag,
ICameraService.USE_CALLING_UID, 0 /*oomScoreOffset*/,
getContext().getApplicationInfo().targetSdkVersion,
- /*overrideToPortrait*/false, DEVICE_ID_DEFAULT, DEVICE_POLICY_DEFAULT);
+ ICameraService.ROTATION_OVERRIDE_NONE, DEVICE_ID_DEFAULT,
+ DEVICE_POLICY_DEFAULT);
assertNotNull(String.format("Camera %s was null", cameraId), cameraUser);
Log.v(TAG, String.format("Camera %s connected", cameraId));
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java
index dc8647f..6cf2a41 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java
@@ -248,7 +248,7 @@
mCameraUser = mUtils.getCameraService().connectDevice(mMockCb, mCameraId,
clientPackageName, clientAttributionTag, ICameraService.USE_CALLING_UID,
/*oomScoreOffset*/0, getContext().getApplicationInfo().targetSdkVersion,
- /*overrideToPortrait*/false, DEVICE_ID_DEFAULT, DEVICE_POLICY_DEFAULT);
+ ICameraService.ROTATION_OVERRIDE_NONE, DEVICE_ID_DEFAULT, DEVICE_POLICY_DEFAULT);
assertNotNull(String.format("Camera %s was null", mCameraId), mCameraUser);
mHandlerThread = new HandlerThread(TAG);
mHandlerThread.start();
@@ -415,7 +415,8 @@
@SmallTest
public void testCameraCharacteristics() throws RemoteException {
CameraMetadataNative info = mUtils.getCameraService().getCameraCharacteristics(mCameraId,
- getContext().getApplicationInfo().targetSdkVersion, /*overrideToPortrait*/false,
+ getContext().getApplicationInfo().targetSdkVersion,
+ ICameraService.ROTATION_OVERRIDE_NONE,
DEVICE_ID_DEFAULT, DEVICE_POLICY_DEFAULT);
assertFalse(info.isEmpty());
diff --git a/packages/SettingsLib/res/values/config.xml b/packages/SettingsLib/res/values/config.xml
index 45253bb..68b81db 100644
--- a/packages/SettingsLib/res/values/config.xml
+++ b/packages/SettingsLib/res/values/config.xml
@@ -22,6 +22,9 @@
<!-- Threshold in micro watts above which a charger is rated as "fast"; 1.5A @ 5V -->
<integer name="config_chargingFastThreshold">7500000</integer>
+ <!-- Threshold in micro watts above which a charger is rated as "fast"; 20W -->
+ <integer name="config_chargingFastThreshold_v2">20000000</integer>
+
<!-- When true, show 1/2G networks as 3G. -->
<bool name="config_showMin3G">false</bool>
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 1515811..adbfc72 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1745,4 +1745,7 @@
<string name="feminine">Feminine</string>
<!-- List entry in developer settings to set the grammatical gender to Masculine [CHAR LIMIT=30]-->
<string name="masculine">Masculine</string>
+
+ <!-- The name of the screen for seeing and installing system updates. [CHAR LIMIT=40]-->
+ <string name="system_update_settings_list_item_title">System Updates</string>
</resources>
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/RecentAppOpsAccess.java b/packages/SettingsLib/src/com/android/settingslib/applications/RecentAppOpsAccess.java
index 169c330..57bde56 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/RecentAppOpsAccess.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/RecentAppOpsAccess.java
@@ -29,6 +29,7 @@
import android.os.UserManager;
import android.permission.PermissionManager;
import android.text.format.DateUtils;
+import android.util.ArrayMap;
import android.util.IconDrawableFactory;
import android.util.Log;
@@ -127,6 +128,7 @@
final long now = mClock.millis();
final UserManager um = mContext.getSystemService(UserManager.class);
final List<UserHandle> profiles = um.getUserProfiles();
+ ArrayMap<UserHandle, Boolean> shouldIncludeAppsByUsers = new ArrayMap<>();
for (int i = 0; i < appOpsCount; ++i) {
AppOpsManager.PackageOps ops = appOps.get(i);
@@ -134,9 +136,13 @@
int uid = ops.getUid();
UserHandle user = UserHandle.getUserHandleForUid(uid);
+ if (!shouldIncludeAppsByUsers.containsKey(user)) {
+ shouldIncludeAppsByUsers.put(user, shouldHideUser(um, user));
+ }
+
// Don't show apps belonging to background users except for profiles that shouldn't
// be shown in quiet mode.
- if (!profiles.contains(user) || isHideInQuietEnabledForProfile(um, user)) {
+ if (!profiles.contains(user) || !shouldIncludeAppsByUsers.get(user)) {
continue;
}
@@ -200,7 +206,7 @@
return accesses;
}
- private boolean isHideInQuietEnabledForProfile(UserManager userManager, UserHandle userHandle) {
+ private boolean shouldHideUser(UserManager userManager, UserHandle userHandle) {
if (android.multiuser.Flags.enablePrivateSpaceFeatures()
&& android.multiuser.Flags.handleInterleavedSettingsForPrivateSpace()) {
return userManager.isQuietModeEnabled(userHandle)
diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java
index 2032328..f659e38 100644
--- a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java
+++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java
@@ -145,7 +145,8 @@
final int slowThreshold = context.getResources().getInteger(
R.integer.config_chargingSlowlyThreshold);
final int fastThreshold = context.getResources().getInteger(
- R.integer.config_chargingFastThreshold);
+ getFastChargingThresholdResId());
+
return maxChargingWattage <= 0 ? CHARGING_UNKNOWN :
maxChargingWattage < slowThreshold ? CHARGING_SLOWLY :
maxChargingWattage > fastThreshold ? CHARGING_FAST :
@@ -382,7 +383,7 @@
< context.getResources().getInteger(R.integer.config_chargingSlowlyThreshold)) {
return CHARGING_SLOWLY;
} else if (maxChargingMicroWatt
- > context.getResources().getInteger(R.integer.config_chargingFastThreshold)) {
+ > context.getResources().getInteger(getFastChargingThresholdResId())) {
return CHARGING_FAST;
} else {
return CHARGING_REGULAR;
@@ -410,4 +411,10 @@
return -1;
}
}
+
+ private static int getFastChargingThresholdResId() {
+ return BatteryUtils.isChargingStringV2Enabled()
+ ? R.integer.config_chargingFastThreshold_v2
+ : R.integer.config_chargingFastThreshold;
+ }
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryUtils.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryUtils.java
index 327e470..ca3af53 100644
--- a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryUtils.java
@@ -27,6 +27,7 @@
import android.util.ArraySet;
import android.view.accessibility.AccessibilityManager;
+import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import java.util.List;
@@ -97,9 +98,18 @@
/** Used to override the system property to enable or reset for charging string V2. */
@VisibleForTesting
public static void setChargingStringV2Enabled(Boolean enabled) {
- SystemProperties.set(
- BatteryUtils.PROPERTY_CHARGING_STRING_V2_KEY,
- enabled == null ? "" : String.valueOf(enabled));
+ setChargingStringV2Enabled(enabled, true /* updateProperty */);
+ }
+
+ /** Used to override the system property to enable or reset for charging string V2. */
+ @VisibleForTesting
+ public static void setChargingStringV2Enabled(
+ @Nullable Boolean enabled, boolean updateProperty) {
+ if (updateProperty) {
+ SystemProperties.set(
+ BatteryUtils.PROPERTY_CHARGING_STRING_V2_KEY,
+ enabled == null ? "" : String.valueOf(enabled));
+ }
BatteryUtils.sChargingStringV2Enabled = enabled;
}
}
diff --git a/packages/SettingsLib/tests/unit/src/com/android/settingslib/fuelgague/BatteryStatusTest.kt b/packages/SettingsLib/tests/unit/src/com/android/settingslib/fuelgague/BatteryStatusTest.kt
index 6c0c1a7..4940610 100644
--- a/packages/SettingsLib/tests/unit/src/com/android/settingslib/fuelgague/BatteryStatusTest.kt
+++ b/packages/SettingsLib/tests/unit/src/com/android/settingslib/fuelgague/BatteryStatusTest.kt
@@ -38,6 +38,7 @@
import com.android.settingslib.fuelgauge.BatteryStatus.CHARGING_SLOWLY
import com.android.settingslib.fuelgauge.BatteryStatus.CHARGING_UNKNOWN
import com.android.settingslib.fuelgauge.BatteryStatus.isBatteryDefender
+import com.android.settingslib.fuelgauge.BatteryUtils
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
import java.util.Optional
@@ -253,12 +254,17 @@
private val maxChargingCurrent: Optional<Int>,
private val maxChargingVoltage: Optional<Int>,
private val expectedChargingSpeed: Int,
+ private val chargingStringV2Enabled: Boolean,
) {
val context: Context = ApplicationProvider.getApplicationContext()
@Test
fun getChargingSpeed_() {
+ BatteryUtils.setChargingStringV2Enabled(
+ chargingStringV2Enabled,
+ false /* updateProperty */
+ )
val batteryChangedIntent =
Intent(Intent.ACTION_BATTERY_CHANGED).apply {
maxChargingCurrent.ifPresent { putExtra(EXTRA_MAX_CHARGING_CURRENT, it) }
@@ -278,37 +284,57 @@
"maxCurrent=n/a, maxVoltage=n/a -> UNKNOWN",
Optional.empty<Int>(),
Optional.empty<Int>(),
- CHARGING_UNKNOWN
+ CHARGING_UNKNOWN,
+ false /* chargingStringV2Enabled */
),
arrayOf(
"maxCurrent=0, maxVoltage=9000000 -> UNKNOWN",
Optional.of(0),
Optional.of(0),
- CHARGING_UNKNOWN
+ CHARGING_UNKNOWN,
+ false /* chargingStringV2Enabled */
),
arrayOf(
"maxCurrent=1500000, maxVoltage=5000000 -> CHARGING_REGULAR",
Optional.of(1500000),
Optional.of(5000000),
- CHARGING_REGULAR
+ CHARGING_REGULAR,
+ false /* chargingStringV2Enabled */
),
arrayOf(
"maxCurrent=1000000, maxVoltage=5000000 -> CHARGING_REGULAR",
Optional.of(1000000),
Optional.of(5000000),
- CHARGING_REGULAR
+ CHARGING_REGULAR,
+ false /* chargingStringV2Enabled */
),
arrayOf(
"maxCurrent=1500001, maxVoltage=5000000 -> CHARGING_FAST",
Optional.of(1501000),
Optional.of(5000000),
- CHARGING_FAST
+ CHARGING_FAST,
+ false /* chargingStringV2Enabled */
),
arrayOf(
"maxCurrent=999999, maxVoltage=5000000 -> CHARGING_SLOWLY",
Optional.of(999999),
Optional.of(5000000),
- CHARGING_SLOWLY
+ CHARGING_SLOWLY,
+ false /* chargingStringV2Enabled */
+ ),
+ arrayOf(
+ "maxCurrent=3000000, maxVoltage=9000000 -> CHARGING_FAST",
+ Optional.of(3000000),
+ Optional.of(9000000),
+ CHARGING_FAST,
+ true /* chargingStringV2Enabled */
+ ),
+ arrayOf(
+ "maxCurrent=2200000, maxVoltage=9000000 -> CHARGING_REGULAR",
+ Optional.of(2200000),
+ Optional.of(9000000),
+ CHARGING_REGULAR,
+ true /* chargingStringV2Enabled */
),
)
}
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index e2af631..65c5708 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -69,6 +69,219 @@
visibility: ["//visibility:private"],
}
+filegroup {
+ name: "SystemUI-tests-robofiles",
+ srcs: [
+ "tests/src/**/*.kt",
+ "tests/src/**/*.java",
+ ],
+ visibility: ["//visibility:private"],
+}
+
+// We are running robolectric tests in the tests directory as well as
+// multivalent tests. If you add a test, and it doesn't run in robolectric,
+// it should be added to this exclusion list. go/multivalent-tests
+filegroup {
+ name: "SystemUI-tests-broken-robofiles",
+ srcs: [
+ "tests/src/**/*DeviceOnlyTest.java",
+ "tests/src/**/*DeviceOnlyTest.kt",
+ "tests/src/**/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt",
+ "tests/src/**/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepositoryImplForDeviceTest.kt",
+ "tests/src/**/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt",
+ "tests/src/**/systemui/controls/management/ControlsFavoritingActivityTest.kt",
+ "tests/src/**/systemui/controls/management/ControlsProviderSelectorActivityTest.kt",
+ "tests/src/**/systemui/controls/start/ControlsStartableTest.kt",
+ "tests/src/**/systemui/haptics/slider/SliderStateTrackerTest.kt",
+ "tests/src/**/systemui/keyboard/stickykeys/ui/StickyKeysIndicatorCoordinatorTest.kt",
+ "tests/src/**/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt",
+ "tests/src/**/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceCoreStartableTest.kt",
+ "tests/src/**/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt",
+ "tests/src/**/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt",
+ "tests/src/**/systemui/keyguard/ResourceTrimmerTest.kt",
+ "tests/src/**/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt",
+ "tests/src/**/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt",
+ "tests/src/**/systemui/media/controls/domain/pipeline/MediaTimeoutListenerTest.kt",
+ "tests/src/**/systemui/media/controls/ui/controller/MediaHierarchyManagerTest.kt",
+ "tests/src/**/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherCoreStartableTest.kt",
+ "tests/src/**/systemui/media/taptotransfer/receiver/FakeMediaTttChipControllerReceiver.kt",
+ "tests/src/**/systemui/qs/tileimpl/QSTileViewImplTest.kt",
+ "tests/src/**/systemui/qs/tiles/DeviceControlsTileTest.kt",
+ "tests/src/**/systemui/screenshot/ActionExecutorTest.kt",
+ "tests/src/**/systemui/screenshot/ActionIntentCreatorTest.kt",
+ "tests/src/**/systemui/screenshot/DefaultScreenshotActionsProviderTest.kt",
+ "tests/src/**/systemui/screenshot/TakeScreenshotServiceTest.kt",
+ "tests/src/**/systemui/statusbar/commandline/CommandRegistryTest.kt",
+ "tests/src/**/systemui/statusbar/notification/collection/TargetSdkResolverTest.kt",
+ "tests/src/**/systemui/statusbar/notification/icon/IconManagerTest.kt",
+ "tests/src/**/systemui/statusbar/notification/row/BigPictureIconManagerTest.kt",
+ "tests/src/**/systemui/statusbar/notification/row/NotificationSettingsControllerTest.kt",
+ "tests/src/**/systemui/statusbar/notification/stack/ui/view/NotificationStatsLoggerTest.kt",
+ "tests/src/**/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt",
+ "tests/src/**/systemui/statusbar/policy/BatteryStateNotifierTest.kt",
+ "tests/src/**/systemui/statusbar/policy/FlashlightControllerImplTest.kt",
+ "tests/src/**/systemui/statusbar/policy/SensitiveNotificationProtectionControllerFlagDisabledTest.kt",
+ "tests/src/**/systemui/stylus/StylusUsiPowerStartableTest.kt",
+ "tests/src/**/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt",
+ "tests/src/**/keyguard/ClockEventControllerTest.kt",
+ "tests/src/**/keyguard/LegacyLockIconViewControllerWithCoroutinesTest.kt",
+ "tests/src/**/keyguard/LegacyLockIconViewControllerBaseTest.kt",
+ "tests/src/**/keyguard/LegacyLockIconViewControllerTest.java",
+ "tests/src/**/systemui/animation/TransitionAnimatorTest.kt",
+ "tests/src/**/systemui/bluetooth/qsdialog/BluetoothAutoOnRepositoryTest.kt",
+ "tests/src/**/systemui/bluetooth/qsdialog/BluetoothStateInteractorTest.kt",
+ "tests/src/**/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegateTest.kt",
+ "tests/src/**/systemui/bluetooth/qsdialog/BluetoothTileDialogRepositoryTest.kt",
+ "tests/src/**/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModelTest.kt",
+ "tests/src/**/systemui/bluetooth/qsdialog/DeviceItemFactoryTest.kt",
+ "tests/src/**/systemui/bluetooth/qsdialog/DeviceItemInteractorTest.kt",
+ "tests/src/**/systemui/broadcast/UserBroadcastDispatcherTest.kt",
+ "tests/src/**/systemui/charging/WiredChargingRippleControllerTest.kt",
+ "tests/src/**/systemui/clipboardoverlay/ClipboardModelTest.kt",
+ "tests/src/**/systemui/controls/controller/AuxiliaryPersistenceWrapperTest.kt",
+ "tests/src/**/systemui/controls/controller/ControlsBindingControllerImplTest.kt",
+ "tests/src/**/systemui/controls/controller/ControlsControllerImplTest.kt",
+ "tests/src/**/systemui/controls/controller/DeletionJobServiceTest.kt",
+ "tests/src/**/systemui/controls/settings/ControlsSettingsDialogManagerImplTest.kt",
+ "tests/src/**/systemui/controls/ui/ControlsUiControllerImplTest.kt",
+ "tests/src/**/systemui/controls/ui/ControlViewHolderTest.kt",
+ "tests/src/**/systemui/controls/ui/SelectionItemTest.kt",
+ "tests/src/**/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt",
+ "tests/src/**/systemui/media/controls/domain/pipeline/LegacyMediaDataFilterImplTest.kt",
+ "tests/src/**/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt",
+ "tests/src/**/systemui/media/controls/domain/pipeline/MediaDataFilterImplTest.kt",
+ "tests/src/**/systemui/media/controls/ui/animation/AnimationBindHandlerTest.kt",
+ "tests/src/**/systemui/media/controls/ui/animation/ColorSchemeTransitionTest.kt",
+ "tests/src/**/systemui/media/controls/ui/animation/MetadataAnimationHandlerTest.kt",
+ "tests/src/**/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt",
+ "tests/src/**/systemui/media/controls/ui/controller/MediaControlPanelTest.kt",
+ "tests/src/**/systemui/media/controls/ui/controller/MediaViewControllerTest.kt",
+ "tests/src/**/systemui/media/controls/ui/drawable/SquigglyProgressTest.kt",
+ "tests/src/**/systemui/media/controls/ui/MediaPlayerDataTest.kt",
+ "tests/src/**/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt",
+ "tests/src/**/systemui/navigationbar/gestural/BackPanelControllerTest.kt",
+ "tests/src/**/systemui/notetask/NoteTaskControllerTest.kt",
+ "tests/src/**/systemui/notetask/NoteTaskInitializerTest.kt",
+ "tests/src/**/systemui/power/domain/interactor/PowerInteractorTest.kt",
+ "tests/src/**/systemui/privacy/AppOpsPrivacyItemMonitorTest.kt",
+ "tests/src/**/systemui/privacy/PrivacyItemControllerTest.kt",
+ "tests/src/**/systemui/qs/external/CustomTileStatePersisterTest.kt",
+ "tests/src/**/systemui/qs/external/TileRequestDialogTest.kt",
+ "tests/src/**/systemui/qs/external/TileServiceRequestControllerTest.kt",
+ "tests/src/**/systemui/qs/tileimpl/TilesStatesTextTest.kt",
+ "tests/src/**/systemui/qs/tiles/AlarmTileTest.kt",
+ "tests/src/**/systemui/qs/tiles/BluetoothTileTest.kt",
+ "tests/src/**/systemui/screenshot/ScreenshotPolicyImplTest.kt",
+ "tests/src/**/systemui/settings/DisplayTrackerImplTest.kt",
+ "tests/src/**/systemui/settings/UserFileManagerImplTest.kt",
+ "tests/src/**/systemui/settings/UserTrackerImplReceiveTest.kt",
+ "tests/src/**/systemui/settings/UserTrackerImplTest.kt",
+ "tests/src/**/systemui/shade/GlanceableHubContainerControllerTest.kt",
+ "tests/src/**/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt",
+ "tests/src/**/systemui/shade/NotificationsQSContainerControllerTest.kt",
+ "tests/src/**/systemui/shade/ShadeExpansionStateManagerTest.kt",
+ "tests/src/**/systemui/shade/ShadeHeaderControllerTest.kt",
+ "tests/src/**/systemui/shade/transition/LargeScreenShadeInterpolatorImplTest.kt",
+ "tests/src/**/systemui/statusbar/commandline/CommandParserTest.kt",
+ "tests/src/**/systemui/statusbar/connectivity/MobileStateTest.kt",
+ "tests/src/**/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt",
+ "tests/src/**/systemui/statusbar/gesture/GenericGestureDetectorTest.kt",
+ "tests/src/**/systemui/statusbar/LightRevealScrimTest.kt",
+ "tests/src/**/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt",
+ "tests/src/**/systemui/statusbar/notification/collection/coordinator/DataStoreCoordinatorTest.kt",
+ "tests/src/**/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt",
+ "tests/src/**/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinatorTest.kt",
+ "tests/src/**/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorTest.kt",
+ "tests/src/**/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt",
+ "tests/src/**/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinatorTest.kt",
+ "tests/src/**/systemui/statusbar/notification/logging/NotificationMemoryLoggerTest.kt",
+ "tests/src/**/systemui/statusbar/notification/logging/NotificationMemoryMeterTest.kt",
+ "tests/src/**/systemui/statusbar/notification/logging/NotificationMemoryViewWalkerTest.kt",
+ "tests/src/**/systemui/statusbar/notification/RoundableTest.kt",
+ "tests/src/**/systemui/statusbar/notification/row/ChannelEditorDialogControllerTest.kt",
+ "tests/src/**/systemui/statusbar/notification/row/SingleLineConversationViewBinderTest.kt",
+ "tests/src/**/systemui/statusbar/notification/row/SingleLineViewBinderTest.kt",
+ "tests/src/**/systemui/statusbar/notification/row/SingleLineViewInflaterTest.kt",
+ "tests/src/**/systemui/statusbar/notification/row/TextPrecomputerTest.kt",
+ "tests/src/**/systemui/statusbar/phone/FoldStateListenerTest.kt",
+ "tests/src/**/systemui/statusbar/phone/fragment/MultiSourceMinAlphaControllerTest.kt",
+ "tests/src/**/systemui/statusbar/pipeline/mobile/data/model/SystemUiCarrierConfigTest.kt",
+ "tests/src/**/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt",
+ "tests/src/**/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt",
+ "tests/src/**/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt",
+ "tests/src/**/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt",
+ "tests/src/**/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepositoryImplTest.kt",
+ "tests/src/**/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModelTest.kt",
+ "tests/src/**/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt",
+ "tests/src/**/systemui/statusbar/policy/DeviceControlsControllerImplTest.kt",
+ "tests/src/**/systemui/statusbar/policy/VariableDateViewControllerTest.kt",
+ "tests/src/**/systemui/statusbar/policy/WalletControllerImplTest.kt",
+ "tests/src/**/systemui/statusbar/SplitShadeLockScreenOverScrollerTest.kt",
+ "tests/src/**/systemui/stylus/StylusUsiPowerUiTest.kt",
+ "tests/src/**/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt",
+ "tests/src/**/keyguard/KeyguardUpdateMonitorTest.java",
+ "tests/src/**/keyguard/LegacyLockIconViewControllerBaseTest.java",
+ "tests/src/**/keyguard/CarrierTextManagerTest.java",
+ "tests/src/**/systemui/ScreenDecorationsTest.java",
+ "tests/src/**/systemui/temporarydisplay/chipbar/SwipeChipbarAwayGestureHandlerTest.kt",
+ "tests/src/**/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt",
+ "tests/src/**/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt",
+ "tests/src/**/systemui/shared/system/RemoteTransitionTest.java",
+ "tests/src/**/systemui/navigationbar/NavigationBarControllerImplTest.java",
+ "tests/src/**/systemui/bluetooth/qsdialog/AudioSharingInteractorTest.kt",
+ "tests/src/**/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt",
+ "tests/src/**/systemui/notetask/LaunchNotesRoleSettingsTrampolineActivityTest.kt",
+ "tests/src/**/systemui/notetask/shortcut/LaunchNoteTaskActivityTest.kt",
+ "tests/src/**/systemui/DisplayCutoutBaseViewTest.kt",
+ "tests/src/**/systemui/qs/tiles/dialog/InternetDialogDelegateTest.java",
+ "tests/src/**/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java",
+ "tests/src/**/systemui/qs/tiles/HotspotTileTest.java",
+ "tests/src/**/systemui/qs/external/TileLifecycleManagerTest.java",
+ "tests/src/**/systemui/recents/OverviewProxyServiceTest.kt",
+ "tests/src/**/systemui/stylus/StylusManagerTest.kt",
+ "tests/src/**/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java",
+ "tests/src/**/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java",
+ "tests/src/**/systemui/statusbar/policy/BatteryControllerStartableTest.java",
+ "tests/src/**/systemui/statusbar/policy/BatteryControllerTest.java",
+ "tests/src/**/systemui/statusbar/policy/SensitiveNotificationProtectionControllerTest.kt",
+ "tests/src/**/systemui/statusbar/notification/NotificationSectionsFeatureManagerTest.kt",
+ "tests/src/**/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepositoryTest.kt",
+ "tests/src/**/systemui/statusbar/KeyboardShortcutsReceiverTest.java",
+ "tests/src/**/systemui/wmshell/BubblesTest.java",
+ "tests/src/**/systemui/biometrics/AuthRippleControllerTest.kt",
+ "tests/src/**/keyguard/KeyguardAbsKeyInputViewControllerTest.java",
+ "tests/src/**/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java",
+ "tests/src/**/systemui/clipboardoverlay/ClipboardListenerTest.java",
+ "tests/src/**/systemui/doze/DozeScreenStateTest.java",
+ "tests/src/**/systemui/keyguard/WorkLockActivityControllerTest.java",
+ "tests/src/**/systemui/media/dialog/MediaOutputControllerTest.java",
+ "tests/src/**/systemui/navigationbar/NavigationBarTest.java",
+ "tests/src/**/systemui/power/PowerNotificationWarningsTest.java",
+ "tests/src/**/systemui/power/PowerUITest.java",
+ "tests/src/**/systemui/qs/QSFooterViewControllerTest.java",
+ "tests/src/**/systemui/qs/QSImplTest.java",
+ "tests/src/**/systemui/qs/QSSecurityFooterTest.java",
+ "tests/src/**/systemui/qs/tileimpl/QSTileImplTest.java",
+ "tests/src/**/systemui/qs/tiles/QuickAccessWalletTileTest.java",
+ "tests/src/**/systemui/shade/carrier/ShadeCarrierGroupControllerTest.java",
+ "tests/src/**/systemui/shared/plugins/PluginActionManagerTest.java",
+ "tests/src/**/systemui/statusbar/CommandQueueTest.java",
+ "tests/src/**/systemui/statusbar/connectivity/CallbackHandlerTest.java",
+ "tests/src/**/systemui/statusbar/connectivity/NetworkControllerBaseTest.java",
+ "tests/src/**/systemui/statusbar/KeyguardIndicationControllerTest.java",
+ "tests/src/**/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java",
+ "tests/src/**/systemui/statusbar/phone/ScrimControllerTest.java",
+ "tests/src/**/systemui/statusbar/policy/RotationLockControllerImplTest.java",
+ "tests/src/**/systemui/statusbar/policy/SecurityControllerTest.java",
+ "tests/src/**/systemui/toast/ToastUITest.java",
+ "tests/src/**/systemui/statusbar/connectivity/NetworkControllerDataTest.java",
+ "tests/src/**/systemui/statusbar/connectivity/NetworkControllerEthernetTest.java",
+ "tests/src/**/systemui/statusbar/connectivity/NetworkControllerSignalTest.java",
+ "tests/src/**/systemui/statusbar/connectivity/NetworkControllerWifiTest.java",
+ ],
+ visibility: ["//visibility:private"],
+}
+
//Create a library to expose SystemUI's resources to other modules.
android_library {
name: "SystemUI-res",
@@ -433,6 +646,21 @@
plugins: ["dagger2-compiler"],
}
+java_library {
+ name: "RoboTestLibraries",
+ static_libs: [
+ "dagger2",
+ "androidx.test.uiautomator_uiautomator",
+ "androidx.core_core-animation-testing",
+ "androidx.test.ext.junit",
+ "inline-mockito-robolectric-prebuilt",
+ "platform-parametric-runner-lib",
+ "SystemUICustomizationTestUtils",
+ "kotlin-test",
+ "kosmos",
+ ],
+}
+
android_robolectric_test {
name: "SystemUiRoboTests",
srcs: [
@@ -442,14 +670,40 @@
":SystemUI-tests-multivalent",
],
static_libs: [
- "dagger2",
- "androidx.test.uiautomator_uiautomator",
- "androidx.core_core-animation-testing",
- "androidx.test.ext.junit",
- "inline-mockito-robolectric-prebuilt",
- "platform-parametric-runner-lib",
- "SystemUICustomizationTestUtils",
- "kosmos",
+ "RoboTestLibraries",
+ ],
+ libs: [
+ "android.test.runner",
+ "android.test.base",
+ "android.test.mock",
+ "truth",
+ ],
+
+ upstream: true,
+
+ instrumentation_for: "SystemUIRobo-stub",
+ java_resource_dirs: ["tests/robolectric/config"],
+ plugins: [
+ "dagger2-compiler",
+ ],
+}
+
+// in-place tests which use Robolectric in the tests directory
+// instead of multivalentTests
+android_robolectric_test {
+ name: "SystemUiRoboTestsInplace",
+ srcs: [
+ "tests/robolectric/src/**/*.kt",
+ "tests/robolectric/src/**/*.java",
+ ":SystemUI-tests-utils",
+ ":SystemUI-tests-multivalent",
+ ":SystemUI-tests-robofiles",
+ ],
+ exclude_srcs: [
+ ":SystemUI-tests-broken-robofiles",
+ ],
+ static_libs: [
+ "RoboTestLibraries",
],
libs: [
"android.test.runner",
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/tests/Android.bp b/packages/SystemUI/accessibility/accessibilitymenu/tests/Android.bp
index 64dcf6e..395354e 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/tests/Android.bp
+++ b/packages/SystemUI/accessibility/accessibilitymenu/tests/Android.bp
@@ -34,6 +34,7 @@
"compatibility-device-util-axt",
"platform-test-annotations",
"truth",
+ "uiautomator-helpers",
],
srcs: [
"src/**/*.java",
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java b/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java
index 0ab99fa..66943d4 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java
+++ b/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java
@@ -30,6 +30,8 @@
import static com.android.systemui.accessibility.accessibilitymenu.AccessibilityMenuService.INTENT_TOGGLE_MENU;
import static com.android.systemui.accessibility.accessibilitymenu.AccessibilityMenuService.PACKAGE_NAME;
+import static com.google.common.truth.Truth.assertWithMessage;
+
import android.accessibilityservice.AccessibilityServiceInfo;
import android.app.Instrumentation;
import android.app.KeyguardManager;
@@ -43,6 +45,8 @@
import android.hardware.display.DisplayManager;
import android.media.AudioManager;
import android.os.PowerManager;
+import android.os.RemoteException;
+import android.platform.uiautomator_helpers.WaitUtils;
import android.provider.Settings;
import android.util.Log;
import android.view.Display;
@@ -51,6 +55,8 @@
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.uiautomator.Configurator;
+import androidx.test.uiautomator.UiDevice;
import com.android.compatibility.common.util.TestUtils;
import com.android.systemui.accessibility.accessibilitymenu.model.A11yMenuShortcut.ShortcutId;
@@ -60,7 +66,6 @@
import org.junit.Assume;
import org.junit.Before;
import org.junit.BeforeClass;
-import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -76,11 +81,13 @@
private static final int TIMEOUT_SERVICE_STATUS_CHANGE_S = 5;
private static final int TIMEOUT_UI_CHANGE_S = 5;
private static final int NO_GLOBAL_ACTION = -1;
- private static final Intent INTENT_OPEN_MENU = new Intent(INTENT_TOGGLE_MENU)
- .setPackage(PACKAGE_NAME);
+ private static final Intent INTENT_OPEN_MENU =
+ new Intent(INTENT_TOGGLE_MENU).setPackage(PACKAGE_NAME);
+ private static final String SERVICE_NAME = PACKAGE_NAME + "/.AccessibilityMenuService";
private static Instrumentation sInstrumentation;
private static UiAutomation sUiAutomation;
+ private static UiDevice sUiDevice;
private static final AtomicInteger sLastGlobalAction = new AtomicInteger(NO_GLOBAL_ACTION);
private static final AtomicBoolean sOpenBlocked = new AtomicBoolean(false);
@@ -91,12 +98,14 @@
@BeforeClass
public static void classSetup() throws Throwable {
- final String serviceName = PACKAGE_NAME + "/.AccessibilityMenuService";
+ Configurator.getInstance()
+ .setUiAutomationFlags(UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES);
sInstrumentation = InstrumentationRegistry.getInstrumentation();
sUiAutomation = sInstrumentation.getUiAutomation(
UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES);
sUiAutomation.adoptShellPermissionIdentity(
UiAutomation.ALL_PERMISSIONS.toArray(new String[0]));
+ sUiDevice = UiDevice.getInstance(sInstrumentation);
final Context context = sInstrumentation.getTargetContext();
sAccessibilityManager = context.getSystemService(AccessibilityManager.class);
@@ -117,13 +126,13 @@
// Enable a11yMenu service.
Settings.Secure.putString(context.getContentResolver(),
- Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, serviceName);
+ Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, SERVICE_NAME);
TestUtils.waitUntil("Failed to enable service",
TIMEOUT_SERVICE_STATUS_CHANGE_S,
() -> sAccessibilityManager.getEnabledAccessibilityServiceList(
AccessibilityServiceInfo.FEEDBACK_ALL_MASK).stream().filter(
- info -> info.getId().contains(serviceName)).count() == 1);
+ info -> info.getId().contains(SERVICE_NAME)).count() == 1);
context.registerReceiver(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -159,8 +168,11 @@
public void tearDown() throws Throwable {
closeMenu();
sLastGlobalAction.set(NO_GLOBAL_ACTION);
+ // Leave the device in clean state when the test finished
+ unlockSignal();
// dismisses screenshot popup if present.
- sUiAutomation.executeShellCommand("input keyevent KEYCODE_BACK");
+ sUiDevice.pressBack();
+ sUiDevice.pressHome();
}
private static boolean isMenuVisible() {
@@ -168,38 +180,25 @@
return root != null && root.getPackageName().toString().equals(PACKAGE_NAME);
}
- private static void wakeUpScreen() throws Throwable {
- sUiAutomation.executeShellCommand("input keyevent KEYCODE_WAKEUP");
- TestUtils.waitUntil("Screen did not wake up.",
- TIMEOUT_UI_CHANGE_S,
- () -> sPowerManager.isInteractive());
+ private static void wakeUpScreen() throws RemoteException {
+ sUiDevice.wakeUp();
+ WaitUtils.waitForValueToSettle("Screen On", AccessibilityMenuServiceTest::isScreenOn);
+ assertWithMessage("Screen is on").that(isScreenOn()).isTrue();
}
private static void closeScreen() throws Throwable {
- Display display = sDisplayManager.getDisplay(Display.DEFAULT_DISPLAY);
sUiAutomation.performGlobalAction(GLOBAL_ACTION_LOCK_SCREEN);
- TestUtils.waitUntil("Screen did not close.",
- TIMEOUT_UI_CHANGE_S,
- () -> !sPowerManager.isInteractive()
- && display.getState() == Display.STATE_OFF
- );
+ WaitUtils.waitForValueToSettle("Screen Off", AccessibilityMenuServiceTest::isScreenOff);
+ assertWithMessage("Screen is off").that(isScreenOff()).isTrue();
}
private static void openMenu() throws Throwable {
unlockSignal();
- sInstrumentation.getContext().sendBroadcast(INTENT_OPEN_MENU);
-
- TestUtils.waitUntil("Timed out before menu could appear.",
- TIMEOUT_UI_CHANGE_S,
- () -> {
- if (isMenuVisible()) {
- return true;
- } else {
- unlockSignal();
- sInstrumentation.getContext().sendBroadcast(INTENT_OPEN_MENU);
- return false;
- }
- });
+ if (!isMenuVisible()) {
+ sInstrumentation.getTargetContext().sendBroadcast(INTENT_OPEN_MENU);
+ sUiDevice.waitForIdle();
+ WaitUtils.ensureThat("Accessibility Menu is visible", () -> isMenuVisible());
+ }
}
private static void closeMenu() throws Throwable {
@@ -342,7 +341,9 @@
sUiAutomation.executeAndWaitForEvent(
() -> assistantButton.performAction(CLICK_ID),
- (event) -> expectedPackage.contains(event.getPackageName()),
+ (event) ->
+ event.getPackageName() != null
+ && expectedPackage.contains(event.getPackageName()),
TIMEOUT_UI_CHANGE_S * 1000
);
}
@@ -358,7 +359,9 @@
sUiAutomation.executeAndWaitForEvent(
() -> settingsButton.performAction(CLICK_ID),
- (event) -> expectedPackage.contains(event.getPackageName()),
+ (event) ->
+ event.getPackageName() != null
+ && expectedPackage.contains(event.getPackageName()),
TIMEOUT_UI_CHANGE_S * 1000
);
}
@@ -454,24 +457,40 @@
}
@Test
- @Ignore("Test failure in pre/postsubmit cannot be replicated on local devices. "
- + "Coverage is low-impact.")
public void testOnScreenLock_cannotOpenMenu() throws Throwable {
closeScreen();
wakeUpScreen();
+ sInstrumentation.getContext().sendBroadcast(INTENT_OPEN_MENU);
+ sUiDevice.waitForIdle();
TestUtils.waitUntil("Did not receive signal that menu cannot open",
TIMEOUT_UI_CHANGE_S,
- () -> {
- sInstrumentation.getContext().sendBroadcast(INTENT_OPEN_MENU);
- return sOpenBlocked.get();
- });
+ sOpenBlocked::get);
}
- private static void unlockSignal() {
- // MENU unlocks screen,
- // BACK closes any menu that may appear if the screen wasn't locked.
- sUiAutomation.executeShellCommand("input keyevent KEYCODE_MENU");
- sUiAutomation.executeShellCommand("input keyevent KEYCODE_BACK");
+ private static void unlockSignal() throws RemoteException {
+ if (!sKeyguardManager.isKeyguardLocked()) {
+ return;
+ }
+ // go/adb-cheats#unlock-screen
+ wakeUpScreen();
+ if (sKeyguardManager.isKeyguardLocked()) {
+ sUiDevice.pressMenu();
+ }
+ WaitUtils.ensureThat(
+ "Device unlocked & isInteractive",
+ () -> isScreenOn() && !sKeyguardManager.isKeyguardLocked());
+ }
+
+ private static boolean isScreenOn() {
+ int display = Display.DEFAULT_DISPLAY;
+ return sPowerManager.isInteractive(display)
+ && sDisplayManager.getDisplay(display).getState() == Display.STATE_ON;
+ }
+
+ private static boolean isScreenOff() {
+ int display = Display.DEFAULT_DISPLAY;
+ return !sPowerManager.isInteractive(display)
+ && sDisplayManager.getDisplay(display).getState() == Display.STATE_OFF;
}
}
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index b3aa7e1..fd90bd9 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -26,13 +26,6 @@
}
flag {
- name: "refactor_keyguard_dismiss_intent"
- namespace: "systemui"
- description: "Update how keyguard dismiss intents are stored."
- bug: "275069969"
-}
-
-flag {
name: "notification_heads_up_cycling"
namespace: "systemui"
@@ -372,13 +365,6 @@
}
flag {
- name: "media_in_scene_container"
- namespace: "systemui"
- description: "Enable media in the scene container framework"
- bug: "296122467"
-}
-
-flag {
name: "pss_task_switcher"
namespace: "systemui"
description: "Enable the task switcher feature for partial screen sharing"
@@ -817,6 +803,16 @@
}
flag {
+ name: "shade_collapse_activity_launch_fix"
+ namespace: "systemui"
+ description: "Avoid collapsing the shade on activity launch if it is already collapsed, as this causes a flicker."
+ bug: "331591373"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "slice_broadcast_relay_in_background"
namespace: "systemui"
description: "Move handling of slice broadcast relay broadcasts to background threads"
@@ -852,3 +848,13 @@
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "communal_bouncer_do_not_modify_plugin_open"
+ namespace: "systemui"
+ description: "do not modify notification shade when handling bouncer expansion."
+ bug: "338252661"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt
index 9ad0fc5..fd79f62 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt
@@ -191,14 +191,20 @@
// so we have to take the optical insets into account.
ghostedView.getLocationOnScreen(ghostedViewLocation)
val insets = backgroundInsets
- state.top = ghostedViewLocation[1] + insets.top
+ val boundCorrections: Rect =
+ if (ghostedView is LaunchableView) {
+ ghostedView.getPaddingForLaunchAnimation()
+ } else {
+ Rect()
+ }
+ state.top = ghostedViewLocation[1] + insets.top + boundCorrections.top
state.bottom =
ghostedViewLocation[1] + (ghostedView.height * ghostedView.scaleY).roundToInt() -
- insets.bottom
- state.left = ghostedViewLocation[0] + insets.left
+ insets.bottom + boundCorrections.bottom
+ state.left = ghostedViewLocation[0] + insets.left + boundCorrections.left
state.right =
ghostedViewLocation[0] + (ghostedView.width * ghostedView.scaleX).roundToInt() -
- insets.right
+ insets.right + boundCorrections.right
}
override fun onTransitionAnimationStart(isExpandingFullyAbove: Boolean) {
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableView.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableView.kt
index da6ccaa..330ab0f 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableView.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableView.kt
@@ -16,6 +16,7 @@
package com.android.systemui.animation
+import android.graphics.Rect
import android.view.View
/** A view that can expand/launch into an app or a dialog. */
@@ -41,6 +42,9 @@
/** Perform an action when the activity launch animation ends */
fun onActivityLaunchAnimationEnd() {}
+
+ /** Provide an optional correction applied to the visible area during a launch animation */
+ fun getPaddingForLaunchAnimation(): Rect = Rect()
}
/** A delegate that can be used by views to make the implementation of [LaunchableView] easier. */
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 e07cd05..ec3c003 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
@@ -27,6 +27,7 @@
import com.android.compose.animation.scene.transitions
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.communal.shared.model.CommunalTransitionKeys
+import com.android.systemui.communal.ui.compose.extensions.allowGestures
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
import com.android.systemui.communal.util.CommunalColors
import com.android.systemui.res.R
@@ -79,6 +80,7 @@
) {
val coroutineScope = rememberCoroutineScope()
val currentSceneKey: SceneKey by viewModel.currentScene.collectAsState(CommunalScenes.Blank)
+ val touchesAllowed by viewModel.touchesAllowed.collectAsState(initial = false)
val state: MutableSceneTransitionLayoutState = remember {
MutableSceneTransitionLayoutState(
initialScene = currentSceneKey,
@@ -128,6 +130,10 @@
CommunalScene(viewModel, colors, dialogFactory, modifier = modifier)
}
}
+
+ // Touches on the notification shade in blank areas fall through to the glanceable hub. When the
+ // shade is showing, we block all touches in order to prevent this unwanted behavior.
+ Box(modifier = Modifier.fillMaxSize().allowGestures(touchesAllowed))
}
/** Scene containing the glanceable hub UI. */
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 338987a..a592aa9 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
@@ -20,9 +20,15 @@
import android.graphics.drawable.Icon
import android.os.Bundle
import android.util.SizeF
+import android.view.View.IMPORTANT_FOR_ACCESSIBILITY_AUTO
+import android.view.View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
import android.widget.FrameLayout
import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.animation.AnimatedVisibilityScope
+import androidx.compose.animation.ExperimentalAnimationApi
+import androidx.compose.animation.core.LinearEasing
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.BorderStroke
@@ -81,10 +87,13 @@
import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.alpha
+import androidx.compose.ui.draw.scale
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.ColorMatrix
+import androidx.compose.ui.graphics.TransformOrigin
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.input.key.onPreviewKeyEvent
import androidx.compose.ui.input.pointer.motionEventSpy
@@ -103,6 +112,7 @@
import androidx.compose.ui.semantics.CustomAccessibilityAction
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.customActions
+import androidx.compose.ui.semantics.onClick
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.semantics.testTagsAsResourceId
import androidx.compose.ui.text.style.TextAlign
@@ -115,8 +125,6 @@
import androidx.compose.ui.window.Popup
import androidx.core.view.setPadding
import androidx.window.layout.WindowMetricsCalculator
-import com.android.compose.modifiers.height
-import com.android.compose.modifiers.padding
import com.android.compose.modifiers.thenIf
import com.android.compose.theme.LocalAndroidColorScheme
import com.android.compose.ui.graphics.painter.rememberDrawablePainter
@@ -288,23 +296,21 @@
removeEnabled = removeButtonEnabled
)
}
+ if (currentPopup == PopupType.CtaTile) {
+ PopupOnDismissCtaTile(viewModel::onHidePopup)
+ }
- if (currentPopup != null) {
- when (currentPopup) {
- PopupType.CtaTile -> {
- PopupOnDismissCtaTile(viewModel::onHidePopup)
- }
- PopupType.CustomizeWidgetButton -> {
- ButtonToEditWidgets(
- onClick = {
- viewModel.onHidePopup()
- viewModel.onOpenWidgetEditor(selectedKey.value)
- },
- onHide = { viewModel.onHidePopup()}
- )
- }
- null -> {}
- }
+ AnimatedVisibility(
+ visible = currentPopup == PopupType.CustomizeWidgetButton,
+ modifier = Modifier.fillMaxSize()
+ ) {
+ ButtonToEditWidgets(
+ onClick = {
+ viewModel.onHidePopup()
+ viewModel.onOpenWidgetEditor(selectedKey.value)
+ },
+ onHide = { viewModel.onHidePopup() }
+ )
}
if (viewModel is CommunalViewModel && dialogFactory != null) {
@@ -374,7 +380,7 @@
liveContentKeys.indexOfFirst { !prevLiveContentKeys.contains(it) }
// Scroll if current position is behind the first updated content
- if (indexOfFirstUpdatedContent in 0..<gridState.firstVisibleItemIndex) {
+ if (indexOfFirstUpdatedContent in 0 until gridState.firstVisibleItemIndex) {
// Launching with a scope to prevent the job from being canceled in the case of a
// recomposition during scrolling
coroutineScope.launch { gridState.animateScrollToItem(indexOfFirstUpdatedContent) }
@@ -653,30 +659,67 @@
}
}
+@OptIn(ExperimentalAnimationApi::class)
@Composable
-private fun ButtonToEditWidgets(
+private fun AnimatedVisibilityScope.ButtonToEditWidgets(
onClick: () -> Unit,
onHide: () -> Unit,
) {
- Popup(alignment = Alignment.TopCenter, offset = IntOffset(0, 40), onDismissRequest = onHide) {
+ Popup(
+ alignment = Alignment.TopCenter,
+ offset = IntOffset(0, 40),
+ onDismissRequest = onHide,
+ ) {
val colors = LocalAndroidColorScheme.current
Button(
modifier =
- Modifier.height(56.dp).background(colors.secondary, RoundedCornerShape(50.dp)),
+ Modifier.height(56.dp)
+ .graphicsLayer { transformOrigin = TransformOrigin(0f, 0f) }
+ .animateEnterExit(
+ enter =
+ fadeIn(
+ initialAlpha = 0f,
+ animationSpec = tween(durationMillis = 500, easing = LinearEasing)
+ ),
+ exit =
+ fadeOut(
+ animationSpec = tween(durationMillis = 500, easing = LinearEasing)
+ )
+ )
+ .background(colors.secondary, RoundedCornerShape(50.dp)),
onClick = onClick,
) {
- Icon(
- imageVector = Icons.Outlined.Widgets,
- contentDescription = stringResource(R.string.button_to_configure_widgets_text),
- tint = colors.onSecondary,
- modifier = Modifier.size(20.dp)
- )
- Spacer(modifier = Modifier.size(8.dp))
- Text(
- text = stringResource(R.string.button_to_configure_widgets_text),
- style = MaterialTheme.typography.titleSmall,
- color = colors.onSecondary,
- )
+ Row(
+ modifier =
+ Modifier.animateEnterExit(
+ enter =
+ fadeIn(
+ animationSpec =
+ tween(
+ durationMillis = 167,
+ delayMillis = 500,
+ easing = LinearEasing
+ )
+ ),
+ exit =
+ fadeOut(
+ animationSpec = tween(durationMillis = 167, easing = LinearEasing)
+ )
+ )
+ ) {
+ Icon(
+ imageVector = Icons.Outlined.Widgets,
+ contentDescription = stringResource(R.string.button_to_configure_widgets_text),
+ tint = colors.onSecondary,
+ modifier = Modifier.size(20.dp)
+ )
+ Spacer(modifier = Modifier.size(8.dp))
+ Text(
+ text = stringResource(R.string.button_to_configure_widgets_text),
+ style = MaterialTheme.typography.titleSmall,
+ color = colors.onSecondary
+ )
+ }
}
}
}
@@ -841,17 +884,31 @@
widgetConfigurator: WidgetConfigurator?,
modifier: Modifier = Modifier,
) {
+ val context = LocalContext.current
+ val isFocusable by viewModel.isFocusable.collectAsState(initial = false)
+ val accessibilityLabel =
+ remember(model, context) {
+ model.providerInfo.loadLabel(context.packageManager).toString().trim()
+ }
+ val clickActionLabel = stringResource(R.string.accessibility_action_label_select_widget)
Box(
modifier =
- modifier.thenIf(!viewModel.isEditMode && model.inQuietMode) {
- Modifier.pointerInput(Unit) {
- // consume tap to prevent the child view from triggering interactions with the
- // app widget
- observeTaps(shouldConsume = true) { _ ->
- viewModel.onOpenEnableWorkProfileDialog()
+ modifier
+ .thenIf(!viewModel.isEditMode && model.inQuietMode) {
+ Modifier.pointerInput(Unit) {
+ // consume tap to prevent the child view from triggering interactions with
+ // the app widget
+ observeTaps(shouldConsume = true) { _ ->
+ viewModel.onOpenEnableWorkProfileDialog()
+ }
}
}
- }
+ .thenIf(viewModel.isEditMode) {
+ Modifier.semantics {
+ contentDescription = accessibilityLabel
+ onClick(label = clickActionLabel, action = null)
+ }
+ }
) {
AndroidView(
modifier = Modifier.fillMaxSize().allowGestures(allowed = !viewModel.isEditMode),
@@ -863,8 +920,19 @@
// Remove the extra padding applied to AppWidgetHostView to allow widgets to
// occupy the entire box.
setPadding(0)
+ accessibilityDelegate = viewModel.widgetAccessibilityDelegate
}
},
+ update = {
+ it.apply {
+ importantForAccessibility =
+ if (isFocusable) {
+ IMPORTANT_FOR_ACCESSIBILITY_AUTO
+ } else {
+ IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
+ }
+ }
+ },
// For reusing composition in lazy lists.
onReset = {},
)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt
index e7bfee3..33d2cc4 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt
@@ -40,6 +40,7 @@
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.input.pointer.pointerInteropFilter
+import androidx.compose.ui.semantics.clearAndSetSemantics
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.toOffset
import androidx.compose.ui.unit.toSize
@@ -274,6 +275,9 @@
AnimatedVisibility(
modifier =
Modifier.matchParentSize()
+ // Avoid taking focus away from the content when using explore-by-touch with
+ // accessibility tools.
+ .clearAndSetSemantics {}
// Do not consume motion events in the highlighted item and pass them down to
// the content.
.pointerInteropFilter { 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 985d3a1..6e987bd 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
@@ -65,7 +65,6 @@
import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.NestedScrollBehavior
import com.android.compose.animation.scene.SceneScope
-import com.android.compose.animation.scene.SceneTransitionLayoutState
import com.android.compose.modifiers.height
import com.android.systemui.common.ui.compose.windowinsets.LocalRawScreenHeight
import com.android.systemui.common.ui.compose.windowinsets.LocalScreenCornerRadius
@@ -157,6 +156,7 @@
fun SceneScope.NotificationScrollingStack(
viewModel: NotificationsPlaceholderViewModel,
maxScrimTop: () -> Float,
+ shouldPunchHoleBehindScrim: Boolean,
modifier: Modifier = Modifier,
) {
val density = LocalDensity.current
@@ -233,7 +233,8 @@
// in step with the transition so that it is 0 when it completes.
if (
scrimOffset.value < 0 &&
- layoutState.isTransitioning(from = Scenes.Shade, to = Scenes.Gone)
+ layoutState.isTransitioning(from = Scenes.Shade, to = Scenes.Gone) ||
+ layoutState.isTransitioning(from = Scenes.Shade, to = Scenes.Lockscreen)
) {
IntOffset(x = 0, y = (scrimOffset.value * expansionFraction).roundToInt())
} else {
@@ -246,7 +247,7 @@
scrimCornerRadius,
screenCornerRadius,
{ expansionFraction },
- layoutState.isNotificationScrimTransitioning(),
+ shouldPunchHoleBehindScrim,
)
.let { scrimRounding.value.toRoundedCornerShape(it) }
clip = true
@@ -270,18 +271,20 @@
) {
// Creates a cutout in the background scrim in the shape of the notifications scrim.
// Only visible when notif scrim alpha < 1, during shade expansion.
- Spacer(
- modifier =
- Modifier.fillMaxSize().drawBehind {
- drawRect(Color.Black, blendMode = BlendMode.DstOut)
- }
- )
+ if (shouldPunchHoleBehindScrim) {
+ Spacer(
+ modifier =
+ Modifier.fillMaxSize().drawBehind {
+ drawRect(Color.Black, blendMode = BlendMode.DstOut)
+ }
+ )
+ }
Box(
modifier =
Modifier.fillMaxSize()
.graphicsLayer {
alpha =
- if (layoutState.isNotificationScrimTransitioning()) {
+ if (shouldPunchHoleBehindScrim) {
(expansionFraction / EXPANSION_FOR_MAX_SCRIM_ALPHA).coerceAtMost(1f)
} else 1f
}
@@ -429,13 +432,6 @@
)
}
-private fun SceneTransitionLayoutState.isNotificationScrimTransitioning(): Boolean {
- return isTransitioningBetween(Scenes.Gone, Scenes.Shade) ||
- isTransitioningBetween(Scenes.Lockscreen, Scenes.Shade) ||
- isTransitioningBetween(Scenes.Gone, Scenes.QuickSettings) ||
- isTransitioningBetween(Scenes.Lockscreen, Scenes.QuickSettings)
-}
-
private const val TAG = "FlexiNotifs"
private val DEBUG_STACK_COLOR = Color(1f, 0f, 0f, 0.2f)
private val DEBUG_HUN_COLOR = Color(0f, 0f, 1f, 0.2f)
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 62619f5..b33ea78 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
@@ -61,6 +61,7 @@
import com.android.compose.animation.scene.SceneScope
import com.android.compose.animation.scene.TransitionState
import com.android.compose.animation.scene.animateSceneFloatAsState
+import com.android.compose.modifiers.thenIf
import com.android.compose.windowsizeclass.LocalWindowSizeClass
import com.android.systemui.battery.BatteryMeterViewController
import com.android.systemui.common.ui.compose.windowinsets.LocalRawScreenHeight
@@ -155,15 +156,22 @@
qsSceneAdapter = viewModel.qsSceneAdapter
)
+ val shouldPunchHoleBehindScrim =
+ layoutState.isTransitioningBetween(Scenes.Gone, Scenes.QuickSettings) ||
+ layoutState.isTransitioningBetween(Scenes.Lockscreen, Scenes.QuickSettings)
+
// TODO(b/280887232): implement the real UI.
Box(
modifier =
- modifier.fillMaxSize().graphicsLayer {
- // Render the scene to an offscreen buffer so that BlendMode.DstOut only clears this
- // scene (and not the one under it) during a scene transition.
- compositingStrategy = CompositingStrategy.Offscreen
- alpha = contentAlpha
- }
+ modifier
+ .fillMaxSize()
+ .graphicsLayer { alpha = contentAlpha }
+ .thenIf(shouldPunchHoleBehindScrim) {
+ // Render the scene to an offscreen buffer so that BlendMode.DstOut only clears
+ // this
+ // scene (and not the one under it) during a scene transition.
+ Modifier.graphicsLayer(compositingStrategy = CompositingStrategy.Offscreen)
+ }
) {
val isCustomizing by viewModel.qsSceneAdapter.isCustomizing.collectAsState()
val screenHeight = LocalRawScreenHeight.current
@@ -313,6 +321,7 @@
NotificationScrollingStack(
viewModel = notificationsPlaceholderViewModel,
maxScrimTop = { screenHeight },
+ shouldPunchHoleBehindScrim = shouldPunchHoleBehindScrim,
modifier =
Modifier.fillMaxWidth().offset { IntOffset(x = 0, y = screenHeight.roundToInt()) },
)
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 4c656b0..19c5800c 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
@@ -16,6 +16,7 @@
import com.android.systemui.scene.ui.composable.transitions.lockscreenToQuickSettingsTransition
import com.android.systemui.scene.ui.composable.transitions.lockscreenToShadeTransition
import com.android.systemui.scene.ui.composable.transitions.shadeToQuickSettingsTransition
+import com.android.systemui.shade.ui.composable.Shade
/**
* Comprehensive definition of all transitions between scenes in [SceneContainer].
@@ -73,10 +74,14 @@
// Scene overscroll
+ overscroll(Scenes.Gone, Orientation.Vertical) {}
overscroll(Scenes.Bouncer, Orientation.Vertical) {
translate(Bouncer.Elements.Content, y = { absoluteDistance })
}
overscroll(Scenes.Shade, Orientation.Vertical) {
- translate(Notifications.Elements.NotificationScrim, y = { absoluteDistance })
+ translate(
+ Notifications.Elements.NotificationScrim,
+ y = { Shade.Dimensions.ScrimOverscrollLimit }
+ )
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromShadeToQuickSettingsTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromShadeToQuickSettingsTransition.kt
index a9e5be9..8cd357e 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromShadeToQuickSettingsTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromShadeToQuickSettingsTransition.kt
@@ -1,10 +1,15 @@
package com.android.systemui.scene.ui.composable.transitions
import androidx.compose.animation.core.tween
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.ui.unit.IntSize
import com.android.compose.animation.scene.Edge
import com.android.compose.animation.scene.TransitionBuilder
+import com.android.compose.animation.scene.UserActionDistance
+import com.android.compose.animation.scene.UserActionDistanceScope
import com.android.systemui.notifications.ui.composable.Notifications
import com.android.systemui.qs.ui.composable.QuickSettings
+import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.ui.composable.ShadeHeader
import kotlin.time.Duration.Companion.milliseconds
@@ -12,6 +17,18 @@
durationScale: Double = 1.0,
) {
spec = tween(durationMillis = (DefaultDuration * durationScale).inWholeMilliseconds.toInt())
+ distance =
+ object : UserActionDistance {
+ override fun UserActionDistanceScope.absoluteDistance(
+ fromSceneSize: IntSize,
+ orientation: Orientation,
+ ): Float {
+ val distance =
+ Notifications.Elements.NotificationScrim.targetOffset(Scenes.Shade)?.y
+ ?: return 0f
+ return fromSceneSize.height - distance
+ }
+ }
translate(Notifications.Elements.NotificationScrim, Edge.Bottom)
timestampRange(endMillis = 83) { fade(QuickSettings.Elements.FooterActions) }
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 2f59217..df47cba 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,11 +16,19 @@
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 androidx.compose.foundation.gestures.Orientation
+import androidx.compose.ui.unit.IntSize
import com.android.compose.animation.scene.Edge
import com.android.compose.animation.scene.TransitionBuilder
+import com.android.compose.animation.scene.UserActionDistance
+import com.android.compose.animation.scene.UserActionDistanceScope
import com.android.systemui.notifications.ui.composable.Notifications
import com.android.systemui.qs.ui.composable.QuickSettings
+import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.shade.ui.composable.Shade
import com.android.systemui.shade.ui.composable.ShadeHeader
import kotlin.time.Duration.Companion.milliseconds
@@ -28,6 +36,20 @@
durationScale: Double = 1.0,
) {
spec = tween(durationMillis = (DefaultDuration * durationScale).inWholeMilliseconds.toInt())
+ swipeSpec =
+ spring(
+ stiffness = Spring.StiffnessMediumLow,
+ visibilityThreshold = Shade.Dimensions.ScrimVisibilityThreshold,
+ )
+ distance =
+ object : UserActionDistance {
+ override fun UserActionDistanceScope.absoluteDistance(
+ fromSceneSize: IntSize,
+ orientation: Orientation,
+ ): Float {
+ return Notifications.Elements.NotificationScrim.targetOffset(Scenes.Shade)?.y ?: 0f
+ }
+ }
fractionRange(start = .58f) {
fade(ShadeHeader.Elements.Clock)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt
new file mode 100644
index 0000000..d528736
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt
@@ -0,0 +1,147 @@
+/*
+ * 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.shade.ui.composable
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.ReadOnlyComposable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.unit.dp
+import com.android.compose.animation.scene.ElementKey
+import com.android.compose.animation.scene.LowestZIndexScenePicker
+import com.android.compose.animation.scene.SceneScope
+import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.shade.ui.viewmodel.OverlayShadeViewModel
+
+/** The overlay shade renders a lightweight shade UI container on top of a background scene. */
+@Composable
+fun SceneScope.OverlayShade(
+ viewModel: OverlayShadeViewModel,
+ horizontalArrangement: Arrangement.Horizontal,
+ modifier: Modifier = Modifier,
+ content: @Composable () -> Unit,
+) {
+ val backgroundScene by viewModel.backgroundScene.collectAsState()
+
+ Box(modifier) {
+ if (backgroundScene == Scenes.Lockscreen) {
+ Lockscreen()
+ }
+
+ Scrim(onClicked = viewModel::onScrimClicked)
+
+ Row(
+ modifier = Modifier.fillMaxSize().padding(OverlayShade.Dimensions.ScrimContentPadding),
+ horizontalArrangement = horizontalArrangement,
+ ) {
+ Panel(content = content)
+ }
+ }
+}
+
+@Composable
+private fun Lockscreen(
+ modifier: Modifier = Modifier,
+) {
+ // TODO(b/338025605): This is a placeholder, replace with the actual lockscreen.
+ Box(modifier = modifier.fillMaxSize().background(Color.LightGray)) {
+ Text(text = "Lockscreen", modifier = Modifier.align(Alignment.Center))
+ }
+}
+
+@Composable
+private fun SceneScope.Scrim(
+ onClicked: () -> Unit,
+ modifier: Modifier = Modifier,
+) {
+ Spacer(
+ modifier =
+ modifier
+ .element(OverlayShade.Elements.Scrim)
+ .fillMaxSize()
+ .background(OverlayShade.Colors.ScrimBackground)
+ .clickable(onClick = onClicked, interactionSource = null, indication = null)
+ )
+}
+
+@Composable
+private fun SceneScope.Panel(
+ modifier: Modifier = Modifier,
+ content: @Composable () -> Unit,
+) {
+ Box(
+ modifier =
+ modifier
+ .width(OverlayShade.Dimensions.PanelWidth)
+ .clip(OverlayShade.Shapes.RoundedCornerPanel)
+ ) {
+ Spacer(
+ modifier =
+ Modifier.element(OverlayShade.Elements.PanelBackground)
+ .matchParentSize()
+ .background(
+ color = OverlayShade.Colors.PanelBackground,
+ shape = OverlayShade.Shapes.RoundedCornerPanel,
+ ),
+ )
+
+ // This content is intentionally rendered as a separate element from the background in order
+ // to allow for more flexibility when defining transitions.
+ content()
+ }
+}
+
+object OverlayShade {
+ object Elements {
+ val Scrim = ElementKey("OverlayShadeScrim", scenePicker = LowestZIndexScenePicker)
+ val PanelBackground =
+ ElementKey("OverlayShadePanelBackground", scenePicker = LowestZIndexScenePicker)
+ }
+
+ object Colors {
+ val ScrimBackground = Color(0, 0, 0, alpha = 255 / 3)
+ val PanelBackground: Color
+ @Composable @ReadOnlyComposable get() = MaterialTheme.colorScheme.surfaceContainer
+ }
+
+ object Dimensions {
+ val ScrimContentPadding = 16.dp
+ val PanelCornerRadius = 46.dp
+ // TODO(b/338033836): This width should not be fixed.
+ val PanelWidth = 390.dp
+ }
+
+ object Shapes {
+ val RoundedCornerPanel = RoundedCornerShape(Dimensions.PanelCornerRadius)
+ }
+}
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 cda8059..3122b99 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
@@ -98,6 +98,8 @@
object Dimensions {
val ScrimCornerSize = 32.dp
val HorizontalPadding = 16.dp
+ const val ScrimOverscrollLimit = 100f
+ const val ScrimVisibilityThreshold = 5f
}
object Shapes {
@@ -195,12 +197,26 @@
) {
val maxNotifScrimTop = remember { mutableStateOf(0f) }
val tileSquishiness by
- animateSceneFloatAsState(value = 1f, key = QuickSettings.SharedValues.TilesSquishiness)
+ animateSceneFloatAsState(
+ value = 1f,
+ key = QuickSettings.SharedValues.TilesSquishiness,
+ canOverflow = false
+ )
val isClickable by viewModel.isClickable.collectAsState()
- // Render the scene to an offscreen buffer so that BlendMode.DstOut only clears this scene
- // (and not the one under it) during a scene transition.
- Box(modifier = modifier.graphicsLayer(compositingStrategy = CompositingStrategy.Offscreen)) {
+ val shouldPunchHoleBehindScrim =
+ layoutState.isTransitioningBetween(Scenes.Gone, Scenes.Shade) ||
+ layoutState.isTransitioningBetween(Scenes.Lockscreen, Scenes.Shade)
+
+ Box(
+ modifier =
+ modifier.thenIf(shouldPunchHoleBehindScrim) {
+ // Render the scene to an offscreen buffer so that BlendMode.DstOut only clears this
+ // scene
+ // (and not the one under it) during a scene transition.
+ Modifier.graphicsLayer(compositingStrategy = CompositingStrategy.Offscreen)
+ }
+ ) {
Box(
modifier =
Modifier.fillMaxSize()
@@ -231,10 +247,7 @@
Box(Modifier.element(QuickSettings.Elements.QuickQuickSettings)) {
QuickSettings(
viewModel.qsSceneAdapter,
- {
- (viewModel.qsSceneAdapter.qqsHeight * tileSquishiness)
- .roundToInt()
- },
+ { viewModel.qsSceneAdapter.qqsHeight },
isSplitShade = false,
squishiness = tileSquishiness,
)
@@ -254,6 +267,7 @@
NotificationScrollingStack(
viewModel = viewModel.notifications,
maxScrimTop = { maxNotifScrimTop.value },
+ shouldPunchHoleBehindScrim = shouldPunchHoleBehindScrim,
)
},
)
@@ -425,6 +439,7 @@
NotificationScrollingStack(
viewModel = viewModel.notifications,
maxScrimTop = { 0f },
+ shouldPunchHoleBehindScrim = false,
modifier =
Modifier.weight(1f)
.fillMaxHeight()
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/SliceAndroidView.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/SliceAndroidView.kt
index f354b80..74af3ca 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/SliceAndroidView.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/SliceAndroidView.kt
@@ -98,10 +98,10 @@
}
override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {
- return onClick != null || super.onInterceptTouchEvent(ev)
+ return (isSliceViewClickable && onClick != null) || super.onInterceptTouchEvent(ev)
}
override fun onClick(v: View?) {
- onClick?.let { it() } ?: super.onClick(v)
+ onClick?.takeIf { isSliceViewClickable }?.let { it() } ?: super.onClick(v)
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt
index 4f3a6c8..874c0a2 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt
@@ -38,6 +38,8 @@
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.role
import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.semantics.toggleableState
+import androidx.compose.ui.state.ToggleableState
import androidx.compose.ui.unit.dp
import com.android.systemui.common.ui.compose.Icon
import com.android.systemui.volume.panel.component.button.ui.viewmodel.ButtonViewModel
@@ -79,6 +81,12 @@
modifier =
Modifier.fillMaxSize().padding(8.dp).semantics {
role = Role.Switch
+ toggleableState =
+ if (viewModel.isActive) {
+ ToggleableState.On
+ } else {
+ ToggleableState.Off
+ }
contentDescription = label
},
onClick = { onCheckedChange(!viewModel.isActive) },
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/mediaoutput/MediaOutputModule.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/mediaoutput/MediaOutputModule.kt
index c73656e..f1cc71b 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/mediaoutput/MediaOutputModule.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/mediaoutput/MediaOutputModule.kt
@@ -16,9 +16,9 @@
package com.android.systemui.volume.panel.component.mediaoutput
-import com.android.systemui.volume.panel.component.mediaoutput.domain.MediaOutputAvailabilityCriteria
import com.android.systemui.volume.panel.component.mediaoutput.ui.composable.MediaOutputComponent
import com.android.systemui.volume.panel.component.shared.model.VolumePanelComponents
+import com.android.systemui.volume.panel.domain.AlwaysAvailableCriteria
import com.android.systemui.volume.panel.domain.ComponentAvailabilityCriteria
import com.android.systemui.volume.panel.shared.model.VolumePanelUiComponent
import dagger.Binds
@@ -39,6 +39,6 @@
@IntoMap
@StringKey(VolumePanelComponents.MEDIA_OUTPUT)
fun bindComponentAvailabilityCriteria(
- criteria: MediaOutputAvailabilityCriteria
+ criteria: AlwaysAvailableCriteria
): ComponentAvailabilityCriteria
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
index 4273b4f..ca64323 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
@@ -217,18 +217,18 @@
maybePruneMaps(layoutImpl, prevElement, prevSceneState)
}
- override fun isMeasurementApproachComplete(lookaheadSize: IntSize): Boolean {
+ override fun isMeasurementApproachInProgress(lookaheadSize: IntSize): Boolean {
// TODO(b/324191441): Investigate whether making this check more complex (checking if this
// element is shared or transformed) would lead to better performance.
- return layoutImpl.state.currentTransitions.isEmpty()
+ return layoutImpl.state.isTransitioning()
}
- override fun Placeable.PlacementScope.isPlacementApproachComplete(
+ override fun Placeable.PlacementScope.isPlacementApproachInProgress(
lookaheadCoordinates: LayoutCoordinates
): Boolean {
// TODO(b/324191441): Investigate whether making this check more complex (checking if this
// element is shared or transformed) would lead to better performance.
- return layoutImpl.state.currentTransitions.isEmpty()
+ return layoutImpl.state.isTransitioning()
}
@ExperimentalComposeUiApi
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt
index 7fb5a4d..339868c 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt
@@ -26,7 +26,7 @@
import androidx.compose.runtime.setValue
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
-import androidx.compose.ui.layout.intermediateLayout
+import androidx.compose.ui.layout.approachLayout
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.zIndex
@@ -74,7 +74,9 @@
Box(
modifier
.zIndex(zIndex)
- .intermediateLayout { measurable, constraints ->
+ .approachLayout(
+ isMeasurementApproachInProgress = { scope.layoutState.isTransitioning() }
+ ) { measurable, constraints ->
targetSize = lookaheadSize
val placeable = measurable.measure(constraints)
layout(placeable.width, placeable.height) { placeable.place(0, 0) }
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
index ad691ba..d383cec 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
@@ -251,8 +251,8 @@
private class LayoutNode(var layoutImpl: SceneTransitionLayoutImpl) :
Modifier.Node(), ApproachLayoutModifierNode {
- override fun isMeasurementApproachComplete(lookaheadSize: IntSize): Boolean {
- return layoutImpl.state.currentTransition == null
+ override fun isMeasurementApproachInProgress(lookaheadSize: IntSize): Boolean {
+ return layoutImpl.state.isTransitioning()
}
@ExperimentalComposeUiApi
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/modifiers/Size.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/modifiers/Size.kt
index bd36cb8..b392c67 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/modifiers/Size.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/modifiers/Size.kt
@@ -18,15 +18,17 @@
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
-import androidx.compose.ui.layout.intermediateLayout
+import androidx.compose.ui.layout.approachLayout
import androidx.compose.ui.unit.Constraints
import com.android.compose.animation.scene.SceneTransitionLayoutState
@OptIn(ExperimentalComposeUiApi::class)
internal fun Modifier.noResizeDuringTransitions(layoutState: SceneTransitionLayoutState): Modifier {
- return intermediateLayout { measurable, constraints ->
+ return approachLayout(isMeasurementApproachInProgress = { layoutState.isTransitioning() }) {
+ measurable,
+ constraints ->
if (layoutState.currentTransition == null) {
- return@intermediateLayout measurable.measure(constraints).run {
+ return@approachLayout measurable.measure(constraints).run {
layout(width, height) { place(0, 0) }
}
}
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 b1d7055..92e1b2c 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
@@ -46,7 +46,7 @@
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.layout.intermediateLayout
+import androidx.compose.ui.layout.approachLayout
import androidx.compose.ui.platform.LocalViewConfiguration
import androidx.compose.ui.test.assertIsNotDisplayed
import androidx.compose.ui.test.assertPositionInRootIsEqualTo
@@ -91,7 +91,9 @@
modifier
.offset(offset)
.element(key)
- .intermediateLayout { measurable, constraints ->
+ .approachLayout(
+ isMeasurementApproachInProgress = { layoutState.isTransitioning() }
+ ) { measurable, constraints ->
onLayout()
val placement = measurable.measure(constraints)
layout(placement.width, placement.height) {
@@ -525,7 +527,7 @@
// page should be composed.
HorizontalPager(
pagerState,
- outOfBoundsPageCount = 0,
+ beyondViewportPageCount = 0,
) { page ->
when (page) {
0 -> Box(Modifier.element(TestElements.Foo).fillMaxSize())
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnectionTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnectionTest.kt
index ac7717b..ce4c5275 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnectionTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnectionTest.kt
@@ -202,11 +202,11 @@
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun data(): List<TestCase> =
- listOf(
- TestCase(NestedScrollSource.Drag),
- TestCase(NestedScrollSource.Fling),
- TestCase(NestedScrollSource.Wheel),
+ fun data(): List<TestCase> {
+ return listOf(
+ TestCase(NestedScrollSource.UserInput),
+ TestCase(NestedScrollSource.SideEffect),
)
+ }
}
}
diff --git a/packages/SystemUI/docs/scene.md b/packages/SystemUI/docs/scene.md
index fb8a271..2f50bbd 100644
--- a/packages/SystemUI/docs/scene.md
+++ b/packages/SystemUI/docs/scene.md
@@ -62,7 +62,7 @@
NOTE: in case these instructions become stale and don't actually enable the
framework, please make sure `SceneContainerFlag.isEnabled` in the
-[`SceneContainerFlags.kt`](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt)
+[`SceneContainerFlag.kt`](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlag.kt)
file evalutes to `true`.
1. Set a collection of **aconfig flags** to `true` by running the following
@@ -74,10 +74,9 @@
$ adb shell device_config override systemui com.android.systemui.keyguard_wm_state_refactor true
$ adb shell device_config override systemui com.android.systemui.media_in_scene_container true
$ adb shell device_config override systemui com.android.systemui.migrate_clocks_to_blueprint true
- $ adb shell device_config override systemui com.android.systemui.notification_heads_up_refactor true
+ $ adb shell device_config override systemui com.android.systemui.notifications_heads_up_refactor true
$ adb shell device_config override systemui com.android.systemui.predictive_back_sysui true
$ adb shell device_config override systemui com.android.systemui.device_entry_udfps_refactor true
- $ adb shell device_config override systemui com.android.systemui.refactor_keyguard_dismiss_intent true
```
2. **Restart** System UI by issuing the following command:
```console
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
index aa70c45..3554b8fb 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
@@ -211,7 +211,6 @@
if (!SceneContainerFlag.isEnabled) {
mSetFlagsRule.disableFlags(
AConfigFlags.FLAG_KEYGUARD_WM_STATE_REFACTOR,
- AConfigFlags.FLAG_REFACTOR_KEYGUARD_DISMISS_INTENT,
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandlerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandlerTest.java
index 3395268..630bcd6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandlerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandlerTest.java
@@ -24,6 +24,7 @@
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.animation.AnimatorListenerAdapter;
@@ -163,6 +164,7 @@
* Ensures expansion only happens when touch down happens in valid part of the screen.
*/
@Test
+ @DisableFlags(Flags.FLAG_COMMUNAL_BOUNCER_DO_NOT_MODIFY_PLUGIN_OPEN)
public void testSessionStart() {
mTouchHandler.getTouchInitiationRegion(SCREEN_BOUNDS, mRegion, null);
@@ -193,7 +195,31 @@
2)).isTrue();
}
+
+ /**
+ * Ensures expansion only happens when touch down happens in valid part of the screen.
+ */
@Test
+ @EnableFlags(Flags.FLAG_COMMUNAL_BOUNCER_DO_NOT_MODIFY_PLUGIN_OPEN)
+ public void testSessionStart_doesNotModifyNotificationShadeWindow() {
+ mTouchHandler.getTouchInitiationRegion(SCREEN_BOUNDS, mRegion, null);
+
+ verify(mRegion).union(mRectCaptor.capture());
+ final Rect bounds = mRectCaptor.getValue();
+
+ final Rect expected = new Rect();
+
+ expected.set(0, Math.round(SCREEN_HEIGHT_PX * (1 - TOUCH_REGION)), SCREEN_WIDTH_PX,
+ SCREEN_HEIGHT_PX);
+
+ assertThat(bounds).isEqualTo(expected);
+
+ mTouchHandler.onSessionStart(mTouchSession);
+ verifyNoMoreInteractions(mNotificationShadeWindowController);
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_COMMUNAL_BOUNCER_DO_NOT_MODIFY_PLUGIN_OPEN)
public void testSwipeUp_whenBouncerInitiallyShowing_reduceHeightWithExclusionRects() {
mTouchHandler.getTouchInitiationRegion(SCREEN_BOUNDS, mRegion,
new Rect(0, 0, SCREEN_WIDTH_PX, SCREEN_HEIGHT_PX));
@@ -213,6 +239,7 @@
}
@Test
+ @DisableFlags(Flags.FLAG_COMMUNAL_BOUNCER_DO_NOT_MODIFY_PLUGIN_OPEN)
public void testSwipeUp_exclusionRectAtTop_doesNotIntersectGestureArea() {
mTouchHandler.getTouchInitiationRegion(SCREEN_BOUNDS, mRegion,
new Rect(0, 0, SCREEN_WIDTH_PX, SCREEN_HEIGHT_PX / 4));
@@ -228,6 +255,7 @@
}
@Test
+ @DisableFlags(Flags.FLAG_COMMUNAL_BOUNCER_DO_NOT_MODIFY_PLUGIN_OPEN)
public void testSwipeUp_exclusionRectBetweenNormalAndMinimumSwipeArea() {
final int normalSwipeAreaTop = SCREEN_HEIGHT_PX
- Math.round(SCREEN_HEIGHT_PX * TOUCH_REGION);
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
index c96a8ce..569116c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
@@ -24,17 +24,21 @@
import android.provider.Settings
import android.widget.RemoteViews
import androidx.test.filters.SmallTest
+import com.android.compose.animation.scene.ObservableTransitionState
import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
import com.android.systemui.SysuiTestCase
import com.android.systemui.communal.data.repository.FakeCommunalMediaRepository
+import com.android.systemui.communal.data.repository.FakeCommunalRepository
import com.android.systemui.communal.data.repository.FakeCommunalTutorialRepository
import com.android.systemui.communal.data.repository.FakeCommunalWidgetRepository
import com.android.systemui.communal.data.repository.fakeCommunalMediaRepository
+import com.android.systemui.communal.data.repository.fakeCommunalRepository
import com.android.systemui.communal.data.repository.fakeCommunalTutorialRepository
import com.android.systemui.communal.data.repository.fakeCommunalWidgetRepository
import com.android.systemui.communal.domain.interactor.communalInteractor
import com.android.systemui.communal.domain.interactor.communalTutorialInteractor
import com.android.systemui.communal.domain.model.CommunalContentModel
+import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel.Companion.POPUP_AUTO_HIDE_TIMEOUT_MS
@@ -45,8 +49,14 @@
import com.android.systemui.flags.andSceneContainer
import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.StatusBarState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.kosmos.testScope
import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager
@@ -63,6 +73,7 @@
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
@@ -94,6 +105,8 @@
private lateinit var mediaRepository: FakeCommunalMediaRepository
private lateinit var userRepository: FakeUserRepository
private lateinit var shadeTestUtil: ShadeTestUtil
+ private lateinit var keyguardTransitionRepository: FakeKeyguardTransitionRepository
+ private lateinit var communalRepository: FakeCommunalRepository
private lateinit var underTest: CommunalViewModel
@@ -106,12 +119,14 @@
MockitoAnnotations.initMocks(this)
keyguardRepository = kosmos.fakeKeyguardRepository
+ keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
tutorialRepository = kosmos.fakeCommunalTutorialRepository
widgetRepository = kosmos.fakeCommunalWidgetRepository
smartspaceRepository = kosmos.fakeSmartspaceRepository
mediaRepository = kosmos.fakeCommunalMediaRepository
userRepository = kosmos.fakeUserRepository
shadeTestUtil = kosmos.shadeTestUtil
+ communalRepository = kosmos.fakeCommunalRepository
kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, true)
mSetFlagsRule.enableFlags(FLAG_COMMUNAL_HUB)
@@ -125,6 +140,8 @@
underTest =
CommunalViewModel(
testScope,
+ context.resources,
+ kosmos.keyguardTransitionInteractor,
kosmos.communalInteractor,
kosmos.communalTutorialInteractor,
kosmos.shadeInteractor,
@@ -326,6 +343,129 @@
assertThat(underTest.canChangeScene()).isFalse()
}
+ @Test
+ fun touchesAllowed_shadeNotExpanded() =
+ testScope.runTest {
+ val touchesAllowed by collectLastValue(underTest.touchesAllowed)
+
+ // On keyguard without any shade expansion.
+ kosmos.fakeKeyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
+ shadeTestUtil.setLockscreenShadeExpansion(0f)
+ runCurrent()
+ assertThat(touchesAllowed).isTrue()
+ }
+
+ @Test
+ fun touchesAllowed_shadeExpanded() =
+ testScope.runTest {
+ val touchesAllowed by collectLastValue(underTest.touchesAllowed)
+
+ // On keyguard with shade fully expanded.
+ kosmos.fakeKeyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
+ shadeTestUtil.setLockscreenShadeExpansion(1f)
+ runCurrent()
+ assertThat(touchesAllowed).isFalse()
+ }
+
+ @Test
+ fun isFocusable_isFalse_whenTransitioningAwayFromGlanceableHub() =
+ testScope.runTest {
+ val isFocusable by collectLastValue(underTest.isFocusable)
+
+ // Shade not expanded.
+ shadeTestUtil.setLockscreenShadeExpansion(0f)
+ // On communal scene.
+ communalRepository.setTransitionState(
+ flowOf(ObservableTransitionState.Idle(CommunalScenes.Communal))
+ )
+ // Open bouncer.
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.GLANCEABLE_HUB,
+ to = KeyguardState.PRIMARY_BOUNCER,
+ transitionState = TransitionState.STARTED,
+ )
+ )
+
+ keyguardTransitionRepository.sendTransitionStep(
+ from = KeyguardState.GLANCEABLE_HUB,
+ to = KeyguardState.PRIMARY_BOUNCER,
+ transitionState = TransitionState.RUNNING,
+ value = 0.5f,
+ )
+ assertThat(isFocusable).isEqualTo(false)
+
+ // Transitioned to bouncer.
+ keyguardTransitionRepository.sendTransitionStep(
+ from = KeyguardState.GLANCEABLE_HUB,
+ to = KeyguardState.PRIMARY_BOUNCER,
+ transitionState = TransitionState.FINISHED,
+ value = 1f,
+ )
+ assertThat(isFocusable).isEqualTo(false)
+ }
+
+ @Test
+ fun isFocusable_isFalse_whenNotOnCommunalScene() =
+ testScope.runTest {
+ val isFocusable by collectLastValue(underTest.isFocusable)
+
+ keyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GLANCEABLE_HUB,
+ testScope = testScope,
+ )
+ shadeTestUtil.setLockscreenShadeExpansion(0f)
+ // Transitioned away from communal scene.
+ communalRepository.setTransitionState(
+ flowOf(ObservableTransitionState.Idle(CommunalScenes.Blank))
+ )
+
+ assertThat(isFocusable).isEqualTo(false)
+ }
+
+ @Test
+ fun isFocusable_isTrue_whenIdleOnCommunal_andShadeNotExpanded() =
+ testScope.runTest {
+ val isFocusable by collectLastValue(underTest.isFocusable)
+
+ // On communal scene.
+ communalRepository.setTransitionState(
+ flowOf(ObservableTransitionState.Idle(CommunalScenes.Communal))
+ )
+ // Transitioned to Glanceable hub.
+ keyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GLANCEABLE_HUB,
+ testScope = testScope,
+ )
+ // Shade not expanded.
+ shadeTestUtil.setLockscreenShadeExpansion(0f)
+
+ assertThat(isFocusable).isEqualTo(true)
+ }
+
+ @Test
+ fun isFocusable_isFalse_whenQsIsExpanded() =
+ testScope.runTest {
+ val isFocusable by collectLastValue(underTest.isFocusable)
+
+ // On communal scene.
+ communalRepository.setTransitionState(
+ flowOf(ObservableTransitionState.Idle(CommunalScenes.Communal))
+ )
+ // Transitioned to Glanceable hub.
+ keyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GLANCEABLE_HUB,
+ testScope = testScope,
+ )
+ // Qs is expanded.
+ shadeTestUtil.setQsExpansion(1f)
+
+ assertThat(isFocusable).isEqualTo(false)
+ }
+
private suspend fun setIsMainUser(isMainUser: Boolean) {
whenever(user.isMain).thenReturn(isMainUser)
userRepository.setUserInfos(listOf(user))
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorTest.kt
index d702330..360f284 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorTest.kt
@@ -33,6 +33,7 @@
package com.android.systemui.keyguard.domain.interactor
import android.os.PowerManager
+import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
@@ -46,6 +47,7 @@
import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.keyguard.util.KeyguardTransitionRepositorySpySubject.Companion.assertThat
import com.android.systemui.kosmos.testScope
import com.android.systemui.power.domain.interactor.PowerInteractor
@@ -317,6 +319,51 @@
}
@Test
+ @DisableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ fun testTransitionToGone_onWakeUp_ifPowerButtonGestureDetectedInAod_fromGone() =
+ testScope.runTest {
+ powerInteractor.setAwakeForTest()
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.AOD,
+ to = KeyguardState.GONE,
+ testScope,
+ )
+ runCurrent()
+
+ // Make sure we're GONE.
+ assertEquals(KeyguardState.GONE, kosmos.keyguardTransitionInteractor.getFinishedState())
+
+ // Start going to AOD on first button push
+ powerInteractor.onStartedGoingToSleep(PowerManager.GO_TO_SLEEP_REASON_MIN)
+ transitionRepository.sendTransitionSteps(
+ listOf(
+ TransitionStep(
+ from = KeyguardState.GONE,
+ to = KeyguardState.AOD,
+ transitionState = TransitionState.STARTED,
+ value = 0f
+ ),
+ TransitionStep(
+ from = KeyguardState.GONE,
+ to = KeyguardState.AOD,
+ transitionState = TransitionState.RUNNING,
+ value = 0.1f
+ ),
+ ),
+ testScope = testScope,
+ )
+
+ // Detect a power gesture and then wake up.
+ reset(transitionRepository)
+ powerInteractor.onCameraLaunchGestureDetected()
+ powerInteractor.setAwakeForTest()
+ advanceTimeBy(100) // account for debouncing
+
+ assertThat(transitionRepository)
+ .startedTransition(from = KeyguardState.AOD, to = KeyguardState.GONE)
+ }
+
+ @Test
fun testTransitionToLockscreen_onWakeUpFromAod_dismissibleKeyguard_securitySwipe() =
testScope.runTest {
kosmos.fakeKeyguardRepository.setKeyguardShowing(true)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
index 12f8918..323087c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
@@ -24,23 +24,23 @@
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.systemui.Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR
import com.android.systemui.SysuiTestCase
-import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
-import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
-import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
+import com.android.systemui.bouncer.data.repository.keyguardBouncerRepository
+import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.EnableSceneContainer
-import com.android.systemui.keyguard.data.repository.FakeCommandQueue
+import com.android.systemui.keyguard.data.repository.fakeCommandQueue
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.CameraLaunchSourceModel
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.StatusBarState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.kosmos.testScope
-import com.android.systemui.power.domain.interactor.PowerInteractorFactory
+import com.android.systemui.res.R
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.model.Scenes
-import com.android.systemui.shade.data.repository.FakeShadeRepository
-import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor
+import com.android.systemui.shade.data.repository.shadeRepository
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -60,33 +60,17 @@
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
- private val repository by lazy { kosmos.fakeKeyguardRepository }
- private val sceneInteractor by lazy { kosmos.sceneInteractor }
- private val fromGoneTransitionInteractor by lazy { kosmos.fromGoneTransitionInteractor }
- private val commandQueue by lazy { FakeCommandQueue() }
- private val bouncerRepository = FakeKeyguardBouncerRepository()
- private val shadeRepository = FakeShadeRepository()
+ private val repository = kosmos.fakeKeyguardRepository
+ private val sceneInteractor = kosmos.sceneInteractor
+ private val fromGoneTransitionInteractor = kosmos.fromGoneTransitionInteractor
+ private val commandQueue = kosmos.fakeCommandQueue
+ private val configRepository = kosmos.fakeConfigurationRepository
+ private val bouncerRepository = kosmos.keyguardBouncerRepository
+ private val shadeRepository = kosmos.shadeRepository
private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
private val transitionState: MutableStateFlow<ObservableTransitionState> =
MutableStateFlow(ObservableTransitionState.Idle(Scenes.Gone))
-
- private val underTest by lazy {
- KeyguardInteractor(
- repository = repository,
- commandQueue = commandQueue,
- powerInteractor = PowerInteractorFactory.create().powerInteractor,
- bouncerRepository = bouncerRepository,
- configurationInteractor = ConfigurationInteractor(FakeConfigurationRepository()),
- shadeRepository = shadeRepository,
- keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor,
- sceneInteractorProvider = { sceneInteractor },
- fromGoneTransitionInteractor = { fromGoneTransitionInteractor },
- sharedNotificationContainerInteractor = {
- kosmos.sharedNotificationContainerInteractor
- },
- applicationScope = testScope,
- )
- }
+ private val underTest = kosmos.keyguardInteractor
@Before
fun setUp() {
@@ -247,6 +231,84 @@
}
@Test
+ fun keyguardTranslationY_whenGoneEmitsZero() =
+ testScope.runTest {
+ val keyguardTranslationY by collectLastValue(underTest.keyguardTranslationY)
+
+ configRepository.setDimensionPixelSize(
+ R.dimen.keyguard_translate_distance_on_swipe_up,
+ 100
+ )
+ configRepository.onAnyConfigurationChange()
+
+ shadeRepository.setLegacyShadeExpansion(0f)
+
+ keyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.AOD,
+ to = KeyguardState.GONE,
+ testScope,
+ )
+
+ assertThat(keyguardTranslationY).isEqualTo(0f)
+ }
+
+ @Test
+ fun keyguardTranslationY_whenNotGoneAndShadeIsFullyCollapsedEmitsZero() =
+ testScope.runTest {
+ val keyguardTranslationY by collectLastValue(underTest.keyguardTranslationY)
+
+ configRepository.setDimensionPixelSize(
+ R.dimen.keyguard_translate_distance_on_swipe_up,
+ 100
+ )
+ configRepository.onAnyConfigurationChange()
+
+ shadeRepository.setLegacyShadeExpansion(0f)
+
+ keyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.AOD,
+ to = KeyguardState.LOCKSCREEN,
+ testScope,
+ )
+
+ assertThat(keyguardTranslationY).isEqualTo(0f)
+ }
+
+ @Test
+ fun keyguardTranslationY_whenTransitioningToGoneAndShadeIsExpandingEmitsNonZero() =
+ testScope.runTest {
+ val keyguardTranslationY by collectLastValue(underTest.keyguardTranslationY)
+
+ configRepository.setDimensionPixelSize(
+ R.dimen.keyguard_translate_distance_on_swipe_up,
+ 100
+ )
+ configRepository.onAnyConfigurationChange()
+
+ shadeRepository.setLegacyShadeExpansion(0.5f)
+
+ keyguardTransitionRepository.sendTransitionSteps(
+ listOf(
+ TransitionStep(
+ from = KeyguardState.AOD,
+ to = KeyguardState.GONE,
+ value = 0f,
+ transitionState = TransitionState.STARTED,
+ ),
+ TransitionStep(
+ from = KeyguardState.AOD,
+ to = KeyguardState.GONE,
+ value = 0.1f,
+ transitionState = TransitionState.RUNNING,
+ ),
+ ),
+ testScope,
+ )
+
+ assertThat(keyguardTranslationY).isGreaterThan(0f)
+ }
+
+ @Test
@EnableSceneContainer
fun animationDozingTransitions() =
testScope.runTest {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt
similarity index 85%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt
index 0a29821..3b6f6a2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -12,20 +12,19 @@
* 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.domain.interactor
-import androidx.test.ext.junit.runners.AndroidJUnit4
+import android.platform.test.flag.junit.FlagsParameterization
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository
-import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
import com.android.systemui.common.ui.domain.interactor.configurationInteractor
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.doze.util.BurnInHelperWrapper
+import com.android.systemui.flags.andSceneContainer
import com.android.systemui.keyguard.data.repository.FakeCommandQueue
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.shared.model.StatusBarState
@@ -33,7 +32,10 @@
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
import com.android.systemui.power.domain.interactor.PowerInteractorFactory
-import com.android.systemui.shade.data.repository.FakeShadeRepository
+import com.android.systemui.shade.data.repository.fakeShadeRepository
+import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.shade.domain.interactor.shadeLockscreenInteractor
+import com.android.systemui.shade.shadeTestUtil
import com.android.systemui.statusbar.phone.SystemUIDialogManager
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.argumentCaptor
@@ -50,15 +52,18 @@
import org.mockito.Mock
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
@ExperimentalCoroutinesApi
@SmallTest
-@RunWith(AndroidJUnit4::class)
-class UdfpsKeyguardInteractorTest : SysuiTestCase() {
+@RunWith(ParameterizedAndroidJunit4::class)
+class UdfpsKeyguardInteractorTest(flags: FlagsParameterization?) : SysuiTestCase() {
val kosmos = testKosmos()
val testScope = kosmos.testScope
- val configRepository = kosmos.fakeConfigurationRepository
val keyguardRepository = kosmos.fakeKeyguardRepository
+ val shadeRepository = kosmos.fakeShadeRepository
+ val shadeTestUtil by lazy { kosmos.shadeTestUtil }
private val burnInProgress = 1f
private val burnInYOffset = 20
@@ -67,7 +72,6 @@
private lateinit var bouncerRepository: KeyguardBouncerRepository
private lateinit var fakeCommandQueue: FakeCommandQueue
private lateinit var burnInInteractor: BurnInInteractor
- private lateinit var shadeRepository: FakeShadeRepository
private lateinit var powerInteractor: PowerInteractor
@Mock private lateinit var burnInHelper: BurnInHelperWrapper
@@ -75,11 +79,22 @@
private lateinit var underTest: UdfpsKeyguardInteractor
+ companion object {
+ @JvmStatic
+ @Parameters(name = "{0}")
+ fun getParams(): List<FlagsParameterization> {
+ return FlagsParameterization.allCombinationsOf().andSceneContainer()
+ }
+ }
+
+ init {
+ mSetFlagsRule.setFlagsParameterization(flags!!)
+ }
+
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
bouncerRepository = FakeKeyguardBouncerRepository()
- shadeRepository = FakeShadeRepository()
fakeCommandQueue = FakeCommandQueue()
burnInInteractor =
BurnInInteractor(
@@ -93,10 +108,10 @@
underTest =
UdfpsKeyguardInteractor(
- configRepository,
burnInInteractor,
kosmos.keyguardInteractor,
- shadeRepository,
+ kosmos.shadeInteractor,
+ kosmos.shadeLockscreenInteractor,
dialogManager,
)
}
@@ -183,13 +198,13 @@
val qsProgress by collectLastValue(underTest.qsProgress)
assertThat(qsProgress).isEqualTo(0f)
- shadeRepository.setQsExpansion(.22f)
+ shadeTestUtil.setQsExpansion(.22f)
assertThat(qsProgress).isEqualTo(.44f)
- shadeRepository.setQsExpansion(.5f)
+ shadeTestUtil.setQsExpansion(.5f)
assertThat(qsProgress).isEqualTo(1f)
- shadeRepository.setQsExpansion(.7f)
+ shadeTestUtil.setQsExpansion(.7f)
assertThat(qsProgress).isEqualTo(1f)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModelTest.kt
index 5bf0f4b..692ffee 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModelTest.kt
@@ -36,7 +36,6 @@
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
-import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -54,11 +53,6 @@
private val sysuiStatusBarStateController = kosmos.sysuiStatusBarStateController
private val underTest by lazy { kosmos.alternateBouncerToGoneTransitionViewModel }
- @Before
- fun setup() {
- mSetFlagsRule.disableFlags(com.android.systemui.Flags.FLAG_REFACTOR_KEYGUARD_DISMISS_INTENT)
- }
-
@Test
fun deviceEntryParentViewDisappear() =
testScope.runTest {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModelTest.kt
index 854a478..9fab603 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModelTest.kt
@@ -32,7 +32,6 @@
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
-import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -48,11 +47,6 @@
private val keyguardTransitionRepository by lazy { kosmos.fakeKeyguardTransitionRepository }
private val underTest by lazy { kosmos.alternateBouncerToPrimaryBouncerTransitionViewModel }
- @Before
- fun setup() {
- mSetFlagsRule.disableFlags(com.android.systemui.Flags.FLAG_REFACTOR_KEYGUARD_DISMISS_INTENT)
- }
-
@Test
fun deviceEntryParentViewDisappear() =
testScope.runTest {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsTest.kt
index ee217a6..fee18dd 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsTest.kt
@@ -69,7 +69,6 @@
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
- mSetFlagsRule.disableFlags(com.android.systemui.Flags.FLAG_REFACTOR_KEYGUARD_DISMISS_INTENT)
whenever(primaryBouncerInteractor.willRunDismissFromKeyguard()).thenReturn(false)
sysuiStatusBarStateController.setLeaveOpenOnKeyguardHide(false)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
index e3eca67..2e1765a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
@@ -19,8 +19,8 @@
package com.android.systemui.keyguard.ui.viewmodel
+import android.platform.test.flag.junit.FlagsParameterization
import android.view.View
-import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.systemui.Flags as AConfigFlags
@@ -30,13 +30,15 @@
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
+import com.android.systemui.flags.parameterizeSceneContainerFlag
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
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.TransitionStep
import com.android.systemui.kosmos.testScope
-import com.android.systemui.shade.data.repository.fakeShadeRepository
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import com.android.systemui.shade.shadeTestUtil
import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationsKeyguardInteractor
import com.android.systemui.statusbar.phone.dozeParameters
import com.android.systemui.statusbar.phone.screenOffAnimationController
@@ -53,32 +55,48 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
@SmallTest
-@RunWith(AndroidJUnit4::class)
-class KeyguardRootViewModelTest : SysuiTestCase() {
+@RunWith(ParameterizedAndroidJunit4::class)
+class KeyguardRootViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
- private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
- private val keyguardRepository = kosmos.fakeKeyguardRepository
- private val communalRepository = kosmos.communalRepository
- private val screenOffAnimationController = kosmos.screenOffAnimationController
- private val deviceEntryRepository = kosmos.fakeDeviceEntryRepository
- private val notificationsKeyguardInteractor = kosmos.notificationsKeyguardInteractor
- private val dozeParameters = kosmos.dozeParameters
- private val shadeRepository = kosmos.fakeShadeRepository
+ private val keyguardTransitionRepository by lazy { kosmos.fakeKeyguardTransitionRepository }
+ private val keyguardRepository by lazy { kosmos.fakeKeyguardRepository }
+ private val communalRepository by lazy { kosmos.communalRepository }
+ private val screenOffAnimationController by lazy { kosmos.screenOffAnimationController }
+ private val deviceEntryRepository by lazy { kosmos.fakeDeviceEntryRepository }
+ private val notificationsKeyguardInteractor by lazy { kosmos.notificationsKeyguardInteractor }
+ private val dozeParameters by lazy { kosmos.dozeParameters }
+ private val shadeTestUtil by lazy { kosmos.shadeTestUtil }
private val underTest by lazy { kosmos.keyguardRootViewModel }
private val viewState = ViewStateAccessor()
+ // add to init block
+ companion object {
+ @JvmStatic
+ @Parameters(name = "{0}")
+ fun getParams(): List<FlagsParameterization> {
+ return parameterizeSceneContainerFlag()
+ }
+ }
+
+ init {
+ mSetFlagsRule.setFlagsParameterization(flags!!)
+ }
+
@Before
fun setUp() {
- mSetFlagsRule.enableFlags(AConfigFlags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR)
mSetFlagsRule.enableFlags(FLAG_NEW_AOD_TRANSITION)
- mSetFlagsRule.disableFlags(
- AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT,
- AConfigFlags.FLAG_REFACTOR_KEYGUARD_DISMISS_INTENT,
- )
+ if (!SceneContainerFlag.isEnabled) {
+ mSetFlagsRule.enableFlags(AConfigFlags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR)
+ mSetFlagsRule.disableFlags(
+ AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT,
+ )
+ }
}
@Test
@@ -336,10 +354,10 @@
testScope,
)
- shadeRepository.setQsExpansion(0f)
+ shadeTestUtil.setQsExpansion(0f)
assertThat(alpha).isEqualTo(1f)
- shadeRepository.setQsExpansion(0.5f)
+ shadeTestUtil.setQsExpansion(0.5f)
assertThat(alpha).isEqualTo(0f)
}
@@ -356,11 +374,11 @@
)
// Open the shade.
- shadeRepository.setQsExpansion(1f)
+ shadeTestUtil.setQsExpansion(1f)
assertThat(alpha).isEqualTo(0f)
// Close the shade, alpha returns to 1.
- shadeRepository.setQsExpansion(0f)
+ shadeTestUtil.setQsExpansion(0f)
assertThat(alpha).isEqualTo(1f)
}
@@ -377,11 +395,11 @@
)
// Open the shade.
- shadeRepository.setQsExpansion(1f)
+ shadeTestUtil.setQsExpansion(1f)
assertThat(alpha).isEqualTo(0f)
// Close the shade, alpha is still 0 since we're not on the lockscreen.
- shadeRepository.setQsExpansion(0f)
+ shadeTestUtil.setQsExpansion(0f)
assertThat(alpha).isEqualTo(0f)
}
@@ -400,7 +418,7 @@
assertThat(alpha).isEqualTo(0f)
// Try pulling down shade and ensure the value doesn't change
- shadeRepository.setQsExpansion(0.5f)
+ shadeTestUtil.setQsExpansion(0.5f)
assertThat(alpha).isEqualTo(0f)
}
@@ -419,7 +437,7 @@
assertThat(alpha).isEqualTo(0f)
// Try pulling down shade and ensure the value doesn't change
- shadeRepository.setQsExpansion(0.5f)
+ shadeTestUtil.setQsExpansion(0.5f)
assertThat(alpha).isEqualTo(0f)
}
@@ -438,7 +456,7 @@
assertThat(alpha).isEqualTo(0f)
// Try pulling down shade and ensure the value doesn't change
- shadeRepository.setQsExpansion(0.5f)
+ shadeTestUtil.setQsExpansion(0.5f)
assertThat(alpha).isEqualTo(0f)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt
index 4907359..ec2cb04 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt
@@ -16,13 +16,15 @@
package com.android.systemui.keyguard.ui.viewmodel
-import androidx.test.ext.junit.runners.AndroidJUnit4
+import android.platform.test.flag.junit.FlagsParameterization
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.authController
import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.DisableSceneContainer
import com.android.systemui.flags.Flags
+import com.android.systemui.flags.andSceneContainer
import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.keyguard.data.repository.fakeKeyguardClockRepository
import com.android.systemui.keyguard.shared.model.ClockSize
@@ -40,15 +42,29 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
@SmallTest
-@RunWith(AndroidJUnit4::class)
-class LockscreenContentViewModelTest : SysuiTestCase() {
+@RunWith(ParameterizedAndroidJunit4::class)
+class LockscreenContentViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() {
private val kosmos: Kosmos = testKosmos()
lateinit var underTest: LockscreenContentViewModel
+ companion object {
+ @JvmStatic
+ @Parameters(name = "{0}")
+ fun getParams(): List<FlagsParameterization> {
+ return FlagsParameterization.allCombinationsOf().andSceneContainer()
+ }
+ }
+
+ init {
+ mSetFlagsRule.setFlagsParameterization(flags!!)
+ }
+
@Before
fun setup() {
with(kosmos) {
@@ -77,6 +93,7 @@
}
@Test
+ @DisableSceneContainer
fun clockSize_withLargeClock_true() =
with(kosmos) {
testScope.runTest {
@@ -87,6 +104,7 @@
}
@Test
+ @DisableSceneContainer
fun clockSize_withSmallClock_false() =
with(kosmos) {
testScope.runTest {
@@ -109,6 +127,7 @@
}
@Test
+ @DisableSceneContainer
fun areNotificationsVisible_withSmallClock_true() =
with(kosmos) {
testScope.runTest {
@@ -119,6 +138,7 @@
}
@Test
+ @DisableSceneContainer
fun areNotificationsVisible_withLargeClock_false() =
with(kosmos) {
testScope.runTest {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
index 776f1a5..bc9d257 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
@@ -33,6 +33,7 @@
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
+import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.kosmos.testScope
import com.android.systemui.power.data.repository.fakePowerRepository
import com.android.systemui.power.shared.model.WakefulnessState
@@ -57,6 +58,7 @@
@SmallTest
@RunWith(ParameterizedAndroidJunit4::class)
+@EnableSceneContainer
class LockscreenSceneViewModelTest : SysuiTestCase() {
companion object {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt
index 18e9009..278c90a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt
@@ -55,7 +55,6 @@
@Before
fun setUp() {
- mSetFlagsRule.disableFlags(com.android.systemui.Flags.FLAG_REFACTOR_KEYGUARD_DISMISS_INTENT)
whenever(primaryBouncerInteractor.willRunDismissFromKeyguard()).thenReturn(false)
sysuiStatusBarStateController.setLeaveOpenOnKeyguardHide(false)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepositoryImplTest.kt
index 9c0674d..bc57ce6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepositoryImplTest.kt
@@ -245,6 +245,33 @@
assertThat(flowForUser2).isNotEqualTo(flowForUser1)
}
+ // Tests that a ServiceInfo that is returned by queryIntentServicesAsUser but shortly
+ // after uninstalled, doesn't crash SystemUI.
+ @Test
+ fun packageUninstalledAfterQuery_noCrash_noComponent() =
+ testScope.runTest {
+ val userId = 0
+ val resolveInfo =
+ ResolveInfo(TEST_COMPONENT, hasPermission = true, defaultEnabled = true)
+
+ val componentNames by collectLastValue(underTest.getInstalledTilesComponents(userId))
+
+ whenever(
+ packageManager.queryIntentServicesAsUser(
+ matchIntent(),
+ matchFlags(),
+ eq(userId)
+ )
+ )
+ .thenReturn(listOf(resolveInfo))
+ whenever(packageManager.getComponentEnabledSetting(TEST_COMPONENT))
+ .thenThrow(IllegalArgumentException())
+ kosmos.fakePackageChangeRepository.notifyChange(PackageChangeModel.Empty)
+ runCurrent()
+
+ assertThat(componentNames).isEmpty()
+ }
+
companion object {
private val INTENT = Intent(TileService.ACTION_QS_TILE)
private val FLAGS =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractorTest.kt
index 769a54a..13d6411 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractorTest.kt
@@ -58,13 +58,16 @@
import com.android.systemui.util.CarrierConfigTracker
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
+import org.junit.Assume.assumeFalse
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-@TestableLooper.RunWithLooper
+@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
class InternetTileDataInteractorTest : SysuiTestCase() {
@@ -141,6 +144,7 @@
underTest =
InternetTileDataInteractor(
context,
+ testScope.coroutineContext,
testScope.backgroundScope,
airplaneModeRepository,
connectivityRepository,
@@ -433,8 +437,44 @@
.isEqualTo(expectedCd)
}
+ /**
+ * We expect a RuntimeException because [underTest] instantiates a SignalDrawable on the
+ * provided context, and so the SignalDrawable constructor attempts to instantiate a Handler()
+ * on the mentioned context. Since that context does not have a looper assigned to it, the
+ * handler instantiation will throw a RuntimeException.
+ *
+ * TODO(b/338068066): Robolectric behavior differs in that it does not throw the exception
+ * So either we should make Robolectric behvase similar to the device test, or change this
+ * test to look for a different signal than the exception, when run by Robolectric. For now
+ * we just assume the test is not Robolectric.
+ */
+ @Test(expected = java.lang.RuntimeException::class)
+ fun mobileDefault_usesNetworkNameAndIcon_throwsRunTimeException() =
+ testScope.runTest {
+ assumeFalse(isRobolectricTest());
+
+ collectLastValue(underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest)))
+
+ connectivityRepository.setMobileConnected()
+ mobileConnectionsRepository.mobileIsDefault.value = true
+ mobileConnectionRepository.apply {
+ setAllLevels(3)
+ setAllRoaming(false)
+ networkName.value = NetworkNameModel.Default("test network")
+ }
+
+ runCurrent()
+ }
+
+ /**
+ * See [mobileDefault_usesNetworkNameAndIcon_throwsRunTimeException] for description of the
+ * problem this test solves. The solution here is to assign a looper to the context via
+ * RunWithLooper. In the production code, the solution is to use a Main CoroutineContext for
+ * creating the SignalDrawable.
+ */
+ @TestableLooper.RunWithLooper
@Test
- fun mobileDefault_usesNetworkNameAndIcon() =
+ fun mobileDefault_run_withLooper_usesNetworkNameAndIcon() =
testScope.runTest {
val latest by
collectLastValue(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
index 2fb8212..f58e01f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
@@ -30,6 +30,7 @@
import com.android.systemui.scene.data.repository.sceneContainerRepository
import com.android.systemui.scene.sceneContainerConfig
import com.android.systemui.scene.sceneKeys
+import com.android.systemui.scene.shared.model.SceneContainerConfig
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.shared.model.fakeSceneDataSource
import com.android.systemui.testKosmos
@@ -39,7 +40,6 @@
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
-import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -54,19 +54,46 @@
private lateinit var underTest: SceneInteractor
- @Before
- fun setUp() {
- underTest = kosmos.sceneInteractor
- }
-
@Test
fun allSceneKeys() {
+ underTest = kosmos.sceneInteractor
assertThat(underTest.allSceneKeys()).isEqualTo(kosmos.sceneKeys)
}
@Test
+ fun changeScene_toUnknownScene_doesNothing() =
+ testScope.runTest {
+ val sceneKeys =
+ listOf(
+ Scenes.QuickSettings,
+ Scenes.Shade,
+ Scenes.Lockscreen,
+ Scenes.Gone,
+ Scenes.Communal,
+ )
+ val navigationDistances =
+ mapOf(
+ Scenes.Gone to 0,
+ Scenes.Lockscreen to 0,
+ Scenes.Communal to 1,
+ Scenes.Shade to 2,
+ Scenes.QuickSettings to 3,
+ )
+ kosmos.sceneContainerConfig =
+ SceneContainerConfig(sceneKeys, Scenes.Lockscreen, navigationDistances)
+ underTest = kosmos.sceneInteractor
+ val currentScene by collectLastValue(underTest.currentScene)
+ val previousScene = currentScene
+ assertThat(previousScene).isNotEqualTo(Scenes.Bouncer)
+ underTest.changeScene(Scenes.Bouncer, "reason")
+ assertThat(currentScene).isEqualTo(previousScene)
+ }
+
+ @Test
fun changeScene() =
testScope.runTest {
+ underTest = kosmos.sceneInteractor
+
val currentScene by collectLastValue(underTest.currentScene)
assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
@@ -77,6 +104,8 @@
@Test
fun changeScene_toGoneWhenUnl_doesNotThrow() =
testScope.runTest {
+ underTest = kosmos.sceneInteractor
+
val currentScene by collectLastValue(underTest.currentScene)
assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
@@ -91,11 +120,15 @@
@Test(expected = IllegalStateException::class)
fun changeScene_toGoneWhenStillLocked_throws() =
- testScope.runTest { underTest.changeScene(Scenes.Gone, "reason") }
+ testScope.runTest {
+ underTest = kosmos.sceneInteractor
+ underTest.changeScene(Scenes.Gone, "reason")
+ }
@Test
fun sceneChanged_inDataSource() =
testScope.runTest {
+ underTest = kosmos.sceneInteractor
val currentScene by collectLastValue(underTest.currentScene)
assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
@@ -107,6 +140,7 @@
@Test
fun transitionState() =
testScope.runTest {
+ underTest = kosmos.sceneInteractor
val underTest = kosmos.sceneContainerRepository
val transitionState =
MutableStateFlow<ObservableTransitionState>(
@@ -143,6 +177,7 @@
@Test
fun transitioningTo() =
testScope.runTest {
+ underTest = kosmos.sceneInteractor
val transitionState =
MutableStateFlow<ObservableTransitionState>(
ObservableTransitionState.Idle(underTest.currentScene.value)
@@ -179,6 +214,7 @@
@Test
fun isTransitionUserInputOngoing_idle_false() =
testScope.runTest {
+ underTest = kosmos.sceneInteractor
val transitionState =
MutableStateFlow<ObservableTransitionState>(
ObservableTransitionState.Idle(Scenes.Shade)
@@ -193,6 +229,7 @@
@Test
fun isTransitionUserInputOngoing_transition_true() =
testScope.runTest {
+ underTest = kosmos.sceneInteractor
val transitionState =
MutableStateFlow<ObservableTransitionState>(
ObservableTransitionState.Transition(
@@ -213,6 +250,7 @@
@Test
fun isTransitionUserInputOngoing_updateMidTransition_false() =
testScope.runTest {
+ underTest = kosmos.sceneInteractor
val transitionState =
MutableStateFlow<ObservableTransitionState>(
ObservableTransitionState.Transition(
@@ -244,6 +282,7 @@
@Test
fun isTransitionUserInputOngoing_updateOnIdle_false() =
testScope.runTest {
+ underTest = kosmos.sceneInteractor
val transitionState =
MutableStateFlow<ObservableTransitionState>(
ObservableTransitionState.Transition(
@@ -268,6 +307,7 @@
@Test
fun isVisible() =
testScope.runTest {
+ underTest = kosmos.sceneInteractor
val isVisible by collectLastValue(underTest.isVisible)
assertThat(isVisible).isTrue()
@@ -281,6 +321,7 @@
@Test
fun isVisible_duringRemoteUserInteraction_forcedVisible() =
testScope.runTest {
+ underTest = kosmos.sceneInteractor
underTest.setVisible(false, "reason")
val isVisible by collectLastValue(underTest.isVisible)
assertThat(isVisible).isFalse()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/OverlayShadeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/OverlayShadeViewModelTest.kt
new file mode 100644
index 0000000..0ffabd8
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/OverlayShadeViewModelTest.kt
@@ -0,0 +1,163 @@
+/*
+ * 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.shade.ui.viewmodel
+
+import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
+import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
+import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
+import com.android.systemui.flags.EnableSceneContainer
+import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@TestableLooper.RunWithLooper
+@EnableSceneContainer
+class OverlayShadeViewModelTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val sceneInteractor = kosmos.sceneInteractor
+ private val deviceUnlockedInteractor by lazy { kosmos.deviceUnlockedInteractor }
+
+ private val underTest = kosmos.overlayShadeViewModel
+
+ @Test
+ fun backgroundScene_deviceLocked_lockscreen() =
+ testScope.runTest {
+ val backgroundScene by collectLastValue(underTest.backgroundScene)
+
+ lockDevice()
+
+ assertThat(backgroundScene).isEqualTo(Scenes.Lockscreen)
+ }
+
+ @Test
+ fun backgroundScene_deviceUnlocked_gone() =
+ testScope.runTest {
+ val backgroundScene by collectLastValue(underTest.backgroundScene)
+
+ lockDevice()
+ unlockDevice()
+
+ assertThat(backgroundScene).isEqualTo(Scenes.Gone)
+ }
+
+ @Test
+ fun backgroundScene_authMethodSwipe_lockscreenNotDismissed_goesToLockscreen() =
+ testScope.runTest {
+ val backgroundScene by collectLastValue(underTest.backgroundScene)
+ val deviceUnlockStatus by collectLastValue(deviceUnlockedInteractor.deviceUnlockStatus)
+
+ kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.None
+ )
+ assertThat(deviceUnlockStatus?.isUnlocked).isTrue()
+ sceneInteractor.changeScene(Scenes.Lockscreen, "reason")
+ runCurrent()
+
+ assertThat(backgroundScene).isEqualTo(Scenes.Lockscreen)
+ }
+
+ @Test
+ fun backgroundScene_authMethodSwipe_lockscreenDismissed_goesToGone() =
+ testScope.runTest {
+ val backgroundScene by collectLastValue(underTest.backgroundScene)
+ val deviceUnlockStatus by collectLastValue(deviceUnlockedInteractor.deviceUnlockStatus)
+
+ kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.None
+ )
+ assertThat(deviceUnlockStatus?.isUnlocked).isTrue()
+ sceneInteractor.changeScene(Scenes.Gone, "reason")
+ runCurrent()
+
+ assertThat(backgroundScene).isEqualTo(Scenes.Gone)
+ }
+
+ @Test
+ fun onScrimClicked_onLockscreen_goesToLockscreen() =
+ testScope.runTest {
+ val currentScene by collectLastValue(sceneInteractor.currentScene)
+ lockDevice()
+ sceneInteractor.changeScene(Scenes.Bouncer, "reason")
+ runCurrent()
+ assertThat(currentScene).isNotEqualTo(Scenes.Lockscreen)
+
+ underTest.onScrimClicked()
+
+ assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
+ }
+
+ @Test
+ fun onScrimClicked_deviceWasEntered_goesToGone() =
+ testScope.runTest {
+ val currentScene by collectLastValue(sceneInteractor.currentScene)
+ val backgroundScene by collectLastValue(underTest.backgroundScene)
+
+ lockDevice()
+ unlockDevice()
+ sceneInteractor.changeScene(Scenes.QuickSettings, "reason")
+ runCurrent()
+ assertThat(backgroundScene).isEqualTo(Scenes.Gone)
+ assertThat(currentScene).isNotEqualTo(Scenes.Gone)
+
+ underTest.onScrimClicked()
+
+ assertThat(currentScene).isEqualTo(Scenes.Gone)
+ }
+
+ private fun TestScope.lockDevice() {
+ val deviceUnlockStatus by collectLastValue(deviceUnlockedInteractor.deviceUnlockStatus)
+
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+ assertThat(deviceUnlockStatus?.isUnlocked).isFalse()
+ sceneInteractor.changeScene(Scenes.Lockscreen, "reason")
+ runCurrent()
+ }
+
+ private fun TestScope.unlockDevice() {
+ val deviceUnlockStatus by collectLastValue(deviceUnlockedInteractor.deviceUnlockStatus)
+
+ kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+ SuccessFingerprintAuthenticationStatus(0, true)
+ )
+ assertThat(deviceUnlockStatus?.isUnlocked).isTrue()
+ sceneInteractor.changeScene(Scenes.Gone, "reason")
+ runCurrent()
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/domain/MediaOutputAvailabilityCriteriaTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/domain/MediaOutputAvailabilityCriteriaTest.kt
deleted file mode 100644
index 96b4dae..0000000
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/domain/MediaOutputAvailabilityCriteriaTest.kt
+++ /dev/null
@@ -1,82 +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.volume.panel.component.mediaoutput.domain
-
-import android.media.AudioManager
-import android.testing.TestableLooper.RunWithLooper
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.kosmos.testScope
-import com.android.systemui.testKosmos
-import com.android.systemui.volume.data.repository.audioRepository
-import com.android.systemui.volume.domain.interactor.audioModeInteractor
-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
-import org.junit.runner.RunWith
-
-@OptIn(ExperimentalCoroutinesApi::class)
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-@RunWithLooper(setAsMainLooper = true)
-class MediaOutputAvailabilityCriteriaTest : SysuiTestCase() {
-
- private val kosmos = testKosmos()
-
- private lateinit var underTest: MediaOutputAvailabilityCriteria
-
- @Before
- fun setup() {
- underTest =
- MediaOutputAvailabilityCriteria(
- kosmos.audioModeInteractor,
- )
- }
-
- @Test
- fun notInCall_isAvailable_true() {
- with(kosmos) {
- testScope.runTest {
- audioRepository.setMode(AudioManager.MODE_NORMAL)
-
- val isAvailable by collectLastValue(underTest.isAvailable())
- runCurrent()
-
- assertThat(isAvailable).isTrue()
- }
- }
- }
-
- @Test
- fun inCall_isAvailable_false() {
- with(kosmos) {
- testScope.runTest {
- audioRepository.setMode(AudioManager.MODE_IN_CALL)
-
- val isAvailable by collectLastValue(underTest.isAvailable())
- runCurrent()
-
- assertThat(isAvailable).isFalse()
- }
- }
- }
-}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaDeviceSessionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaDeviceSessionInteractorTest.kt
index b5c5809..64c9429 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaDeviceSessionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaDeviceSessionInteractorTest.kt
@@ -28,6 +28,7 @@
import com.android.systemui.volume.localMediaController
import com.android.systemui.volume.mediaControllerRepository
import com.android.systemui.volume.mediaOutputInteractor
+import com.android.systemui.volume.panel.shared.model.filterData
import com.android.systemui.volume.remoteMediaController
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -67,7 +68,8 @@
fun playbackInfo_returnsPlaybackInfo() {
with(kosmos) {
testScope.runTest {
- val session by collectLastValue(mediaOutputInteractor.defaultActiveMediaSession)
+ val session by
+ collectLastValue(mediaOutputInteractor.defaultActiveMediaSession.filterData())
runCurrent()
val info by collectLastValue(underTest.playbackInfo(session!!))
runCurrent()
@@ -81,7 +83,8 @@
fun playbackState_returnsPlaybackState() {
with(kosmos) {
testScope.runTest {
- val session by collectLastValue(mediaOutputInteractor.defaultActiveMediaSession)
+ val session by
+ collectLastValue(mediaOutputInteractor.defaultActiveMediaSession.filterData())
runCurrent()
val state by collectLastValue(underTest.playbackState(session!!))
runCurrent()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModelTest.kt
index 30524d9..49f82d4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModelTest.kt
@@ -30,6 +30,8 @@
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
+import com.android.systemui.volume.domain.interactor.audioModeInteractor
+import com.android.systemui.volume.domain.interactor.audioOutputInteractor
import com.android.systemui.volume.localMediaController
import com.android.systemui.volume.localMediaRepository
import com.android.systemui.volume.mediaControllerRepository
@@ -64,6 +66,8 @@
testScope.backgroundScope,
mediaOutputActionsInteractor,
mediaDeviceSessionInteractor,
+ audioOutputInteractor,
+ audioModeInteractor,
mediaOutputInteractor,
uiEventLogger,
)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModelTest.kt
index 818c19c..4e06855 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModelTest.kt
@@ -16,22 +16,26 @@
package com.android.systemui.volume.panel.ui.viewmodel
+import android.content.Intent
+import android.content.applicationContext
import android.content.res.Configuration
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.broadcast.broadcastDispatcher
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testScope
import com.android.systemui.statusbar.policy.fakeConfigurationController
import com.android.systemui.testKosmos
import com.android.systemui.volume.panel.componentByKey
import com.android.systemui.volume.panel.componentsLayoutManager
import com.android.systemui.volume.panel.criteriaByKey
-import com.android.systemui.volume.panel.dagger.factory.KosmosVolumePanelComponentFactory
import com.android.systemui.volume.panel.mockVolumePanelUiComponentProvider
import com.android.systemui.volume.panel.shared.model.VolumePanelComponentKey
import com.android.systemui.volume.panel.ui.layout.DefaultComponentsLayoutManager
import com.android.systemui.volume.panel.unavailableCriteria
+import com.android.systemui.volume.panel.volumePanelViewModel
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runCurrent
@@ -51,67 +55,49 @@
private lateinit var underTest: VolumePanelViewModel
- private fun initUnderTest() {
- underTest =
- VolumePanelViewModel(
- testableResources.resources,
- kosmos.testScope.backgroundScope,
- KosmosVolumePanelComponentFactory(kosmos),
- kosmos.fakeConfigurationController,
+ @Test
+ fun dismissingPanel_changesVisibility() = test {
+ testScope.runTest {
+ assertThat(underTest.volumePanelState.value.isVisible).isTrue()
+
+ underTest.dismissPanel()
+ runCurrent()
+
+ assertThat(underTest.volumePanelState.value.isVisible).isFalse()
+ }
+ }
+
+ @Test
+ fun orientationChanges_panelOrientationChanges() = test {
+ testScope.runTest {
+ val volumePanelState by collectLastValue(underTest.volumePanelState)
+ testableResources.overrideConfiguration(
+ Configuration().apply { orientation = Configuration.ORIENTATION_PORTRAIT }
)
- }
+ assertThat(volumePanelState!!.orientation).isEqualTo(Configuration.ORIENTATION_PORTRAIT)
- @Test
- fun dismissingPanel_changesVisibility() {
- with(kosmos) {
- testScope.runTest {
- initUnderTest()
- assertThat(underTest.volumePanelState.value.isVisible).isTrue()
+ fakeConfigurationController.onConfigurationChanged(
+ Configuration().apply { orientation = Configuration.ORIENTATION_LANDSCAPE }
+ )
+ runCurrent()
- underTest.dismissPanel()
- runCurrent()
-
- assertThat(underTest.volumePanelState.value.isVisible).isFalse()
- }
+ assertThat(volumePanelState!!.orientation)
+ .isEqualTo(Configuration.ORIENTATION_LANDSCAPE)
}
}
@Test
- fun orientationChanges_panelOrientationChanges() {
- with(kosmos) {
- testScope.runTest {
- initUnderTest()
- val volumePanelState by collectLastValue(underTest.volumePanelState)
- testableResources.overrideConfiguration(
- Configuration().apply { orientation = Configuration.ORIENTATION_PORTRAIT }
+ fun components_areReturned() =
+ test({
+ componentByKey =
+ mapOf(
+ COMPONENT_1 to mockVolumePanelUiComponentProvider,
+ COMPONENT_2 to mockVolumePanelUiComponentProvider,
+ BOTTOM_BAR to mockVolumePanelUiComponentProvider,
)
- assertThat(volumePanelState!!.orientation)
- .isEqualTo(Configuration.ORIENTATION_PORTRAIT)
-
- fakeConfigurationController.onConfigurationChanged(
- Configuration().apply { orientation = Configuration.ORIENTATION_LANDSCAPE }
- )
- runCurrent()
-
- assertThat(volumePanelState!!.orientation)
- .isEqualTo(Configuration.ORIENTATION_LANDSCAPE)
- }
- }
- }
-
- @Test
- fun components_areReturned() {
- with(kosmos) {
+ criteriaByKey = mapOf(COMPONENT_2 to unavailableCriteria)
+ }) {
testScope.runTest {
- componentByKey =
- mapOf(
- COMPONENT_1 to mockVolumePanelUiComponentProvider,
- COMPONENT_2 to mockVolumePanelUiComponentProvider,
- BOTTOM_BAR to mockVolumePanelUiComponentProvider,
- )
- criteriaByKey = mapOf(COMPONENT_2 to unavailableCriteria)
- initUnderTest()
-
val componentsLayout by collectLastValue(underTest.componentsLayout)
runCurrent()
@@ -124,11 +110,45 @@
assertThat(componentsLayout!!.bottomBarComponent.isVisible).isTrue()
}
}
+
+ @Test
+ fun dismissPanel_dismissesPanel() = test {
+ testScope.runTest {
+ val volumePanelState by collectLastValue(underTest.volumePanelState)
+ underTest.dismissPanel()
+ runCurrent()
+
+ assertThat(volumePanelState!!.isVisible).isFalse()
+ }
}
+ @Test
+ fun dismissBroadcast_dismissesPanel() = test {
+ testScope.runTest {
+ runCurrent() // run the flows to let allow the receiver to be registered
+ val volumePanelState by collectLastValue(underTest.volumePanelState)
+ broadcastDispatcher.sendIntentToMatchingReceiversOnly(
+ applicationContext,
+ Intent(DISMISS_ACTION),
+ )
+ runCurrent()
+
+ assertThat(volumePanelState!!.isVisible).isFalse()
+ }
+ }
+
+ private fun test(setup: Kosmos.() -> Unit = {}, test: Kosmos.() -> Unit) =
+ with(kosmos) {
+ setup()
+ underTest = volumePanelViewModel
+ test()
+ }
+
private companion object {
const val BOTTOM_BAR: VolumePanelComponentKey = "test_bottom_bar"
const val COMPONENT_1: VolumePanelComponentKey = "test_component:1"
const val COMPONENT_2: VolumePanelComponentKey = "test_component:2"
+
+ const val DISMISS_ACTION = "com.android.systemui.action.DISMISS_VOLUME_PANEL_DIALOG"
}
}
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_password_motion_layout.xml b/packages/SystemUI/res-keyguard/layout/keyguard_password_motion_layout.xml
index 3b6b5a0..2a8f1b5 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_password_motion_layout.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_password_motion_layout.xml
@@ -66,6 +66,7 @@
<com.android.systemui.bouncer.ui.BouncerMessageView
android:id="@+id/bouncer_message_view"
+ android:importantForAccessibility="noHideDescendants"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" />
@@ -127,4 +128,4 @@
</androidx.constraintlayout.motion.widget.MotionLayout>
-</com.android.keyguard.KeyguardPasswordView>
\ No newline at end of file
+</com.android.keyguard.KeyguardPasswordView>
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml
index 5aac653..76f6f59 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml
@@ -31,6 +31,7 @@
<com.android.systemui.bouncer.ui.BouncerMessageView
android:id="@+id/bouncer_message_view"
+ android:importantForAccessibility="noHideDescendants"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_pattern_motion_layout.xml b/packages/SystemUI/res-keyguard/layout/keyguard_pattern_motion_layout.xml
index 6780e57..5879c11 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_pattern_motion_layout.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_pattern_motion_layout.xml
@@ -67,6 +67,7 @@
<com.android.systemui.bouncer.ui.BouncerMessageView
android:id="@+id/bouncer_message_view"
+ android:importantForAccessibility="noHideDescendants"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" />
@@ -107,4 +108,4 @@
</androidx.constraintlayout.motion.widget.MotionLayout>
-</com.android.keyguard.KeyguardPatternView>
\ No newline at end of file
+</com.android.keyguard.KeyguardPatternView>
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_pattern_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_pattern_view.xml
index d991581..3f7b028 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_pattern_view.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_pattern_view.xml
@@ -35,6 +35,7 @@
<com.android.systemui.bouncer.ui.BouncerMessageView
android:id="@+id/bouncer_message_view"
+ android:importantForAccessibility="noHideDescendants"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" />
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_pin_motion_layout.xml b/packages/SystemUI/res-keyguard/layout/keyguard_pin_motion_layout.xml
index 6c79d5a..b464fb3 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_pin_motion_layout.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_pin_motion_layout.xml
@@ -74,6 +74,7 @@
<com.android.systemui.bouncer.ui.BouncerMessageView
android:id="@+id/bouncer_message_view"
+ android:importantForAccessibility="noHideDescendants"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" />
@@ -241,4 +242,4 @@
</androidx.constraintlayout.motion.widget.MotionLayout>
-</com.android.keyguard.KeyguardPINView>
\ No newline at end of file
+</com.android.keyguard.KeyguardPINView>
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml
index f3cd9e4..2158073 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml
@@ -32,6 +32,7 @@
<com.android.systemui.bouncer.ui.BouncerMessageView
android:id="@+id/bouncer_message_view"
+ android:importantForAccessibility="noHideDescendants"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" />
diff --git a/packages/SystemUI/res/drawable/ic_satellite_connected_0.xml b/packages/SystemUI/res/drawable/ic_satellite_connected_0.xml
index 045c19e..65d6e28 100644
--- a/packages/SystemUI/res/drawable/ic_satellite_connected_0.xml
+++ b/packages/SystemUI/res/drawable/ic_satellite_connected_0.xml
@@ -14,36 +14,20 @@
~ 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="24.0"
- android:viewportHeight="24.0">
+ android:width="16dp"
+ android:height="16dp"
+ android:viewportWidth="16.0"
+ android:viewportHeight="16.0">
<path
- android:pathData="M14.73,3.36L17.63,6.2C17.83,6.39 17.83,6.71 17.63,6.91L16.89,7.65C16.69,7.85 16.37,7.85 16.18,7.65L13.34,4.78C13.15,4.59 13.15,4.28 13.34,4.08L14.01,3.37C14.2,3.17 14.52,3.16 14.72,3.36H14.73ZM14.37,1C13.85,1 13.32,1.2 12.93,1.61L11.56,3.06C10.8,3.84 10.81,5.09 11.58,5.86L15.13,9.41C15.52,9.8 16.03,10 16.55,10C17.07,10 17.58,9.8 17.97,9.41L19.42,7.96C20.21,7.17 20.2,5.89 19.4,5.12L15.77,1.57C15.38,1.19 14.88,1 14.37,1Z"
+ android:pathData="M15.632,8H14.902C14.707,8 14.553,8.155 14.543,8.35C14.367,11.692 11.693,14.417 8.356,14.552C8.158,14.56 8,14.712 8,14.909V15.552C8,15.84 8.172,16.009 8.377,16C12.493,15.808 15.808,12.494 16,8.378C16.009,8.172 15.837,8 15.632,8Z"
+ android:fillAlpha="0.18"
android:fillColor="#fff"/>
<path
- android:pathData="M4.73,13.36L7.63,16.2C7.83,16.39 7.83,16.71 7.63,16.91L6.89,17.65C6.69,17.85 6.37,17.85 6.18,17.65L3.34,14.78C3.15,14.59 3.15,14.28 3.34,14.08L4.01,13.37C4.2,13.17 4.52,13.16 4.72,13.36H4.73ZM4.37,11C3.85,11 3.32,11.2 2.93,11.61L1.56,13.06C0.8,13.84 0.81,15.09 1.58,15.86L5.13,19.41C5.52,19.8 6.03,20 6.55,20C7.07,20 7.58,19.8 7.97,19.41L9.42,17.96C10.21,17.17 10.2,15.89 9.4,15.12L5.77,11.57C5.38,11.19 4.88,11 4.37,11Z"
+ android:pathData="M12.643,8C12.932,8 13.105,8.175 13.09,8.382C12.903,10.892 10.892,12.903 8.382,13.09C8.175,13.105 8,12.93 8,12.723V11.993C8,11.798 8.153,11.648 8.347,11.63C10.089,11.467 11.546,10.092 11.645,8.362C11.657,8.163 11.807,8 12.006,8L12.643,8Z"
+ android:fillAlpha="0.18"
android:fillColor="#fff"/>
<path
- android:pathData="M8.622,5.368L5.372,8.618L10.112,13.358C11.009,14.255 12.464,14.255 13.362,13.358C14.259,12.46 14.259,11.005 13.362,10.108L8.622,5.368Z"
- android:fillColor="#fff"/>
- <path
- android:pathData="M16.766,3.169L13.471,6.464L14.532,7.525L17.827,4.23L16.766,3.169Z"
- android:fillColor="#fff"/>
- <path
- android:pathData="M14.728,5.226L3.478,16.476L4.538,17.536L15.788,6.286L14.728,5.226Z"
- android:fillColor="#fff"/>
- <path
- android:pathData="M12.63,9.38L9.38,12.63L4.67,7.92C3.77,7.02 3.77,5.57 4.67,4.67C5.57,3.77 7.02,3.77 7.92,4.67L12.63,9.38Z"
- android:fillColor="#fff"/>
- <path
- android:pathData="M11,22.48V21.48C11,21.21 11.22,21 11.49,20.99C16.63,20.8 20.75,16.62 20.99,11.48C21,11.21 21.21,11 21.48,11H22.48C22.76,11 23,11.24 22.99,11.52C22.72,17.73 17.73,22.73 11.52,22.99C11.24,23 11,22.77 11,22.48Z"
- android:fillAlpha="0.3"
- android:fillColor="#fff"/>
- <path
- android:pathData="M11,18.98V17.98C11,17.71 11.21,17.51 11.48,17.49C14.69,17.26 17.33,14.7 17.49,11.49C17.5,11.22 17.71,11.01 17.98,11.01H18.79C19.26,11.01 19.5,11.25 19.48,11.53C19.22,15.8 15.79,19.23 11.52,19.49C11.24,19.51 11,19.27 11,18.99V18.98Z"
- android:fillAlpha="0.3"
+ android:pathData="M7.743,10.057L6.14,8.451L5.205,9.386L6.105,10.266C6.684,10.832 6.69,11.76 6.119,12.333L5.065,13.391C4.78,13.676 4.407,13.818 4.035,13.818C3.662,13.818 3.289,13.676 3.004,13.391L0.425,10.807C-0.136,10.245 -0.143,9.338 0.41,8.768L1.404,7.716C1.69,7.421 2.07,7.273 2.45,7.273C2.817,7.273 3.184,7.411 3.467,7.688L4.425,8.624L5.369,7.68L1.712,4.026C1.57,3.884 1.57,3.654 1.712,3.512L3.513,1.711C3.655,1.57 3.885,1.57 4.027,1.711L7.684,5.366L8.608,4.442L7.701,3.534C7.14,2.972 7.134,2.065 7.686,1.495L8.68,0.443C8.966,0.148 9.346,0 9.726,0C10.093,0 10.46,0.138 10.743,0.415L13.381,2.992C13.959,3.557 13.965,4.488 13.394,5.061L12.341,6.118C12.056,6.403 11.684,6.545 11.311,6.545C10.938,6.545 10.565,6.403 10.281,6.118L9.379,5.214L8.456,6.137L10.059,7.743C10.2,7.885 10.2,8.115 10.058,8.257L8.257,10.057C8.115,10.199 7.885,10.199 7.743,10.057ZM10.412,4.182L11.053,4.83C11.195,4.973 11.427,4.974 11.569,4.831L12.104,4.294C12.247,4.151 12.245,3.919 12.101,3.777L11.451,3.142L10.412,4.182ZM8.99,2.744L9.645,3.406L10.671,2.379L9.989,1.713C9.844,1.571 9.61,1.576 9.471,1.724L8.984,2.239C8.85,2.381 8.852,2.604 8.99,2.744ZM3.395,9.653L2.713,8.986C2.568,8.844 2.334,8.849 2.194,8.997L1.708,9.512C1.574,9.654 1.576,9.878 1.714,10.017L2.369,10.679L3.395,9.653ZM4.825,11.051L4.176,10.416L3.136,11.455L3.777,12.103C3.919,12.247 4.151,12.247 4.293,12.104L4.828,11.567C4.971,11.424 4.969,11.192 4.825,11.051Z"
android:fillColor="#fff"/>
</vector>
diff --git a/packages/SystemUI/res/drawable/ic_satellite_connected_1.xml b/packages/SystemUI/res/drawable/ic_satellite_connected_1.xml
index 5e012ab..0399b81 100644
--- a/packages/SystemUI/res/drawable/ic_satellite_connected_1.xml
+++ b/packages/SystemUI/res/drawable/ic_satellite_connected_1.xml
@@ -16,34 +16,18 @@
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
+ android:width="16dp"
+ android:height="16dp"
+ android:viewportWidth="16.0"
+ android:viewportHeight="16.0">
<path
- android:pathData="M14.73,3.36L17.63,6.2C17.83,6.39 17.83,6.71 17.63,6.91L16.89,7.65C16.69,7.85 16.37,7.85 16.18,7.65L13.34,4.78C13.15,4.59 13.15,4.28 13.34,4.08L14.01,3.37C14.2,3.17 14.52,3.16 14.72,3.36H14.73ZM14.37,1C13.85,1 13.32,1.2 12.93,1.61L11.56,3.06C10.8,3.84 10.81,5.09 11.58,5.86L15.13,9.41C15.52,9.8 16.03,10 16.55,10C17.07,10 17.58,9.8 17.97,9.41L19.42,7.96C20.21,7.17 20.2,5.89 19.4,5.12L15.77,1.57C15.38,1.19 14.88,1 14.37,1Z"
+ android:pathData="M15.632,8H14.902C14.707,8 14.553,8.155 14.543,8.35C14.367,11.692 11.693,14.417 8.356,14.552C8.158,14.56 8,14.712 8,14.909V15.552C8,15.84 8.172,16.009 8.377,16C12.493,15.808 15.808,12.494 16,8.378C16.009,8.172 15.837,8 15.632,8Z"
+ android:fillAlpha="0.18"
android:fillColor="#fff"/>
<path
- android:pathData="M4.73,13.36L7.63,16.2C7.83,16.39 7.83,16.71 7.63,16.91L6.89,17.65C6.69,17.85 6.37,17.85 6.18,17.65L3.34,14.78C3.15,14.59 3.15,14.28 3.34,14.08L4.01,13.37C4.2,13.17 4.52,13.16 4.72,13.36H4.73ZM4.37,11C3.85,11 3.32,11.2 2.93,11.61L1.56,13.06C0.8,13.84 0.81,15.09 1.58,15.86L5.13,19.41C5.52,19.8 6.03,20 6.55,20C7.07,20 7.58,19.8 7.97,19.41L9.42,17.96C10.21,17.17 10.2,15.89 9.4,15.12L5.77,11.57C5.38,11.19 4.88,11 4.37,11Z"
+ android:pathData="M12.643,8C12.932,8 13.105,8.175 13.09,8.382C12.903,10.892 10.892,12.903 8.382,13.09C8.175,13.105 8,12.93 8,12.723V11.993C8,11.798 8.153,11.648 8.347,11.63C10.089,11.467 11.546,10.092 11.645,8.362C11.657,8.163 11.807,8 12.006,8L12.643,8Z"
android:fillColor="#fff"/>
<path
- android:pathData="M8.622,5.368L5.372,8.618L10.112,13.358C11.009,14.255 12.464,14.255 13.362,13.358C14.259,12.46 14.259,11.005 13.362,10.108L8.622,5.368Z"
- android:fillColor="#fff"/>
- <path
- android:pathData="M16.766,3.169L13.471,6.464L14.532,7.525L17.827,4.23L16.766,3.169Z"
- android:fillColor="#fff"/>
- <path
- android:pathData="M14.728,5.226L3.478,16.476L4.538,17.536L15.788,6.286L14.728,5.226Z"
- android:fillColor="#fff"/>
- <path
- android:pathData="M12.63,9.38L9.38,12.63L4.67,7.92C3.77,7.02 3.77,5.57 4.67,4.67C5.57,3.77 7.02,3.77 7.92,4.67L12.63,9.38Z"
- android:fillColor="#fff"/>
- <path
- android:pathData="M11,22.48V21.48C11,21.21 11.22,21 11.49,20.99C16.63,20.8 20.75,16.62 20.99,11.48C21,11.21 21.21,11 21.48,11H22.48C22.76,11 23,11.24 22.99,11.52C22.72,17.73 17.73,22.73 11.52,22.99C11.24,23 11,22.77 11,22.48Z"
- android:fillAlpha="0.3"
- android:fillColor="#fff"/>
- <path
- android:pathData="M11,18.98V17.98C11,17.71 11.21,17.51 11.48,17.49C14.69,17.26 17.33,14.7 17.49,11.49C17.5,11.22 17.71,11.01 17.98,11.01H18.79C19.26,11.01 19.5,11.25 19.48,11.53C19.22,15.8 15.79,19.23 11.52,19.49C11.24,19.51 11,19.27 11,18.99V18.98Z"
+ android:pathData="M7.743,10.057L6.14,8.451L5.205,9.386L6.105,10.266C6.684,10.832 6.69,11.76 6.119,12.333L5.065,13.391C4.78,13.676 4.407,13.818 4.035,13.818C3.662,13.818 3.289,13.676 3.004,13.391L0.425,10.807C-0.136,10.245 -0.143,9.338 0.41,8.768L1.404,7.716C1.69,7.421 2.07,7.273 2.45,7.273C2.817,7.273 3.184,7.411 3.467,7.688L4.425,8.624L5.369,7.68L1.712,4.026C1.57,3.884 1.57,3.654 1.712,3.512L3.513,1.711C3.655,1.57 3.885,1.57 4.027,1.711L7.684,5.366L8.608,4.442L7.701,3.534C7.14,2.972 7.134,2.065 7.686,1.495L8.68,0.443C8.966,0.148 9.346,0 9.726,0C10.093,0 10.46,0.138 10.743,0.415L13.381,2.992C13.959,3.557 13.965,4.488 13.394,5.061L12.341,6.118C12.056,6.403 11.684,6.545 11.311,6.545C10.938,6.545 10.565,6.403 10.281,6.118L9.379,5.214L8.456,6.137L10.059,7.743C10.2,7.885 10.2,8.115 10.058,8.257L8.257,10.057C8.115,10.199 7.885,10.199 7.743,10.057ZM10.412,4.182L11.053,4.83C11.195,4.973 11.427,4.974 11.569,4.831L12.104,4.294C12.247,4.151 12.245,3.919 12.101,3.777L11.451,3.142L10.412,4.182ZM8.99,2.744L9.645,3.406L10.671,2.379L9.989,1.713C9.844,1.571 9.61,1.576 9.471,1.724L8.984,2.239C8.85,2.381 8.852,2.604 8.99,2.744ZM3.395,9.653L2.713,8.986C2.568,8.844 2.334,8.849 2.194,8.997L1.708,9.512C1.574,9.654 1.576,9.878 1.714,10.017L2.369,10.679L3.395,9.653ZM4.825,11.051L4.176,10.416L3.136,11.455L3.777,12.103C3.919,12.247 4.151,12.247 4.293,12.104L4.828,11.567C4.971,11.424 4.969,11.192 4.825,11.051Z"
android:fillColor="#fff"/>
</vector>
-
diff --git a/packages/SystemUI/res/drawable/ic_satellite_connected_2.xml b/packages/SystemUI/res/drawable/ic_satellite_connected_2.xml
index d8a9a70..b0acbdc 100644
--- a/packages/SystemUI/res/drawable/ic_satellite_connected_2.xml
+++ b/packages/SystemUI/res/drawable/ic_satellite_connected_2.xml
@@ -16,32 +16,17 @@
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
+ android:width="16dp"
+ android:height="16dp"
+ android:viewportWidth="16.0"
+ android:viewportHeight="16.0">
<path
- android:pathData="M14.73,3.36L17.63,6.2C17.83,6.39 17.83,6.71 17.63,6.91L16.89,7.65C16.69,7.85 16.37,7.85 16.18,7.65L13.34,4.78C13.15,4.59 13.15,4.28 13.34,4.08L14.01,3.37C14.2,3.17 14.52,3.16 14.72,3.36H14.73ZM14.37,1C13.85,1 13.32,1.2 12.93,1.61L11.56,3.06C10.8,3.84 10.81,5.09 11.58,5.86L15.13,9.41C15.52,9.8 16.03,10 16.55,10C17.07,10 17.58,9.8 17.97,9.41L19.42,7.96C20.21,7.17 20.2,5.89 19.4,5.12L15.77,1.57C15.38,1.19 14.88,1 14.37,1Z"
+ android:pathData="M15.632,8H14.902C14.707,8 14.553,8.155 14.543,8.35C14.367,11.692 11.693,14.417 8.356,14.552C8.158,14.56 8,14.712 8,14.909V15.552C8,15.84 8.172,16.009 8.377,16C12.493,15.808 15.808,12.494 16,8.378C16.009,8.172 15.837,8 15.632,8Z"
android:fillColor="#fff"/>
<path
- android:pathData="M4.73,13.36L7.63,16.2C7.83,16.39 7.83,16.71 7.63,16.91L6.89,17.65C6.69,17.85 6.37,17.85 6.18,17.65L3.34,14.78C3.15,14.59 3.15,14.28 3.34,14.08L4.01,13.37C4.2,13.17 4.52,13.16 4.72,13.36H4.73ZM4.37,11C3.85,11 3.32,11.2 2.93,11.61L1.56,13.06C0.8,13.84 0.81,15.09 1.58,15.86L5.13,19.41C5.52,19.8 6.03,20 6.55,20C7.07,20 7.58,19.8 7.97,19.41L9.42,17.96C10.21,17.17 10.2,15.89 9.4,15.12L5.77,11.57C5.38,11.19 4.88,11 4.37,11Z"
+ android:pathData="M12.643,8C12.932,8 13.105,8.175 13.09,8.382C12.903,10.892 10.892,12.903 8.382,13.09C8.175,13.105 8,12.93 8,12.723V11.993C8,11.798 8.153,11.648 8.347,11.63C10.089,11.467 11.546,10.092 11.645,8.362C11.657,8.163 11.807,8 12.006,8L12.643,8Z"
android:fillColor="#fff"/>
<path
- android:pathData="M8.622,5.368L5.372,8.618L10.112,13.358C11.009,14.255 12.464,14.255 13.362,13.358C14.259,12.46 14.259,11.005 13.362,10.108L8.622,5.368Z"
- android:fillColor="#fff"/>
- <path
- android:pathData="M16.766,3.169L13.471,6.464L14.532,7.525L17.827,4.23L16.766,3.169Z"
- android:fillColor="#fff"/>
- <path
- android:pathData="M14.728,5.226L3.478,16.476L4.538,17.536L15.788,6.286L14.728,5.226Z"
- android:fillColor="#fff"/>
- <path
- android:pathData="M12.63,9.38L9.38,12.63L4.67,7.92C3.77,7.02 3.77,5.57 4.67,4.67C5.57,3.77 7.02,3.77 7.92,4.67L12.63,9.38Z"
- android:fillColor="#fff"/>
- <path
- android:pathData="M11,22.48V21.48C11,21.21 11.22,21 11.49,20.99C16.63,20.8 20.75,16.62 20.99,11.48C21,11.21 21.21,11 21.48,11H22.48C22.76,11 23,11.24 22.99,11.52C22.72,17.73 17.73,22.73 11.52,22.99C11.24,23 11,22.77 11,22.48Z"
- android:fillColor="#fff"/>
- <path
- android:pathData="M11,18.98V17.98C11,17.71 11.21,17.51 11.48,17.49C14.69,17.26 17.33,14.7 17.49,11.49C17.5,11.22 17.71,11.01 17.98,11.01H18.79C19.26,11.01 19.5,11.25 19.48,11.53C19.22,15.8 15.79,19.23 11.52,19.49C11.24,19.51 11,19.27 11,18.99V18.98Z"
+ android:pathData="M7.743,10.057L6.14,8.451L5.205,9.386L6.105,10.266C6.684,10.832 6.69,11.76 6.119,12.333L5.065,13.391C4.78,13.676 4.407,13.818 4.035,13.818C3.662,13.818 3.289,13.676 3.004,13.391L0.425,10.807C-0.136,10.245 -0.143,9.338 0.41,8.768L1.404,7.716C1.69,7.421 2.07,7.273 2.45,7.273C2.817,7.273 3.184,7.411 3.467,7.688L4.425,8.624L5.369,7.68L1.712,4.026C1.57,3.884 1.57,3.654 1.712,3.512L3.513,1.711C3.655,1.57 3.885,1.57 4.027,1.711L7.684,5.366L8.608,4.442L7.701,3.534C7.14,2.972 7.134,2.065 7.686,1.495L8.68,0.443C8.966,0.148 9.346,0 9.726,0C10.093,0 10.46,0.138 10.743,0.415L13.381,2.992C13.959,3.557 13.965,4.488 13.394,5.061L12.341,6.118C12.056,6.403 11.684,6.545 11.311,6.545C10.938,6.545 10.565,6.403 10.281,6.118L9.379,5.214L8.456,6.137L10.059,7.743C10.2,7.885 10.2,8.115 10.058,8.257L8.257,10.057C8.115,10.199 7.885,10.199 7.743,10.057ZM10.412,4.182L11.053,4.83C11.195,4.973 11.427,4.974 11.569,4.831L12.104,4.294C12.247,4.151 12.245,3.919 12.101,3.777L11.451,3.142L10.412,4.182ZM8.99,2.744L9.645,3.406L10.671,2.379L9.989,1.713C9.844,1.571 9.61,1.576 9.471,1.724L8.984,2.239C8.85,2.381 8.852,2.604 8.99,2.744ZM3.395,9.653L2.713,8.986C2.568,8.844 2.334,8.849 2.194,8.997L1.708,9.512C1.574,9.654 1.576,9.878 1.714,10.017L2.369,10.679L3.395,9.653ZM4.825,11.051L4.176,10.416L3.136,11.455L3.777,12.103C3.919,12.247 4.151,12.247 4.293,12.104L4.828,11.567C4.971,11.424 4.969,11.192 4.825,11.051Z"
android:fillColor="#fff"/>
</vector>
diff --git a/packages/SystemUI/res/drawable/ic_satellite_not_connected.xml b/packages/SystemUI/res/drawable/ic_satellite_not_connected.xml
index a80d3b4..2cab043 100644
--- a/packages/SystemUI/res/drawable/ic_satellite_not_connected.xml
+++ b/packages/SystemUI/res/drawable/ic_satellite_not_connected.xml
@@ -16,28 +16,11 @@
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0"
- android:alpha="0.3"
- >
+ android:width="14dp"
+ android:height="14dp"
+ android:viewportWidth="14.0"
+ android:viewportHeight="14.0">
<path
- android:pathData="M14.73,3.36L17.63,6.2C17.83,6.39 17.83,6.71 17.63,6.91L16.89,7.65C16.69,7.85 16.37,7.85 16.18,7.65L13.34,4.78C13.15,4.59 13.15,4.28 13.34,4.08L14.01,3.37C14.2,3.17 14.52,3.16 14.72,3.36H14.73ZM14.37,1C13.85,1 13.32,1.2 12.93,1.61L11.56,3.06C10.8,3.84 10.81,5.09 11.58,5.86L15.13,9.41C15.52,9.8 16.03,10 16.55,10C17.07,10 17.58,9.8 17.97,9.41L19.42,7.96C20.21,7.17 20.2,5.89 19.4,5.12L15.77,1.57C15.38,1.19 14.88,1 14.37,1Z"
- android:fillColor="#fff"/>
- <path
- android:pathData="M4.73,13.36L7.63,16.2C7.83,16.39 7.83,16.71 7.63,16.91L6.89,17.65C6.69,17.85 6.37,17.85 6.18,17.65L3.34,14.78C3.15,14.59 3.15,14.28 3.34,14.08L4.01,13.37C4.2,13.17 4.52,13.16 4.72,13.36H4.73ZM4.37,11C3.85,11 3.32,11.2 2.93,11.61L1.56,13.06C0.8,13.84 0.81,15.09 1.58,15.86L5.13,19.41C5.52,19.8 6.03,20 6.55,20C7.07,20 7.58,19.8 7.97,19.41L9.42,17.96C10.21,17.17 10.2,15.89 9.4,15.12L5.77,11.57C5.38,11.19 4.88,11 4.37,11Z"
- android:fillColor="#fff"/>
- <path
- android:pathData="M8.622,5.368L5.372,8.618L10.112,13.358C11.009,14.255 12.464,14.255 13.362,13.358C14.259,12.46 14.259,11.005 13.362,10.108L8.622,5.368Z"
- android:fillColor="#fff"/>
- <path
- android:pathData="M16.766,3.169L13.471,6.464L14.532,7.525L17.827,4.23L16.766,3.169Z"
- android:fillColor="#fff"/>
- <path
- android:pathData="M14.728,5.226L3.478,16.476L4.538,17.536L15.788,6.286L14.728,5.226Z"
- android:fillColor="#fff"/>
- <path
- android:pathData="M12.63,9.38L9.38,12.63L4.67,7.92C3.77,7.02 3.77,5.57 4.67,4.67C5.57,3.77 7.02,3.77 7.92,4.67L12.63,9.38Z"
+ android:pathData="M7.743,10.057L6.14,8.451L5.205,9.386L6.105,10.266C6.684,10.832 6.69,11.76 6.119,12.333L5.065,13.391C4.78,13.676 4.407,13.818 4.035,13.818C3.662,13.818 3.289,13.676 3.004,13.391L0.425,10.807C-0.136,10.245 -0.143,9.338 0.41,8.768L1.404,7.716C1.69,7.421 2.07,7.273 2.45,7.273C2.817,7.273 3.184,7.411 3.467,7.688L4.425,8.624L5.369,7.68L1.712,4.026C1.57,3.884 1.57,3.654 1.712,3.512L3.513,1.711C3.655,1.57 3.885,1.57 4.027,1.711L7.684,5.366L8.608,4.442L7.701,3.534C7.14,2.972 7.134,2.065 7.686,1.495L8.68,0.443C8.966,0.148 9.346,0 9.726,0C10.093,0 10.46,0.138 10.743,0.415L13.381,2.992C13.959,3.557 13.965,4.488 13.394,5.061L12.341,6.118C12.056,6.403 11.684,6.545 11.311,6.545C10.938,6.545 10.565,6.403 10.281,6.118L9.379,5.214L8.456,6.137L10.059,7.743C10.2,7.885 10.2,8.115 10.058,8.257L8.257,10.057C8.115,10.199 7.885,10.199 7.743,10.057ZM10.412,4.182L11.053,4.83C11.195,4.973 11.427,4.974 11.569,4.831L12.104,4.294C12.247,4.151 12.245,3.919 12.101,3.777L11.451,3.142L10.412,4.182ZM8.99,2.744L9.645,3.406L10.671,2.379L9.989,1.713C9.844,1.571 9.61,1.576 9.471,1.724L8.984,2.239C8.85,2.381 8.852,2.604 8.99,2.744ZM3.395,9.653L2.713,8.986C2.568,8.844 2.334,8.849 2.194,8.997L1.708,9.512C1.574,9.654 1.576,9.878 1.714,10.017L2.369,10.679L3.395,9.653ZM4.825,11.051L4.176,10.416L3.136,11.455L3.777,12.103C3.919,12.247 4.151,12.247 4.293,12.104L4.828,11.567C4.971,11.424 4.969,11.192 4.825,11.051Z"
android:fillColor="#fff"/>
</vector>
diff --git a/packages/SystemUI/res/layout-land/biometric_prompt_constraint_layout.xml b/packages/SystemUI/res/layout-land/biometric_prompt_constraint_layout.xml
index d13efd2..f644584f 100644
--- a/packages/SystemUI/res/layout-land/biometric_prompt_constraint_layout.xml
+++ b/packages/SystemUI/res/layout-land/biometric_prompt_constraint_layout.xml
@@ -174,7 +174,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
- android:accessibilityLiveRegion="polite"
+ android:accessibilityLiveRegion="assertive"
android:fadingEdge="horizontal"
android:gravity="center_horizontal"
android:scrollHorizontally="true"
diff --git a/packages/SystemUI/res/layout-sw600dp/biometric_prompt_constraint_layout.xml b/packages/SystemUI/res/layout-sw600dp/biometric_prompt_constraint_layout.xml
index a6e660f..46b8e46 100644
--- a/packages/SystemUI/res/layout-sw600dp/biometric_prompt_constraint_layout.xml
+++ b/packages/SystemUI/res/layout-sw600dp/biometric_prompt_constraint_layout.xml
@@ -152,7 +152,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
- android:accessibilityLiveRegion="polite"
+ android:accessibilityLiveRegion="assertive"
android:fadingEdge="horizontal"
android:gravity="center_horizontal"
android:scrollHorizontally="true"
diff --git a/packages/SystemUI/res/layout/biometric_prompt_constraint_layout.xml b/packages/SystemUI/res/layout/biometric_prompt_constraint_layout.xml
index c724d24..d51fe58 100644
--- a/packages/SystemUI/res/layout/biometric_prompt_constraint_layout.xml
+++ b/packages/SystemUI/res/layout/biometric_prompt_constraint_layout.xml
@@ -150,7 +150,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
- android:accessibilityLiveRegion="polite"
+ android:accessibilityLiveRegion="assertive"
android:fadingEdge="horizontal"
android:gravity="center_horizontal"
android:scrollHorizontally="true"
diff --git a/packages/SystemUI/res/layout/screenshot_shelf.xml b/packages/SystemUI/res/layout/screenshot_shelf.xml
index 79b82bf..7adfa6c 100644
--- a/packages/SystemUI/res/layout/screenshot_shelf.xml
+++ b/packages/SystemUI/res/layout/screenshot_shelf.xml
@@ -20,133 +20,138 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
- <FrameLayout
- android:id="@+id/actions_container_background"
- android:visibility="gone"
- android:layout_height="wrap_content"
- android:layout_width="wrap_content"
- android:elevation="4dp"
- android:background="@drawable/shelf_action_chip_container_background"
- android:layout_marginHorizontal="@dimen/overlay_action_container_margin_horizontal"
- android:layout_marginBottom="@dimen/screenshot_shelf_vertical_margin"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintBottom_toTopOf="@id/guideline"
- >
- <HorizontalScrollView
- android:id="@+id/actions_container"
- android:layout_width="wrap_content"
+ <androidx.constraintlayout.widget.ConstraintLayout
+ android:id="@+id/screenshot_static"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <FrameLayout
+ android:id="@+id/actions_container_background"
+ android:visibility="gone"
android:layout_height="wrap_content"
- android:layout_marginVertical="@dimen/overlay_action_container_padding_vertical"
- android:layout_marginHorizontal="@dimen/overlay_action_chip_margin_start"
- android:background="@drawable/shelf_action_container_clipping_shape"
- android:clipToOutline="true"
- android:scrollbars="none">
- <LinearLayout
- android:id="@+id/screenshot_actions"
+ android:layout_width="wrap_content"
+ android:elevation="4dp"
+ android:background="@drawable/shelf_action_chip_container_background"
+ android:layout_marginHorizontal="@dimen/overlay_action_container_margin_horizontal"
+ android:layout_marginBottom="@dimen/screenshot_shelf_vertical_margin"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintBottom_toTopOf="@id/guideline"
+ >
+ <HorizontalScrollView
+ android:id="@+id/actions_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:showDividers="middle"
- android:divider="@drawable/shelf_action_chip_divider"
- android:animateLayoutChanges="true"
- />
- </HorizontalScrollView>
- </FrameLayout>
- <View
- android:id="@+id/screenshot_preview_border"
- android:layout_width="0dp"
- android:layout_height="0dp"
- android:layout_marginStart="@dimen/overlay_action_container_margin_horizontal"
- android:layout_marginTop="@dimen/overlay_border_width_neg"
- android:layout_marginEnd="@dimen/overlay_border_width_neg"
- android:layout_marginBottom="@dimen/screenshot_shelf_vertical_margin"
- android:elevation="4dp"
- android:background="@drawable/overlay_border"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toTopOf="@id/screenshot_preview"
- app:layout_constraintEnd_toEndOf="@id/screenshot_preview"
- app:layout_constraintBottom_toTopOf="@id/actions_container_background"/>
- <ImageView
- android:id="@+id/screenshot_preview"
- android:layout_width="@dimen/overlay_x_scale"
- android:layout_height="wrap_content"
- android:layout_marginStart="@dimen/overlay_border_width"
- android:layout_marginBottom="@dimen/overlay_border_width"
- android:layout_gravity="center"
- android:elevation="4dp"
- android:contentDescription="@string/screenshot_edit_description"
- android:scaleType="fitEnd"
- android:background="@drawable/overlay_preview_background"
- android:adjustViewBounds="true"
- android:clickable="true"
- app:layout_constraintStart_toStartOf="@id/screenshot_preview_border"
- app:layout_constraintBottom_toBottomOf="@id/screenshot_preview_border"/>
- <ImageView
- android:id="@+id/screenshot_badge"
- android:layout_width="56dp"
- android:layout_height="56dp"
- android:visibility="gone"
- android:elevation="5dp"
- app:layout_constraintBottom_toBottomOf="@id/screenshot_preview_border"
- app:layout_constraintEnd_toEndOf="@id/screenshot_preview_border"/>
- <FrameLayout
- android:id="@+id/screenshot_dismiss_button"
- android:layout_width="@dimen/overlay_dismiss_button_tappable_size"
- android:layout_height="@dimen/overlay_dismiss_button_tappable_size"
- android:elevation="7dp"
- android:visibility="gone"
- app:layout_constraintStart_toEndOf="@id/screenshot_preview"
- app:layout_constraintEnd_toEndOf="@id/screenshot_preview"
- app:layout_constraintTop_toTopOf="@id/screenshot_preview"
- app:layout_constraintBottom_toTopOf="@id/screenshot_preview"
- android:contentDescription="@string/screenshot_dismiss_description">
+ android:layout_marginVertical="@dimen/overlay_action_container_padding_vertical"
+ android:layout_marginHorizontal="@dimen/overlay_action_chip_margin_start"
+ android:background="@drawable/shelf_action_container_clipping_shape"
+ android:clipToOutline="true"
+ android:scrollbars="none">
+ <LinearLayout
+ android:id="@+id/screenshot_actions"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:showDividers="middle"
+ android:divider="@drawable/shelf_action_chip_divider"
+ android:animateLayoutChanges="true"
+ />
+ </HorizontalScrollView>
+ </FrameLayout>
+ <View
+ android:id="@+id/screenshot_preview_border"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:layout_marginStart="@dimen/overlay_action_container_margin_horizontal"
+ android:layout_marginTop="@dimen/overlay_border_width_neg"
+ android:layout_marginEnd="@dimen/overlay_border_width_neg"
+ android:layout_marginBottom="@dimen/screenshot_shelf_vertical_margin"
+ android:elevation="4dp"
+ android:background="@drawable/overlay_border"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="@id/screenshot_preview"
+ app:layout_constraintEnd_toEndOf="@id/screenshot_preview"
+ app:layout_constraintBottom_toTopOf="@id/actions_container_background"/>
<ImageView
- android:id="@+id/screenshot_dismiss_image"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_margin="@dimen/overlay_dismiss_button_margin"
- android:background="@drawable/circular_background"
- android:backgroundTint="?androidprv:attr/materialColorPrimary"
- android:tint="?androidprv:attr/materialColorOnPrimary"
- android:padding="4dp"
- android:src="@drawable/ic_close"/>
- </FrameLayout>
- <ImageView
- android:id="@+id/screenshot_scrollable_preview"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:scaleType="matrix"
- android:visibility="gone"
- app:layout_constraintStart_toStartOf="@id/screenshot_preview"
- app:layout_constraintTop_toTopOf="@id/screenshot_preview"
- android:elevation="7dp"/>
+ android:id="@+id/screenshot_preview"
+ android:layout_width="@dimen/overlay_x_scale"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="@dimen/overlay_border_width"
+ android:layout_marginBottom="@dimen/overlay_border_width"
+ android:layout_gravity="center"
+ android:elevation="4dp"
+ android:contentDescription="@string/screenshot_edit_description"
+ android:scaleType="fitEnd"
+ android:background="@drawable/overlay_preview_background"
+ android:adjustViewBounds="true"
+ android:clickable="true"
+ app:layout_constraintStart_toStartOf="@id/screenshot_preview_border"
+ app:layout_constraintBottom_toBottomOf="@id/screenshot_preview_border"/>
+ <ImageView
+ android:id="@+id/screenshot_badge"
+ android:layout_width="56dp"
+ android:layout_height="56dp"
+ android:visibility="gone"
+ android:elevation="5dp"
+ app:layout_constraintBottom_toBottomOf="@id/screenshot_preview_border"
+ app:layout_constraintEnd_toEndOf="@id/screenshot_preview_border"/>
+ <FrameLayout
+ android:id="@+id/screenshot_dismiss_button"
+ android:layout_width="@dimen/overlay_dismiss_button_tappable_size"
+ android:layout_height="@dimen/overlay_dismiss_button_tappable_size"
+ android:elevation="7dp"
+ android:visibility="gone"
+ app:layout_constraintStart_toEndOf="@id/screenshot_preview"
+ app:layout_constraintEnd_toEndOf="@id/screenshot_preview"
+ app:layout_constraintTop_toTopOf="@id/screenshot_preview"
+ app:layout_constraintBottom_toTopOf="@id/screenshot_preview"
+ android:contentDescription="@string/screenshot_dismiss_description">
+ <ImageView
+ android:id="@+id/screenshot_dismiss_image"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_margin="@dimen/overlay_dismiss_button_margin"
+ android:background="@drawable/circular_background"
+ android:backgroundTint="?androidprv:attr/materialColorPrimary"
+ android:tint="?androidprv:attr/materialColorOnPrimary"
+ android:padding="4dp"
+ android:src="@drawable/ic_close"/>
+ </FrameLayout>
+ <ImageView
+ android:id="@+id/screenshot_scrollable_preview"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:scaleType="matrix"
+ android:visibility="gone"
+ app:layout_constraintStart_toStartOf="@id/screenshot_preview"
+ app:layout_constraintTop_toTopOf="@id/screenshot_preview"
+ android:elevation="7dp"/>
- <androidx.constraintlayout.widget.Guideline
- android:id="@+id/guideline"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:orientation="horizontal"
- app:layout_constraintGuide_end="0dp" />
+ <androidx.constraintlayout.widget.Guideline
+ android:id="@+id/guideline"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ app:layout_constraintGuide_end="0dp" />
- <FrameLayout
- android:id="@+id/screenshot_message_container"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:layout_marginHorizontal="@dimen/overlay_action_container_margin_horizontal"
- android:layout_marginTop="4dp"
- android:layout_marginBottom="@dimen/overlay_action_container_margin_bottom"
- android:paddingHorizontal="@dimen/overlay_action_container_padding_end"
- android:paddingVertical="@dimen/overlay_action_container_padding_vertical"
- android:elevation="4dp"
- android:background="@drawable/action_chip_container_background"
- android:visibility="gone"
- app:layout_constraintTop_toBottomOf="@id/guideline"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintWidth_max="450dp"
- app:layout_constraintHorizontal_bias="0">
- <include layout="@layout/screenshot_work_profile_first_run" />
- <include layout="@layout/screenshot_detection_notice" />
- </FrameLayout>
+ <FrameLayout
+ android:id="@+id/screenshot_message_container"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_marginHorizontal="@dimen/overlay_action_container_margin_horizontal"
+ android:layout_marginTop="4dp"
+ android:layout_marginBottom="@dimen/overlay_action_container_margin_bottom"
+ android:paddingHorizontal="@dimen/overlay_action_container_padding_end"
+ android:paddingVertical="@dimen/overlay_action_container_padding_vertical"
+ android:elevation="4dp"
+ android:background="@drawable/action_chip_container_background"
+ android:visibility="gone"
+ app:layout_constraintTop_toBottomOf="@id/guideline"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintWidth_max="450dp"
+ app:layout_constraintHorizontal_bias="0">
+ <include layout="@layout/screenshot_work_profile_first_run" />
+ <include layout="@layout/screenshot_detection_notice" />
+ </FrameLayout>
+ </androidx.constraintlayout.widget.ConstraintLayout>
<ImageView
android:id="@+id/screenshot_flash"
android:layout_width="match_parent"
diff --git a/packages/SystemUI/res/layout/volume_dialog_row.xml b/packages/SystemUI/res/layout/volume_dialog_row.xml
index f35de05..dc9c4f1 100644
--- a/packages/SystemUI/res/layout/volume_dialog_row.xml
+++ b/packages/SystemUI/res/layout/volume_dialog_row.xml
@@ -52,7 +52,6 @@
android:paddingRight="0dp"
android:paddingStart="0dp"
android:paddingEnd="0dp"
- android:clickable="true"
android:layout_width="@dimen/volume_row_slider_height"
android:layout_height="match_parent"
android:layout_gravity="center"
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 19273ec..6bfd088 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -461,10 +461,11 @@
This name is in the ComponentName flattened format (package/class) -->
<string name="config_screenshotEditor" translatable="false"></string>
- <!-- ComponentName for the file browsing app that the system would expect to be used in work
- profile. The icon for this app will be shown to the user when informing them that a
- screenshot has been saved to work profile. If blank, a default icon will be shown. -->
- <string name="config_sceenshotWorkProfileFilesApp" translatable="false"></string>
+ <!-- ComponentName for the file browsing app that the system would expect to be used for
+ screenshots. The icon for this app will be shown to the user when informing them that a
+ screenshot has been saved to a different profile (e.g. work profile). If blank, a default
+ icon will be shown. -->
+ <string name="config_screenshotFilesApp" translatable="false"></string>
<!-- The component name of the screenshot editing activity that provides the App Clips flow.
The App Clips flow includes taking a screenshot, showing user screenshot cropping activity
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index b8e78a4..dcdd4f0 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -257,6 +257,8 @@
<string name="screenshot_right_boundary_pct">Right boundary <xliff:g id="percent" example="50">%1$d</xliff:g> percent</string>
<!-- Notification displayed when a screenshot is saved in a work profile. [CHAR LIMIT=NONE] -->
<string name="screenshot_work_profile_notification">Saved in <xliff:g id="app" example="Files">%1$s</xliff:g> in the work profile</string>
+ <!-- Notification displayed when a screenshot is saved in the private profile. [CHAR LIMIT=NONE] -->
+ <string name="screenshot_private_profile_notification">Saved in <xliff:g id="app" example="Files">%1$s</xliff:g> in the private profile</string>
<!-- Default name referring to the app on the device that lets the user browse stored files. [CHAR LIMIT=NONE] -->
<string name="screenshot_default_files_app_name">Files</string>
<!-- A notice shown to the user to indicate that an app has detected the screenshot that the user has just taken. [CHAR LIMIT=75] -->
@@ -1182,6 +1184,8 @@
<string name="accessibility_action_label_edit_widgets">Customize widgets</string>
<!-- Accessibility content description for communal hub. [CHAR LIMIT=NONE] -->
<string name="accessibility_content_description_for_communal_hub">Widgets on lock screen</string>
+ <!-- Label for accessibility action to select a widget in edit mode. [CHAR LIMIT=NONE] -->
+ <string name="accessibility_action_label_select_widget">select widget</string>
<!-- Related to user switcher --><skip/>
@@ -1630,12 +1634,15 @@
<!-- Hint for accessibility. A stream name is a parameter. For example: double tap to unmute media [CHAR_LIMIT=NONE] -->
<string name="volume_panel_hint_unmute">unmute %s</string>
- <!-- Title with application label for media output settings. [CHAR LIMIT=20] -->
+ <!-- Title with application label for media output settings when there is media playing. [CHAR LIMIT=20] -->
<string name="media_output_label_title">Playing <xliff:g id="label" example="Music Player">%s</xliff:g> on</string>
<!-- Title for media output settings without media is playing. [CHAR LIMIT=20] -->
<string name="media_output_title_without_playing">Audio will play on</string>
+ <!-- Title for media output settings when there is an ongoing call in progress. [CHAR LIMIT=20] -->
+ <string name="media_output_title_ongoing_call">Calling on</string>
+
<!-- Name of special SystemUI debug settings -->
<string name="system_ui_tuner">System UI Tuner</string>
@@ -1719,9 +1726,7 @@
<string name="accessibility_status_bar_satellite_available">Satellite, connection available</string>
<!-- Text displayed indicating that the user is connected to a satellite signal. -->
- <string name="satellite_connected_carrier_text">Connected to satellite</string>
- <!-- Text displayed indicating that the user is not connected to a satellite signal. -->
- <string name="satellite_not_connected_carrier_text">Not connected to satellite</string>
+ <string name="satellite_connected_carrier_text">Satellite SOS</string>
<!-- Accessibility label for managed profile icon (not shown on screen) [CHAR LIMIT=NONE] -->
<string name="accessibility_managed_profile">Work profile</string>
@@ -2261,6 +2266,9 @@
<!-- Accessibility description when QS tile is to be added, indicating the destination position [CHAR LIMIT=NONE] -->
<string name="accessibility_qs_edit_tile_add_to_position">Add to position <xliff:g id="position" example="5">%1$d</xliff:g></string>
+ <!-- Accessibility description when QS tile would be added or moved, but the current position is not valid for adding or moving to [CHAR LIMIT=NONE] -->
+ <string name="accessibilit_qs_edit_tile_add_move_invalid_position">Position invalid.</string>
+
<!-- Accessibility description indicating the currently selected tile's position. Only used for tiles that are currently in use [CHAR LIMIT=NONE] -->
<string name="accessibility_qs_edit_position">Position <xliff:g id="position" example="5">%1$d</xliff:g></string>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java
index 6e1b670..660f0db 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java
@@ -68,6 +68,7 @@
import java.io.PrintWriter;
import java.util.Optional;
import java.util.concurrent.Executor;
+import java.util.concurrent.ThreadPoolExecutor;
import java.util.function.Supplier;
/**
@@ -199,6 +200,10 @@
return mContext;
}
+ /**
+ * We should pass single threaded executor (rather than {@link ThreadPoolExecutor}) as we will
+ * make binder calls on that executor and ordering is vital.
+ */
public void setBgExecutor(Executor bgExecutor) {
mBgExecutor = bgExecutor;
}
@@ -230,25 +235,22 @@
mListenersRegistered = true;
mBgExecutor.execute(() -> {
+ if (registerRotationWatcher) {
+ try {
+ WindowManagerGlobal.getWindowManagerService()
+ .watchRotation(mRotationWatcher, DEFAULT_DISPLAY);
+ mRotationWatcherRegistered = true;
+ } catch (IllegalArgumentException e) {
+ Log.w(TAG, "RegisterListeners for the display failed", e);
+ } catch (RemoteException e) {
+ Log.e(TAG, "RegisterListeners caught a RemoteException", e);
+ }
+ }
final Intent intent = mContext.registerReceiver(mDockedReceiver,
new IntentFilter(Intent.ACTION_DOCK_EVENT));
mContext.getMainExecutor().execute(() -> updateDockedState(intent));
});
- if (registerRotationWatcher) {
- try {
- WindowManagerGlobal.getWindowManagerService()
- .watchRotation(mRotationWatcher, DEFAULT_DISPLAY);
- mRotationWatcherRegistered = true;
- } catch (IllegalArgumentException e) {
- mListenersRegistered = false;
- Log.w(TAG, "RegisterListeners for the display failed", e);
- } catch (RemoteException e) {
- Log.e(TAG, "RegisterListeners caught a RemoteException", e);
- return;
- }
- }
-
TaskStackChangeListeners.getInstance().registerTaskStackListener(mTaskStackListener);
}
@@ -265,17 +267,16 @@
} catch (IllegalArgumentException e) {
Log.e(TAG, "Docked receiver already unregistered", e);
}
- });
- if (mRotationWatcherRegistered) {
- try {
- WindowManagerGlobal.getWindowManagerService().removeRotationWatcher(
- mRotationWatcher);
- } catch (RemoteException e) {
- Log.e(TAG, "UnregisterListeners caught a RemoteException", e);
- return;
+ if (mRotationWatcherRegistered) {
+ try {
+ WindowManagerGlobal.getWindowManagerService().removeRotationWatcher(
+ mRotationWatcher);
+ } catch (RemoteException e) {
+ Log.e(TAG, "UnregisterListeners caught a RemoteException", e);
+ }
}
- }
+ });
TaskStackChangeListeners.getInstance().unregisterTaskStackListener(mTaskStackListener);
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index b916fc2..91fb688 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -81,7 +81,6 @@
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.keyguard.KeyguardWmStateRefactor;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
-import com.android.systemui.keyguard.shared.RefactorKeyguardDismissIntent;
import com.android.systemui.log.SessionTracker;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.FalsingManager;
@@ -304,7 +303,7 @@
*/
@Override
public void finish(int targetUserId) {
- if (!RefactorKeyguardDismissIntent.isEnabled()) {
+ if (!SceneContainerFlag.isEnabled()) {
// If there's a pending runnable because the user interacted with a widget
// and we're leaving keyguard, then run it.
boolean deferKeyguardDone = false;
@@ -614,7 +613,7 @@
* @param action callback to be invoked when keyguard disappear animation completes.
*/
public void setOnDismissAction(ActivityStarter.OnDismissAction action, Runnable cancelAction) {
- if (RefactorKeyguardDismissIntent.isEnabled()) {
+ if (SceneContainerFlag.isEnabled()) {
return;
}
if (mCancelAction != null) {
@@ -908,7 +907,7 @@
mUiEventLogger.log(uiEvent, getSessionId());
}
- if (RefactorKeyguardDismissIntent.isEnabled()) {
+ if (SceneContainerFlag.isEnabled()) {
if (authenticatedWithPrimaryAuth) {
mPrimaryBouncerInteractor.get()
.notifyKeyguardAuthenticatedPrimaryAuth(targetUserId);
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index b8af59d..4c9af66 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -121,6 +121,9 @@
SystemProperties.getBoolean("debug.disable_screen_decorations", false);
private static final boolean DEBUG_SCREENSHOT_ROUNDED_CORNERS =
SystemProperties.getBoolean("debug.screenshot_rounded_corners", false);
+
+ private static final boolean sToolkitSetFrameRateReadOnly =
+ android.view.flags.Flags.toolkitSetFrameRateReadOnly();
private boolean mDebug = DEBUG_SCREENSHOT_ROUNDED_CORNERS;
private int mDebugColor = Color.RED;
@@ -892,6 +895,10 @@
lp.width = MATCH_PARENT;
lp.height = MATCH_PARENT;
lp.setTitle("ScreenDecorHwcOverlay");
+ if (sToolkitSetFrameRateReadOnly) {
+ lp.setFrameRateBoostOnTouchEnabled(false);
+ lp.setFrameRatePowerSavingsBalanced(false);
+ }
lp.gravity = Gravity.TOP | Gravity.START;
if (!mDebug) {
lp.setColorMode(ActivityInfo.COLOR_MODE_A8);
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
index c6716e4..68a69d3 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
@@ -192,6 +192,7 @@
private final ShadeController mShadeController;
private final Lazy<PanelExpansionInteractor> mPanelExpansionInteractor;
private final StatusBarWindowCallback mNotificationShadeCallback;
+ private final ScreenshotHelper mScreenshotHelper;
private boolean mDismissNotificationShadeActionRegistered;
@Inject
@@ -221,6 +222,7 @@
(keyguardShowing, keyguardOccluded, keyguardGoingAway, bouncerShowing, mDozing,
panelExpanded, isDreaming) ->
registerOrUnregisterDismissNotificationShadeAction();
+ mScreenshotHelper = new ScreenshotHelper(mContext);
}
@Override
@@ -516,8 +518,7 @@
}
private void handleTakeScreenshot() {
- ScreenshotHelper screenshotHelper = new ScreenshotHelper(mContext);
- screenshotHelper.takeScreenshot(
+ mScreenshotHelper.takeScreenshot(
SCREENSHOT_ACCESSIBILITY_ACTIONS, new Handler(Looper.getMainLooper()), null);
}
diff --git a/packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.java b/packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.java
index 85aeb27..019f498 100644
--- a/packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.java
@@ -254,7 +254,11 @@
mVelocityTracker = mVelocityTrackerFactory.obtain();
mTouchSession = session;
mVelocityTracker.clear();
- mNotificationShadeWindowController.setForcePluginOpen(true, this);
+
+ if (!Flags.communalBouncerDoNotModifyPluginOpen()) {
+ mNotificationShadeWindowController.setForcePluginOpen(true, this);
+ }
+
mScrimManager.addCallback(mScrimManagerCallback);
mCurrentScrimController = mScrimManager.getCurrentController();
@@ -265,7 +269,10 @@
}
mScrimManager.removeCallback(mScrimManagerCallback);
mCapture = null;
- mNotificationShadeWindowController.setForcePluginOpen(false, this);
+
+ if (!Flags.communalBouncerDoNotModifyPluginOpen()) {
+ mNotificationShadeWindowController.setForcePluginOpen(false, this);
+ }
});
session.registerGestureListener(mOnGestureListener);
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
index a211147..da56951 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
@@ -112,10 +112,6 @@
}
// set selected to enable marquee unless a screen reader is enabled
- logoView.isSelected =
- !accessibilityManager.isEnabled || !accessibilityManager.isTouchExplorationEnabled
- logoDescriptionView.isSelected =
- !accessibilityManager.isEnabled || !accessibilityManager.isTouchExplorationEnabled
titleView.isSelected =
!accessibilityManager.isEnabled || !accessibilityManager.isTouchExplorationEnabled
subtitleView.isSelected =
@@ -419,19 +415,6 @@
indicatorMessageView.isSelected =
!accessibilityManager.isEnabled ||
!accessibilityManager.isTouchExplorationEnabled
-
- /**
- * Note: Talkback 14.0 has new rate-limitation design to reduce frequency of
- * TYPE_WINDOW_CONTENT_CHANGED events to once every 30 seconds. (context:
- * b/281765653#comment18) Using {@link View#announceForAccessibility}
- * instead as workaround since sending events exceeding this frequency is
- * required.
- */
- indicatorMessageView?.text?.let {
- if (it.isNotBlank()) {
- view.announceForAccessibility(it)
- }
- }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
index 511bdc4..db251fd 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
@@ -18,6 +18,7 @@
import android.content.ComponentName
import android.os.UserHandle
+import android.view.View
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.compose.animation.scene.SceneKey
import com.android.compose.animation.scene.TransitionKey
@@ -37,8 +38,8 @@
) {
val currentScene: Flow<SceneKey> = communalInteractor.desiredScene
- /** Whether communal hub can be focused to enable accessibility actions. */
- val isFocusable: Flow<Boolean> = communalInteractor.isIdleOnCommunal
+ /** Whether communal hub should be focused by accessibility tools. */
+ open val isFocusable: Flow<Boolean> = MutableStateFlow(false)
/** Whether widgets are currently being re-ordered. */
open val reorderingWidgets: StateFlow<Boolean> = MutableStateFlow(false)
@@ -49,6 +50,9 @@
val selectedKey: StateFlow<String?>
get() = _selectedKey
+ /** Accessibility delegate to be set on CommunalAppWidgetHostView. */
+ open val widgetAccessibilityDelegate: View.AccessibilityDelegate? = null
+
fun signalUserInteraction() {
communalInteractor.signalUserInteraction()
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
index 9dacf8c..1120466 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
@@ -16,12 +16,18 @@
package com.android.systemui.communal.ui.viewmodel
+import android.content.res.Resources
+import android.view.View
+import android.view.accessibility.AccessibilityNodeInfo
import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.communal.domain.interactor.CommunalTutorialInteractor
import com.android.systemui.communal.domain.model.CommunalContentModel
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.Logger
import com.android.systemui.log.dagger.CommunalLog
@@ -29,7 +35,9 @@
import com.android.systemui.media.controls.ui.view.MediaHost
import com.android.systemui.media.controls.ui.view.MediaHostState
import com.android.systemui.media.dagger.MediaModule
+import com.android.systemui.res.R
import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.util.kotlin.BooleanFlowOperators.not
import javax.inject.Inject
import javax.inject.Named
import kotlinx.coroutines.CoroutineScope
@@ -54,6 +62,8 @@
@Inject
constructor(
@Application private val scope: CoroutineScope,
+ @Main private val resources: Resources,
+ keyguardTransitionInteractor: KeyguardTransitionInteractor,
private val communalInteractor: CommunalInteractor,
tutorialInteractor: CommunalTutorialInteractor,
private val shadeInteractor: ShadeInteractor,
@@ -93,6 +103,37 @@
private val _currentPopup: MutableStateFlow<PopupType?> = MutableStateFlow(null)
override val currentPopup: Flow<PopupType?> = _currentPopup.asStateFlow()
+ // The widget is focusable for accessibility when the hub is fully visible and shade is not
+ // opened.
+ override val isFocusable: Flow<Boolean> =
+ combine(
+ keyguardTransitionInteractor.isFinishedInState(KeyguardState.GLANCEABLE_HUB),
+ communalInteractor.isIdleOnCommunal,
+ shadeInteractor.isAnyFullyExpanded,
+ ) { transitionedToGlanceableHub, isIdleOnCommunal, isAnyFullyExpanded ->
+ transitionedToGlanceableHub && isIdleOnCommunal && !isAnyFullyExpanded
+ }
+ .distinctUntilChanged()
+
+ override val widgetAccessibilityDelegate =
+ object : View.AccessibilityDelegate() {
+ override fun onInitializeAccessibilityNodeInfo(
+ host: View,
+ info: AccessibilityNodeInfo
+ ) {
+ super.onInitializeAccessibilityNodeInfo(host, info)
+ // Hint user to long press in order to enter edit mode
+ info.addAction(
+ AccessibilityNodeInfo.AccessibilityAction(
+ AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK.id,
+ resources
+ .getString(R.string.accessibility_action_label_edit_widgets)
+ .lowercase()
+ )
+ )
+ }
+ }
+
private val _isEnableWidgetDialogShowing: MutableStateFlow<Boolean> = MutableStateFlow(false)
val isEnableWidgetDialogShowing: Flow<Boolean> = _isEnableWidgetDialogShowing.asStateFlow()
@@ -191,6 +232,14 @@
return !shadeInteractor.isAnyFullyExpanded.value
}
+ /**
+ * Whether touches should be disabled in communal.
+ *
+ * This is needed because the notification shade does not block touches in blank areas and these
+ * fall through to the glanceable hub, which we don't want.
+ */
+ val touchesAllowed: Flow<Boolean> = not(shadeInteractor.isAnyFullyExpanded)
+
companion object {
const val POPUP_AUTO_HIDE_TIMEOUT_MS = 12000L
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DefaultBroadcastReceiverBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DefaultBroadcastReceiverBinder.java
index 2fa42ec..7ced932 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DefaultBroadcastReceiverBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DefaultBroadcastReceiverBinder.java
@@ -24,7 +24,6 @@
import com.android.systemui.people.widget.PeopleSpaceWidgetPinnedReceiver;
import com.android.systemui.people.widget.PeopleSpaceWidgetProvider;
import com.android.systemui.screenshot.SmartActionsReceiver;
-import com.android.systemui.volume.VolumePanelDialogReceiver;
import dagger.Binds;
import dagger.Module;
@@ -59,15 +58,6 @@
*/
@Binds
@IntoMap
- @ClassKey(VolumePanelDialogReceiver.class)
- public abstract BroadcastReceiver bindVolumePanelDialogReceiver(
- VolumePanelDialogReceiver broadcastReceiver);
-
- /**
- *
- */
- @Binds
- @IntoMap
@ClassKey(PeopleSpaceWidgetPinnedReceiver.class)
public abstract BroadcastReceiver bindPeopleSpaceWidgetPinnedReceiver(
PeopleSpaceWidgetPinnedReceiver broadcastReceiver);
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
index 0bc29a8..95bc514 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
@@ -123,6 +123,7 @@
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.globalactions.domain.interactor.GlobalActionsInteractor;
+import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.GlobalActions.GlobalActionsManager;
import com.android.systemui.plugins.GlobalActionsPanelPlugin;
import com.android.systemui.scrim.ScrimDrawable;
@@ -186,6 +187,7 @@
private static final String GLOBAL_ACTION_KEY_LOGOUT = "logout";
static final String GLOBAL_ACTION_KEY_EMERGENCY = "emergency";
static final String GLOBAL_ACTION_KEY_SCREENSHOT = "screenshot";
+ static final String GLOBAL_ACTION_KEY_SYSTEM_UPDATE = "system_update";
// See NotificationManagerService#scheduleDurationReachedLocked
private static final long TOAST_FADE_TIME = 333;
@@ -213,6 +215,7 @@
private final TelecomManager mTelecomManager;
private final MetricsLogger mMetricsLogger;
private final UiEventLogger mUiEventLogger;
+ private final ActivityStarter mActivityStarter;
// Used for RingerModeTracker
private final LifecycleRegistry mLifecycle = new LifecycleRegistry(this);
@@ -317,7 +320,10 @@
GA_CLOSE_TAP_OUTSIDE(810),
@UiEvent(doc = "Power menu was closed via power + volume up.")
- GA_CLOSE_POWER_VOLUP(811);
+ GA_CLOSE_POWER_VOLUP(811),
+
+ @UiEvent(doc = "System Update button was pressed.")
+ GA_SYSTEM_UPDATE_PRESS(1716);
private final int mId;
@@ -349,6 +355,7 @@
@NonNull VibratorHelper vibrator,
@Main Resources resources,
ConfigurationController configurationController,
+ ActivityStarter activityStarter,
UserTracker userTracker,
KeyguardStateController keyguardStateController,
UserManager userManager,
@@ -385,6 +392,7 @@
mSecureSettings = secureSettings;
mResources = resources;
mConfigurationController = configurationController;
+ mActivityStarter = activityStarter;
mUserTracker = userTracker;
mUserManager = userManager;
mTrustManager = trustManager;
@@ -659,6 +667,8 @@
if (shouldDisplayEmergency()) {
addIfShouldShowAction(tempActions, new EmergencyDialerAction());
}
+ } else if (GLOBAL_ACTION_KEY_SYSTEM_UPDATE.equals(actionKey)) {
+ addIfShouldShowAction(tempActions, new SystemUpdateAction());
} else {
Log.e(TAG, "Invalid global action key " + actionKey);
}
@@ -1145,6 +1155,40 @@
}
}
+ @VisibleForTesting
+ final class SystemUpdateAction extends SinglePressAction {
+
+ SystemUpdateAction() {
+ super(com.android.settingslib.R.drawable.ic_system_update,
+ com.android.settingslib.R.string.system_update_settings_list_item_title);
+ }
+
+ @Override
+ public void onPress() {
+ mUiEventLogger.log(GlobalActionsEvent.GA_SYSTEM_UPDATE_PRESS);
+ launchSystemUpdate();
+ }
+
+ @Override
+ public boolean showDuringKeyguard() {
+ return true;
+ }
+
+ @Override
+ public boolean showBeforeProvisioning() {
+ return false;
+ }
+
+ private void launchSystemUpdate() {
+ Intent intent = new Intent(Settings.ACTION_SYSTEM_UPDATE_SETTINGS);
+ intent.addFlags(
+ Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
+ // postStartActivityDismissingKeyguard is used for showing keyguard
+ // input/pin/password screen if lockscreen is secured, before sending the intent.
+ mActivityStarter.postStartActivityDismissingKeyguard(intent, 0);
+ }
+ }
+
private Action getSettingsAction() {
return new SinglePressAction(R.drawable.ic_settings,
R.string.global_action_settings) {
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt
index db2ec8f..ea8d7d7 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt
@@ -189,7 +189,6 @@
durations?.get(1) ?: LongPressHapticBuilder.INVALID_DURATION,
effectDuration
)
- _postedActionType.value = ActionType.INITIALIZE_ANIMATOR
setState(State.IDLE)
return true
}
@@ -209,6 +208,5 @@
START_ANIMATOR,
REVERSE_ANIMATOR,
CANCEL_ANIMATOR,
- INITIALIZE_ANIMATOR,
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffectViewBinder.kt b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffectViewBinder.kt
index c591af2..c464ed1 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffectViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffectViewBinder.kt
@@ -57,6 +57,7 @@
qsLongPressEffect.clearActionType()
}
QSLongPressEffect.ActionType.LONG_PRESS -> {
+ tile.prepareForLaunch()
tile.performLongClick()
qsLongPressEffect.clearActionType()
}
@@ -66,8 +67,32 @@
qsLongPressEffect.clearActionType()
}
QSLongPressEffect.ActionType.START_ANIMATOR -> {
- if (effectAnimator?.isRunning == false) {
- effectAnimator?.start()
+ if (effectAnimator?.isRunning != true) {
+ effectAnimator =
+ ValueAnimator.ofFloat(0f, 1f).apply {
+ this.duration =
+ qsLongPressEffect.effectDuration.toLong()
+ interpolator = AccelerateDecelerateInterpolator()
+
+ doOnStart {
+ qsLongPressEffect.handleAnimationStart()
+ }
+ addUpdateListener {
+ val value = animatedValue as Float
+ if (value == 0f) {
+ tile.bringToFront()
+ } else {
+ tile.updateLongPressEffectProperties(value)
+ }
+ }
+ doOnEnd {
+ qsLongPressEffect.handleAnimationComplete()
+ }
+ doOnCancel {
+ qsLongPressEffect.handleAnimationCancel()
+ }
+ start()
+ }
}
}
QSLongPressEffect.ActionType.REVERSE_ANIMATOR -> {
@@ -81,26 +106,6 @@
tile.resetLongPressEffectProperties()
effectAnimator?.cancel()
}
- QSLongPressEffect.ActionType.INITIALIZE_ANIMATOR -> {
- effectAnimator =
- ValueAnimator.ofFloat(0f, 1f).apply {
- this.duration =
- qsLongPressEffect.effectDuration.toLong()
- interpolator = AccelerateDecelerateInterpolator()
-
- doOnStart { qsLongPressEffect.handleAnimationStart() }
- addUpdateListener {
- val value = animatedValue as Float
- if (value == 0f) {
- tile.bringToFront()
- } else {
- tile.updateLongPressEffectProperties(value)
- }
- }
- doOnEnd { qsLongPressEffect.handleAnimationComplete() }
- doOnCancel { qsLongPressEffect.handleAnimationCancel() }
- }
- }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 5d31d1e..6ef39f3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -3214,6 +3214,10 @@
mHideAnimationRun = false;
adjustStatusBarLocked();
sendUserPresentBroadcast();
+
+ if (!KeyguardWmStateRefactor.isEnabled()) {
+ mKeyguardInteractor.dismissKeyguard();
+ }
}
private Configuration.Builder createInteractionJankMonitorConf(int cuj) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt
index 6138330..3956901 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt
@@ -76,4 +76,9 @@
@Binds fun trustRepository(impl: TrustRepositoryImpl): TrustRepository
@Binds fun keyguardClockRepository(impl: KeyguardClockRepositoryImpl): KeyguardClockRepository
+
+ @Binds
+ fun keyguardSmartspaceRepository(
+ impl: KeyguardSmartspaceRepositoryImpl
+ ): KeyguardSmartspaceRepository
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardSmartspaceRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardSmartspaceRepository.kt
index afe9151..956125c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardSmartspaceRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardSmartspaceRepository.kt
@@ -16,19 +16,68 @@
package com.android.systemui.keyguard.data.repository
+import android.content.Context
+import android.provider.Settings
import android.view.View
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.util.settings.SecureSettings
+import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.stateIn
+
+interface KeyguardSmartspaceRepository {
+ val bcSmartspaceVisibility: StateFlow<Int>
+ val isWeatherEnabled: StateFlow<Boolean>
+ fun setBcSmartspaceVisibility(visibility: Int)
+}
@SysUISingleton
-class KeyguardSmartspaceRepository @Inject constructor() {
+class KeyguardSmartspaceRepositoryImpl
+@Inject
+constructor(
+ context: Context,
+ private val secureSettings: SecureSettings,
+ private val userTracker: UserTracker,
+ @Application private val applicationScope: CoroutineScope,
+) : KeyguardSmartspaceRepository {
private val _bcSmartspaceVisibility: MutableStateFlow<Int> = MutableStateFlow(View.GONE)
- val bcSmartspaceVisibility: StateFlow<Int> = _bcSmartspaceVisibility.asStateFlow()
+ override val bcSmartspaceVisibility: StateFlow<Int> = _bcSmartspaceVisibility.asStateFlow()
+ val defaultValue =
+ context.resources.getBoolean(
+ com.android.internal.R.bool.config_lockscreenWeatherEnabledByDefault
+ )
+ override val isWeatherEnabled: StateFlow<Boolean> =
+ secureSettings
+ .observerFlow(
+ names = arrayOf(Settings.Secure.LOCK_SCREEN_WEATHER_ENABLED),
+ userId = userTracker.userId,
+ )
+ .onStart { emit(Unit) }
+ .map { getLockscreenWeatherEnabled() }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = getLockscreenWeatherEnabled()
+ )
- fun setBcSmartspaceVisibility(visibility: Int) {
+ override fun setBcSmartspaceVisibility(visibility: Int) {
_bcSmartspaceVisibility.value = visibility
}
+
+ private fun getLockscreenWeatherEnabled(): Boolean {
+ return secureSettings.getIntForUser(
+ Settings.Secure.LOCK_SCREEN_WEATHER_ENABLED,
+ if (defaultValue) 1 else 0,
+ userTracker.userId
+ ) == 1
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
index 1fba737..c2843d8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
@@ -168,11 +168,13 @@
keyguardInteractor.isKeyguardOccluded
.filterRelevantKeyguardStateAnd { isOccluded -> isOccluded }
.collect {
- startTransitionTo(
- toState = KeyguardState.OCCLUDED,
- modeOnCanceled = TransitionModeOnCanceled.RESET,
- ownerReason = "isOccluded = true",
- )
+ if (!maybeHandleInsecurePowerGesture()) {
+ startTransitionTo(
+ toState = KeyguardState.OCCLUDED,
+ modeOnCanceled = TransitionModeOnCanceled.RESET,
+ ownerReason = "isOccluded = true",
+ )
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index d191768..8d107ab 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -90,6 +90,7 @@
keyguardTransitionInteractor: KeyguardTransitionInteractor,
sceneInteractorProvider: Provider<SceneInteractor>,
private val fromGoneTransitionInteractor: Provider<FromGoneTransitionInteractor>,
+ private val fromLockscreenTransitionInteractor: Provider<FromLockscreenTransitionInteractor>,
sharedNotificationContainerInteractor: Provider<SharedNotificationContainerInteractor>,
@Application applicationScope: CoroutineScope,
) {
@@ -307,19 +308,27 @@
configurationInteractor
.dimensionPixelSize(R.dimen.keyguard_translate_distance_on_swipe_up)
.flatMapLatest { translationDistance ->
- combine(
+ combineTransform(
shadeRepository.legacyShadeExpansion.onStart { emit(0f) },
keyguardTransitionInteractor.transitionValue(GONE).onStart { emit(0f) },
) { legacyShadeExpansion, goneValue ->
- if (goneValue == 1f || legacyShadeExpansion == 0f) {
+ if (goneValue == 1f || (goneValue == 0f && legacyShadeExpansion == 0f)) {
// Reset the translation value
- 0f
- } else {
- // On swipe up, translate the keyguard to reveal the bouncer
- MathUtils.lerp(
- translationDistance,
- 0,
- Interpolators.FAST_OUT_LINEAR_IN.getInterpolation(legacyShadeExpansion)
+ emit(0f)
+ } else if (legacyShadeExpansion > 0f && legacyShadeExpansion < 1f) {
+ // On swipe up, translate the keyguard to reveal the bouncer, OR a GONE
+ // transition is running, which means this is a swipe to dismiss. Values of
+ // 0f and 1f need to be ignored in the legacy shade expansion. These can
+ // flip arbitrarily as the legacy shade is reset, and would cause the
+ // translation value to jump around unexpectedly.
+ emit(
+ MathUtils.lerp(
+ translationDistance,
+ 0,
+ Interpolators.FAST_OUT_LINEAR_IN.getInterpolation(
+ legacyShadeExpansion
+ ),
+ )
)
}
}
@@ -417,6 +426,11 @@
fromGoneTransitionInteractor.get().showKeyguard()
}
+ /** Temporary shim, until [KeyguardWmStateRefactor] is enabled */
+ fun dismissKeyguard() {
+ fromLockscreenTransitionInteractor.get().dismissKeyguard()
+ }
+
companion object {
private const val TAG = "KeyguardInteractor"
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractor.kt
index 4abd6c6..f385671 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractor.kt
@@ -77,7 +77,9 @@
// transition, to ensure we don't transition while moving between, for example,
// *_BOUNCER -> LOCKSCREEN.
return powerInteractor.detailedWakefulness.value.powerButtonLaunchGestureTriggered &&
- KeyguardState.deviceIsAsleepInState(transitionInteractor.getStartedState())
+ KeyguardState.deviceIsAsleepInState(
+ transitionInteractor.currentTransitionInfoInternal.value.to
+ )
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSmartspaceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSmartspaceInteractor.kt
index bb633b5..45a4b70a2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSmartspaceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSmartspaceInteractor.kt
@@ -28,6 +28,7 @@
private val keyguardSmartspaceRepository: KeyguardSmartspaceRepository,
) {
val bcSmartspaceVisibility: StateFlow<Int> = keyguardSmartspaceRepository.bcSmartspaceVisibility
+ val isWeatherEnabled: StateFlow<Boolean> = keyguardSmartspaceRepository.isWeatherEnabled
fun setBcSmartspaceVisibility(visibility: Int) {
keyguardSmartspaceRepository.setBcSmartspaceVisibility(visibility)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
index 2850165..b2a24ca 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
@@ -225,10 +225,12 @@
if (!KeyguardWmStateRefactor.isEnabled) {
scope.launch {
keyguardInteractor.onCameraLaunchDetected.filterRelevantKeyguardState().collect {
- startTransitionTo(
- toState = KeyguardState.OCCLUDED,
- modeOnCanceled = TransitionModeOnCanceled.RESET,
- )
+ if (!maybeHandleInsecurePowerGesture()) {
+ startTransitionTo(
+ toState = KeyguardState.OCCLUDED,
+ modeOnCanceled = TransitionModeOnCanceled.RESET,
+ )
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractor.kt
index f5cd767..63bfba6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractor.kt
@@ -19,10 +19,10 @@
import android.animation.FloatEvaluator
import android.animation.IntEvaluator
-import com.android.systemui.common.ui.data.repository.ConfigurationRepository
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.shared.model.StatusBarState
-import com.android.systemui.shade.data.repository.ShadeRepository
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.shade.domain.interactor.ShadeLockscreenInteractor
import com.android.systemui.statusbar.phone.SystemUIDialogManager
import com.android.systemui.statusbar.phone.hideAffordancesRequest
import javax.inject.Inject
@@ -38,17 +38,16 @@
class UdfpsKeyguardInteractor
@Inject
constructor(
- configRepo: ConfigurationRepository,
burnInInteractor: BurnInInteractor,
keyguardInteractor: KeyguardInteractor,
- shadeRepository: ShadeRepository,
+ shadeInteractor: ShadeInteractor,
+ shadeLockscreenInteractor: ShadeLockscreenInteractor,
dialogManager: SystemUIDialogManager,
) {
private val intEvaluator = IntEvaluator()
private val floatEvaluator = FloatEvaluator()
val dozeAmount = keyguardInteractor.dozeAmount
- val scaleForResolution = configRepo.scaleForResolution
/** Burn-in offsets for the UDFPS view to mitigate burn-in on AOD. */
val burnInOffsets: Flow<Offsets> =
@@ -68,13 +67,14 @@
val dialogHideAffordancesRequest: Flow<Boolean> = dialogManager.hideAffordancesRequest
val qsProgress: Flow<Float> =
- shadeRepository.qsExpansion // swipe from top of LS
+ shadeInteractor.qsExpansion // swipe from top of LS
.map { (it * 2).coerceIn(0f, 1f) }
.onStart { emit(0f) }
val shadeExpansion: Flow<Float> =
combine(
- shadeRepository.udfpsTransitionToFullShadeProgress, // swipe from middle of LS
+ shadeLockscreenInteractor
+ .udfpsTransitionToFullShadeProgress, // swipe from middle of LS
keyguardInteractor.statusBarState, // quick swipe from middle of LS
) { shadeProgress, statusBarState ->
if (statusBarState == StatusBarState.SHADE_LOCKED) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt
index cccb93c..b0d45ed 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt
@@ -25,6 +25,7 @@
import androidx.core.view.isInvisible
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
+import com.android.app.tracing.coroutines.launch
import com.android.systemui.common.ui.view.LongPressHandlingView
import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
import com.android.systemui.keyguard.ui.view.DeviceEntryIconView
@@ -40,6 +41,7 @@
@ExperimentalCoroutinesApi
object DeviceEntryIconViewBinder {
+ private const val TAG = "DeviceEntryIconViewBinder"
/**
* Updates UI for:
@@ -82,22 +84,22 @@
// GONE => AOD transition (even though the view may not be visible until the middle
// of the transition.
repeatOnLifecycle(Lifecycle.State.CREATED) {
- launch {
+ launch("$TAG#viewModel.isVisible") {
viewModel.isVisible.collect { isVisible ->
longPressHandlingView.isInvisible = !isVisible
}
}
- launch {
+ launch("$TAG#viewModel.isLongPressEnabled") {
viewModel.isLongPressEnabled.collect { isEnabled ->
longPressHandlingView.setLongPressHandlingEnabled(isEnabled)
}
}
- launch {
+ launch("$TAG#viewModel.accessibilityDelegateHint") {
viewModel.accessibilityDelegateHint.collect { hint ->
view.accessibilityHintType = hint
}
}
- launch {
+ launch("$TAG#viewModel.useBackgroundProtection") {
viewModel.useBackgroundProtection.collect { useBackgroundProtection ->
if (useBackgroundProtection) {
bgView.visibility = View.VISIBLE
@@ -106,7 +108,7 @@
}
}
}
- launch {
+ launch("$TAG#viewModel.burnInOffsets") {
viewModel.burnInOffsets.collect { burnInOffsets ->
view.translationX = burnInOffsets.x.toFloat()
view.translationY = burnInOffsets.y.toFloat()
@@ -114,7 +116,9 @@
}
}
- launch { viewModel.deviceEntryViewAlpha.collect { alpha -> view.alpha = alpha } }
+ launch("$TAG#viewModel.deviceEntryViewAlpha") {
+ viewModel.deviceEntryViewAlpha.collect { alpha -> view.alpha = alpha }
+ }
}
}
@@ -122,7 +126,7 @@
repeatOnLifecycle(Lifecycle.State.STARTED) {
// Start with an empty state
fgIconView.setImageState(StateSet.NOTHING, /* merge */ false)
- launch {
+ launch("$TAG#fpIconView.viewModel") {
fgViewModel.viewModel.collect { viewModel ->
fgIconView.setImageState(
view.getIconState(viewModel.type, viewModel.useAodVariant),
@@ -144,8 +148,10 @@
bgView.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.CREATED) {
- launch { bgViewModel.alpha.collect { alpha -> bgView.alpha = alpha } }
- launch {
+ launch("$TAG#bgViewModel.alpha") {
+ bgViewModel.alpha.collect { alpha -> bgView.alpha = alpha }
+ }
+ launch("$TAG#bgViewModel.color") {
bgViewModel.color.collect { color ->
bgView.imageTintList = ColorStateList.valueOf(color)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardDismissActionBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardDismissActionBinder.kt
index 93b3ba5..b293027 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardDismissActionBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardDismissActionBinder.kt
@@ -20,8 +20,8 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.domain.interactor.KeyguardDismissActionInteractor
-import com.android.systemui.keyguard.shared.RefactorKeyguardDismissIntent
import com.android.systemui.log.core.LogLevel
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.util.kotlin.sample
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
@@ -40,7 +40,7 @@
) : CoreStartable {
override fun start() {
- if (!RefactorKeyguardDismissIntent.isEnabled) {
+ if (!SceneContainerFlag.isEnabled) {
return
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardDismissBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardDismissBinder.kt
index f77d012..ac24591 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardDismissBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardDismissBinder.kt
@@ -22,9 +22,9 @@
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.flags.FeatureFlagsClassic
import com.android.systemui.keyguard.domain.interactor.KeyguardDismissInteractor
-import com.android.systemui.keyguard.shared.RefactorKeyguardDismissIntent
import com.android.systemui.keyguard.shared.model.KeyguardDone
import com.android.systemui.log.core.LogLevel
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
@@ -44,7 +44,7 @@
) : CoreStartable {
override fun start() {
- if (!RefactorKeyguardDismissIntent.isEnabled) {
+ if (!SceneContainerFlag.isEnabled) {
return
}
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 9ec7a65..487c2e9 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
@@ -42,10 +42,10 @@
val context: Context,
val keyguardClockViewModel: KeyguardClockViewModel,
val keyguardSmartspaceViewModel: KeyguardSmartspaceViewModel,
- val keyguardSmartspaceInteractor: KeyguardSmartspaceInteractor,
+ private val keyguardSmartspaceInteractor: KeyguardSmartspaceInteractor,
val smartspaceController: LockscreenSmartspaceController,
val keyguardUnlockAnimationController: KeyguardUnlockAnimationController,
- val blueprintInteractor: Lazy<KeyguardBlueprintInteractor>,
+ private val blueprintInteractor: Lazy<KeyguardBlueprintInteractor>,
) : KeyguardSection() {
private var smartspaceView: View? = null
private var weatherView: View? = null
@@ -61,12 +61,10 @@
weatherView = smartspaceController.buildAndConnectWeatherView(constraintLayout)
dateView = smartspaceController.buildAndConnectDateView(constraintLayout)
pastVisibility = smartspaceView?.visibility ?: View.GONE
- if (keyguardSmartspaceViewModel.isSmartspaceEnabled) {
- constraintLayout.addView(smartspaceView)
- if (keyguardSmartspaceViewModel.isDateWeatherDecoupled) {
- constraintLayout.addView(weatherView)
- constraintLayout.addView(dateView)
- }
+ constraintLayout.addView(smartspaceView)
+ if (keyguardSmartspaceViewModel.isDateWeatherDecoupled) {
+ constraintLayout.addView(weatherView)
+ constraintLayout.addView(dateView)
}
keyguardUnlockAnimationController.lockscreenSmartspace = smartspaceView
smartspaceVisibilityListener = OnGlobalLayoutListener {
@@ -205,13 +203,9 @@
constraintSet.apply {
val weatherVisibility =
- when (keyguardClockViewModel.hasCustomWeatherDataDisplay.value) {
- true -> ConstraintSet.GONE
- false ->
- when (keyguardSmartspaceViewModel.isWeatherEnabled) {
- true -> ConstraintSet.VISIBLE
- false -> ConstraintSet.GONE
- }
+ when (keyguardSmartspaceViewModel.isWeatherVisible.value) {
+ true -> ConstraintSet.VISIBLE
+ false -> ConstraintSet.GONE
}
setVisibility(sharedR.id.weather_smartspace_view, weatherVisibility)
setAlpha(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModel.kt
index 7e39a88..adc090d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModel.kt
@@ -57,5 +57,15 @@
)
}
+ fun notificationAlpha(viewState: ViewStateAccessor): Flow<Float> {
+ var startAlpha = 1f
+ return transitionAnimation.sharedFlow(
+ duration = 200.milliseconds,
+ onStart = { startAlpha = viewState.alpha() },
+ onStep = { startAlpha },
+ onFinish = { 1f },
+ )
+ }
+
override val deviceEntryParentViewAlpha = transitionAnimation.immediatelyTransitionTo(0f)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlows.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlows.kt
index 24429fa..e0a3af6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlows.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlows.kt
@@ -19,11 +19,11 @@
import com.android.app.animation.Interpolators.EMPHASIZED_ACCELERATE
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardDismissActionInteractor
-import com.android.systemui.keyguard.shared.RefactorKeyguardDismissIntent
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
import com.android.systemui.keyguard.shared.model.ScrimAlpha
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.SysuiStatusBarStateController
import dagger.Lazy
@@ -48,7 +48,7 @@
) {
/** Common fade for scrim alpha values during *BOUNCER->GONE */
fun scrimAlpha(duration: Duration, fromState: KeyguardState): Flow<ScrimAlpha> {
- return if (RefactorKeyguardDismissIntent.isEnabled) {
+ return if (SceneContainerFlag.isEnabled) {
keyguardDismissActionInteractor
.get()
.willAnimateDismissActionOnLockscreen
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
index 45dca99..e26b75f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
@@ -50,6 +50,7 @@
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.shareIn
+import kotlinx.coroutines.flow.stateIn
/** Models the UI state for the containing device entry icon & long-press handling view. */
@ExperimentalCoroutinesApi
@@ -110,27 +111,7 @@
)
}
- private val dozeAmount: Flow<Float> =
- combine(
- transitionInteractor.startedKeyguardTransitionStep,
- merge(
- transitionInteractor.transitionStepsFromState(KeyguardState.AOD).map {
- 1f - it.value
- },
- transitionInteractor.transitionStepsToState(KeyguardState.AOD).map { it.value }
- ),
- ) { startedKeyguardTransitionStep, aodTransitionAmount ->
- if (
- startedKeyguardTransitionStep.to == KeyguardState.AOD ||
- startedKeyguardTransitionStep.from == KeyguardState.AOD
- ) {
- aodTransitionAmount
- } else {
- // in case a new transition (ie: to occluded) cancels a transition to or from
- // aod, then we want to make sure the doze amount is still updated to 0
- 0f
- }
- }
+ private val dozeAmount: Flow<Float> = transitionInteractor.transitionValue(KeyguardState.AOD)
// Burn-in offsets that animate based on the transition amount to AOD
private val animatedBurnInOffsets: Flow<BurnInOffsets> =
combine(nonAnimatedBurnInOffsets, dozeAmount) { burnInOffsets, dozeAmount ->
@@ -141,54 +122,57 @@
)
}
- val deviceEntryViewAlpha: Flow<Float> =
+ val deviceEntryViewAlpha: StateFlow<Float> =
combine(
- transitionAlpha,
- alphaMultiplierFromShadeExpansion,
- ) { alpha, alphaMultiplier ->
- alpha * alphaMultiplier
- }
+ transitionAlpha,
+ alphaMultiplierFromShadeExpansion,
+ ) { alpha, alphaMultiplier ->
+ alpha * alphaMultiplier
+ }
+ .stateIn(scope = scope, started = SharingStarted.WhileSubscribed(), initialValue = 0f)
val useBackgroundProtection: StateFlow<Boolean> = isUdfpsSupported
val burnInOffsets: Flow<BurnInOffsets> =
- deviceEntryUdfpsInteractor.isUdfpsEnrolledAndEnabled.flatMapLatest { udfpsEnrolled ->
- if (udfpsEnrolled) {
- combine(
- transitionInteractor.startedKeyguardTransitionStep.sample(
- shadeInteractor.isAnyFullyExpanded,
- ::Pair
- ),
- animatedBurnInOffsets,
- nonAnimatedBurnInOffsets,
- ) {
- (startedTransitionStep, shadeExpanded),
- animatedBurnInOffsets,
- nonAnimatedBurnInOffsets ->
- if (startedTransitionStep.to == KeyguardState.AOD) {
- when (startedTransitionStep.from) {
- KeyguardState.ALTERNATE_BOUNCER -> animatedBurnInOffsets
- KeyguardState.LOCKSCREEN ->
- if (shadeExpanded) {
- nonAnimatedBurnInOffsets
- } else {
- animatedBurnInOffsets
- }
- else -> nonAnimatedBurnInOffsets
+ deviceEntryUdfpsInteractor.isUdfpsEnrolledAndEnabled
+ .flatMapLatest { udfpsEnrolled ->
+ if (udfpsEnrolled) {
+ combine(
+ transitionInteractor.startedKeyguardTransitionStep.sample(
+ shadeInteractor.isAnyFullyExpanded,
+ ::Pair
+ ),
+ animatedBurnInOffsets,
+ nonAnimatedBurnInOffsets,
+ ) {
+ (startedTransitionStep, shadeExpanded),
+ animatedBurnInOffsets,
+ nonAnimatedBurnInOffsets ->
+ if (startedTransitionStep.to == KeyguardState.AOD) {
+ when (startedTransitionStep.from) {
+ KeyguardState.ALTERNATE_BOUNCER -> animatedBurnInOffsets
+ KeyguardState.LOCKSCREEN ->
+ if (shadeExpanded) {
+ nonAnimatedBurnInOffsets
+ } else {
+ animatedBurnInOffsets
+ }
+ else -> nonAnimatedBurnInOffsets
+ }
+ } else if (startedTransitionStep.from == KeyguardState.AOD) {
+ when (startedTransitionStep.to) {
+ KeyguardState.LOCKSCREEN -> animatedBurnInOffsets
+ else -> BurnInOffsets(x = 0, y = 0, progress = 0f)
+ }
+ } else {
+ BurnInOffsets(x = 0, y = 0, progress = 0f)
}
- } else if (startedTransitionStep.from == KeyguardState.AOD) {
- when (startedTransitionStep.to) {
- KeyguardState.LOCKSCREEN -> animatedBurnInOffsets
- else -> BurnInOffsets(x = 0, y = 0, progress = 0f)
- }
- } else {
- BurnInOffsets(x = 0, y = 0, progress = 0f)
}
+ } else {
+ // If UDFPS isn't enrolled, we don't show any UI on AOD so there's no need
+ // to use burn in offsets at all
+ flowOf(BurnInOffsets(x = 0, y = 0, progress = 0f))
}
- } else {
- // If UDFPS isn't enrolled, we don't show any UI on AOD so there's no need
- // to use burn in offsets at all
- flowOf(BurnInOffsets(x = 0, y = 0, progress = 0f))
}
- }
+ .distinctUntilChanged()
private val isUnlocked: Flow<Boolean> =
keyguardInteractor.isKeyguardDismissible.flatMapLatest { isUnlocked ->
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
index 1f544c1..198e9f2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
@@ -84,8 +84,8 @@
combine(
isLargeClockVisible,
currentClock,
- ) { isLargeClock, clock ->
- clock?.let { clock ->
+ ) { isLargeClock, currentClock ->
+ currentClock?.let { clock ->
val face = if (isLargeClock) clock.largeClock else clock.smallClock
face.config.hasCustomWeatherDataDisplay
}
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 dc053aa..c0b1f95 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
@@ -26,6 +26,7 @@
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
@@ -41,8 +42,8 @@
/** Whether the smartspace section is available in the build. */
val isSmartspaceEnabled: Boolean = smartspaceController.isEnabled()
/** Whether the weather area is available in the build. */
- // TODO(b/317891876): this should be a Flow as the value can change over time.
- val isWeatherEnabled: Boolean = smartspaceController.isWeatherEnabled()
+ private val isWeatherEnabled: StateFlow<Boolean> = smartspaceInteractor.isWeatherEnabled
+
/** Whether the data and weather areas are decoupled in the build. */
val isDateWeatherDecoupled: Boolean = smartspaceController.isDateWeatherDecoupled()
@@ -58,8 +59,10 @@
/** Whether the weather area should be visible. */
val isWeatherVisible: StateFlow<Boolean> =
- keyguardClockViewModel.hasCustomWeatherDataDisplay
- .map { clockIncludesCustomWeatherDisplay ->
+ combine(
+ isWeatherEnabled,
+ keyguardClockViewModel.hasCustomWeatherDataDisplay,
+ ) { isWeatherEnabled, clockIncludesCustomWeatherDisplay ->
isWeatherVisible(
clockIncludesCustomWeatherDisplay = clockIncludesCustomWeatherDisplay,
isWeatherEnabled = isWeatherEnabled,
@@ -72,7 +75,7 @@
isWeatherVisible(
clockIncludesCustomWeatherDisplay =
keyguardClockViewModel.hasCustomWeatherDataDisplay.value,
- isWeatherEnabled = isWeatherEnabled,
+ isWeatherEnabled = smartspaceInteractor.isWeatherEnabled.value,
)
)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt
index a08a234..b1fa710 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt
@@ -20,11 +20,11 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor.Companion.TO_GONE_DURATION
import com.android.systemui.keyguard.domain.interactor.KeyguardDismissActionInteractor
-import com.android.systemui.keyguard.shared.RefactorKeyguardDismissIntent
import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
import com.android.systemui.keyguard.shared.model.ScrimAlpha
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.statusbar.SysuiStatusBarStateController
import dagger.Lazy
import javax.inject.Inject
@@ -80,7 +80,7 @@
/** Bouncer container alpha */
val bouncerAlpha: Flow<Float> =
- if (RefactorKeyguardDismissIntent.isEnabled) {
+ if (SceneContainerFlag.isEnabled) {
keyguardDismissActionInteractor
.get()
.willAnimateDismissActionOnLockscreen
@@ -104,7 +104,7 @@
/** Lockscreen alpha */
val lockscreenAlpha: Flow<Float> =
- if (RefactorKeyguardDismissIntent.isEnabled) {
+ if (SceneContainerFlag.isEnabled) {
keyguardDismissActionInteractor
.get()
.willAnimateDismissActionOnLockscreen
diff --git a/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt b/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt
index c997617..bf80e18 100644
--- a/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt
+++ b/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt
@@ -70,8 +70,7 @@
// dispatcher to use. We don't want it to run on the Dispatchers.Default thread pool as
// default behavior. Instead, we want it to run on the view's UI thread since the user will
// presumably want to call view methods that require being called from said UI thread.
- val lifecycleCoroutineContext =
- Dispatchers.Main + createCoroutineTracingContext() + coroutineContext
+ val lifecycleCoroutineContext = MAIN_DISPATCHER_SINGLETON + coroutineContext
val traceName =
if (Compile.IS_DEBUG && coroutineTracing()) {
inferTraceSectionName()
@@ -205,17 +204,28 @@
StackWalker.getInstance().walk { stream ->
stream.filter(::isFrameInteresting).limit(5).findFirst()
}
- if (interestingFrame.isPresent) {
+ return if (interestingFrame.isPresent) {
val f = interestingFrame.get()
- return "${f.className}#${f.methodName}:${f.lineNumber} [$DEFAULT_TRACE_NAME]"
+ "${f.className}#${f.methodName}:${f.lineNumber} [$DEFAULT_TRACE_NAME]"
} else {
- return DEFAULT_TRACE_NAME
+ DEFAULT_TRACE_NAME
}
} finally {
Trace.traceEnd(Trace.TRACE_TAG_APP)
}
}
+/**
+ * Even though there is only has one usage of `Dispatchers.Main` in this file, we cache it in a
+ * top-level property so that we do not unnecessarily create new `CoroutineContext` objects for
+ * tracing on each call to [repeatWhenAttached]. It is okay to reuse a single instance of the
+ * tracing context because it is copied for its children.
+ *
+ * Also, ideally, we would use the injected `@Main CoroutineDispatcher`, but [repeatWhenAttached] is
+ * an extension function, and plumbing dagger-injected instances for static usage has little
+ * benefit.
+ */
+private val MAIN_DISPATCHER_SINGLETON = Dispatchers.Main + createCoroutineTracingContext()
private const val DEFAULT_TRACE_NAME = "repeatWhenAttached"
private const val CURRENT_CLASS_NAME = "com.android.systemui.lifecycle.RepeatWhenAttachedKt"
private const val JAVA_ADAPTER_CLASS_NAME = "com.android.systemui.util.kotlin.JavaAdapterKt"
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
index 4e77d13..6a6eba1 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
@@ -51,8 +51,7 @@
fun isRemoteResumeAllowed() = featureFlags.isEnabled(Flags.MEDIA_REMOTE_RESUME)
/** Check whether to use scene framework */
- fun isSceneContainerEnabled() =
- SceneContainerFlag.isEnabled && MediaInSceneContainerFlag.isEnabled
+ fun isSceneContainerEnabled() = SceneContainerFlag.isEnabled
/** Check whether to use media refactor code */
fun isMediaControlsRefactorEnabled() = MediaControlsRefactorFlag.isEnabled
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaInSceneContainerFlag.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaInSceneContainerFlag.kt
deleted file mode 100644
index 77279f2..0000000
--- a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaInSceneContainerFlag.kt
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * 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.systemui.media.controls.util
-
-import com.android.systemui.Flags
-import com.android.systemui.flags.FlagToken
-import com.android.systemui.flags.RefactorFlagUtils
-
-/** Helper for reading or using the media_in_scene_container flag state. */
-@Suppress("NOTHING_TO_INLINE")
-object MediaInSceneContainerFlag {
- /** The aconfig flag name */
- const val FLAG_NAME = Flags.FLAG_MEDIA_IN_SCENE_CONTAINER
-
- /** A token used for dependency declaration */
- val token: FlagToken
- get() = FlagToken(FLAG_NAME, isEnabled)
-
- /** Is the flag enabled? */
- @JvmStatic
- inline val isEnabled
- get() = Flags.mediaInSceneContainer()
-
- /**
- * 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 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/people/NotificationHelper.java b/packages/SystemUI/src/com/android/systemui/people/NotificationHelper.java
index d863dcc..710142b6 100644
--- a/packages/SystemUI/src/com/android/systemui/people/NotificationHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/people/NotificationHelper.java
@@ -96,7 +96,7 @@
if (messages2 == null) {
return -1;
}
- return (int) (n2.when - n1.when);
+ return (int) (n2.getWhen() - n1.getWhen());
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
index 042fb63f..4ee2db7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
@@ -75,10 +75,6 @@
@Override
protected void onFinishInflate() {
super.onFinishInflate();
- // set layer to make alpha animation of brightness slider nicer - otherwise elements
- // of slider are animated separately and it doesn't look good. See b/329244723
- setLayerType(LAYER_TYPE_HARDWARE, null);
-
mQSPanelContainer = findViewById(R.id.expanded_qs_scroll_view);
mQSPanel = findViewById(R.id.quick_settings_panel);
mHeader = findViewById(R.id.header);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
index 58858df..829c419 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
@@ -332,6 +332,14 @@
return mTiles.size();
}
+ public int getItemCountForAccessibility() {
+ if (mAccessibilityAction == ACTION_MOVE) {
+ return mEditIndex;
+ } else {
+ return getItemCount();
+ }
+ }
+
@Override
public boolean onFailedToRecycleView(Holder holder) {
holder.stopDrag();
@@ -406,6 +414,10 @@
} else if (selectable && mAccessibilityAction == ACTION_MOVE) {
info.state.contentDescription = mContext.getString(
R.string.accessibility_qs_edit_tile_move_to_position, position);
+ } else if (!selectable && (mAccessibilityAction == ACTION_MOVE
+ || mAccessibilityAction == ACTION_ADD)) {
+ info.state.contentDescription = mContext.getString(
+ R.string.accessibilit_qs_edit_tile_add_move_invalid_position);
} else {
info.state.contentDescription = info.state.label;
}
@@ -424,14 +436,15 @@
holder.mTileView.setOnClickListener(null);
holder.mTileView.setFocusable(true);
holder.mTileView.setFocusableInTouchMode(true);
+ holder.mTileView.setAccessibilityTraversalBefore(View.NO_ID);
if (mAccessibilityAction != ACTION_NONE) {
holder.mTileView.setClickable(selectable);
holder.mTileView.setFocusable(selectable);
holder.mTileView.setFocusableInTouchMode(selectable);
- holder.mTileView.setImportantForAccessibility(selectable
- ? View.IMPORTANT_FOR_ACCESSIBILITY_YES
- : View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
+// holder.mTileView.setImportantForAccessibility(selectable
+// ? View.IMPORTANT_FOR_ACCESSIBILITY_YES
+// : View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
if (selectable) {
holder.mTileView.setOnClickListener(new OnClickListener() {
@Override
@@ -911,4 +924,5 @@
int estimatedTileViewHeight = mTempTextView.getMeasuredHeight() * 2 + padding * 2;
mMinTileViewHeight = Math.max(minHeight, estimatedTileViewHeight);
}
+
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepository.kt
index 93021ba..cfcea98 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepository.kt
@@ -91,7 +91,15 @@
.queryIntentServicesAsUser(INTENT, FLAGS, userId)
.mapNotNull { it.serviceInfo }
.filter { it.permission == BIND_QUICK_SETTINGS_TILE }
- .filter { packageManager.isComponentActuallyEnabled(it) }
+ .filter {
+ try {
+ packageManager.isComponentActuallyEnabled(it)
+ } catch (e: IllegalArgumentException) {
+ // If the package is not found, it means it was uninstalled between query
+ // and now. So it's clearly not enabled.
+ false
+ }
+ }
.mapTo(mutableSetOf()) { it.componentName }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSLongPressProperties.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSLongPressProperties.kt
index a2ded6a..71cf9e6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSLongPressProperties.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSLongPressProperties.kt
@@ -22,8 +22,8 @@
* These properties are used during animation if a tile supports a long-press action.
*/
data class QSLongPressProperties(
- var xScale: Float,
- var yScale: Float,
+ var height: Float,
+ var width: Float,
var cornerRadius: Float,
var backgroundColor: Int,
var labelColor: Int,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
index 40cf4a4..ca5b771 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
@@ -25,6 +25,7 @@
import android.content.res.Resources.ID_NULL
import android.graphics.Color
import android.graphics.PorterDuff
+import android.graphics.Rect
import android.graphics.drawable.Drawable
import android.graphics.drawable.GradientDrawable
import android.graphics.drawable.LayerDrawable
@@ -46,6 +47,7 @@
import android.widget.Switch
import android.widget.TextView
import androidx.annotation.VisibleForTesting
+import androidx.core.graphics.drawable.updateBounds
import com.android.app.tracing.traceSection
import com.android.settingslib.Utils
import com.android.systemui.Flags
@@ -62,7 +64,6 @@
import com.android.systemui.qs.logging.QSLogger
import com.android.systemui.qs.tileimpl.QSIconViewImpl.QS_ANIM_LENGTH
import com.android.systemui.res.R
-import com.android.systemui.util.children
import kotlinx.coroutines.DisposableHandle
import java.util.Objects
@@ -83,6 +84,10 @@
const val UNAVAILABLE_ALPHA = 0.3f
@VisibleForTesting
internal const val TILE_STATE_RES_PREFIX = "tile_states_"
+ @VisibleForTesting
+ internal const val LONG_PRESS_EFFECT_WIDTH_SCALE = 1.1f
+ @VisibleForTesting
+ internal const val LONG_PRESS_EFFECT_HEIGHT_SCALE = 1.2f
}
private val icon: QSIconViewImpl = QSIconViewImpl(context)
@@ -180,6 +185,8 @@
private val locInScreen = IntArray(2)
/** Visuo-haptic long-press effects */
+ private var haveLongPressPropertiesBeenReset = true
+ private var paddingForLaunch = Rect()
private var initialLongPressProperties: QSLongPressProperties? = null
private var finalLongPressProperties: QSLongPressProperties? = null
private val colorEvaluator = ArgbEvaluator.getInstance()
@@ -326,7 +333,7 @@
private fun updateHeight() {
// TODO(b/332900989): Find a more robust way of resetting the tile if not reset by the
// launch animation.
- if (scaleX != 1f || scaleY != 1f) {
+ if (!haveLongPressPropertiesBeenReset && longPressEffect != null) {
// The launch animation of a long-press effect did not reset the long-press effect so
// we must do it here
resetLongPressEffectProperties()
@@ -632,7 +639,7 @@
)
}
showRippleEffect = false
- initializeLongPressProperties()
+ initializeLongPressProperties(measuredHeight, measuredWidth)
} else {
// Long-press effects might have been enabled before but the new state does not
// handle a long-press. In this case, we go back to the behaviour of a regular tile
@@ -765,8 +772,60 @@
override fun onActivityLaunchAnimationEnd() = resetLongPressEffectProperties()
+ fun prepareForLaunch() {
+ val startingHeight = initialLongPressProperties?.height?.toInt() ?: 0
+ val startingWidth = initialLongPressProperties?.width?.toInt() ?: 0
+ val deltaH = finalLongPressProperties?.height?.minus(startingHeight)?.toInt() ?: 0
+ val deltaW = finalLongPressProperties?.width?.minus(startingWidth)?.toInt() ?: 0
+ paddingForLaunch.left = -deltaW / 2
+ paddingForLaunch.top = -deltaH / 2
+ paddingForLaunch.right = deltaW / 2
+ paddingForLaunch.bottom = deltaH / 2
+ }
+
+ override fun getPaddingForLaunchAnimation(): Rect = paddingForLaunch
+
fun updateLongPressEffectProperties(effectProgress: Float) {
if (!isLongClickable || longPressEffect == null) return
+
+ if (haveLongPressPropertiesBeenReset) haveLongPressPropertiesBeenReset = false
+
+ // Dimensions change
+ val newHeight =
+ interpolateFloat(
+ effectProgress,
+ initialLongPressProperties?.height ?: 0f,
+ finalLongPressProperties?.height ?: 0f,
+ ).toInt()
+ val newWidth =
+ interpolateFloat(
+ effectProgress,
+ initialLongPressProperties?.width ?: 0f,
+ finalLongPressProperties?.width ?: 0f,
+ ).toInt()
+
+ val startingHeight = initialLongPressProperties?.height?.toInt() ?: 0
+ val startingWidth = initialLongPressProperties?.width?.toInt() ?: 0
+ val deltaH = (newHeight - startingHeight) / 2
+ val deltaW = (newWidth - startingWidth) / 2
+
+ background.updateBounds(
+ left = -deltaW,
+ top = -deltaH,
+ right = newWidth - deltaW,
+ bottom = newHeight - deltaH,
+ )
+
+ // Radius change
+ val newRadius =
+ interpolateFloat(
+ effectProgress,
+ initialLongPressProperties?.cornerRadius ?: 0f,
+ finalLongPressProperties?.cornerRadius ?: 0f,
+ )
+ changeCornerRadius(newRadius)
+
+ // Color change
setAllColors(
colorEvaluator.evaluate(
effectProgress,
@@ -802,32 +861,6 @@
finalLongPressProperties?.iconColor ?: 0,
) as Int,
)
-
- val newScaleX =
- interpolateFloat(
- effectProgress,
- initialLongPressProperties?.xScale ?: 1f,
- finalLongPressProperties?.xScale ?: 1f,
- )
- val newScaleY =
- interpolateFloat(
- effectProgress,
- initialLongPressProperties?.xScale ?: 1f,
- finalLongPressProperties?.xScale ?: 1f,
- )
- val newRadius =
- interpolateFloat(
- effectProgress,
- initialLongPressProperties?.cornerRadius ?: 0f,
- finalLongPressProperties?.cornerRadius ?: 0f,
- )
- scaleX = newScaleX
- scaleY = newScaleY
- for (child in children) {
- child.scaleX = 1f / newScaleX
- child.scaleY = 1f / newScaleY
- }
- changeCornerRadius(newRadius)
}
private fun unbindLongPressEffect() {
@@ -839,12 +872,12 @@
start + fraction * (end - start)
fun resetLongPressEffectProperties() {
- scaleY = 1f
- scaleX = 1f
- for (child in children) {
- child.scaleY = 1f
- child.scaleX = 1f
- }
+ background.updateBounds(
+ left = 0,
+ top = 0,
+ right = initialLongPressProperties?.width?.toInt() ?: 0,
+ bottom = initialLongPressProperties?.height?.toInt() ?: 0,
+ )
changeCornerRadius(resources.getDimensionPixelSize(R.dimen.qs_corner_radius).toFloat())
setAllColors(
getBackgroundColorForState(lastState, lastDisabledByPolicy),
@@ -854,13 +887,15 @@
getOverlayColorForState(lastState),
)
icon.setTint(icon.mIcon as ImageView, lastIconTint)
+ haveLongPressPropertiesBeenReset = true
}
- private fun initializeLongPressProperties() {
+ @VisibleForTesting
+ fun initializeLongPressProperties(startingHeight: Int, startingWidth: Int) {
initialLongPressProperties =
QSLongPressProperties(
- /* xScale= */1f,
- /* yScale= */1f,
+ height = startingHeight.toFloat(),
+ width = startingWidth.toFloat(),
resources.getDimensionPixelSize(R.dimen.qs_corner_radius).toFloat(),
getBackgroundColorForState(lastState),
getLabelColorForState(lastState),
@@ -872,8 +907,8 @@
finalLongPressProperties =
QSLongPressProperties(
- /* xScale= */1.1f,
- /* yScale= */1.2f,
+ height = LONG_PRESS_EFFECT_HEIGHT_SCALE * startingHeight,
+ width = LONG_PRESS_EFFECT_WIDTH_SCALE * startingWidth,
resources.getDimensionPixelSize(R.dimen.qs_corner_radius).toFloat() - 20,
getBackgroundColorForState(Tile.STATE_ACTIVE),
getLabelColorForState(Tile.STATE_ACTIVE),
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
index d5b05ef..60469c0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
@@ -116,8 +116,6 @@
public class InternetDialogController implements AccessPointController.AccessPointCallback {
private static final String TAG = "InternetDialogController";
- private static final String ACTION_NETWORK_PROVIDER_SETTINGS =
- "android.settings.NETWORK_PROVIDER_SETTINGS";
private static final String ACTION_WIFI_SCANNING_SETTINGS =
"android.settings.WIFI_SCANNING_SETTINGS";
/**
@@ -361,7 +359,8 @@
@VisibleForTesting
protected Intent getSettingsIntent() {
- return new Intent(ACTION_NETWORK_PROVIDER_SETTINGS).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ return new Intent(Settings.ACTION_NETWORK_PROVIDER_SETTINGS).addFlags(
+ Intent.FLAG_ACTIVITY_NEW_TASK);
}
@Nullable
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractor.kt
index fdc596b..eec5d3d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractor.kt
@@ -26,6 +26,7 @@
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.common.shared.model.Text
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor
import com.android.systemui.qs.tiles.impl.internet.domain.model.InternetTileModel
@@ -38,7 +39,9 @@
import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon
+import com.android.systemui.utils.coroutines.flow.mapLatestConflated
import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
@@ -48,6 +51,7 @@
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.withContext
@OptIn(ExperimentalCoroutinesApi::class)
/** Observes internet state changes providing the [InternetTileModel]. */
@@ -55,6 +59,7 @@
@Inject
constructor(
private val context: Context,
+ @Main private val mainCoroutineContext: CoroutineContext,
@Application private val scope: CoroutineScope,
airplaneModeRepository: AirplaneModeRepository,
private val connectivityRepository: ConnectivityRepository,
@@ -111,42 +116,48 @@
notConnectedFlow
} else {
combine(
- it.networkName,
- it.signalLevelIcon,
- mobileDataContentName,
- ) { networkNameModel, signalIcon, dataContentDescription ->
- when (signalIcon) {
- is SignalIconModel.Cellular -> {
- val secondary =
- mobileDataContentConcat(
- networkNameModel.name,
- dataContentDescription
+ it.networkName,
+ it.signalLevelIcon,
+ mobileDataContentName,
+ ) { networkNameModel, signalIcon, dataContentDescription ->
+ Triple(networkNameModel, signalIcon, dataContentDescription)
+ }
+ .mapLatestConflated { (networkNameModel, signalIcon, dataContentDescription) ->
+ when (signalIcon) {
+ is SignalIconModel.Cellular -> {
+ val secondary =
+ mobileDataContentConcat(
+ networkNameModel.name,
+ dataContentDescription
+ )
+
+ val drawable =
+ withContext(mainCoroutineContext) { SignalDrawable(context) }
+ drawable.setLevel(signalIcon.level)
+ val loadedIcon = Icon.Loaded(drawable, null)
+
+ InternetTileModel.Active(
+ secondaryTitle = secondary,
+ icon = loadedIcon,
+ stateDescription =
+ ContentDescription.Loaded(secondary.toString()),
+ contentDescription = ContentDescription.Loaded(internetLabel),
)
-
- val stateLevel = signalIcon.level
- val drawable = SignalDrawable(context)
- drawable.setLevel(stateLevel)
- val loadedIcon = Icon.Loaded(drawable, null)
-
- InternetTileModel.Active(
- secondaryTitle = secondary,
- icon = loadedIcon,
- stateDescription = ContentDescription.Loaded(secondary.toString()),
- contentDescription = ContentDescription.Loaded(internetLabel),
- )
- }
- is SignalIconModel.Satellite -> {
- val secondary =
- signalIcon.icon.contentDescription.loadContentDescription(context)
- InternetTileModel.Active(
- secondaryTitle = secondary,
- iconId = signalIcon.icon.res,
- stateDescription = ContentDescription.Loaded(secondary),
- contentDescription = ContentDescription.Loaded(internetLabel),
- )
+ }
+ is SignalIconModel.Satellite -> {
+ val secondary =
+ signalIcon.icon.contentDescription.loadContentDescription(
+ context
+ )
+ InternetTileModel.Active(
+ secondaryTitle = secondary,
+ iconId = signalIcon.icon.res,
+ stateDescription = ContentDescription.Loaded(secondary),
+ contentDescription = ContentDescription.Loaded(internetLabel),
+ )
+ }
}
}
- }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt b/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt
index 2a73b53..063a52c 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt
@@ -16,10 +16,16 @@
package com.android.systemui.scene
+import com.android.systemui.CoreStartable
+import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor
+import com.android.systemui.scene.domain.startable.SceneContainerStartable
import com.android.systemui.scene.shared.model.SceneContainerConfig
import com.android.systemui.scene.shared.model.Scenes
+import dagger.Binds
import dagger.Module
import dagger.Provides
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
/** Scene framework Dagger module suitable for variants that want to exclude "keyguard" scenes. */
@Module(
@@ -31,28 +37,41 @@
ShadeSceneModule::class,
],
)
-object KeyguardlessSceneContainerFrameworkModule {
+interface KeyguardlessSceneContainerFrameworkModule {
- // TODO(b/298234162): provide a SceneContainerStartable without lockscreen and bouncer.
+ @Binds
+ @IntoMap
+ @ClassKey(SceneContainerStartable::class)
+ fun containerStartable(impl: SceneContainerStartable): CoreStartable
- @Provides
- fun containerConfig(): SceneContainerConfig {
- return SceneContainerConfig(
- // Note that this list is in z-order. The first one is the bottom-most and the
- // last one is top-most.
- sceneKeys =
- listOf(
- Scenes.Gone,
- Scenes.QuickSettings,
- Scenes.Shade,
- ),
- initialSceneKey = Scenes.Gone,
- navigationDistances =
- mapOf(
- Scenes.Gone to 0,
- Scenes.Shade to 1,
- Scenes.QuickSettings to 2,
- ),
- )
+ @Binds
+ @IntoMap
+ @ClassKey(WindowRootViewVisibilityInteractor::class)
+ fun bindWindowRootViewVisibilityInteractor(
+ impl: WindowRootViewVisibilityInteractor
+ ): CoreStartable
+
+ companion object {
+
+ @Provides
+ fun containerConfig(): SceneContainerConfig {
+ return SceneContainerConfig(
+ // Note that this list is in z-order. The first one is the bottom-most and the
+ // last one is top-most.
+ sceneKeys =
+ listOf(
+ Scenes.Gone,
+ Scenes.QuickSettings,
+ Scenes.Shade,
+ ),
+ initialSceneKey = Scenes.Gone,
+ navigationDistances =
+ mapOf(
+ Scenes.Gone to 0,
+ Scenes.Shade to 1,
+ Scenes.QuickSettings to 2,
+ ),
+ )
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt b/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt
index 994b012..5748ad4 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt
@@ -81,14 +81,6 @@
toScene: SceneKey,
transitionKey: TransitionKey? = null,
) {
- check(allSceneKeys().contains(toScene)) {
- """
- Cannot set the desired scene key to "$toScene". The configuration does not
- contain a scene with that key.
- """
- .trimIndent()
- }
-
dataSource.changeScene(
toScene = toScene,
transitionKey = transitionKey,
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
index 2ccd3b9..93cef61 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
@@ -162,6 +162,10 @@
loggingReason: String,
transitionKey: TransitionKey? = null,
) {
+ if (!repository.allSceneKeys().contains(toScene)) {
+ return
+ }
+
check(
toScene != Scenes.Gone || deviceUnlockedInteractor.deviceUnlockStatus.value.isUnlocked
) {
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
index 39ec12f..d5de28a3 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
@@ -57,12 +57,14 @@
import com.android.systemui.statusbar.phone.CentralSurfaces
import com.android.systemui.statusbar.policy.domain.interactor.DeviceProvisioningInteractor
import com.android.systemui.util.asIndenting
+import com.android.systemui.util.kotlin.getOrNull
import com.android.systemui.util.kotlin.pairwise
import com.android.systemui.util.kotlin.sample
import com.android.systemui.util.printSection
import com.android.systemui.util.println
import dagger.Lazy
import java.io.PrintWriter
+import java.util.Optional
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -107,7 +109,7 @@
private val authenticationInteractor: Lazy<AuthenticationInteractor>,
private val windowController: NotificationShadeWindowController,
private val deviceProvisioningInteractor: DeviceProvisioningInteractor,
- private val centralSurfaces: CentralSurfaces,
+ private val centralSurfacesOptLazy: Lazy<Optional<CentralSurfaces>>,
private val headsUpInteractor: HeadsUpNotificationInteractor,
private val occlusionInteractor: SceneContainerOcclusionInteractor,
private val faceUnlockInteractor: DeviceEntryFaceAuthInteractor,
@@ -115,6 +117,8 @@
private val uiEventLogger: UiEventLogger,
private val sceneBackInteractor: SceneBackInteractor,
) : CoreStartable {
+ private val centralSurfaces: CentralSurfaces?
+ get() = centralSurfacesOptLazy.get().getOrNull()
override fun start() {
if (SceneContainerFlag.isEnabled) {
@@ -542,7 +546,7 @@
}
.collect { isInteractingOrNull ->
isInteractingOrNull?.let { isInteracting ->
- centralSurfaces.setInteracting(
+ centralSurfaces?.setInteracting(
StatusBarManager.WINDOW_STATUS_BAR,
isInteracting,
)
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlag.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlag.kt
index 234eda8..cf33c4a 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlag.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlag.kt
@@ -27,8 +27,6 @@
import com.android.systemui.keyguard.KeyguardWmStateRefactor
import com.android.systemui.keyguard.MigrateClocksToBlueprint
import com.android.systemui.keyguard.shared.ComposeLockscreen
-import com.android.systemui.keyguard.shared.RefactorKeyguardDismissIntent
-import com.android.systemui.media.controls.util.MediaInSceneContainerFlag
import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor
import com.android.systemui.statusbar.phone.PredictiveBackSysUiFlag
@@ -44,12 +42,10 @@
ComposeLockscreen.isEnabled &&
KeyguardBottomAreaRefactor.isEnabled &&
KeyguardWmStateRefactor.isEnabled &&
- MediaInSceneContainerFlag.isEnabled &&
MigrateClocksToBlueprint.isEnabled &&
NotificationsHeadsUpRefactor.isEnabled &&
PredictiveBackSysUiFlag.isEnabled &&
- DeviceEntryUdfpsRefactor.isEnabled &&
- RefactorKeyguardDismissIntent.isEnabled
+ DeviceEntryUdfpsRefactor.isEnabled
// NOTE: Changes should also be made in getSecondaryFlags and @EnableSceneContainer
/** The main aconfig flag. */
@@ -61,12 +57,10 @@
ComposeLockscreen.token,
KeyguardBottomAreaRefactor.token,
KeyguardWmStateRefactor.token,
- MediaInSceneContainerFlag.token,
MigrateClocksToBlueprint.token,
NotificationsHeadsUpRefactor.token,
PredictiveBackSysUiFlag.token,
DeviceEntryUdfpsRefactor.token,
- RefactorKeyguardDismissIntent.token,
// NOTE: Changes should also be made in isEnabled and @EnableSceneContainer
)
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java b/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java
index 864f29a..d4e711e 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java
@@ -229,6 +229,8 @@
return CallbackToFutureAdapter.getFuture(
(completer) -> {
executor.execute(() -> {
+ // save images as quickly as possible on the background thread
+ Thread.currentThread().setPriority(Thread.MAX_PRIORITY);
try {
completer.set(task.execute());
} catch (ImageExportException | InterruptedException e) {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/MessageContainerController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/MessageContainerController.kt
index 7130fa1..5960462 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/MessageContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/MessageContainerController.kt
@@ -3,15 +3,19 @@
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.ValueAnimator
-import android.os.UserHandle
import android.view.View
import android.view.ViewGroup
import android.view.ViewGroup.MarginLayoutParams
import android.view.ViewTreeObserver
import android.view.animation.AccelerateDecelerateInterpolator
import androidx.constraintlayout.widget.Guideline
+import com.android.systemui.Flags.screenshotPrivateProfileBehaviorFix
+import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.res.R
+import com.android.systemui.screenshot.message.ProfileMessageController
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
/**
* MessageContainerController controls the display of content in the screenshot message container.
@@ -20,7 +24,9 @@
@Inject
constructor(
private val workProfileMessageController: WorkProfileMessageController,
+ private val profileMessageController: ProfileMessageController,
private val screenshotDetectionController: ScreenshotDetectionController,
+ @Application private val mainScope: CoroutineScope,
) {
private lateinit var container: ViewGroup
private lateinit var guideline: Guideline
@@ -42,43 +48,52 @@
detectionNoticeView.visibility = View.GONE
}
- // Minimal implementation for use when Flags.SCREENSHOT_METADATA isn't turned on.
- fun onScreenshotTaken(userHandle: UserHandle) {
- val workProfileData = workProfileMessageController.onScreenshotTaken(userHandle)
- if (workProfileData != null) {
- workProfileFirstRunView.visibility = View.VISIBLE
- detectionNoticeView.visibility = View.GONE
-
- workProfileMessageController.populateView(
- workProfileFirstRunView,
- workProfileData,
- this::animateOutMessageContainer
- )
- animateInMessageContainer()
- }
- }
-
fun onScreenshotTaken(screenshot: ScreenshotData) {
- val workProfileData = workProfileMessageController.onScreenshotTaken(screenshot.userHandle)
- var notifiedApps: List<CharSequence> =
- screenshotDetectionController.maybeNotifyOfScreenshot(screenshot)
+ if (screenshotPrivateProfileBehaviorFix()) {
+ mainScope.launch {
+ val profileData = profileMessageController.onScreenshotTaken(screenshot.userHandle)
+ var notifiedApps: List<CharSequence> =
+ screenshotDetectionController.maybeNotifyOfScreenshot(screenshot)
- // If work profile first run needs to show, bias towards that, otherwise show screenshot
- // detection notification if needed.
- if (workProfileData != null) {
- workProfileFirstRunView.visibility = View.VISIBLE
- detectionNoticeView.visibility = View.GONE
- workProfileMessageController.populateView(
- workProfileFirstRunView,
- workProfileData,
- this::animateOutMessageContainer
- )
- animateInMessageContainer()
- } else if (notifiedApps.isNotEmpty()) {
- detectionNoticeView.visibility = View.VISIBLE
- workProfileFirstRunView.visibility = View.GONE
- screenshotDetectionController.populateView(detectionNoticeView, notifiedApps)
- animateInMessageContainer()
+ // If profile first run needs to show, bias towards that, otherwise show screenshot
+ // detection notification if needed.
+ if (profileData != null) {
+ workProfileFirstRunView.visibility = View.VISIBLE
+ detectionNoticeView.visibility = View.GONE
+ profileMessageController.bindView(workProfileFirstRunView, profileData) {
+ animateOutMessageContainer()
+ }
+ animateInMessageContainer()
+ } else if (notifiedApps.isNotEmpty()) {
+ detectionNoticeView.visibility = View.VISIBLE
+ workProfileFirstRunView.visibility = View.GONE
+ screenshotDetectionController.populateView(detectionNoticeView, notifiedApps)
+ animateInMessageContainer()
+ }
+ }
+ } else {
+ val workProfileData =
+ workProfileMessageController.onScreenshotTaken(screenshot.userHandle)
+ var notifiedApps: List<CharSequence> =
+ screenshotDetectionController.maybeNotifyOfScreenshot(screenshot)
+
+ // If work profile first run needs to show, bias towards that, otherwise show screenshot
+ // detection notification if needed.
+ if (workProfileData != null) {
+ workProfileFirstRunView.visibility = View.VISIBLE
+ detectionNoticeView.visibility = View.GONE
+ workProfileMessageController.populateView(
+ workProfileFirstRunView,
+ workProfileData,
+ this::animateOutMessageContainer
+ )
+ animateInMessageContainer()
+ } else if (notifiedApps.isNotEmpty()) {
+ detectionNoticeView.visibility = View.VISIBLE
+ workProfileFirstRunView.visibility = View.GONE
+ screenshotDetectionController.populateView(detectionNoticeView, notifiedApps)
+ animateInMessageContainer()
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ReferenceScreenshotModule.java b/packages/SystemUI/src/com/android/systemui/screenshot/ReferenceScreenshotModule.java
index afb0280..d1b08f1 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ReferenceScreenshotModule.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ReferenceScreenshotModule.java
@@ -35,4 +35,10 @@
@Binds
ScreenshotActionsProvider.Factory bindScreenshotActionsProviderFactory(
DefaultScreenshotActionsProvider.Factory defaultScreenshotActionsProviderFactory);
+
+ /** */
+ @Provides
+ static ThumbnailObserver providesThumbnailObserver() {
+ return new ThumbnailObserver();
+ };
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotShelfViewProxy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotShelfViewProxy.kt
index 12a3daa..9b754f3 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotShelfViewProxy.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotShelfViewProxy.kt
@@ -32,6 +32,7 @@
import android.window.OnBackInvokedCallback
import android.window.OnBackInvokedDispatcher
import androidx.core.animation.doOnEnd
+import androidx.core.animation.doOnStart
import com.android.internal.logging.UiEventLogger
import com.android.systemui.log.DebugLogger.debugLog
import com.android.systemui.res.R
@@ -56,6 +57,7 @@
private val logger: UiEventLogger,
private val viewModel: ScreenshotViewModel,
private val windowManager: WindowManager,
+ private val thumbnailObserver: ThumbnailObserver,
@Assisted private val context: Context,
@Assisted private val displayId: Int
) : ScreenshotViewProxy {
@@ -85,6 +87,7 @@
onDismissalRequested = { event, velocity -> requestDismissal(event, velocity) },
onDismissalCancelled = { animationController.getSwipeReturnAnimation().start() }
)
+ view.updateInsets(windowManager.currentWindowMetrics.windowInsets)
addPredictiveBackListener { requestDismissal(SCREENSHOT_DISMISSED_OTHER) }
setOnKeyListener { requestDismissal(SCREENSHOT_DISMISSED_OTHER) }
debugLog(DEBUG_WINDOW) { "adding OnComputeInternalInsetsListener" }
@@ -99,6 +102,10 @@
info.touchableRegion.set(touchableRegion)
}
screenshotPreview = view.screenshotPreview
+ thumbnailObserver.setViews(
+ view.screenshotPreview,
+ view.requireViewById(R.id.screenshot_preview_border)
+ )
}
override fun reset() {
@@ -106,13 +113,19 @@
isPendingSharedTransition = false
viewModel.reset()
}
- override fun updateInsets(insets: WindowInsets) {}
+ override fun updateInsets(insets: WindowInsets) {
+ view.updateInsets(insets)
+ }
override fun updateOrientation(insets: WindowInsets) {}
override fun createScreenshotDropInAnimation(screenRect: Rect, showFlash: Boolean): Animator {
val entrance = animationController.getEntranceAnimation(screenRect, showFlash)
- // reset the timeout when animation finishes
- entrance.doOnEnd { callbacks?.onUserInteraction() }
+ entrance.doOnStart { thumbnailObserver.onEntranceStarted() }
+ entrance.doOnEnd {
+ // reset the timeout when animation finishes
+ callbacks?.onUserInteraction()
+ thumbnailObserver.onEntranceComplete()
+ }
return entrance
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ThumbnailObserver.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ThumbnailObserver.kt
new file mode 100644
index 0000000..cf62a14
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ThumbnailObserver.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot
+
+import android.view.View
+import android.widget.ImageView
+
+/** An observer of thumbnail UI and entrance state that can be overridden if needed. */
+open class ThumbnailObserver {
+ /** Thumbnail image and border views. */
+ open fun setViews(image: ImageView, border: View) {}
+
+ /** Entrance animation has begun. */
+ open fun onEntranceStarted() {}
+
+ /** Entrance animation has completed/stopped. */
+ open fun onEntranceComplete() {}
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/WorkProfileMessageController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/WorkProfileMessageController.kt
index c801ca5..b93aedd 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/WorkProfileMessageController.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/WorkProfileMessageController.kt
@@ -53,8 +53,9 @@
if (userManager.isManagedProfile(userHandle.identifier) && !messageAlreadyDismissed()) {
var badgedIcon: Drawable? = null
var label: CharSequence? = null
- val fileManager = fileManagerComponentName()
- ?: return WorkProfileFirstRunData(defaultFileAppName(), null)
+ val fileManager =
+ fileManagerComponentName()
+ ?: return WorkProfileFirstRunData(defaultFileAppName(), null)
try {
val info = packageManager.getActivityInfo(fileManager, ComponentInfoFlags.of(0L))
val icon = packageManager.getActivityIcon(fileManager)
@@ -103,9 +104,7 @@
context.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE)
private fun fileManagerComponentName() =
- ComponentName.unflattenFromString(
- context.getString(R.string.config_sceenshotWorkProfileFilesApp)
- )
+ ComponentName.unflattenFromString(context.getString(R.string.config_screenshotFilesApp))
private fun defaultFileAppName() = context.getString(R.string.screenshot_default_files_app_name)
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java b/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java
index 9b8d047..8235325 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java
@@ -38,6 +38,7 @@
import com.android.systemui.screenshot.TakeScreenshotService;
import com.android.systemui.screenshot.appclips.AppClipsScreenshotHelperService;
import com.android.systemui.screenshot.appclips.AppClipsService;
+import com.android.systemui.screenshot.message.MessageModule;
import com.android.systemui.screenshot.policy.ScreenshotPolicyModule;
import com.android.systemui.screenshot.proxy.SystemUiProxyModule;
import com.android.systemui.screenshot.ui.viewmodel.ScreenshotViewModel;
@@ -51,7 +52,7 @@
/**
* Defines injectable resources for Screenshots
*/
-@Module(includes = {ScreenshotPolicyModule.class, SystemUiProxyModule.class})
+@Module(includes = {ScreenshotPolicyModule.class, SystemUiProxyModule.class, MessageModule.class})
public abstract class ScreenshotModule {
@Binds
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/message/MessageModule.kt b/packages/SystemUI/src/com/android/systemui/screenshot/message/MessageModule.kt
new file mode 100644
index 0000000..9d0f87f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/message/MessageModule.kt
@@ -0,0 +1,38 @@
+/*
+ * 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.screenshot.message
+
+import com.android.systemui.dagger.SysUISingleton
+import dagger.Binds
+import dagger.Module
+
+@Module
+interface MessageModule {
+ @Binds
+ @SysUISingleton
+ fun bindProfileFirstRunResources(
+ impl: ProfileFirstRunFileResourcesImpl
+ ): ProfileFirstRunFileResources
+
+ @Binds
+ @SysUISingleton
+ fun bindPackageLabelIconProvider(impl: PackageLabelIconProviderImpl): PackageLabelIconProvider
+
+ @Binds
+ @SysUISingleton
+ fun bindProfileFirstRunSettings(impl: ProfileFirstRunSettingsImpl): ProfileFirstRunSettings
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/message/PackageLabelIconProvider.kt b/packages/SystemUI/src/com/android/systemui/screenshot/message/PackageLabelIconProvider.kt
new file mode 100644
index 0000000..fd073ae
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/message/PackageLabelIconProvider.kt
@@ -0,0 +1,56 @@
+/*
+ * 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.screenshot.message
+
+import android.content.ComponentName
+import android.content.pm.PackageManager
+import android.graphics.drawable.Drawable
+import android.os.UserHandle
+import javax.inject.Inject
+
+data class LabeledIcon(
+ val label: CharSequence,
+ val badgedIcon: Drawable?,
+)
+
+/** An object that can fetch a label and icon for a given component. */
+interface PackageLabelIconProvider {
+ /**
+ * @return the label and icon for the given component.
+ * @throws PackageManager.NameNotFoundException if the component was not found.
+ */
+ suspend fun getPackageLabelIcon(
+ componentName: ComponentName,
+ userHandle: UserHandle
+ ): LabeledIcon
+}
+
+class PackageLabelIconProviderImpl @Inject constructor(private val packageManager: PackageManager) :
+ PackageLabelIconProvider {
+
+ override suspend fun getPackageLabelIcon(
+ componentName: ComponentName,
+ userHandle: UserHandle
+ ): LabeledIcon {
+ val info =
+ packageManager.getActivityInfo(componentName, PackageManager.ComponentInfoFlags.of(0L))
+ val icon = packageManager.getActivityIcon(componentName)
+ val badgedIcon = packageManager.getUserBadgedIcon(icon, userHandle)
+ val label = info.loadLabel(packageManager)
+ return LabeledIcon(label, badgedIcon)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/message/ProfileFirstRunFileResources.kt b/packages/SystemUI/src/com/android/systemui/screenshot/message/ProfileFirstRunFileResources.kt
new file mode 100644
index 0000000..e58c76d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/message/ProfileFirstRunFileResources.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot.message
+
+import android.content.ComponentName
+import android.content.Context
+import com.android.systemui.res.R
+import javax.inject.Inject
+
+/** Provides various configuration resource values for the profile first run flow. */
+interface ProfileFirstRunFileResources {
+ /** @return the ComponentName for the Files app, if available. */
+ fun fileManagerComponentName(): ComponentName?
+
+ /** @return a default LabeledIcon describing the files app */
+ fun defaultFileApp(): LabeledIcon
+}
+
+class ProfileFirstRunFileResourcesImpl @Inject constructor(private val context: Context) :
+ ProfileFirstRunFileResources {
+ override fun fileManagerComponentName() =
+ ComponentName.unflattenFromString(context.getString(R.string.config_screenshotFilesApp))
+
+ override fun defaultFileApp() =
+ LabeledIcon(
+ context.getString(R.string.screenshot_default_files_app_name),
+ badgedIcon = null
+ )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/message/ProfileFirstRunSettings.kt b/packages/SystemUI/src/com/android/systemui/screenshot/message/ProfileFirstRunSettings.kt
new file mode 100644
index 0000000..5ec14a3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/message/ProfileFirstRunSettings.kt
@@ -0,0 +1,68 @@
+/*
+ * 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.screenshot.message
+
+import android.content.Context
+import javax.inject.Inject
+
+/**
+ * An interfaces for the settings related to the profile first run experience, storing a bit
+ * indicating whether the user has already dismissed the message for the given profile.
+ */
+interface ProfileFirstRunSettings {
+ /** @return true if the user has already dismissed the first run message for this profile. */
+ fun messageAlreadyDismissed(profileType: ProfileMessageController.FirstRunProfile): Boolean
+ /**
+ * Update storage to reflect the fact that the user has dismissed a first run message for the
+ * given profile.
+ */
+ fun onMessageDismissed(profileType: ProfileMessageController.FirstRunProfile)
+}
+
+class ProfileFirstRunSettingsImpl @Inject constructor(private val context: Context) :
+ ProfileFirstRunSettings {
+
+ override fun messageAlreadyDismissed(
+ profileType: ProfileMessageController.FirstRunProfile
+ ): Boolean {
+ val preferenceKey = preferenceKey(profileType)
+ return sharedPreference().getBoolean(preferenceKey, false)
+ }
+
+ override fun onMessageDismissed(profileType: ProfileMessageController.FirstRunProfile) {
+ val preferenceKey = preferenceKey(profileType)
+ val editor = sharedPreference().edit()
+ editor.putBoolean(preferenceKey, true)
+ editor.apply()
+ }
+
+ private fun preferenceKey(profileType: ProfileMessageController.FirstRunProfile): String {
+ return when (profileType) {
+ ProfileMessageController.FirstRunProfile.WORK -> WORK_PREFERENCE_KEY
+ ProfileMessageController.FirstRunProfile.PRIVATE -> PRIVATE_PREFERENCE_KEY
+ }
+ }
+
+ private fun sharedPreference() =
+ context.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE)
+
+ companion object {
+ const val SHARED_PREFERENCES_NAME = "com.android.systemui.screenshot"
+ const val WORK_PREFERENCE_KEY = "work_profile_first_run"
+ const val PRIVATE_PREFERENCE_KEY = "private_profile_first_run"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/message/ProfileMessageController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/message/ProfileMessageController.kt
new file mode 100644
index 0000000..9212a6a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/message/ProfileMessageController.kt
@@ -0,0 +1,108 @@
+/*
+ * 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.screenshot.message
+
+import android.os.UserHandle
+import android.view.View
+import android.view.ViewGroup
+import android.widget.ImageView
+import android.widget.TextView
+import com.android.systemui.res.R
+import com.android.systemui.screenshot.data.model.ProfileType
+import com.android.systemui.screenshot.data.repository.ProfileTypeRepository
+import javax.inject.Inject
+
+/**
+ * Handles work profile and private profile first run, determining whether a first run UI should be
+ * shown and populating that UI if needed.
+ */
+class ProfileMessageController
+@Inject
+constructor(
+ private val packageLabelIconProvider: PackageLabelIconProvider,
+ private val fileResources: ProfileFirstRunFileResources,
+ private val firstRunSettings: ProfileFirstRunSettings,
+ private val profileTypes: ProfileTypeRepository,
+) {
+
+ /**
+ * @return a populated ProfileFirstRunData object if a profile first run message should be
+ * shown, otherwise null.
+ */
+ suspend fun onScreenshotTaken(userHandle: UserHandle?): ProfileFirstRunData? {
+ if (userHandle == null) return null
+ val profileType =
+ when (profileTypes.getProfileType(userHandle.identifier)) {
+ ProfileType.WORK -> FirstRunProfile.WORK
+ ProfileType.PRIVATE -> FirstRunProfile.PRIVATE
+ else -> return null
+ }
+
+ if (firstRunSettings.messageAlreadyDismissed(profileType)) {
+ return null
+ }
+
+ val fileApp =
+ runCatching {
+ fileResources.fileManagerComponentName()?.let { fileManager ->
+ packageLabelIconProvider.getPackageLabelIcon(fileManager, userHandle)
+ }
+ }
+ .getOrNull() ?: fileResources.defaultFileApp()
+
+ return ProfileFirstRunData(fileApp, profileType)
+ }
+
+ /**
+ * Use the provided ProfileFirstRunData to populate the profile first run UI in the given view.
+ */
+ fun bindView(view: ViewGroup, data: ProfileFirstRunData, animateOut: () -> Unit) {
+ if (data.labeledIcon.badgedIcon != null) {
+ // Replace the default icon if one is provided.
+ val imageView = view.requireViewById<ImageView>(R.id.screenshot_message_icon)
+ imageView.setImageDrawable(data.labeledIcon.badgedIcon)
+ }
+ val messageContent = view.requireViewById<TextView>(R.id.screenshot_message_content)
+ messageContent.text =
+ view.context.getString(messageTemplate(data.profileType), data.labeledIcon.label)
+ view.requireViewById<View>(R.id.message_dismiss_button).setOnClickListener {
+ animateOut()
+ firstRunSettings.onMessageDismissed(data.profileType)
+ }
+ }
+
+ private fun messageTemplate(profile: FirstRunProfile): Int {
+ return when (profile) {
+ FirstRunProfile.WORK -> R.string.screenshot_work_profile_notification
+ FirstRunProfile.PRIVATE -> R.string.screenshot_private_profile_notification
+ }
+ }
+
+ data class ProfileFirstRunData(
+ val labeledIcon: LabeledIcon,
+ val profileType: FirstRunProfile,
+ )
+
+ enum class FirstRunProfile {
+ WORK,
+ PRIVATE
+ }
+
+ companion object {
+ const val TAG = "PrivateProfileMessageCtrl"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotAnimationController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotAnimationController.kt
index 2e4473e..4eceb17 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotAnimationController.kt
@@ -25,6 +25,7 @@
import android.util.MathUtils
import android.view.View
import android.view.animation.AnimationUtils
+import android.widget.ImageView
import androidx.core.animation.doOnEnd
import androidx.core.animation.doOnStart
import com.android.systemui.res.R
@@ -34,7 +35,7 @@
class ScreenshotAnimationController(private val view: ScreenshotShelfView) {
private var animator: Animator? = null
- private val screenshotPreview = view.requireViewById<View>(R.id.screenshot_preview)
+ private val screenshotPreview = view.requireViewById<ImageView>(R.id.screenshot_preview)
private val flashView = view.requireViewById<View>(R.id.screenshot_flash)
private val actionContainer = view.requireViewById<View>(R.id.actions_container_background)
private val fastOutSlowIn =
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotShelfView.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotShelfView.kt
index f9af4b9..4437bf5 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotShelfView.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotShelfView.kt
@@ -17,20 +17,25 @@
package com.android.systemui.screenshot.ui
import android.content.Context
+import android.content.res.Configuration
import android.graphics.Insets
import android.graphics.Rect
import android.graphics.Region
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.View
+import android.view.ViewGroup
+import android.view.WindowInsets
+import android.widget.FrameLayout
import android.widget.ImageView
-import androidx.constraintlayout.widget.ConstraintLayout
import com.android.systemui.res.R
import com.android.systemui.screenshot.FloatingWindowUtil
+import kotlin.math.max
class ScreenshotShelfView(context: Context, attrs: AttributeSet? = null) :
- ConstraintLayout(context, attrs) {
+ FrameLayout(context, attrs) {
lateinit var screenshotPreview: ImageView
+ private lateinit var screenshotStatic: ViewGroup
var onTouchInterceptListener: ((MotionEvent) -> Boolean)? = null
private val displayMetrics = context.resources.displayMetrics
@@ -43,6 +48,7 @@
// Get focus so that the key events go to the layout.
isFocusableInTouchMode = true
screenshotPreview = requireViewById(R.id.screenshot_preview)
+ screenshotStatic = requireViewById(R.id.screenshot_static)
actionsContainerBackground = requireViewById(R.id.actions_container_background)
dismissButton = requireViewById(R.id.screenshot_dismiss_button)
}
@@ -66,6 +72,40 @@
return region
}
+ fun updateInsets(insets: WindowInsets) {
+ val orientation = mContext.resources.configuration.orientation
+ val inPortrait = orientation == Configuration.ORIENTATION_PORTRAIT
+ val p = screenshotStatic.layoutParams as LayoutParams
+ val cutout = insets.displayCutout
+ val navBarInsets = insets.getInsets(WindowInsets.Type.navigationBars())
+ if (cutout == null) {
+ p.setMargins(0, 0, 0, navBarInsets.bottom)
+ } else {
+ val waterfall = cutout.waterfallInsets
+ if (inPortrait) {
+ p.setMargins(
+ waterfall.left,
+ max(cutout.safeInsetTop.toDouble(), waterfall.top.toDouble()).toInt(),
+ waterfall.right,
+ max(
+ cutout.safeInsetBottom.toDouble(),
+ max(navBarInsets.bottom.toDouble(), waterfall.bottom.toDouble())
+ )
+ .toInt()
+ )
+ } else {
+ p.setMargins(
+ max(cutout.safeInsetLeft.toDouble(), waterfall.left.toDouble()).toInt(),
+ waterfall.top,
+ max(cutout.safeInsetRight.toDouble(), waterfall.right.toDouble()).toInt(),
+ max(navBarInsets.bottom.toDouble(), waterfall.bottom.toDouble()).toInt()
+ )
+ }
+ }
+ screenshotStatic.layoutParams = p
+ screenshotStatic.requestLayout()
+ }
+
private fun getSwipeRegion(): Region {
val swipeRegion = Region()
val padding = FloatingWindowUtil.dpToPx(displayMetrics, -1 * TOUCH_PADDING_DP).toInt()
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ScreenshotShelfViewBinder.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ScreenshotShelfViewBinder.kt
index 3376b8c..734a530 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ScreenshotShelfViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ScreenshotShelfViewBinder.kt
@@ -16,8 +16,11 @@
package com.android.systemui.screenshot.ui.binder
+import android.graphics.Bitmap
import android.view.LayoutInflater
import android.view.View
+import android.view.ViewGroup
+import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.LinearLayout
import androidx.lifecycle.Lifecycle
@@ -68,7 +71,7 @@
launch {
viewModel.preview.collect { bitmap ->
if (bitmap != null) {
- previewView.setImageBitmap(bitmap)
+ setScreenshotBitmap(previewView, bitmap)
previewView.visibility = View.VISIBLE
previewBorder.visibility = View.VISIBLE
} else {
@@ -128,4 +131,23 @@
}
}
}
+
+ private fun setScreenshotBitmap(screenshotPreview: ImageView, bitmap: Bitmap) {
+ screenshotPreview.setImageBitmap(bitmap)
+ val hasPortraitAspectRatio = bitmap.width < bitmap.height
+ val fixedSize = screenshotPreview.resources.getDimensionPixelSize(R.dimen.overlay_x_scale)
+ val params: ViewGroup.LayoutParams = screenshotPreview.layoutParams
+ if (hasPortraitAspectRatio) {
+ params.width = fixedSize
+ params.height = FrameLayout.LayoutParams.WRAP_CONTENT
+ screenshotPreview.scaleType = ImageView.ScaleType.FIT_START
+ } else {
+ params.width = FrameLayout.LayoutParams.WRAP_CONTENT
+ params.height = fixedSize
+ screenshotPreview.scaleType = ImageView.ScaleType.FIT_END
+ }
+
+ screenshotPreview.layoutParams = params
+ screenshotPreview.requestLayout()
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java
index e051dab..92006a4 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java
@@ -63,6 +63,7 @@
@Override
protected void onFinishInflate() {
super.onFinishInflate();
+ setLayerType(LAYER_TYPE_HARDWARE, null);
mSlider = requireViewById(R.id.slider);
mSlider.setAccessibilityLabel(getContentDescription().toString());
diff --git a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
index ff5fdc6..851bfca 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
@@ -229,7 +229,9 @@
// the gesture area doesn't overlap with widgets.
// TODO(b/323035776): adjust gesture areaa for portrait mode
containerView.repeatWhenAttached {
- repeatOnLifecycle(Lifecycle.State.CREATED) {
+ // Run when the touch handling lifecycle is RESUMED, meaning the hub is visible and not
+ // occluded.
+ lifecycleRegistry.repeatOnLifecycle(Lifecycle.State.RESUMED) {
val exclusionRect =
Rect(
0,
@@ -242,12 +244,19 @@
}
}
+ // Listen to bouncer visibility directly as these flows become true as soon as any portion
+ // of the bouncers are visible when the transition starts. The keyguard transition state
+ // only changes once transitions are fully finished, which would mean touches during a
+ // transition to the bouncer would be incorrectly intercepted by the hub.
collectFlow(
containerView,
- keyguardTransitionInteractor.isFinishedInStateWhere(KeyguardState::isBouncerState),
+ or(
+ keyguardInteractor.primaryBouncerShowing,
+ keyguardInteractor.alternateBouncerShowing
+ ),
{
anyBouncerShowing = it
- updateLifecycleState()
+ updateTouchHandlingState()
}
)
collectFlow(
@@ -255,7 +264,7 @@
communalInteractor.isCommunalShowing,
{
hubShowing = it
- updateLifecycleState()
+ updateTouchHandlingState()
}
)
collectFlow(
@@ -263,7 +272,7 @@
and(shadeInteractor.isAnyFullyExpanded, not(shadeInteractor.isUserInteracting)),
{
shadeShowing = it
- updateLifecycleState()
+ updateTouchHandlingState()
}
)
collectFlow(containerView, keyguardInteractor.isDreaming, { isDreaming = it })
@@ -276,13 +285,22 @@
/**
* Updates the lifecycle stored by the [lifecycleRegistry] to control when the [touchMonitor]
* should listen for and intercept top and bottom swipes.
+ *
+ * Also clears gesture exclusion zones when the hub is occluded or gone.
*/
- private fun updateLifecycleState() {
+ private fun updateTouchHandlingState() {
val shouldInterceptGestures = hubShowing && !(shadeShowing || anyBouncerShowing)
if (shouldInterceptGestures) {
lifecycleRegistry.currentState = Lifecycle.State.RESUMED
} else {
+ // Hub is either occluded or no longer showing, turn off touch handling.
lifecycleRegistry.currentState = Lifecycle.State.STARTED
+
+ // Clear exclusion rects if the hub is not showing or is covered, so we don't interfere
+ // with back gestures when the bouncer or shade. We do this here instead of with
+ // repeatOnLifecycle as repeatOnLifecycle does not run when going from RESUMED back to
+ // STARTED, only when going from CREATED to STARTED.
+ communalContainerView!!.systemGestureExclusionRects = emptyList()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 67211b1..9f1b423 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -25,6 +25,7 @@
import static com.android.keyguard.KeyguardClockSwitch.LARGE;
import static com.android.keyguard.KeyguardClockSwitch.SMALL;
import static com.android.systemui.Flags.predictiveBackAnimateShade;
+import static com.android.systemui.Flags.shadeCollapseActivityLaunchFix;
import static com.android.systemui.Flags.smartspaceRelocateToBottom;
import static com.android.systemui.classifier.Classifier.BOUNCER_UNLOCK;
import static com.android.systemui.classifier.Classifier.GENERIC;
@@ -244,6 +245,7 @@
import kotlinx.coroutines.CoroutineDispatcher;
import kotlinx.coroutines.flow.Flow;
+import kotlinx.coroutines.flow.StateFlow;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -3991,7 +3993,12 @@
mExpandLatencyTracking = false;
}
float maxPanelHeight = getMaxPanelTransitionDistance();
- if (mHeightAnimator == null) {
+ if (mHeightAnimator == null && !MigrateClocksToBlueprint.isEnabled()) {
+ // MigrateClocksToBlueprint - There is an edge case where swiping up slightly,
+ // and then swiping down will trigger overscroll logic. Even without this flag
+ // enabled, the notifications can then run into UDFPS. At this point it is
+ // safer to remove overscroll for this one case to prevent overlap.
+
// Split shade has its own overscroll logic
if (isTracking()) {
float overExpansionPixels = Math.max(0, h - maxPanelHeight);
@@ -4055,6 +4062,11 @@
}
@Override
+ public StateFlow<Float> getUdfpsTransitionToFullShadeProgress() {
+ return mShadeRepository.getUdfpsTransitionToFullShadeProgress();
+ }
+
+ @Override
public Flow<Float> getLegacyPanelExpansion() {
return mShadeRepository.getLegacyShadeExpansion();
}
@@ -4091,7 +4103,11 @@
@Override
public boolean canBeCollapsed() {
- return !isFullyCollapsed() && !isTracking() && !isClosing();
+ return !isFullyCollapsed() && !isTracking() && !isClosing()
+ // Don't try to collapse if on keyguard, as the expansion fraction is 1 in this
+ // case.
+ && !(shadeCollapseActivityLaunchFix() && mExpandedFraction == 1f
+ && mBarState == KEYGUARD);
}
public void instantCollapse() {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt
index 0c41efd..9322d31 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt
@@ -25,6 +25,7 @@
import com.android.systemui.statusbar.phone.HeadsUpAppearanceController
import java.util.function.Consumer
import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.flowOf
/** Empty implementation of ShadeViewController for variants with no shade. */
@@ -92,6 +93,7 @@
override val shadeFoldAnimator = ShadeFoldAnimatorEmptyImpl()
@Deprecated("Use SceneInteractor.currentScene instead.")
override val legacyPanelExpansion = flowOf(0f)
+ override val udfpsTransitionToFullShadeProgress = MutableStateFlow(0f)
}
class ShadeHeadsUpTrackerEmptyImpl : ShadeHeadsUpTracker {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt
index 5c79e1e..b934d63 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt
@@ -28,7 +28,7 @@
* Amount qs has expanded, [0-1]. 0 means fully collapsed, 1 means fully expanded. Quick
* Settings can be expanded without the full shade expansion.
*/
- val qsExpansion: StateFlow<Float>
+ @Deprecated("Use ShadeInteractor.qsExpansion instead") val qsExpansion: StateFlow<Float>
/** Amount shade has expanded with regard to the UDFPS location */
val udfpsTransitionToFullShadeProgress: StateFlow<Float>
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractor.kt
index 2611092..987c016 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractor.kt
@@ -15,9 +15,14 @@
*/
package com.android.systemui.shade.domain.interactor
+import kotlinx.coroutines.flow.StateFlow
+
/** Allows the lockscreen to control the shade. */
interface ShadeLockscreenInteractor {
+ /** Amount shade has expanded with regard to the UDFPS location */
+ val udfpsTransitionToFullShadeProgress: StateFlow<Float>
+
/**
* Expand shade so that notifications are visible. Non-split shade: just expanding shade or
* collapsing QS when they're expanded. Split shade: only expanding shade, notifications are
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorImpl.kt
index 318da55..6a8b9ee 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorImpl.kt
@@ -20,6 +20,7 @@
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.shade.data.repository.ShadeRepository
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
@@ -32,7 +33,12 @@
private val shadeInteractor: ShadeInteractor,
private val sceneInteractor: SceneInteractor,
private val lockIconViewController: LockIconViewController,
+ shadeRepository: ShadeRepository,
) : ShadeLockscreenInteractor {
+
+ override val udfpsTransitionToFullShadeProgress =
+ shadeRepository.udfpsTransitionToFullShadeProgress
+
override fun expandToNotifications() {
changeToShadeScene()
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/RefactorKeyguardDismissIntent.kt b/packages/SystemUI/src/com/android/systemui/shade/shared/flag/DualShade.kt
similarity index 70%
rename from packages/SystemUI/src/com/android/systemui/keyguard/shared/RefactorKeyguardDismissIntent.kt
rename to packages/SystemUI/src/com/android/systemui/shade/shared/flag/DualShade.kt
index a43eb71..4db4058 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/RefactorKeyguardDismissIntent.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/shared/flag/DualShade.kt
@@ -14,26 +14,29 @@
* limitations under the License.
*/
-package com.android.systemui.keyguard.shared
+package com.android.systemui.shade.shared.flag
import com.android.systemui.Flags
import com.android.systemui.flags.FlagToken
import com.android.systemui.flags.RefactorFlagUtils
-/** Helper for reading or using the refactor_keyguard_dismiss_intent flag. */
-@Suppress("NOTHING_TO_INLINE")
-object RefactorKeyguardDismissIntent {
- /** The aconfig flag name */
- const val FLAG_NAME = Flags.FLAG_REFACTOR_KEYGUARD_DISMISS_INTENT
+/** Helper for reading and using the Dual Shade feature flag. */
+object DualShade {
- /** A token used for dependency declaration */
+ /** The aconfig flag name. */
+ const val FLAG_NAME = Flags.FLAG_DUAL_SHADE
+
+ /** The flag description -- not an aconfig flag name. */
+ const val DESCRIPTION = "DualShadeFlag"
+
+ /** A token used for dependency declaration. */
val token: FlagToken
get() = FlagToken(FLAG_NAME, isEnabled)
- /** Is the refactor enabled */
+ /** Whether the feature is enabled. */
@JvmStatic
inline val isEnabled
- get() = Flags.refactorKeyguardDismissIntent()
+ get() = Flags.dualShade()
/**
* Called to ensure code is only run when the flag is enabled. This protects users from the
@@ -41,13 +44,13 @@
* build to ensure that the refactor author catches issues in testing.
*/
@JvmStatic
- inline fun isUnexpectedlyInLegacyMode() =
- RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME)
+ fun isUnexpectedlyInLegacyMode() =
+ RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, DESCRIPTION)
/**
* 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)
+ fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, DESCRIPTION)
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/OverlayShadeViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/OverlayShadeViewModel.kt
new file mode 100644
index 0000000..b8dd628
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/OverlayShadeViewModel.kt
@@ -0,0 +1,67 @@
+/*
+ * 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.shade.ui.viewmodel
+
+import com.android.compose.animation.scene.SceneKey
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
+import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.model.Scenes
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+
+/**
+ * Models UI state and handles user input for the overlay shade UI, which shows a shade as an
+ * overlay on top of another scene UI.
+ */
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
+class OverlayShadeViewModel
+@Inject
+constructor(
+ @Application private val applicationScope: CoroutineScope,
+ private val sceneInteractor: SceneInteractor,
+ deviceEntryInteractor: DeviceEntryInteractor,
+) {
+ /** The scene to show in the background when the overlay shade is open. */
+ val backgroundScene: StateFlow<SceneKey> =
+ deviceEntryInteractor.isDeviceEntered
+ .map(::backgroundScene)
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = backgroundScene(deviceEntryInteractor.isDeviceEntered.value)
+ )
+
+ /** Notifies that the user has clicked the semi-transparent background scrim. */
+ fun onScrimClicked() {
+ sceneInteractor.changeScene(
+ toScene = backgroundScene.value,
+ loggingReason = "Shade scrim clicked",
+ )
+ }
+
+ private fun backgroundScene(isDeviceEntered: Boolean): SceneKey {
+ return if (isDeviceEntered) Scenes.Gone else Scenes.Lockscreen
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
index d465973..7c1101b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
@@ -29,6 +29,7 @@
import android.util.Log;
import com.android.systemui.Dumpable;
+import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.media.controls.domain.pipeline.MediaDataManager;
import com.android.systemui.media.controls.shared.model.MediaData;
@@ -47,6 +48,7 @@
import java.util.HashSet;
import java.util.Objects;
import java.util.Optional;
+import java.util.concurrent.Executor;
/**
* Handles tasks and state related to media notifications. For example, there is a 'current' media
@@ -75,6 +77,8 @@
private final Context mContext;
private final ArrayList<MediaListener> mMediaListeners;
+ private final Executor mBackgroundExecutor;
+
protected NotificationPresenter mPresenter;
private MediaController mMediaController;
private String mMediaNotificationKey;
@@ -115,13 +119,15 @@
NotifPipeline notifPipeline,
NotifCollection notifCollection,
MediaDataManager mediaDataManager,
- DumpManager dumpManager) {
+ DumpManager dumpManager,
+ @Background Executor backgroundExecutor) {
mContext = context;
mMediaListeners = new ArrayList<>();
mVisibilityProvider = visibilityProvider;
mMediaDataManager = mediaDataManager;
mNotifPipeline = notifPipeline;
mNotifCollection = notifCollection;
+ mBackgroundExecutor = backgroundExecutor;
setupNotifPipeline();
@@ -376,14 +382,16 @@
private void clearCurrentMediaNotificationSession() {
mMediaMetadata = null;
- if (mMediaController != null) {
- if (DEBUG_MEDIA) {
- Log.v(TAG, "DEBUG_MEDIA: Disconnecting from old controller: "
- + mMediaController.getPackageName());
+ mBackgroundExecutor.execute(() -> {
+ if (mMediaController != null) {
+ if (DEBUG_MEDIA) {
+ Log.v(TAG, "DEBUG_MEDIA: Disconnecting from old controller: "
+ + mMediaController.getPackageName());
+ }
+ mMediaController.unregisterCallback(mMediaListener);
+ mMediaController = null;
}
- mMediaController.unregisterCallback(mMediaListener);
- }
- mMediaController = null;
+ });
}
public interface MediaListener {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
index 8a53e0c..c17da4b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
@@ -31,6 +31,7 @@
import com.android.systemui.animation.DialogTransitionAnimator;
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dump.DumpHandler;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.media.controls.domain.pipeline.MediaDataManager;
@@ -68,6 +69,8 @@
import dagger.multibindings.ClassKey;
import dagger.multibindings.IntoMap;
+import java.util.concurrent.Executor;
+
import javax.inject.Provider;
/**
@@ -94,14 +97,16 @@
NotifPipeline notifPipeline,
NotifCollection notifCollection,
MediaDataManager mediaDataManager,
- DumpManager dumpManager) {
+ DumpManager dumpManager,
+ @Background Executor backgroundExecutor) {
return new NotificationMediaManager(
context,
visibilityProvider,
notifPipeline,
notifCollection,
mediaDataManager,
- dumpManager);
+ dumpManager,
+ backgroundExecutor);
}
/** */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
index 240ae0c..9c1d073 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
@@ -1243,8 +1243,9 @@
if (cmp != 0) return cmp;
cmp = -1 * Long.compare(
- o1.getRepresentativeEntry().getSbn().getNotification().when,
- o2.getRepresentativeEntry().getSbn().getNotification().when);
+ o1.getRepresentativeEntry().getSbn().getNotification().getWhen(),
+ o2.getRepresentativeEntry().getSbn().getNotification().getWhen());
+
return cmp;
};
@@ -1256,8 +1257,8 @@
if (cmp != 0) return cmp;
cmp = -1 * Long.compare(
- o1.getRepresentativeEntry().getSbn().getNotification().when,
- o2.getRepresentativeEntry().getSbn().getNotification().when);
+ o1.getRepresentativeEntry().getSbn().getNotification().getWhen(),
+ o2.getRepresentativeEntry().getSbn().getNotification().getWhen());
return cmp;
};
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupWhenCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupWhenCoordinator.kt
index 5ce1db2..f253100 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupWhenCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupWhenCoordinator.kt
@@ -89,7 +89,7 @@
var futureTime = Long.MAX_VALUE
groupEntry.children
.asSequence()
- .mapNotNull { child -> child.sbn.notification.`when`.takeIf { it > 0 } }
+ .mapNotNull { child -> child.sbn.notification.getWhen().takeIf { it > 0 } }
.forEach { time ->
val isInThePast = currentTimeMillis - time > 0
if (isInThePast) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
index fb67f7c..f98f77e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
@@ -296,7 +296,9 @@
locationLookupByKey: (String) -> GroupLocation,
): NotificationEntry? = postedEntries.asSequence()
.filter { posted -> !posted.entry.sbn.notification.isGroupSummary }
- .sortedBy { posted -> -posted.entry.sbn.notification.`when` }
+ .sortedBy { posted ->
+ -posted.entry.sbn.notification.getWhen()
+ }
.firstOrNull()
?.let { posted ->
posted.entry.takeIf { entry ->
@@ -317,7 +319,7 @@
.filter { locationLookupByKey(it.key) != GroupLocation.Detached }
.sortedWith(compareBy(
{ !mPostedEntries.contains(it.key) },
- { -it.sbn.notification.`when` },
+ { -it.sbn.notification.getWhen() },
))
.firstOrNull()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt
index 9619aca..bfc5932 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt
@@ -153,12 +153,12 @@
uiEventId = HUN_SUPPRESSED_OLD_WHEN
) {
private fun whenAge(entry: NotificationEntry) =
- systemClock.currentTimeMillis() - entry.sbn.notification.`when`
+ systemClock.currentTimeMillis() - entry.sbn.notification.getWhen()
override fun shouldSuppress(entry: NotificationEntry): Boolean =
when {
// Ignore a "when" of 0, as it is unlikely to be a meaningful timestamp.
- entry.sbn.notification.`when` <= 0L -> false
+ entry.sbn.notification.getWhen() <= 0L -> false
// Assume all HUNs with FSIs, foreground services, or user-initiated jobs are
// time-sensitive, regardless of their "when".
@@ -278,7 +278,7 @@
private fun calculateState(entry: NotificationEntry): State {
if (
entry.ranking.isConversation &&
- entry.sbn.notification.`when` > avalancheProvider.startTime
+ entry.sbn.notification.getWhen() > avalancheProvider.startTime
) {
return State.ALLOW_CONVERSATION_AFTER_AVALANCHE
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
index d591114..9c6a423 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
@@ -623,7 +623,7 @@
return false;
}
- final long when = notification.when;
+ final long when = notification.getWhen();
final long now = mSystemClock.currentTimeMillis();
final long age = now - when;
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 bdeaabf..5e3df7b 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
@@ -2769,7 +2769,7 @@
}
if (!mIsSummaryWithChildren && wasSummary) {
// Reset the 'when' once the row stops being a summary
- mPublicLayout.setNotificationWhen(mEntry.getSbn().getNotification().when);
+ mPublicLayout.setNotificationWhen(mEntry.getSbn().getNotification().getWhen());
}
getShowingLayout().updateBackgroundColor(false /* animate */);
mPrivateLayout.updateExpandButtons(isExpandable());
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt
index 2f9c2f0..6909907 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt
@@ -82,7 +82,7 @@
launch { viewModel.stackBottom.collect { view.setStackBottom(it) } }
launch { viewModel.scrolledToTop.collect { view.setScrolledToTop(it) } }
launch { viewModel.headsUpTop.collect { view.setHeadsUpTop(it) } }
- launch { viewModel.expandFraction.collect { view.setExpandFraction(it) } }
+ launch { viewModel.expandFraction.collect { view.setExpandFraction(it.coerceIn(0f, 1f)) } }
launch { viewModel.isScrollable.collect { view.setScrollingEnabled(it) } }
launch { viewModel.isDozing.collect { isDozing -> view.setDozing(isDozing) } }
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 37bbbd0..ac4bd09 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
@@ -41,6 +41,7 @@
import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING
import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerToGoneTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.AodBurnInViewModel
+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.BurnInParameters
@@ -106,6 +107,7 @@
private val notificationStackAppearanceInteractor: NotificationStackAppearanceInteractor,
private val alternateBouncerToGoneTransitionViewModel:
AlternateBouncerToGoneTransitionViewModel,
+ private val aodToGoneTransitionViewModel: AodToGoneTransitionViewModel,
private val aodToLockscreenTransitionViewModel: AodToLockscreenTransitionViewModel,
private val aodToOccludedTransitionViewModel: AodToOccludedTransitionViewModel,
private val dozingToLockscreenTransitionViewModel: DozingToLockscreenTransitionViewModel,
@@ -464,6 +466,7 @@
val alphaTransitions =
merge(
alternateBouncerToGoneTransitionViewModel.notificationAlpha(viewState),
+ aodToGoneTransitionViewModel.notificationAlpha(viewState),
aodToLockscreenTransitionViewModel.notificationAlpha,
aodToOccludedTransitionViewModel.lockscreenAlpha(viewState),
dozingToLockscreenTransitionViewModel.lockscreenAlpha,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index b2b2cea..7630d43 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -855,6 +855,7 @@
mWakefulnessLifecycle.addObserver(mWakefulnessObserver);
mUiModeManager = mContext.getSystemService(UiModeManager.class);
mBubblesOptional.ifPresent(this::initBubbles);
+ mKeyguardBypassController.listenForQsExpandedChange();
mStatusBarSignalPolicy.init();
mKeyguardIndicationController.init();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
index a8941bb..97791ac 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
@@ -21,7 +21,6 @@
import android.content.res.Resources
import android.hardware.biometrics.BiometricSourceType
import android.provider.Settings
-import androidx.annotation.VisibleForTesting
import com.android.app.tracing.ListenersTracing.forEachTraced
import com.android.systemui.Dumpable
import com.android.systemui.dagger.SysUISingleton
@@ -32,7 +31,7 @@
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.res.R
-import com.android.systemui.shade.data.repository.ShadeRepository
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.NotificationLockscreenUserManager
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm
@@ -41,23 +40,24 @@
import com.android.systemui.statusbar.policy.DevicePostureController.DevicePostureInt
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.tuner.TunerService
+import dagger.Lazy
+import java.io.PrintWriter
+import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
-import java.io.PrintWriter
-import javax.inject.Inject
@SysUISingleton
class KeyguardBypassController @Inject constructor(
@Main resources: Resources,
packageManager: PackageManager,
- @Application applicationScope: CoroutineScope,
+ @Application private val applicationScope: CoroutineScope,
tunerService: TunerService,
private val statusBarStateController: StatusBarStateController,
lockscreenUserManager: NotificationLockscreenUserManager,
private val keyguardStateController: KeyguardStateController,
- private val shadeRepository: ShadeRepository,
+ private val shadeInteractorLazy: Lazy<ShadeInteractor>,
devicePostureController: DevicePostureController,
private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
dumpManager: DumpManager
@@ -144,7 +144,6 @@
}
}
})
- listenForQsExpandedChange(applicationScope)
val dismissByDefault = if (resources.getBoolean(
com.android.internal.R.bool.config_faceAuthDismissesKeyguard)) 1 else 0
tunerService.addTunable({ key, _ ->
@@ -159,10 +158,9 @@
}
}
- @VisibleForTesting
- fun listenForQsExpandedChange(scope: CoroutineScope) =
- scope.launch {
- shadeRepository.qsExpansion.map { it > 0f }.distinctUntilChanged()
+ fun listenForQsExpandedChange() =
+ applicationScope.launch {
+ shadeInteractorLazy.get().qsExpansion.map { it > 0f }.distinctUntilChanged()
.collect { isQsExpanded ->
val changed = qsExpanded != isQsExpanded
qsExpanded = isQsExpanded
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt
index 68d54e7..6b68511 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt
@@ -131,7 +131,12 @@
val runnable = Runnable {
assistManagerLazy.get().hideAssist()
- intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
+ intent.flags =
+ if (intent.flags and Intent.FLAG_ACTIVITY_REORDER_TO_FRONT != 0) {
+ Intent.FLAG_ACTIVITY_NEW_TASK
+ } else {
+ Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
+ }
intent.addFlags(flags)
val result = intArrayOf(ActivityManager.START_CANCELED)
activityTransitionAnimator.startIntentWithAnimation(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index f35d199..7301b87 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -72,7 +72,6 @@
import com.android.systemui.keyguard.domain.interactor.KeyguardSurfaceBehindInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
import com.android.systemui.keyguard.domain.interactor.WindowManagerLockscreenVisibilityInteractor;
-import com.android.systemui.keyguard.shared.RefactorKeyguardDismissIntent;
import com.android.systemui.keyguard.shared.model.DismissAction;
import com.android.systemui.keyguard.shared.model.KeyguardDone;
import com.android.systemui.keyguard.shared.model.KeyguardState;
@@ -800,7 +799,7 @@
public void dismissWithAction(OnDismissAction r, Runnable cancelAction,
boolean afterKeyguardGone, String message) {
- if (RefactorKeyguardDismissIntent.isEnabled()) {
+ if (SceneContainerFlag.isEnabled()) {
if (r == null) {
return;
}
@@ -852,7 +851,7 @@
return;
}
- if (!RefactorKeyguardDismissIntent.isEnabled()) {
+ if (!SceneContainerFlag.isEnabled()) {
mAfterKeyguardGoneAction = r;
mKeyguardGoneCancelAction = cancelAction;
mDismissActionWillAnimateOnKeyguard = r != null
@@ -920,7 +919,7 @@
* Adds a {@param runnable} to be executed after Keyguard is gone.
*/
public void addAfterKeyguardGoneRunnable(Runnable runnable) {
- if (RefactorKeyguardDismissIntent.isEnabled()) {
+ if (SceneContainerFlag.isEnabled()) {
if (runnable != null) {
mKeyguardDismissActionInteractor.get().runAfterKeyguardGone(runnable);
}
@@ -1112,7 +1111,7 @@
// We update the state (which will show the keyguard) only if an animation will run on
// the keyguard. If there is no animation, we wait before updating the state so that we
// go directly from bouncer to launcher/app.
- if (RefactorKeyguardDismissIntent.isEnabled()) {
+ if (SceneContainerFlag.isEnabled()) {
if (mKeyguardDismissActionInteractor.get().runDismissAnimationOnKeyguard()) {
updateStates();
}
@@ -1239,7 +1238,7 @@
}
private void executeAfterKeyguardGoneAction() {
- if (RefactorKeyguardDismissIntent.isEnabled()) {
+ if (SceneContainerFlag.isEnabled()) {
return;
}
if (mAfterKeyguardGoneAction != null) {
@@ -1629,8 +1628,8 @@
pw.println(" isBouncerShowing(): " + isBouncerShowing());
pw.println(" bouncerIsOrWillBeShowing(): " + primaryBouncerIsOrWillBeShowing());
pw.println(" Registered KeyguardViewManagerCallbacks:");
- pw.println(" refactorKeyguardDismissIntent enabled:"
- + RefactorKeyguardDismissIntent.isEnabled());
+ pw.println(" SceneContainerFlag enabled:"
+ + SceneContainerFlag.isEnabled());
for (KeyguardViewManagerCallback callback : mCallbacks) {
pw.println(" " + callback);
}
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 a20468f..ec88b6c 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
@@ -98,7 +98,7 @@
(entry.sbn.key == callNotificationInfo?.key)) {
val newOngoingCallInfo = CallNotificationInfo(
entry.sbn.key,
- entry.sbn.notification.`when`,
+ entry.sbn.notification.getWhen(),
entry.sbn.notification.contentIntent,
entry.sbn.uid,
entry.sbn.notification.extras.getInt(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractor.kt
index 054116d..51c053e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractor.kt
@@ -84,14 +84,15 @@
if (Flags.oemEnabledSatelliteFlag()) {
iconsInteractor.icons.aggregateOver(
selector = { intr ->
- combine(intr.isInService, intr.isEmergencyOnly) {
+ combine(intr.isInService, intr.isEmergencyOnly, intr.isNonTerrestrial) {
isInService,
- isEmergencyOnly ->
- !isInService && !isEmergencyOnly
+ isEmergencyOnly,
+ isNtn ->
+ !isInService && !(isEmergencyOnly || isNtn)
}
}
- ) { isOosAndIsNotEmergencyOnly ->
- isOosAndIsNotEmergencyOnly.all { it }
+ ) { isOosAndNotEmergencyOnlyOrSatellite ->
+ isOosAndNotEmergencyOnlyOrSatellite.all { it }
}
} else {
flowOf(false)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt
index f2255f3..332c121 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt
@@ -126,10 +126,9 @@
) { shouldShow, connectionState ->
if (shouldShow) {
when (connectionState) {
+ SatelliteConnectionState.On,
SatelliteConnectionState.Connected ->
context.getString(R.string.satellite_connected_carrier_text)
- SatelliteConnectionState.On ->
- context.getString(R.string.satellite_not_connected_carrier_text)
SatelliteConnectionState.Off,
SatelliteConnectionState.Unknown -> null
}
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldHapticsPlayer.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldHapticsPlayer.kt
index 1e65566..b3ea9dc 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldHapticsPlayer.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldHapticsPlayer.kt
@@ -6,8 +6,8 @@
import android.os.Vibrator
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
+import com.android.systemui.unfold.config.UnfoldTransitionConfig
import com.android.systemui.unfold.updates.FoldProvider
-import com.android.systemui.unfold.updates.FoldProvider.FoldCallback
import java.util.concurrent.Executor
import javax.inject.Inject
@@ -18,6 +18,7 @@
constructor(
unfoldTransitionProgressProvider: UnfoldTransitionProgressProvider,
foldProvider: FoldProvider,
+ transitionConfig: UnfoldTransitionConfig,
@Main private val mainExecutor: Executor,
private val vibrator: Vibrator?
) : TransitionProgressListener {
@@ -27,22 +28,17 @@
VibrationAttributes.createForUsage(VibrationAttributes.USAGE_HARDWARE_FEEDBACK)
init {
- if (vibrator != null) {
+ if (vibrator != null && transitionConfig.isHapticsEnabled) {
// We don't need to remove the callback because we should listen to it
// the whole time when SystemUI process is alive
unfoldTransitionProgressProvider.addCallback(this)
- }
- foldProvider.registerCallback(
- object : FoldCallback {
- override fun onFoldUpdated(isFolded: Boolean) {
- if (isFolded) {
- isFirstAnimationAfterUnfold = true
- }
+ foldProvider.registerCallback({ isFolded ->
+ if (isFolded) {
+ isFirstAnimationAfterUnfold = true
}
- },
- mainExecutor
- )
+ }, mainExecutor)
+ }
}
private var lastTransitionProgress = TRANSITION_PROGRESS_FULL_OPEN
diff --git a/packages/SystemUI/src/com/android/systemui/util/service/ObservableServiceConnection.java b/packages/SystemUI/src/com/android/systemui/util/service/ObservableServiceConnection.java
index 0dcbe9b2..229bdce 100644
--- a/packages/SystemUI/src/com/android/systemui/util/service/ObservableServiceConnection.java
+++ b/packages/SystemUI/src/com/android/systemui/util/service/ObservableServiceConnection.java
@@ -26,8 +26,9 @@
import android.util.Log;
import androidx.annotation.NonNull;
+import androidx.annotation.WorkerThread;
-import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.util.DumpUtilsKt;
import com.android.systemui.util.annotations.WeaklyReferencedCallback;
@@ -118,7 +119,7 @@
private final Intent mServiceIntent;
private final UserTracker mUserTracker;
private final int mFlags;
- private final Executor mExecutor;
+ private final Executor mBgExecutor;
private final ServiceTransformer<T> mTransformer;
private final ArrayList<WeakReference<Callback<T>>> mCallbacks;
private Optional<Integer> mLastDisconnectReason;
@@ -130,30 +131,34 @@
* Default constructor for {@link ObservableServiceConnection}.
* @param context The context from which the service will be bound with.
* @param serviceIntent The intent to bind service with.
- * @param executor The executor for connection callbacks to be delivered on
+ * @param bgExecutor The executor for connection callbacks to be delivered on
* @param transformer A {@link ServiceTransformer} for transforming the resulting service
* into a desired type.
*/
@Inject
public ObservableServiceConnection(Context context, Intent serviceIntent,
UserTracker userTracker,
- @Main Executor executor,
+ @Background Executor bgExecutor,
ServiceTransformer<T> transformer) {
mContext = context;
mServiceIntent = serviceIntent;
mUserTracker = userTracker;
mFlags = Context.BIND_AUTO_CREATE;
- mExecutor = executor;
+ mBgExecutor = bgExecutor;
mTransformer = transformer;
mCallbacks = new ArrayList<>();
mLastDisconnectReason = Optional.empty();
}
/**
- * Initiate binding to the service.
- * @return {@code true} if initiating binding succeed, {@code false} otherwise.
+ * Initiate binding to the service in the background.
*/
- public boolean bind() {
+ public void bind() {
+ mBgExecutor.execute(this::bindInternal);
+ }
+
+ @WorkerThread
+ private void bindInternal() {
boolean bindResult = false;
try {
bindResult = mContext.bindServiceAsUser(mServiceIntent, this, mFlags,
@@ -166,18 +171,17 @@
if (DEBUG) {
Log.d(TAG, "bind. bound:" + bindResult);
}
- return bindResult;
}
/**
* Disconnect from the service if bound.
*/
public void unbind() {
- onDisconnected(DISCONNECT_REASON_UNBIND);
+ mBgExecutor.execute(() -> onDisconnected(DISCONNECT_REASON_UNBIND));
}
/**
- * Adds a callback for receiving connection updates.
+ * Adds a callback for receiving connection updates. The callback is executed in the background.
* @param callback The {@link Callback} to receive future updates.
*/
public void addCallback(Callback<T> callback) {
@@ -185,7 +189,7 @@
Log.d(TAG, "addCallback:" + callback);
}
- mExecutor.execute(() -> {
+ mBgExecutor.execute(() -> {
final Iterator<WeakReference<Callback<T>>> iterator = mCallbacks.iterator();
while (iterator.hasNext()) {
@@ -210,14 +214,15 @@
* Removes previously added callback from receiving future connection updates.
* @param callback The {@link Callback} to be removed.
*/
- public void removeCallback(Callback callback) {
+ public void removeCallback(Callback<T> callback) {
if (DEBUG) {
Log.d(TAG, "removeCallback:" + callback);
}
- mExecutor.execute(() -> mCallbacks.removeIf(el-> el.get() == callback));
+ mBgExecutor.execute(() -> mCallbacks.removeIf(el-> el.get() == callback));
}
+ @WorkerThread
private void onDisconnected(@DisconnectReason int reason) {
if (DEBUG) {
Log.d(TAG, "onDisconnected:" + reason);
@@ -240,7 +245,7 @@
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
- mExecutor.execute(() -> {
+ mBgExecutor.execute(() -> {
if (DEBUG) {
Log.d(TAG, "onServiceConnected");
}
@@ -268,7 +273,7 @@
final Iterator<WeakReference<Callback<T>>> iterator = mCallbacks.iterator();
while (iterator.hasNext()) {
- final Callback cb = iterator.next().get();
+ final Callback<T> cb = iterator.next().get();
if (cb != null) {
applicator.accept(cb);
} else {
@@ -279,16 +284,16 @@
@Override
public void onServiceDisconnected(ComponentName name) {
- mExecutor.execute(() -> onDisconnected(DISCONNECT_REASON_DISCONNECTED));
+ mBgExecutor.execute(() -> onDisconnected(DISCONNECT_REASON_DISCONNECTED));
}
@Override
public void onBindingDied(ComponentName name) {
- mExecutor.execute(() -> onDisconnected(DISCONNECT_REASON_BINDING_DIED));
+ mBgExecutor.execute(() -> onDisconnected(DISCONNECT_REASON_BINDING_DIED));
}
@Override
public void onNullBinding(ComponentName name) {
- mExecutor.execute(() -> onDisconnected(DISCONNECT_REASON_NULL_BINDING));
+ mBgExecutor.execute(() -> onDisconnected(DISCONNECT_REASON_NULL_BINDING));
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/service/PersistentConnectionManager.java b/packages/SystemUI/src/com/android/systemui/util/service/PersistentConnectionManager.java
index 5979f3e..64f8246 100644
--- a/packages/SystemUI/src/com/android/systemui/util/service/PersistentConnectionManager.java
+++ b/packages/SystemUI/src/com/android/systemui/util/service/PersistentConnectionManager.java
@@ -48,7 +48,7 @@
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private final SystemClock mSystemClock;
- private final DelayableExecutor mMainExecutor;
+ private final DelayableExecutor mBgExecutor;
private final int mBaseReconnectDelayMs;
private final int mMaxReconnectAttempts;
private final int mMinConnectionDuration;
@@ -71,8 +71,8 @@
private final Observer.Callback mObserverCallback = () -> initiateConnectionAttempt();
- private final ObservableServiceConnection.Callback mConnectionCallback =
- new ObservableServiceConnection.Callback() {
+ private final ObservableServiceConnection.Callback<T> mConnectionCallback =
+ new ObservableServiceConnection.Callback<>() {
private long mStartTime;
@Override
@@ -95,12 +95,10 @@
}
};
- // TODO: b/326449074 - Ensure the DelayableExecutor is on the correct thread, and update the
- // qualifier (to @Main) or name (to bgExecutor) to be consistent with that.
@Inject
public PersistentConnectionManager(
SystemClock clock,
- @Background DelayableExecutor mainExecutor,
+ @Background DelayableExecutor bgExecutor,
DumpManager dumpManager,
@Named(DUMPSYS_NAME) String dumpsysName,
@Named(SERVICE_CONNECTION) ObservableServiceConnection<T> serviceConnection,
@@ -109,7 +107,7 @@
@Named(MIN_CONNECTION_DURATION_MS) int minConnectionDurationMs,
@Named(OBSERVER) Observer observer) {
mSystemClock = clock;
- mMainExecutor = mainExecutor;
+ mBgExecutor = bgExecutor;
mConnection = serviceConnection;
mObserver = observer;
mDumpManager = dumpManager;
@@ -195,7 +193,7 @@
"scheduling connection attempt in " + reconnectDelayMs + "milliseconds");
}
- mCurrentReconnectCancelable = mMainExecutor.executeDelayed(mConnectRunnable,
+ mCurrentReconnectCancelable = mBgExecutor.executeDelayed(mConnectRunnable,
reconnectDelayMs);
mReconnectAttempts++;
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumePanelDialogReceiver.kt b/packages/SystemUI/src/com/android/systemui/volume/VolumePanelDialogReceiver.kt
index f11d5d1..0968bde 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumePanelDialogReceiver.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumePanelDialogReceiver.kt
@@ -21,26 +21,29 @@
import android.content.Intent
import android.provider.Settings
import android.text.TextUtils
-import android.util.Log
+import com.android.systemui.volume.domain.interactor.VolumePanelNavigationInteractor
+import com.android.systemui.volume.ui.navigation.VolumeNavigator
import javax.inject.Inject
-private const val TAG = "VolumePanelDialogReceiver"
-private const val LAUNCH_ACTION = "com.android.systemui.action.LAUNCH_VOLUME_PANEL_DIALOG"
-private const val DISMISS_ACTION = "com.android.systemui.action.DISMISS_VOLUME_PANEL_DIALOG"
-
-/**
- * BroadcastReceiver for handling volume panel dialog intent
- */
-class VolumePanelDialogReceiver @Inject constructor(
- private val volumePanelFactory: VolumePanelFactory
+/** [BroadcastReceiver] for handling volume panel dialog intent */
+class VolumePanelDialogReceiver
+@Inject
+constructor(
+ private val volumeNavigator: VolumeNavigator,
+ private val volumePanelNavigationInteractor: VolumePanelNavigationInteractor,
) : BroadcastReceiver() {
+
override fun onReceive(context: Context, intent: Intent) {
- Log.d(TAG, "onReceive intent" + intent.action)
- if (TextUtils.equals(LAUNCH_ACTION, intent.action) ||
- TextUtils.equals(Settings.Panel.ACTION_VOLUME, intent.action)) {
- volumePanelFactory.create(true, null)
- } else if (TextUtils.equals(DISMISS_ACTION, intent.action)) {
- volumePanelFactory.dismiss()
+ if (
+ TextUtils.equals(LAUNCH_ACTION, intent.action) ||
+ TextUtils.equals(Settings.Panel.ACTION_VOLUME, intent.action)
+ ) {
+ volumeNavigator.openVolumePanel(volumePanelNavigationInteractor.getVolumePanelRoute())
}
}
+
+ companion object {
+ const val LAUNCH_ACTION = "com.android.systemui.action.LAUNCH_VOLUME_PANEL_DIALOG"
+ const val DISMISS_ACTION = "com.android.systemui.action.DISMISS_VOLUME_PANEL_DIALOG"
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
index fb92a0f..dc1e8cf 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
@@ -16,6 +16,7 @@
package com.android.systemui.volume.dagger;
+import android.content.BroadcastReceiver;
import android.content.Context;
import android.media.AudioManager;
import android.os.Looper;
@@ -37,6 +38,7 @@
import com.android.systemui.volume.VolumeComponent;
import com.android.systemui.volume.VolumeDialogComponent;
import com.android.systemui.volume.VolumeDialogImpl;
+import com.android.systemui.volume.VolumePanelDialogReceiver;
import com.android.systemui.volume.VolumeUI;
import com.android.systemui.volume.domain.interactor.VolumePanelNavigationInteractor;
import com.android.systemui.volume.panel.dagger.VolumePanelComponent;
@@ -65,6 +67,15 @@
}
)
public interface VolumeModule {
+
+ /**
+ * Binds [VolumePanelDialogReceiver]
+ */
+ @Binds
+ @IntoMap
+ @ClassKey(VolumePanelDialogReceiver.class)
+ BroadcastReceiver bindVolumePanelDialogReceiver(VolumePanelDialogReceiver receiver);
+
/** Starts VolumeUI. */
@Binds
@IntoMap
diff --git a/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractor.kt
index ed44699..19d9c3f 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractor.kt
@@ -32,6 +32,7 @@
import com.android.systemui.volume.panel.component.mediaoutput.data.repository.LocalMediaRepositoryFactory
import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputInteractor
import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
+import com.android.systemui.volume.panel.shared.model.filterData
import javax.inject.Inject
import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.CoroutineScope
@@ -69,6 +70,7 @@
}
} else {
mediaOutputInteractor.defaultActiveMediaSession
+ .filterData()
.flatMapLatest {
localMediaRepositoryFactory
.create(it?.packageName)
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/MediaOutputAvailabilityCriteria.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/MediaOutputAvailabilityCriteria.kt
deleted file mode 100644
index bac7d15..0000000
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/MediaOutputAvailabilityCriteria.kt
+++ /dev/null
@@ -1,35 +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.volume.panel.component.mediaoutput.domain
-
-import com.android.settingslib.volume.domain.interactor.AudioModeInteractor
-import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
-import com.android.systemui.volume.panel.domain.ComponentAvailabilityCriteria
-import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.map
-
-/** Determines if the Media Output Volume Panel component is available. */
-@VolumePanelScope
-class MediaOutputAvailabilityCriteria
-@Inject
-constructor(
- private val audioModeInteractor: AudioModeInteractor,
-) : ComponentAvailabilityCriteria {
-
- override fun isAvailable(): Flow<Boolean> = audioModeInteractor.isOngoingCall.map { !it }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt
index 83b8029..b974f90 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt
@@ -28,6 +28,9 @@
import com.android.systemui.volume.panel.component.mediaoutput.domain.model.MediaDeviceSessions
import com.android.systemui.volume.panel.component.mediaoutput.shared.model.MediaDeviceSession
import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
+import com.android.systemui.volume.panel.shared.model.Result
+import com.android.systemui.volume.panel.shared.model.filterData
+import com.android.systemui.volume.panel.shared.model.wrapInResult
import javax.inject.Inject
import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.CoroutineScope
@@ -72,7 +75,7 @@
}
/** Returns the default [MediaDeviceSession] from [activeMediaDeviceSessions] */
- val defaultActiveMediaSession: StateFlow<MediaDeviceSession?> =
+ val defaultActiveMediaSession: StateFlow<Result<MediaDeviceSession?>> =
activeMediaControllers
.map {
when {
@@ -82,11 +85,13 @@
else -> null
}
}
+ .wrapInResult()
.flowOn(backgroundCoroutineContext)
- .stateIn(coroutineScope, SharingStarted.Eagerly, null)
+ .stateIn(coroutineScope, SharingStarted.Eagerly, Result.Loading())
private val localMediaRepository: SharedFlow<LocalMediaRepository> =
defaultActiveMediaSession
+ .filterData()
.map { it?.packageName }
.distinctUntilChanged()
.map { localMediaRepositoryFactory.create(it) }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt
index d60d981..192e0ec 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt
@@ -18,16 +18,21 @@
import android.content.Context
import com.android.internal.logging.UiEventLogger
+import com.android.settingslib.volume.domain.interactor.AudioModeInteractor
import com.android.systemui.animation.Expandable
import com.android.systemui.common.shared.model.Color
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.res.R
+import com.android.systemui.volume.domain.interactor.AudioOutputInteractor
+import com.android.systemui.volume.domain.model.AudioOutputDevice
import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaDeviceSessionInteractor
import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputActionsInteractor
import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputInteractor
import com.android.systemui.volume.panel.component.mediaoutput.shared.model.SessionWithPlaybackState
import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
import com.android.systemui.volume.panel.shared.model.Result
+import com.android.systemui.volume.panel.shared.model.filterData
+import com.android.systemui.volume.panel.shared.model.wrapInResult
import com.android.systemui.volume.panel.ui.VolumePanelUiEvent
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
@@ -35,6 +40,7 @@
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.mapNotNull
@@ -50,25 +56,25 @@
@VolumePanelScope private val coroutineScope: CoroutineScope,
private val actionsInteractor: MediaOutputActionsInteractor,
private val mediaDeviceSessionInteractor: MediaDeviceSessionInteractor,
+ audioOutputInteractor: AudioOutputInteractor,
+ audioModeInteractor: AudioModeInteractor,
interactor: MediaOutputInteractor,
private val uiEventLogger: UiEventLogger,
) {
private val sessionWithPlaybackState: StateFlow<Result<SessionWithPlaybackState?>> =
interactor.defaultActiveMediaSession
+ .filterData()
.flatMapLatest { session ->
if (session == null) {
- flowOf(Result.Data<SessionWithPlaybackState?>(null))
+ flowOf(null)
} else {
mediaDeviceSessionInteractor.playbackState(session).mapNotNull { playback ->
- playback?.let {
- Result.Data<SessionWithPlaybackState?>(
- SessionWithPlaybackState(session, playback.isActive())
- )
- }
+ playback?.let { SessionWithPlaybackState(session, playback.isActive) }
}
}
}
+ .wrapInResult()
.stateIn(
coroutineScope,
SharingStarted.Eagerly,
@@ -76,23 +82,24 @@
)
val connectedDeviceViewModel: StateFlow<ConnectedDeviceViewModel?> =
- combine(sessionWithPlaybackState, interactor.currentConnectedDevice) {
- mediaDeviceSession,
- currentConnectedDevice ->
- if (mediaDeviceSession !is Result.Data) {
- return@combine null
- }
- ConnectedDeviceViewModel(
- if (mediaDeviceSession.data?.isPlaybackActive == true) {
- context.getString(
- R.string.media_output_label_title,
- mediaDeviceSession.data.session.appLabel
- )
- } else {
- context.getString(R.string.media_output_title_without_playing)
- },
- currentConnectedDevice?.name,
- )
+ combine(
+ sessionWithPlaybackState.filterData(),
+ audioModeInteractor.isOngoingCall,
+ audioOutputInteractor.currentAudioDevice.filter {
+ it !is AudioOutputDevice.Unknown
+ },
+ ) { mediaDeviceSession, isOngoingCall, currentConnectedDevice ->
+ val label =
+ when {
+ isOngoingCall -> context.getString(R.string.media_output_title_ongoing_call)
+ mediaDeviceSession?.isPlaybackActive == true ->
+ context.getString(
+ R.string.media_output_label_title,
+ mediaDeviceSession.session.appLabel
+ )
+ else -> context.getString(R.string.media_output_title_without_playing)
+ }
+ ConnectedDeviceViewModel(label, currentConnectedDevice.name)
}
.stateIn(
coroutineScope,
@@ -101,16 +108,16 @@
)
val deviceIconViewModel: StateFlow<DeviceIconViewModel?> =
- combine(sessionWithPlaybackState, interactor.currentConnectedDevice) {
+ combine(sessionWithPlaybackState.filterData(), audioOutputInteractor.currentAudioDevice) {
mediaDeviceSession,
currentConnectedDevice ->
- if (mediaDeviceSession !is Result.Data) {
- return@combine null
- }
val icon: Icon =
- currentConnectedDevice?.icon?.let { Icon.Loaded(it, null) }
+ currentConnectedDevice
+ .takeIf { currentConnectedDevice !is AudioOutputDevice.Unknown }
+ ?.icon
+ ?.let { Icon.Loaded(it, null) }
?: Icon.Resource(R.drawable.ic_media_home_devices, null)
- if (mediaDeviceSession.data?.isPlaybackActive == true) {
+ if (mediaDeviceSession?.isPlaybackActive == true) {
DeviceIconViewModel.IsPlaying(
icon = icon,
iconColor =
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/domain/interactor/AudioSlidersInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/domain/interactor/AudioSlidersInteractor.kt
index ac8092c..fa40059 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/domain/interactor/AudioSlidersInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/domain/interactor/AudioSlidersInteractor.kt
@@ -25,6 +25,7 @@
import com.android.systemui.volume.panel.component.mediaoutput.shared.model.isTheSameSession
import com.android.systemui.volume.panel.component.volume.domain.model.SliderType
import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
+import com.android.systemui.volume.panel.shared.model.filterData
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.coroutineScope
@@ -46,7 +47,7 @@
val volumePanelSliders: StateFlow<List<SliderType>> =
combineTransform(
mediaOutputInteractor.activeMediaDeviceSessions,
- mediaOutputInteractor.defaultActiveMediaSession,
+ mediaOutputInteractor.defaultActiveMediaSession.filterData(),
audioRepository.communicationDevice,
) { activeSessions, defaultSession, communicationDevice ->
coroutineScope {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
index ee642a6..0386338 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
@@ -18,6 +18,7 @@
import android.content.Context
import android.media.AudioManager
+import android.util.Log
import com.android.internal.logging.UiEventLogger
import com.android.settingslib.volume.domain.interactor.AudioVolumeInteractor
import com.android.settingslib.volume.shared.model.AudioStream
@@ -144,6 +145,7 @@
if (isMutedOrNoVolume) {
when (audioStream.value) {
AudioManager.STREAM_MUSIC -> R.drawable.ic_volume_off
+ AudioManager.STREAM_BLUETOOTH_SCO -> R.drawable.ic_volume_off
AudioManager.STREAM_VOICE_CALL -> R.drawable.ic_volume_off
AudioManager.STREAM_RING ->
if (ringerMode.value == AudioManager.RINGER_MODE_VIBRATE) {
@@ -158,12 +160,18 @@
R.drawable.ic_volume_off
}
AudioManager.STREAM_ALARM -> R.drawable.ic_volume_off
- else -> null
+ else -> {
+ Log.wtf(TAG, "No icon for the stream: $audioStream")
+ R.drawable.ic_volume_off
+ }
}
} else {
iconsByStream[audioStream]
+ ?: run {
+ Log.wtf(TAG, "No icon for the stream: $audioStream")
+ R.drawable.ic_music_note
+ }
}
- ?: error("No icon for the stream: $audioStream")
return Icon.Resource(iconRes, null)
}
@@ -196,4 +204,8 @@
* when using [AudioStream] directly because it expects another type.
*/
class FactoryAudioStreamWrapper(val audioStream: AudioStream)
+
+ private companion object {
+ const val TAG = "AudioStreamSliderViewModel"
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/AudioVolumeComponentViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/AudioVolumeComponentViewModel.kt
index 741f5cf..26d6a9a 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/AudioVolumeComponentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/AudioVolumeComponentViewModel.kt
@@ -26,6 +26,7 @@
import com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel.CastVolumeSliderViewModel
import com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel.SliderViewModel
import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
+import com.android.systemui.volume.panel.shared.model.filterData
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -80,9 +81,13 @@
val isExpanded: StateFlow<Boolean> =
merge(
mutableIsExpanded,
- mediaOutputInteractor.defaultActiveMediaSession.flatMapLatest {
- if (it == null) flowOf(true)
- else mediaDeviceSessionInteractor.playbackState(it).map { it?.isActive != true }
+ mediaOutputInteractor.defaultActiveMediaSession.filterData().flatMapLatest { session
+ ->
+ if (session == null) flowOf(true)
+ else
+ mediaDeviceSessionInteractor.playbackState(session).map {
+ it?.isActive != true
+ }
},
)
.stateIn(scope, SharingStarted.Eagerly, false)
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/shared/model/Result.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/shared/model/Result.kt
index 8793538..5daed99 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/shared/model/Result.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/shared/model/Result.kt
@@ -16,6 +16,10 @@
package com.android.systemui.volume.panel.shared.model
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.mapNotNull
+
/** Models a loadable result */
sealed interface Result<T> {
@@ -25,3 +29,9 @@
/** The data is loaded successfully */
data class Data<T>(val data: T) : Result<T>
}
+
+/** Wraps flow into [Result]. */
+fun <T> Flow<T>.wrapInResult(): Flow<Result<T>> = map { Result.Data(it) }
+
+/** Filters only [Result.Data] from the flow. */
+fun <T> Flow<Result<T>>.filterData(): Flow<T> = mapNotNull { it as? Result.Data<T> }.map { it.data }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModel.kt
index eff408a..a30de1b 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModel.kt
@@ -17,11 +17,14 @@
package com.android.systemui.volume.panel.ui.viewmodel
import android.content.Context
+import android.content.IntentFilter
import android.content.res.Resources
+import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.res.R
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.onConfigChanged
+import com.android.systemui.volume.VolumePanelDialogReceiver
import com.android.systemui.volume.panel.dagger.VolumePanelComponent
import com.android.systemui.volume.panel.dagger.factory.VolumePanelComponentFactory
import com.android.systemui.volume.panel.domain.VolumePanelStartable
@@ -37,6 +40,8 @@
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.flow.stateIn
@@ -49,6 +54,7 @@
coroutineScope: CoroutineScope,
daggerComponentFactory: VolumePanelComponentFactory,
configurationController: ConfigurationController,
+ broadcastDispatcher: BroadcastDispatcher,
) {
private val volumePanelComponent: VolumePanelComponent =
@@ -113,6 +119,10 @@
init {
volumePanelComponent.volumePanelStartables().onEach(VolumePanelStartable::start)
+ broadcastDispatcher
+ .broadcastFlow(IntentFilter(VolumePanelDialogReceiver.DISMISS_ACTION))
+ .onEach { dismissPanel() }
+ .launchIn(scope)
}
fun dismissPanel() {
@@ -125,6 +135,7 @@
@Application private val context: Context,
private val daggerComponentFactory: VolumePanelComponentFactory,
private val configurationController: ConfigurationController,
+ private val broadcastDispatcher: BroadcastDispatcher,
) {
fun create(coroutineScope: CoroutineScope): VolumePanelViewModel {
@@ -133,6 +144,7 @@
coroutineScope,
daggerComponentFactory,
configurationController,
+ broadcastDispatcher
)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractorTest.kt
index 4684b80..79e312f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractorTest.kt
@@ -27,9 +27,11 @@
import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
import com.android.systemui.keyguard.data.repository.deviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.ErrorFingerprintAuthenticationStatus
import com.android.systemui.keyguard.shared.model.FailFingerprintAuthenticationStatus
import com.android.systemui.keyguard.shared.model.HelpFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
import com.android.systemui.kosmos.testScope
import com.android.systemui.plugins.ActivityStarter.OnDismissAction
@@ -229,6 +231,11 @@
givenOnOccludingApp(true)
givenFingerprintAllowed(true)
keyguardRepository.setIsDozing(true)
+ kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.OCCLUDED,
+ to = KeyguardState.DOZING,
+ testScope
+ )
runCurrent()
// ERROR message
@@ -254,7 +261,7 @@
assertThat(message).isNull()
}
- private fun givenOnOccludingApp(isOnOccludingApp: Boolean) {
+ private suspend fun givenOnOccludingApp(isOnOccludingApp: Boolean) {
powerRepository.setInteractive(true)
keyguardRepository.setIsDozing(false)
keyguardRepository.setKeyguardOccluded(isOnOccludingApp)
@@ -262,6 +269,20 @@
keyguardRepository.setDreaming(false)
bouncerRepository.setPrimaryShow(!isOnOccludingApp)
bouncerRepository.setAlternateVisible(!isOnOccludingApp)
+
+ if (isOnOccludingApp) {
+ kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.OCCLUDED,
+ testScope
+ )
+ } else {
+ kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.OCCLUDED,
+ to = KeyguardState.LOCKSCREEN,
+ testScope
+ )
+ }
}
private fun givenFingerprintAllowed(allowed: Boolean) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
index 77b3040..cfe37ee 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
@@ -67,6 +67,7 @@
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.globalactions.domain.interactor.GlobalActionsInteractor;
import com.android.systemui.kosmos.KosmosJavaAdapter;
+import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.GlobalActions;
import com.android.systemui.settings.UserContextProvider;
import com.android.systemui.settings.UserTracker;
@@ -114,6 +115,7 @@
private SecureSettings mSecureSettings;
@Mock private Resources mResources;
@Mock private ConfigurationController mConfigurationController;
+ @Mock private ActivityStarter mActivityStarter;
@Mock private UserTracker mUserTracker;
@Mock private KeyguardStateController mKeyguardStateController;
@Mock private UserManager mUserManager;
@@ -173,6 +175,7 @@
mVibratorHelper,
mResources,
mConfigurationController,
+ mActivityStarter,
mUserTracker,
mKeyguardStateController,
mUserManager,
@@ -458,6 +461,18 @@
}
}
+ private static <T> void assertNoItemsOfType(List<T> stuff, Class<? extends T> klass) {
+ for (int i = 0; i < stuff.size(); i++) {
+ assertThat(stuff.get(i)).isNotInstanceOf(klass);
+ }
+ }
+
+ private static <T> void assertOneItemOfType(List<T> stuff, Class<? extends T> klass) {
+ List<?> classes = stuff.stream().map((item) -> item.getClass()).toList();
+ assertThat(classes).containsNoDuplicates();
+ assertThat(classes).contains(klass);
+ }
+
@Test
public void testCreateActionItems_lockdownEnabled_doesShowLockdown() {
mGlobalActionsDialogLite = spy(mGlobalActionsDialogLite);
@@ -641,6 +656,113 @@
assertThat(mInteractor.isVisible().getValue()).isFalse();
}
+ @Test
+ public void testShouldLogSystemUpdatePress() {
+ GlobalActionsDialogLite.SystemUpdateAction systemUpdateAction =
+ mGlobalActionsDialogLite.new SystemUpdateAction();
+ systemUpdateAction.onPress();
+ verifyLogPosted(GlobalActionsDialogLite.GlobalActionsEvent.GA_SYSTEM_UPDATE_PRESS);
+ }
+
+ @Test
+ public void testCreateActionItems_systemUpdateEnabled_doesShowSystemUpdate() {
+ mGlobalActionsDialogLite = spy(mGlobalActionsDialogLite);
+ doReturn(5).when(mGlobalActionsDialogLite).getMaxShownPowerItems();
+ doReturn(true).when(mGlobalActionsDialogLite).shouldDisplayEmergency();
+ doReturn(true).when(mGlobalActionsDialogLite).shouldDisplayLockdown(any());
+ doReturn(true).when(mGlobalActionsDialogLite).shouldShowAction(any());
+ String[] actions = {
+ GlobalActionsDialogLite.GLOBAL_ACTION_KEY_EMERGENCY,
+ GlobalActionsDialogLite.GLOBAL_ACTION_KEY_LOCKDOWN,
+ GlobalActionsDialogLite.GLOBAL_ACTION_KEY_POWER,
+ GlobalActionsDialogLite.GLOBAL_ACTION_KEY_RESTART,
+ GlobalActionsDialogLite.GLOBAL_ACTION_KEY_SYSTEM_UPDATE
+ };
+ doReturn(actions).when(mGlobalActionsDialogLite).getDefaultActions();
+ mGlobalActionsDialogLite.createActionItems();
+
+ assertItemsOfType(mGlobalActionsDialogLite.mItems,
+ GlobalActionsDialogLite.EmergencyAction.class,
+ GlobalActionsDialogLite.LockDownAction.class,
+ GlobalActionsDialogLite.ShutDownAction.class,
+ GlobalActionsDialogLite.RestartAction.class,
+ GlobalActionsDialogLite.SystemUpdateAction.class);
+ assertThat(mGlobalActionsDialogLite.mOverflowItems).isEmpty();
+ assertThat(mGlobalActionsDialogLite.mPowerItems).isEmpty();
+ }
+
+ @Test
+ public void testCreateActionItems_systemUpdateDisabled_doesntShowSystemUpdateAction() {
+ mGlobalActionsDialogLite = spy(mGlobalActionsDialogLite);
+ doReturn(5).when(mGlobalActionsDialogLite).getMaxShownPowerItems();
+ doReturn(true).when(mGlobalActionsDialogLite).shouldDisplayEmergency();
+ doReturn(true).when(mGlobalActionsDialogLite).shouldDisplayLockdown(any());
+ doReturn(true).when(mGlobalActionsDialogLite).shouldShowAction(any());
+ String[] actions = {
+ GlobalActionsDialogLite.GLOBAL_ACTION_KEY_EMERGENCY,
+ GlobalActionsDialogLite.GLOBAL_ACTION_KEY_LOCKDOWN,
+ GlobalActionsDialogLite.GLOBAL_ACTION_KEY_POWER,
+ GlobalActionsDialogLite.GLOBAL_ACTION_KEY_RESTART
+ };
+ doReturn(actions).when(mGlobalActionsDialogLite).getDefaultActions();
+ mGlobalActionsDialogLite.createActionItems();
+
+ assertNoItemsOfType(mGlobalActionsDialogLite.mItems,
+ GlobalActionsDialogLite.SystemUpdateAction.class);
+ assertThat(mGlobalActionsDialogLite.mOverflowItems).isEmpty();
+ assertThat(mGlobalActionsDialogLite.mPowerItems).isEmpty();
+ }
+
+ @Test
+ public void testCreateActionItems_systemUpdateEnabled_locked_showsSystemUpdate() {
+ mGlobalActionsDialogLite = spy(mGlobalActionsDialogLite);
+ doReturn(5).when(mGlobalActionsDialogLite).getMaxShownPowerItems();
+ doReturn(true).when(mGlobalActionsDialogLite).shouldDisplayEmergency();
+ doReturn(true).when(mGlobalActionsDialogLite).shouldDisplayLockdown(any());
+ String[] actions = {
+ GlobalActionsDialogLite.GLOBAL_ACTION_KEY_EMERGENCY,
+ GlobalActionsDialogLite.GLOBAL_ACTION_KEY_LOCKDOWN,
+ GlobalActionsDialogLite.GLOBAL_ACTION_KEY_POWER,
+ GlobalActionsDialogLite.GLOBAL_ACTION_KEY_RESTART,
+ GlobalActionsDialogLite.GLOBAL_ACTION_KEY_SYSTEM_UPDATE
+ };
+ doReturn(actions).when(mGlobalActionsDialogLite).getDefaultActions();
+
+ // Show dialog with keyguard showing
+ mGlobalActionsDialogLite.showOrHideDialog(true, true, null);
+
+ assertOneItemOfType(mGlobalActionsDialogLite.mItems,
+ GlobalActionsDialogLite.SystemUpdateAction.class);
+
+ // Hide dialog
+ mGlobalActionsDialogLite.showOrHideDialog(true, true, null);
+ }
+
+ @Test
+ public void testCreateActionItems_systemUpdateEnabled_notProvisioned_noSystemUpdate() {
+ mGlobalActionsDialogLite = spy(mGlobalActionsDialogLite);
+ doReturn(5).when(mGlobalActionsDialogLite).getMaxShownPowerItems();
+ doReturn(true).when(mGlobalActionsDialogLite).shouldDisplayEmergency();
+ doReturn(true).when(mGlobalActionsDialogLite).shouldDisplayLockdown(any());
+ String[] actions = {
+ GlobalActionsDialogLite.GLOBAL_ACTION_KEY_EMERGENCY,
+ GlobalActionsDialogLite.GLOBAL_ACTION_KEY_LOCKDOWN,
+ GlobalActionsDialogLite.GLOBAL_ACTION_KEY_POWER,
+ GlobalActionsDialogLite.GLOBAL_ACTION_KEY_RESTART,
+ GlobalActionsDialogLite.GLOBAL_ACTION_KEY_SYSTEM_UPDATE
+ };
+ doReturn(actions).when(mGlobalActionsDialogLite).getDefaultActions();
+
+ // Show dialog with keyguard showing
+ mGlobalActionsDialogLite.showOrHideDialog(false, false, null);
+
+ assertNoItemsOfType(mGlobalActionsDialogLite.mItems,
+ GlobalActionsDialogLite.SystemUpdateAction.class);
+
+ // Hide dialog
+ mGlobalActionsDialogLite.showOrHideDialog(false, false, null);
+ }
+
private UserInfo mockCurrentUser(int flags) {
return new UserInfo(10, "A User", flags);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardSmartspaceRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardSmartspaceRepositoryImplTest.kt
new file mode 100644
index 0000000..a320845
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardSmartspaceRepositoryImplTest.kt
@@ -0,0 +1,83 @@
+/*
+ * 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.keyguard.data.repository
+
+import android.provider.Settings
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.settings.FakeUserTracker
+import com.android.systemui.util.settings.FakeSettings
+import com.google.common.truth.Truth
+import kotlin.test.Test
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestCoroutineScheduler
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.MockitoAnnotations
+
+@RunWith(JUnit4::class)
+@SmallTest
+class KeyguardSmartspaceRepositoryImplTest : SysuiTestCase() {
+
+ private lateinit var scheduler: TestCoroutineScheduler
+ private lateinit var dispatcher: CoroutineDispatcher
+ private lateinit var scope: TestScope
+
+ private lateinit var underTest: KeyguardSmartspaceRepository
+ private lateinit var fakeSettings: FakeSettings
+ private lateinit var fakeUserTracker: FakeUserTracker
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ fakeSettings = FakeSettings()
+ fakeUserTracker = FakeUserTracker()
+ fakeSettings.userId = fakeUserTracker.userId
+ scheduler = TestCoroutineScheduler()
+ dispatcher = StandardTestDispatcher(scheduler)
+ scope = TestScope(dispatcher)
+ underTest =
+ KeyguardSmartspaceRepositoryImpl(
+ context = context,
+ secureSettings = fakeSettings,
+ userTracker = fakeUserTracker,
+ applicationScope = scope.backgroundScope,
+ )
+ }
+
+ @Test
+ fun testWeatherEnabled_true() =
+ scope.runTest {
+ fakeSettings.putInt(Settings.Secure.LOCK_SCREEN_WEATHER_ENABLED, 1)
+ val value = collectLastValue(underTest.isWeatherEnabled)
+ Truth.assertThat(value()).isEqualTo(true)
+ }
+
+ @Test
+ fun testWeatherEnabled_false() =
+ scope.runTest {
+ fakeSettings.putInt(Settings.Secure.LOCK_SCREEN_WEATHER_ENABLED, 0)
+
+ val value = collectLastValue(underTest.isWeatherEnabled)
+ Truth.assertThat(value()).isEqualTo(false)
+ }
+}
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 96b7596..35659c1 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
@@ -30,6 +30,7 @@
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.dock.DockManagerFake
+import com.android.systemui.flags.DisableSceneContainer
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.keyguard.data.quickaffordance.BuiltInKeyguardQuickAffordanceKeys
import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig
@@ -83,6 +84,7 @@
)
@SmallTest
@RunWith(Parameterized::class)
+@DisableSceneContainer
class KeyguardQuickAffordanceInteractorParameterizedTest : SysuiTestCase() {
companion object {
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
new file mode 100644
index 0000000..ef3183a8
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt
@@ -0,0 +1,406 @@
+/*
+ * 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.keyguard.domain.interactor
+
+import android.app.admin.DevicePolicyManager
+import android.content.Intent
+import android.os.UserHandle
+import androidx.test.filters.FlakyTest
+import androidx.test.filters.SmallTest
+import com.android.internal.widget.LockPatternUtils
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.ActivityTransitionAnimator
+import com.android.systemui.animation.DialogTransitionAnimator
+import com.android.systemui.animation.Expandable
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.dock.DockManagerFake
+import com.android.systemui.flags.EnableSceneContainer
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.keyguard.data.quickaffordance.BuiltInKeyguardQuickAffordanceKeys
+import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceProviderClientFactory
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLegacySettingSyncer
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLocalUserSelectionManager
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceRemoteUserSelectionManager
+import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
+import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
+import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
+import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancesMetricsLogger
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.settings.FakeUserTracker
+import com.android.systemui.settings.UserFileManager
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.testKosmos
+import com.android.systemui.util.FakeSharedPreferences
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.settings.FakeSettings
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import org.junit.runners.Parameterized.Parameter
+import org.junit.runners.Parameterized.Parameters
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.anyString
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.ArgumentMatchers.same
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyZeroInteractions
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@FlakyTest(
+ bugId = 292574995,
+ detail = "on certain architectures all permutations with startActivity=true is causing failures"
+)
+@SmallTest
+@RunWith(Parameterized::class)
+@EnableSceneContainer
+class KeyguardQuickAffordanceInteractorSceneContainerTest : SysuiTestCase() {
+
+ companion object {
+ private val INTENT = Intent("some.intent.action")
+ private val DRAWABLE =
+ mock<Icon> {
+ whenever(this.contentDescription)
+ .thenReturn(
+ ContentDescription.Resource(
+ res = CONTENT_DESCRIPTION_RESOURCE_ID,
+ )
+ )
+ }
+ private const val CONTENT_DESCRIPTION_RESOURCE_ID = 1337
+
+ @Parameters(
+ name =
+ "needStrongAuthAfterBoot={0}, canShowWhileLocked={1}," +
+ " keyguardIsUnlocked={2}, needsToUnlockFirst={3}, startActivity={4}"
+ )
+ @JvmStatic
+ fun data() =
+ listOf(
+ arrayOf(
+ /* needStrongAuthAfterBoot= */ false,
+ /* canShowWhileLocked= */ false,
+ /* keyguardIsUnlocked= */ false,
+ /* needsToUnlockFirst= */ true,
+ /* startActivity= */ false,
+ ),
+ arrayOf(
+ /* needStrongAuthAfterBoot= */ false,
+ /* canShowWhileLocked= */ false,
+ /* keyguardIsUnlocked= */ true,
+ /* needsToUnlockFirst= */ false,
+ /* startActivity= */ false,
+ ),
+ arrayOf(
+ /* needStrongAuthAfterBoot= */ false,
+ /* canShowWhileLocked= */ true,
+ /* keyguardIsUnlocked= */ false,
+ /* needsToUnlockFirst= */ false,
+ /* startActivity= */ false,
+ ),
+ arrayOf(
+ /* needStrongAuthAfterBoot= */ false,
+ /* canShowWhileLocked= */ true,
+ /* keyguardIsUnlocked= */ true,
+ /* needsToUnlockFirst= */ false,
+ /* startActivity= */ false,
+ ),
+ arrayOf(
+ /* needStrongAuthAfterBoot= */ true,
+ /* canShowWhileLocked= */ false,
+ /* keyguardIsUnlocked= */ false,
+ /* needsToUnlockFirst= */ true,
+ /* startActivity= */ false,
+ ),
+ arrayOf(
+ /* needStrongAuthAfterBoot= */ true,
+ /* canShowWhileLocked= */ false,
+ /* keyguardIsUnlocked= */ true,
+ /* needsToUnlockFirst= */ true,
+ /* startActivity= */ false,
+ ),
+ arrayOf(
+ /* needStrongAuthAfterBoot= */ true,
+ /* canShowWhileLocked= */ true,
+ /* keyguardIsUnlocked= */ false,
+ /* needsToUnlockFirst= */ true,
+ /* startActivity= */ false,
+ ),
+ arrayOf(
+ /* needStrongAuthAfterBoot= */ true,
+ /* canShowWhileLocked= */ true,
+ /* keyguardIsUnlocked= */ true,
+ /* needsToUnlockFirst= */ true,
+ /* startActivity= */ false,
+ ),
+ arrayOf(
+ /* needStrongAuthAfterBoot= */ false,
+ /* canShowWhileLocked= */ false,
+ /* keyguardIsUnlocked= */ false,
+ /* needsToUnlockFirst= */ true,
+ /* startActivity= */ true,
+ ),
+ arrayOf(
+ /* needStrongAuthAfterBoot= */ false,
+ /* canShowWhileLocked= */ false,
+ /* keyguardIsUnlocked= */ true,
+ /* needsToUnlockFirst= */ false,
+ /* startActivity= */ true,
+ ),
+ arrayOf(
+ /* needStrongAuthAfterBoot= */ false,
+ /* canShowWhileLocked= */ true,
+ /* keyguardIsUnlocked= */ false,
+ /* needsToUnlockFirst= */ false,
+ /* startActivity= */ true,
+ ),
+ arrayOf(
+ /* needStrongAuthAfterBoot= */ false,
+ /* canShowWhileLocked= */ true,
+ /* keyguardIsUnlocked= */ true,
+ /* needsToUnlockFirst= */ false,
+ /* startActivity= */ true,
+ ),
+ arrayOf(
+ /* needStrongAuthAfterBoot= */ true,
+ /* canShowWhileLocked= */ false,
+ /* keyguardIsUnlocked= */ false,
+ /* needsToUnlockFirst= */ true,
+ /* startActivity= */ true,
+ ),
+ arrayOf(
+ /* needStrongAuthAfterBoot= */ true,
+ /* canShowWhileLocked= */ false,
+ /* keyguardIsUnlocked= */ true,
+ /* needsToUnlockFirst= */ true,
+ /* startActivity= */ true,
+ ),
+ arrayOf(
+ /* needStrongAuthAfterBoot= */ true,
+ /* canShowWhileLocked= */ true,
+ /* keyguardIsUnlocked= */ false,
+ /* needsToUnlockFirst= */ true,
+ /* startActivity= */ true,
+ ),
+ arrayOf(
+ /* needStrongAuthAfterBoot= */ true,
+ /* canShowWhileLocked= */ true,
+ /* keyguardIsUnlocked= */ true,
+ /* needsToUnlockFirst= */ true,
+ /* startActivity= */ true,
+ ),
+ )
+
+ private val IMMEDIATE = Dispatchers.Main.immediate
+ }
+
+ @Mock private lateinit var lockPatternUtils: LockPatternUtils
+ @Mock private lateinit var keyguardStateController: KeyguardStateController
+ @Mock private lateinit var activityStarter: ActivityStarter
+ @Mock private lateinit var animationController: ActivityTransitionAnimator.Controller
+ @Mock private lateinit var expandable: Expandable
+ @Mock private lateinit var launchAnimator: DialogTransitionAnimator
+ @Mock private lateinit var devicePolicyManager: DevicePolicyManager
+ @Mock private lateinit var logger: KeyguardQuickAffordancesMetricsLogger
+
+ private lateinit var underTest: KeyguardQuickAffordanceInteractor
+ private lateinit var testScope: TestScope
+
+ @JvmField @Parameter(0) var needStrongAuthAfterBoot: Boolean = false
+ @JvmField @Parameter(1) var canShowWhileLocked: Boolean = false
+ @JvmField @Parameter(2) var keyguardIsUnlocked: Boolean = false
+ @JvmField @Parameter(3) var needsToUnlockFirst: Boolean = false
+ @JvmField @Parameter(4) var startActivity: Boolean = false
+ private lateinit var homeControls: FakeKeyguardQuickAffordanceConfig
+ private lateinit var dockManager: DockManagerFake
+ private lateinit var biometricSettingsRepository: FakeBiometricSettingsRepository
+ private lateinit var userTracker: UserTracker
+
+ private val kosmos = testKosmos()
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ whenever(expandable.activityTransitionController()).thenReturn(animationController)
+
+ userTracker = FakeUserTracker()
+ homeControls =
+ FakeKeyguardQuickAffordanceConfig(BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS)
+ dockManager = DockManagerFake()
+ biometricSettingsRepository = FakeBiometricSettingsRepository()
+ val quickAccessWallet =
+ FakeKeyguardQuickAffordanceConfig(
+ BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET
+ )
+ val qrCodeScanner =
+ FakeKeyguardQuickAffordanceConfig(BuiltInKeyguardQuickAffordanceKeys.QR_CODE_SCANNER)
+ val scope = CoroutineScope(IMMEDIATE)
+ val localUserSelectionManager =
+ KeyguardQuickAffordanceLocalUserSelectionManager(
+ context = context,
+ userFileManager =
+ mock<UserFileManager>().apply {
+ whenever(
+ getSharedPreferences(
+ anyString(),
+ anyInt(),
+ anyInt(),
+ )
+ )
+ .thenReturn(FakeSharedPreferences())
+ },
+ userTracker = userTracker,
+ broadcastDispatcher = fakeBroadcastDispatcher,
+ )
+ val remoteUserSelectionManager =
+ KeyguardQuickAffordanceRemoteUserSelectionManager(
+ scope = scope,
+ userTracker = userTracker,
+ clientFactory = FakeKeyguardQuickAffordanceProviderClientFactory(userTracker),
+ userHandle = UserHandle.SYSTEM,
+ )
+ val quickAffordanceRepository =
+ KeyguardQuickAffordanceRepository(
+ appContext = context,
+ scope = scope,
+ localUserSelectionManager = localUserSelectionManager,
+ remoteUserSelectionManager = remoteUserSelectionManager,
+ userTracker = userTracker,
+ legacySettingSyncer =
+ KeyguardQuickAffordanceLegacySettingSyncer(
+ scope = scope,
+ backgroundDispatcher = IMMEDIATE,
+ secureSettings = FakeSettings(),
+ selectionsManager = localUserSelectionManager,
+ ),
+ configs = setOf(homeControls, quickAccessWallet, qrCodeScanner),
+ dumpManager = mock(),
+ userHandle = UserHandle.SYSTEM,
+ )
+ val featureFlags = FakeFeatureFlags()
+ val testDispatcher = StandardTestDispatcher()
+ testScope = TestScope(testDispatcher)
+ underTest =
+ KeyguardQuickAffordanceInteractor(
+ keyguardInteractor =
+ KeyguardInteractorFactory.create(
+ featureFlags = featureFlags,
+ )
+ .keyguardInteractor,
+ shadeInteractor = kosmos.shadeInteractor,
+ lockPatternUtils = lockPatternUtils,
+ keyguardStateController = keyguardStateController,
+ userTracker = userTracker,
+ activityStarter = activityStarter,
+ featureFlags = featureFlags,
+ repository = { quickAffordanceRepository },
+ launchAnimator = launchAnimator,
+ logger = logger,
+ devicePolicyManager = devicePolicyManager,
+ dockManager = dockManager,
+ biometricSettingsRepository = biometricSettingsRepository,
+ backgroundDispatcher = testDispatcher,
+ appContext = mContext,
+ sceneInteractor = { kosmos.sceneInteractor },
+ )
+ }
+
+ @Test
+ fun onQuickAffordanceTriggered() =
+ testScope.runTest {
+ val key = BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS
+ setUpMocks(
+ needStrongAuthAfterBoot = needStrongAuthAfterBoot,
+ keyguardIsUnlocked = keyguardIsUnlocked,
+ )
+
+ homeControls.setState(
+ lockScreenState =
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible(
+ icon = DRAWABLE,
+ )
+ )
+ homeControls.onTriggeredResult =
+ if (startActivity) {
+ KeyguardQuickAffordanceConfig.OnTriggeredResult.StartActivity(
+ intent = INTENT,
+ canShowWhileLocked = canShowWhileLocked,
+ )
+ } else {
+ KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
+ }
+
+ underTest.onQuickAffordanceTriggered(
+ configKey = "${KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId()}::$key",
+ expandable = expandable,
+ slotId = "",
+ )
+
+ if (startActivity) {
+ if (needsToUnlockFirst) {
+ verify(activityStarter)
+ .postStartActivityDismissingKeyguard(
+ any(),
+ /* delay= */ eq(0),
+ same(animationController),
+ )
+ } else {
+ verify(activityStarter)
+ .startActivity(
+ any(),
+ /* dismissShade= */ eq(true),
+ same(animationController),
+ /* showOverLockscreenWhenLocked= */ eq(true),
+ )
+ }
+ } else {
+ verifyZeroInteractions(activityStarter)
+ }
+ }
+
+ private fun setUpMocks(
+ needStrongAuthAfterBoot: Boolean = true,
+ keyguardIsUnlocked: Boolean = false,
+ ) {
+ whenever(lockPatternUtils.getStrongAuthForUser(any()))
+ .thenReturn(
+ if (needStrongAuthAfterBoot) {
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT
+ } else {
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED
+ }
+ )
+ whenever(keyguardStateController.isUnlocked).thenReturn(keyguardIsUnlocked)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSectionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSectionTest.kt
index 8eccde7..be10b82 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSectionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSectionTest.kt
@@ -64,6 +64,7 @@
private val clockShouldBeCentered = MutableStateFlow(false)
private val hasCustomWeatherDataDisplay = MutableStateFlow(false)
+ private val isWeatherVisibleFlow = MutableStateFlow(false)
@Before
fun setup() {
@@ -89,7 +90,7 @@
.thenReturn(hasCustomWeatherDataDisplay)
whenever(keyguardClockViewModel.clockShouldBeCentered).thenReturn(clockShouldBeCentered)
whenever(keyguardSmartspaceViewModel.isSmartspaceEnabled).thenReturn(true)
-
+ whenever(keyguardSmartspaceViewModel.isWeatherVisible).thenReturn(isWeatherVisibleFlow)
constraintSet = ConstraintSet()
}
@@ -124,7 +125,6 @@
@Test
fun testConstraintsWhenNotHasCustomWeatherDataDisplay() {
whenever(keyguardSmartspaceViewModel.isDateWeatherDecoupled).thenReturn(true)
- hasCustomWeatherDataDisplay.value = false
underTest.addViews(constraintLayout)
underTest.applyConstraints(constraintSet)
assertWeatherSmartspaceConstrains(constraintSet)
@@ -149,13 +149,12 @@
@Test
fun testNormalDateWeatherVisibility() {
- hasCustomWeatherDataDisplay.value = false
- whenever(keyguardSmartspaceViewModel.isWeatherEnabled).thenReturn(true)
+ isWeatherVisibleFlow.value = true
underTest.addViews(constraintLayout)
underTest.applyConstraints(constraintSet)
assertThat(constraintSet.getVisibility(weatherView.id)).isEqualTo(VISIBLE)
- whenever(keyguardSmartspaceViewModel.isWeatherEnabled).thenReturn(false)
+ isWeatherVisibleFlow.value = false
underTest.applyConstraints(constraintSet)
assertThat(constraintSet.getVisibility(weatherView.id)).isEqualTo(GONE)
assertThat(constraintSet.getVisibility(dateView.id)).isEqualTo(VISIBLE)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
index 6b317ea..1881a9e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
@@ -19,6 +19,7 @@
import android.app.admin.DevicePolicyManager
import android.content.Intent
import android.os.UserHandle
+import android.platform.test.flag.junit.FlagsParameterization
import androidx.test.filters.SmallTest
import com.android.internal.logging.testing.UiEventLoggerFake
import com.android.internal.widget.LockPatternUtils
@@ -32,6 +33,7 @@
import com.android.systemui.doze.util.BurnInHelperWrapper
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
+import com.android.systemui.flags.andSceneContainer
import com.android.systemui.keyguard.data.quickaffordance.BuiltInKeyguardQuickAffordanceKeys
import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig
import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceProviderClientFactory
@@ -75,17 +77,18 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.anyString
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.Mockito.verifyZeroInteractions
import org.mockito.MockitoAnnotations
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
@SmallTest
-@RunWith(JUnit4::class)
-class KeyguardBottomAreaViewModelTest : SysuiTestCase() {
+@RunWith(ParameterizedAndroidJunit4::class)
+class KeyguardBottomAreaViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() {
@Mock private lateinit var expandable: Expandable
@Mock private lateinit var burnInHelperWrapper: BurnInHelperWrapper
@@ -111,6 +114,10 @@
private val kosmos = testKosmos()
+ init {
+ mSetFlagsRule.setFlagsParameterization(flags!!)
+ }
+
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
@@ -750,5 +757,11 @@
companion object {
private const val DEFAULT_BURN_IN_OFFSET = 5
private const val RETURNED_BURN_IN_OFFSET = 3
+
+ @JvmStatic
+ @Parameters(name = "{0}")
+ fun getParams(): List<FlagsParameterization> {
+ return FlagsParameterization.allCombinationsOf().andSceneContainer()
+ }
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt
index 1d98dc3..0c98cff 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt
@@ -18,11 +18,12 @@
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.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.flags.DisableSceneContainer
+import com.android.systemui.flags.andSceneContainer
import com.android.systemui.keyguard.data.repository.fakeKeyguardClockRepository
import com.android.systemui.keyguard.data.repository.keyguardClockRepository
import com.android.systemui.keyguard.data.repository.keyguardRepository
@@ -45,14 +46,14 @@
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
import org.mockito.Mock
import org.mockito.MockitoAnnotations
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
@SmallTest
-@RunWith(JUnit4::class)
-@DisableSceneContainer
-class KeyguardClockViewModelTest : SysuiTestCase() {
+@RunWith(ParameterizedAndroidJunit4::class)
+class KeyguardClockViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() {
val kosmos = testKosmos()
val testScope = kosmos.testScope
val underTest = kosmos.keyguardClockViewModel
@@ -65,6 +66,10 @@
var config = ClockConfig("TEST", "Test", "")
var faceConfig = ClockFaceConfig()
+ init {
+ mSetFlagsRule.setFlagsParameterization(flags!!)
+ }
+
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
@@ -276,5 +281,11 @@
companion object {
private const val KEYGUARD_STATUS_BAR_HEIGHT = 20
+
+ @JvmStatic
+ @Parameters(name = "{0}")
+ fun getParams(): List<FlagsParameterization> {
+ return FlagsParameterization.allCombinationsOf().andSceneContainer()
+ }
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModelTest.kt
new file mode 100644
index 0000000..78f4dcc
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModelTest.kt
@@ -0,0 +1,99 @@
+/*
+ * 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.keyguard.ui.viewmodel
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.data.repository.fakeKeyguardClockRepository
+import com.android.systemui.keyguard.data.repository.keyguardClockRepository
+import com.android.systemui.keyguard.data.repository.keyguardSmartspaceRepository
+import com.android.systemui.keyguard.shared.model.ClockSize
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.plugins.clocks.ClockController
+import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Answers
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(JUnit4::class)
+class KeyguardSmartspaceViewModelTest : SysuiTestCase() {
+ val kosmos = testKosmos()
+ val testScope = kosmos.testScope
+ val underTest = kosmos.keyguardSmartspaceViewModel
+ val res = context.resources
+
+ @Mock(answer = Answers.RETURNS_DEEP_STUBS) private lateinit var clockController: ClockController
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ kosmos.fakeKeyguardClockRepository.setCurrentClock(clockController)
+ }
+
+ @Test
+ fun testWhenWeatherEnabled_notCustomWeatherDataDisplay_isWeatherVisible_shouldBeTrue() =
+ testScope.runTest {
+ val isWeatherVisible by collectLastValue(underTest.isWeatherVisible)
+ whenever(clockController.largeClock.config.hasCustomWeatherDataDisplay)
+ .thenReturn(false)
+
+ with(kosmos) {
+ keyguardSmartspaceRepository.setIsWeatherEnabled(true)
+ keyguardClockRepository.setClockSize(ClockSize.LARGE)
+ }
+
+ assertThat(isWeatherVisible).isEqualTo(true)
+ }
+
+ @Test
+ fun testWhenWeatherEnabled_hasCustomWeatherDataDisplay_isWeatherVisible_shouldBeFalse() =
+ testScope.runTest {
+ val isWeatherVisible by collectLastValue(underTest.isWeatherVisible)
+ whenever(clockController.largeClock.config.hasCustomWeatherDataDisplay).thenReturn(true)
+
+ with(kosmos) {
+ keyguardSmartspaceRepository.setIsWeatherEnabled(true)
+ keyguardClockRepository.setClockSize(ClockSize.LARGE)
+ }
+
+ assertThat(isWeatherVisible).isEqualTo(false)
+ }
+
+ @Test
+ fun testWhenWeatherEnabled_notCustomWeatherDataDisplay_notIsWeatherVisible_shouldBeFalse() =
+ testScope.runTest {
+ val isWeatherVisible by collectLastValue(underTest.isWeatherVisible)
+ whenever(clockController.largeClock.config.hasCustomWeatherDataDisplay)
+ .thenReturn(false)
+
+ with(kosmos) {
+ keyguardSmartspaceRepository.setIsWeatherEnabled(false)
+ keyguardClockRepository.setClockSize(ClockSize.LARGE)
+ }
+
+ assertThat(isWeatherVisible).isEqualTo(false)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt
index ecbd0f5..b5ef8c2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt
@@ -17,6 +17,7 @@
package com.android.systemui.qs.tileimpl
import android.content.Context
+import android.graphics.Rect
import android.graphics.drawable.Drawable
import android.service.quicksettings.Tile
import android.testing.AndroidTestingRunner
@@ -474,6 +475,31 @@
assertThat(tileView.isLongPressEffectInitialized).isFalse()
}
+ @Test
+ fun onPrepareForLaunch_paddingForLaunchAnimationIsConfigured() {
+ val startingWidth = 100
+ val startingHeight = 50
+ val deltaWidth = (QSTileViewImpl.LONG_PRESS_EFFECT_WIDTH_SCALE - 1f) * startingWidth
+ val deltaHeight = (QSTileViewImpl.LONG_PRESS_EFFECT_HEIGHT_SCALE - 1f) * startingHeight
+
+ // GIVEN that long-press effect properties are initialized
+ tileView.initializeLongPressProperties(startingHeight, startingWidth)
+
+ // WHEN the tile is preparing for the launch animation
+ tileView.prepareForLaunch()
+
+ // THE animation padding corresponds to the tile's growth due to the effect
+ val padding = tileView.getPaddingForLaunchAnimation()
+ assertThat(padding).isEqualTo(
+ Rect(
+ -deltaWidth.toInt() / 2,
+ -deltaHeight.toInt() / 2,
+ deltaWidth.toInt() / 2,
+ deltaHeight.toInt() / 2,
+ )
+ )
+ }
+
class FakeTileView(
context: Context,
collapsed: Boolean,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/MessageContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/MessageContainerControllerTest.kt
index 72fc65b..924b872 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/MessageContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/MessageContainerControllerTest.kt
@@ -2,6 +2,8 @@
import android.graphics.drawable.Drawable
import android.os.UserHandle
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
import android.testing.AndroidTestingRunner
import android.view.View
import android.view.ViewGroup
@@ -10,10 +12,16 @@
import androidx.constraintlayout.widget.Guideline
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.screenshot.message.LabeledIcon
+import com.android.systemui.screenshot.message.ProfileMessageController
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.whenever
import junit.framework.Assert.assertEquals
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -28,6 +36,7 @@
lateinit var messageContainer: MessageContainerController
@Mock lateinit var workProfileMessageController: WorkProfileMessageController
+ @Mock lateinit var profileMessageController: ProfileMessageController
@Mock lateinit var screenshotDetectionController: ScreenshotDetectionController
@@ -46,12 +55,15 @@
lateinit var workProfileData: WorkProfileMessageController.WorkProfileFirstRunData
@Before
+ @ExperimentalCoroutinesApi
fun setup() {
MockitoAnnotations.initMocks(this)
messageContainer =
MessageContainerController(
workProfileMessageController,
+ profileMessageController,
screenshotDetectionController,
+ TestScope(UnconfinedTestDispatcher())
)
screenshotView = ConstraintLayout(mContext)
workProfileData = WorkProfileMessageController.WorkProfileFirstRunData(appName, icon)
@@ -78,20 +90,11 @@
}
@Test
- fun testOnScreenshotTakenUserHandle_noWorkProfileFirstRun() {
- // (just being explicit here)
- whenever(workProfileMessageController.onScreenshotTaken(eq(userHandle))).thenReturn(null)
-
- messageContainer.onScreenshotTaken(userHandle)
-
- verify(workProfileMessageController, never()).populateView(any(), any(), any())
- }
-
- @Test
+ @DisableFlags(com.android.systemui.Flags.FLAG_SCREENSHOT_PRIVATE_PROFILE_BEHAVIOR_FIX)
fun testOnScreenshotTakenUserHandle_withWorkProfileFirstRun() {
whenever(workProfileMessageController.onScreenshotTaken(eq(userHandle)))
.thenReturn(workProfileData)
- messageContainer.onScreenshotTaken(userHandle)
+ messageContainer.onScreenshotTaken(screenshotData)
verify(workProfileMessageController)
.populateView(eq(workProfileFirstRunView), eq(workProfileData), any())
@@ -100,10 +103,28 @@
}
@Test
+ @EnableFlags(com.android.systemui.Flags.FLAG_SCREENSHOT_PRIVATE_PROFILE_BEHAVIOR_FIX)
+ fun testOnScreenshotTakenUserHandle_withProfileProfileFirstRun() = runTest {
+ val profileData =
+ ProfileMessageController.ProfileFirstRunData(
+ LabeledIcon(appName, icon),
+ ProfileMessageController.FirstRunProfile.PRIVATE
+ )
+ whenever(profileMessageController.onScreenshotTaken(eq(userHandle))).thenReturn(profileData)
+ messageContainer.onScreenshotTaken(screenshotData)
+
+ verify(profileMessageController)
+ .bindView(eq(workProfileFirstRunView), eq(profileData), any())
+ assertEquals(View.VISIBLE, workProfileFirstRunView.visibility)
+ assertEquals(View.GONE, detectionNoticeView.visibility)
+ }
+
+ @Test
fun testOnScreenshotTakenScreenshotData_nothingToShow() {
messageContainer.onScreenshotTaken(screenshotData)
verify(workProfileMessageController, never()).populateView(any(), any(), any())
+ verify(profileMessageController, never()).bindView(any(), any(), any())
verify(screenshotDetectionController, never()).populateView(any(), any())
assertEquals(View.GONE, container.visibility)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/WorkProfileMessageControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/WorkProfileMessageControllerTest.java
index 4b7d5f0..ebdc49f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/WorkProfileMessageControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/WorkProfileMessageControllerTest.java
@@ -87,7 +87,7 @@
when(mMockContext.getSharedPreferences(
eq(WorkProfileMessageController.SHARED_PREFERENCES_NAME),
eq(Context.MODE_PRIVATE))).thenReturn(mSharedPreferences);
- when(mMockContext.getString(R.string.config_sceenshotWorkProfileFilesApp))
+ when(mMockContext.getString(R.string.config_screenshotFilesApp))
.thenReturn(FILES_APP_COMPONENT);
when(mMockContext.getString(R.string.screenshot_default_files_app_name))
.thenReturn(DEFAULT_FILES_APP_LABEL);
@@ -149,7 +149,7 @@
@Test
public void testOnScreenshotTaken_noFilesAppComponentDefined() {
- when(mMockContext.getString(R.string.config_sceenshotWorkProfileFilesApp))
+ when(mMockContext.getString(R.string.config_screenshotFilesApp))
.thenReturn("");
WorkProfileMessageController.WorkProfileFirstRunData data =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/message/ProfileMessageControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/message/ProfileMessageControllerTest.kt
new file mode 100644
index 0000000..ce2facd
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/message/ProfileMessageControllerTest.kt
@@ -0,0 +1,181 @@
+/*
+ * 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.screenshot.message
+
+import android.content.ComponentName
+import android.content.pm.PackageManager
+import android.graphics.Canvas
+import android.graphics.ColorFilter
+import android.graphics.drawable.Drawable
+import android.os.UserHandle
+import com.android.systemui.screenshot.data.model.ProfileType
+import com.android.systemui.screenshot.data.repository.ProfileTypeRepository
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+
+class ProfileMessageControllerTest {
+ @Test
+ fun personalScreenshot() = runTest {
+ assertThat(
+ getMessageController()
+ .onScreenshotTaken(UserHandle.of(profileTypeRepository.personalUser))
+ )
+ .isNull()
+ }
+
+ @Test
+ fun communalScreenshot() = runTest {
+ assertThat(
+ getMessageController()
+ .onScreenshotTaken(UserHandle.of(profileTypeRepository.communalUser))
+ )
+ .isNull()
+ }
+
+ @Test
+ fun noUserScreenshot() = runTest {
+ assertThat(getMessageController().onScreenshotTaken(null)).isNull()
+ }
+
+ @Test
+ fun alreadyDismissed() = runTest {
+ val messageController = getMessageController()
+ profileFirstRunSettings.onMessageDismissed(ProfileMessageController.FirstRunProfile.WORK)
+ assertThat(
+ messageController.onScreenshotTaken(UserHandle.of(profileTypeRepository.workUser))
+ )
+ .isNull()
+ }
+
+ @Test
+ fun noFileManager() = runTest {
+ val messageController = getMessageController(fileManagerComponent = null)
+ val data =
+ messageController.onScreenshotTaken(UserHandle.of(profileTypeRepository.workUser))
+ assertThat(data?.profileType).isEqualTo(ProfileMessageController.FirstRunProfile.WORK)
+ assertThat(data?.labeledIcon?.label).isEqualTo(DEFAULT_APP_NAME)
+ assertThat(data?.labeledIcon?.badgedIcon).isNull()
+ }
+
+ @Test
+ fun fileManagerNotFound() = runTest {
+ val messageController =
+ getMessageController(fileManagerComponent = ComponentName("Something", "Random"))
+ val data =
+ messageController.onScreenshotTaken(UserHandle.of(profileTypeRepository.privateUser))
+ assertThat(data?.profileType).isEqualTo(ProfileMessageController.FirstRunProfile.PRIVATE)
+ assertThat(data?.labeledIcon?.label).isEqualTo(DEFAULT_APP_NAME)
+ assertThat(data?.labeledIcon?.badgedIcon).isNull()
+ }
+
+ @Test
+ fun fileManagerFound() = runTest {
+ val messageController = getMessageController()
+ val data =
+ messageController.onScreenshotTaken(UserHandle.of(profileTypeRepository.privateUser))
+ assertThat(data?.profileType).isEqualTo(ProfileMessageController.FirstRunProfile.PRIVATE)
+ assertThat(data?.labeledIcon?.label).isEqualTo(FILE_MANAGER_LABEL)
+ assertThat(data?.labeledIcon?.badgedIcon).isEqualTo(drawable)
+ }
+
+ private val drawable =
+ object : Drawable() {
+ override fun draw(canvas: Canvas) {}
+
+ override fun setAlpha(alpha: Int) {}
+
+ override fun setColorFilter(colorFilter: ColorFilter?) {}
+
+ override fun getOpacity(): Int = 0
+ }
+
+ private val packageLabelIconProvider =
+ object : PackageLabelIconProvider {
+ override suspend fun getPackageLabelIcon(
+ componentName: ComponentName,
+ userHandle: UserHandle
+ ): LabeledIcon {
+ if (componentName.equals(FILE_MANAGER_COMPONENT)) {
+ return LabeledIcon(FILE_MANAGER_LABEL, drawable)
+ } else {
+ throw PackageManager.NameNotFoundException()
+ }
+ }
+ }
+
+ private class FakeProfileFirstRunResources(private val fileManager: ComponentName?) :
+ ProfileFirstRunFileResources {
+ override fun fileManagerComponentName(): ComponentName? {
+ return fileManager
+ }
+
+ override fun defaultFileApp() = LabeledIcon(DEFAULT_APP_NAME, badgedIcon = null)
+ }
+
+ private val profileFirstRunSettings =
+ object : ProfileFirstRunSettings {
+ private val dismissed =
+ mutableMapOf(
+ ProfileMessageController.FirstRunProfile.WORK to false,
+ ProfileMessageController.FirstRunProfile.PRIVATE to false,
+ )
+
+ override fun messageAlreadyDismissed(
+ profileType: ProfileMessageController.FirstRunProfile
+ ): Boolean {
+ return dismissed.getOrDefault(profileType, false)
+ }
+
+ override fun onMessageDismissed(profileType: ProfileMessageController.FirstRunProfile) {
+ dismissed[profileType] = true
+ }
+ }
+
+ private val profileTypeRepository =
+ object : ProfileTypeRepository {
+ override suspend fun getProfileType(userId: Int): ProfileType {
+ return when (userId) {
+ workUser -> ProfileType.WORK
+ privateUser -> ProfileType.PRIVATE
+ communalUser -> ProfileType.COMMUNAL
+ else -> ProfileType.NONE
+ }
+ }
+
+ val personalUser = 0
+ val workUser = 1
+ val privateUser = 2
+ val communalUser = 3
+ }
+
+ private fun getMessageController(
+ fileManagerComponent: ComponentName? = FILE_MANAGER_COMPONENT
+ ) =
+ ProfileMessageController(
+ packageLabelIconProvider,
+ FakeProfileFirstRunResources(fileManagerComponent),
+ profileFirstRunSettings,
+ profileTypeRepository
+ )
+
+ companion object {
+ val FILE_MANAGER_COMPONENT = ComponentName("package", "component")
+ const val DEFAULT_APP_NAME = "default app"
+ const val FILE_MANAGER_LABEL = "file manager"
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
index 19b137c..99204e7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
@@ -33,6 +33,7 @@
import com.android.systemui.ambient.touch.TouchHandler
import com.android.systemui.ambient.touch.TouchMonitor
import com.android.systemui.ambient.touch.dagger.AmbientTouchComponent
+import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository
import com.android.systemui.communal.data.repository.FakeCommunalRepository
import com.android.systemui.communal.data.repository.fakeCommunalRepository
import com.android.systemui.communal.domain.interactor.communalInteractor
@@ -42,10 +43,8 @@
import com.android.systemui.communal.util.CommunalColors
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.andSceneContainer
-import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
-import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
@@ -238,11 +237,7 @@
goToScene(CommunalScenes.Communal)
// Bouncer is visible.
- fakeKeyguardTransitionRepository.sendTransitionSteps(
- KeyguardState.GLANCEABLE_HUB,
- KeyguardState.PRIMARY_BOUNCER,
- testScope
- )
+ fakeKeyguardBouncerRepository.setPrimaryShow(true)
testableLooper.processAllMessages()
// Touch events are not intercepted.
@@ -368,11 +363,7 @@
goToScene(CommunalScenes.Communal)
// Bouncer is visible.
- fakeKeyguardTransitionRepository.sendTransitionSteps(
- KeyguardState.GLANCEABLE_HUB,
- KeyguardState.PRIMARY_BOUNCER,
- testScope
- )
+ fakeKeyguardBouncerRepository.setPrimaryShow(true)
testableLooper.processAllMessages()
assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.STARTED)
@@ -387,11 +378,7 @@
goToScene(CommunalScenes.Communal)
// Bouncer is visible.
- fakeKeyguardTransitionRepository.sendTransitionSteps(
- KeyguardState.GLANCEABLE_HUB,
- KeyguardState.ALTERNATE_BOUNCER,
- testScope
- )
+ fakeKeyguardBouncerRepository.setAlternateVisible(true)
testableLooper.processAllMessages()
assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.STARTED)
@@ -452,6 +439,53 @@
}
}
+ @Test
+ fun gestureExclusionZone_unsetWhenShadeOpen() =
+ with(kosmos) {
+ testScope.runTest {
+ goToScene(CommunalScenes.Communal)
+
+ // Shade shows up.
+ shadeTestUtil.setQsExpansion(1.0f)
+ testableLooper.processAllMessages()
+
+ // Exclusion rects are unset.
+ assertThat(containerView.systemGestureExclusionRects).isEmpty()
+ }
+ }
+
+ @Test
+ fun gestureExclusionZone_unsetWhenBouncerOpen() =
+ with(kosmos) {
+ testScope.runTest {
+ goToScene(CommunalScenes.Communal)
+
+ // Bouncer is visible.
+ fakeKeyguardBouncerRepository.setPrimaryShow(true)
+ testableLooper.processAllMessages()
+
+ // Exclusion rects are unset.
+ assertThat(containerView.systemGestureExclusionRects).isEmpty()
+ }
+ }
+
+ @Test
+ fun gestureExclusionZone_unsetWhenHubClosed() =
+ with(kosmos) {
+ testScope.runTest {
+ goToScene(CommunalScenes.Communal)
+
+ // Exclusion rect is set.
+ assertThat(containerView.systemGestureExclusionRects).hasSize(1)
+
+ // Leave the hub.
+ goToScene(CommunalScenes.Blank)
+
+ // Exclusion rect is unset.
+ assertThat(containerView.systemGestureExclusionRects).isEmpty()
+ }
+ }
+
private fun initAndAttachContainerView() {
containerView = View(context)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
index 81e20c1..6b57f6e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -18,6 +18,7 @@
import static com.android.keyguard.KeyguardClockSwitch.LARGE;
import static com.android.keyguard.KeyguardClockSwitch.SMALL;
+import static com.android.systemui.Flags.FLAG_SHADE_COLLAPSE_ACTIVITY_LAUNCH_FIX;
import static com.android.systemui.shade.ShadeExpansionStateManagerKt.STATE_CLOSED;
import static com.android.systemui.shade.ShadeExpansionStateManagerKt.STATE_OPEN;
import static com.android.systemui.shade.ShadeExpansionStateManagerKt.STATE_OPENING;
@@ -47,6 +48,7 @@
import android.graphics.Point;
import android.os.PowerManager;
import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.MotionEvent;
@@ -675,6 +677,32 @@
}
@Test
+ @EnableFlags(FLAG_SHADE_COLLAPSE_ACTIVITY_LAUNCH_FIX)
+ public void testCanBeCollapsed_expandedInKeyguard() {
+ mStatusBarStateController.setState(KEYGUARD);
+ mNotificationPanelViewController.setExpandedFraction(1f);
+
+ assertThat(mNotificationPanelViewController.canBeCollapsed()).isFalse();
+ }
+
+ @Test
+ @EnableFlags(FLAG_SHADE_COLLAPSE_ACTIVITY_LAUNCH_FIX)
+ public void testCanBeCollapsed_expandedInShade() {
+ mStatusBarStateController.setState(SHADE);
+ mNotificationPanelViewController.setExpandedFraction(1f);
+ assertThat(mNotificationPanelViewController.canBeCollapsed()).isTrue();
+ }
+
+ @Test
+ @DisableFlags(FLAG_SHADE_COLLAPSE_ACTIVITY_LAUNCH_FIX)
+ public void testCanBeCollapsed_expandedInKeyguard_flagDisabled() {
+ mStatusBarStateController.setState(KEYGUARD);
+ mNotificationPanelViewController.setExpandedFraction(1f);
+
+ assertThat(mNotificationPanelViewController.canBeCollapsed()).isTrue();
+ }
+
+ @Test
public void testSwipeWhileLocked_notifiesKeyguardState() {
mStatusBarStateController.setState(KEYGUARD);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
index 317e35c..04fa590 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
@@ -216,6 +216,7 @@
keyguardTransitionInteractor,
() -> sceneInteractor,
() -> mKosmos.getFromGoneTransitionInteractor(),
+ () -> mKosmos.getFromLockscreenTransitionInteractor(),
() -> mKosmos.getSharedNotificationContainerInteractor(),
mTestScope);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
index 0a9bac9..7ade053 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
@@ -49,6 +49,7 @@
import static org.mockito.Mockito.when;
import android.app.ActivityManager;
+import android.app.Flags;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.PendingIntent;
@@ -58,6 +59,7 @@
import android.os.Handler;
import android.os.PowerManager;
import android.os.RemoteException;
+import android.platform.test.annotations.DisableFlags;
import android.testing.AndroidTestingRunner;
import androidx.test.filters.SmallTest;
@@ -469,6 +471,7 @@
}
@Test
+ @DisableFlags(Flags.FLAG_SORT_SECTION_BY_TIME)
public void testShouldHeadsUp_oldWhen_whenZero() throws Exception {
ensureStateForHeadsUpWhenAwake();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt
index 4dd97bc..9b4f931 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt
@@ -17,19 +17,21 @@
package com.android.systemui.statusbar.phone
import android.content.pm.PackageManager
-import android.testing.AndroidTestingRunner
+import android.platform.test.flag.junit.FlagsParameterization
import android.testing.TestableLooper
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
+import com.android.systemui.flags.andSceneContainer
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.kosmos.testScope
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.res.R
-import com.android.systemui.shade.data.repository.FakeShadeRepository
+import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.shade.shadeTestUtil
import com.android.systemui.statusbar.NotificationLockscreenUserManager
import com.android.systemui.statusbar.policy.DevicePostureController
import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_CLOSED
@@ -58,15 +60,17 @@
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when` as whenever
import org.mockito.junit.MockitoJUnit
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
@SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(ParameterizedAndroidJunit4::class)
@TestableLooper.RunWithLooper
-class KeyguardBypassControllerTest : SysuiTestCase() {
+class KeyguardBypassControllerTest(flags: FlagsParameterization?) : SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
private val featureFlags = FakeFeatureFlags()
- private val shadeRepository = FakeShadeRepository()
+ private val shadeTestUtil by lazy { kosmos.shadeTestUtil }
private lateinit var keyguardBypassController: KeyguardBypassController
private lateinit var postureControllerCallback: DevicePostureController.Callback
@@ -79,6 +83,18 @@
@Mock private lateinit var dumpManager: DumpManager
@Mock private lateinit var packageManager: PackageManager
+ companion object {
+ @JvmStatic
+ @Parameters(name = "{0}")
+ fun getParams(): List<FlagsParameterization> {
+ return FlagsParameterization.allCombinationsOf().andSceneContainer()
+ }
+ }
+
+ init {
+ mSetFlagsRule.setFlagsParameterization(flags!!)
+ }
+
@Captor
private val postureCallbackCaptor =
ArgumentCaptor.forClass(DevicePostureController.Callback::class.java)
@@ -148,7 +164,7 @@
statusBarStateController,
lockscreenUserManager,
keyguardStateController,
- shadeRepository,
+ { kosmos.shadeInteractor },
devicePostureController,
keyguardTransitionInteractor,
dumpManager,
@@ -293,13 +309,13 @@
testScope.runTest {
initKeyguardBypassController()
assertThat(keyguardBypassController.qsExpanded).isFalse()
- val job = keyguardBypassController.listenForQsExpandedChange(this)
- shadeRepository.setQsExpansion(0.5f)
+ val job = keyguardBypassController.listenForQsExpandedChange()
+ shadeTestUtil.setQsExpansion(0.5f)
runCurrent()
assertThat(keyguardBypassController.qsExpanded).isTrue()
- shadeRepository.setQsExpansion(0f)
+ shadeTestUtil.setQsExpansion(0f)
runCurrent()
assertThat(keyguardBypassController.qsExpanded).isFalse()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
index 8e9840a..dfee737 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
@@ -178,6 +178,7 @@
keyguardTransitionInteractor,
() -> mKosmos.getSceneInteractor(),
() -> mKosmos.getFromGoneTransitionInteractor(),
+ () -> mKosmos.getFromLockscreenTransitionInteractor(),
() -> mKosmos.getSharedNotificationContainerInteractor(),
mTestScope);
mViewModel =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
index d365663..ba38f87 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
@@ -314,6 +314,7 @@
private class UnfoldConfig : UnfoldTransitionConfig {
override var isEnabled: Boolean = false
override var isHingeAngleEnabled: Boolean = false
+ override val isHapticsEnabled: Boolean = false
override val halfFoldedTimeoutMillis: Int = 0
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index e38e31d..6b3c005 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -187,8 +187,7 @@
when(mBouncerViewDelegate.getBackCallback()).thenReturn(mBouncerViewDelegateBackCallback);
mSetFlagsRule.disableFlags(
com.android.systemui.Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR,
- com.android.systemui.Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR,
- com.android.systemui.Flags.FLAG_REFACTOR_KEYGUARD_DISMISS_INTENT
+ com.android.systemui.Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR
);
when(mNotificationShadeWindowController.getWindowRootView())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt
index efd7f99..05464f3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt
@@ -23,6 +23,7 @@
import android.app.PendingIntent
import android.app.Person
import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
import android.service.notification.NotificationListenerService.REASON_USER_STOPPED
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
@@ -194,7 +195,7 @@
/** Regression test for b/192379214. */
@Test
- @DisableFlags(android.app.Flags.FLAG_UPDATE_RANKING_TIME)
+ @DisableFlags(android.app.Flags.FLAG_SORT_SECTION_BY_TIME)
fun onEntryUpdated_notificationWhenIsZero_timeHidden() {
val notification = NotificationEntryBuilder(createOngoingCallNotifEntry())
notification.modifyNotification(context).setWhen(0)
@@ -210,6 +211,22 @@
}
@Test
+ @EnableFlags(android.app.Flags.FLAG_SORT_SECTION_BY_TIME)
+ fun onEntryUpdated_notificationWhenIsZero_timeShown() {
+ val notification = NotificationEntryBuilder(createOngoingCallNotifEntry())
+ notification.modifyNotification(context).setWhen(0)
+
+ notifCollectionListener.onEntryUpdated(notification.build())
+ chipView.measure(
+ View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
+ View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
+ )
+
+ assertThat(chipView.findViewById<View>(R.id.ongoing_call_chip_time)?.measuredWidth)
+ .isGreaterThan(0)
+ }
+
+ @Test
fun onEntryUpdated_notificationWhenIsValid_timeShown() {
val notification = NotificationEntryBuilder(createOngoingCallNotifEntry())
notification.modifyNotification(context).setWhen(clock.currentTimeMillis())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractorTest.kt
index c4ab943..405e3ed 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractorTest.kt
@@ -250,7 +250,7 @@
@Test
@EnableFlags(FLAG_OEM_ENABLED_SATELLITE_FLAG)
- fun areAllConnectionsOutOfService_twoConnectionsOos_yes() =
+ fun areAllConnectionsOutOfService_twoConnectionsOos_nonNtn_yes() =
testScope.runTest {
val latest by collectLastValue(underTest.areAllConnectionsOutOfService)
@@ -258,11 +258,13 @@
val i1 = iconsInteractor.getMobileConnectionInteractorForSubId(1)
val i2 = iconsInteractor.getMobileConnectionInteractorForSubId(2)
- // WHEN all of the connections are OOS
+ // WHEN all of the connections are OOS and none are NTN
i1.isInService.value = false
i1.isEmergencyOnly.value = false
+ i1.isNonTerrestrial.value = false
i2.isInService.value = false
i2.isEmergencyOnly.value = false
+ i2.isNonTerrestrial.value = false
// THEN the value is propagated to this interactor
assertThat(latest).isTrue()
@@ -270,7 +272,31 @@
@Test
@EnableFlags(FLAG_OEM_ENABLED_SATELLITE_FLAG)
- fun areAllConnectionsOutOfService_oneConnectionOos_yes() =
+ fun areAllConnectionsOutOfService_twoConnectionsOos_oneNtn_no() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.areAllConnectionsOutOfService)
+
+ // GIVEN, 2 connections
+ val i1 = iconsInteractor.getMobileConnectionInteractorForSubId(1)
+ val i2 = iconsInteractor.getMobileConnectionInteractorForSubId(2)
+
+ // WHEN all of the connections are OOS and one is NTN
+ i1.isInService.value = false
+ i1.isEmergencyOnly.value = false
+ i1.isNonTerrestrial.value = false
+ i2.isInService.value = false
+ i2.isEmergencyOnly.value = false
+
+ // sub2 is non terrestrial, consider it connected for the sake of the iconography
+ i2.isNonTerrestrial.value = true
+
+ // THEN the value is propagated to this interactor
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ @EnableFlags(FLAG_OEM_ENABLED_SATELLITE_FLAG)
+ fun areAllConnectionsOutOfService_oneConnectionOos_nonNtn_yes() =
testScope.runTest {
val latest by collectLastValue(underTest.areAllConnectionsOutOfService)
@@ -280,6 +306,7 @@
// WHEN all of the connections are OOS
i1.isInService.value = false
i1.isEmergencyOnly.value = false
+ i1.isNonTerrestrial.value = false
// THEN the value is propagated to this interactor
assertThat(latest).isTrue()
@@ -287,7 +314,25 @@
@Test
@EnableFlags(FLAG_OEM_ENABLED_SATELLITE_FLAG)
- fun areAllConnectionsOutOfService_oneConnectionInService_no() =
+ fun areAllConnectionsOutOfService_oneConnectionOos_ntn_yes() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.areAllConnectionsOutOfService)
+
+ // GIVEN, 1 connection
+ val i1 = iconsInteractor.getMobileConnectionInteractorForSubId(1)
+
+ // WHEN all of the connections are OOS
+ i1.isInService.value = false
+ i1.isEmergencyOnly.value = false
+ i1.isNonTerrestrial.value = true
+
+ // THEN the value is propagated to this interactor
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ @EnableFlags(FLAG_OEM_ENABLED_SATELLITE_FLAG)
+ fun areAllConnectionsOutOfService_oneConnectionInService_nonNtn_no() =
testScope.runTest {
val latest by collectLastValue(underTest.areAllConnectionsOutOfService)
@@ -296,6 +341,7 @@
// WHEN all of the connections are NOT OOS
i1.isInService.value = true
+ i1.isNonTerrestrial.value = false
// THEN the value is propagated to this interactor
assertThat(latest).isFalse()
@@ -303,7 +349,24 @@
@Test
@EnableFlags(FLAG_OEM_ENABLED_SATELLITE_FLAG)
- fun areAllConnectionsOutOfService_twoConnectionsOneInService_no() =
+ fun areAllConnectionsOutOfService_oneConnectionInService_ntn_no() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.areAllConnectionsOutOfService)
+
+ // GIVEN, 1 connection
+ val i1 = iconsInteractor.getMobileConnectionInteractorForSubId(1)
+
+ // WHEN all of the connections are NOT OOS
+ i1.isInService.value = true
+ i1.isNonTerrestrial.value = true
+
+ // THEN the value is propagated to this interactor
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ @EnableFlags(FLAG_OEM_ENABLED_SATELLITE_FLAG)
+ fun areAllConnectionsOutOfService_twoConnectionsOneInService_nonNtn_no() =
testScope.runTest {
val latest by collectLastValue(underTest.areAllConnectionsOutOfService)
@@ -313,7 +376,9 @@
// WHEN at least 1 connection is NOT OOS.
i1.isInService.value = false
+ i1.isNonTerrestrial.value = false
i2.isInService.value = true
+ i2.isNonTerrestrial.value = false
// THEN the value is propagated to this interactor
assertThat(latest).isFalse()
@@ -321,7 +386,7 @@
@Test
@EnableFlags(FLAG_OEM_ENABLED_SATELLITE_FLAG)
- fun areAllConnectionsOutOfService_twoConnectionsInService_no() =
+ fun areAllConnectionsOutOfService_twoConnectionsInService_nonNtn_no() =
testScope.runTest {
val latest by collectLastValue(underTest.areAllConnectionsOutOfService)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt
index 8eea29b..ceaae9e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt
@@ -587,7 +587,7 @@
advanceTimeBy(10.seconds)
assertThat(latest)
- .isEqualTo(context.getString(R.string.satellite_not_connected_carrier_text))
+ .isEqualTo(context.getString(R.string.satellite_connected_carrier_text))
}
@OptIn(ExperimentalCoroutinesApi::class)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt
index bdd3d18..cfa734a1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt
@@ -16,71 +16,69 @@
package com.android.systemui.statusbar.ui.viewmodel
+import android.platform.test.flag.junit.FlagsParameterization
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
-import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
-import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
-import com.android.systemui.keyguard.domain.interactor.fromGoneTransitionInteractor
-import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
+import com.android.systemui.flags.andSceneContainer
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.keyguardRepository
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.keyguard.shared.model.StatusBarState
import com.android.systemui.kosmos.testScope
-import com.android.systemui.power.domain.interactor.PowerInteractorFactory
-import com.android.systemui.scene.domain.interactor.sceneInteractor
-import com.android.systemui.shade.data.repository.FakeShadeRepository
-import com.android.systemui.statusbar.CommandQueue
-import com.android.systemui.statusbar.data.repository.FakeKeyguardStatusBarRepository
-import com.android.systemui.statusbar.domain.interactor.KeyguardStatusBarInteractor
-import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor
+import com.android.systemui.statusbar.domain.interactor.keyguardStatusBarInteractor
import com.android.systemui.statusbar.policy.BatteryController
+import com.android.systemui.statusbar.policy.batteryController
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.capture
-import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
+import org.junit.Before
import org.junit.Test
+import org.junit.runner.RunWith
import org.mockito.Mockito.verify
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
@SmallTest
@OptIn(ExperimentalCoroutinesApi::class)
-class KeyguardStatusBarViewModelTest : SysuiTestCase() {
+@RunWith(ParameterizedAndroidJunit4::class)
+class KeyguardStatusBarViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
- private val keyguardRepository = FakeKeyguardRepository()
- private val keyguardInteractor =
- KeyguardInteractor(
- keyguardRepository,
- mock<CommandQueue>(),
- PowerInteractorFactory.create().powerInteractor,
- FakeKeyguardBouncerRepository(),
- ConfigurationInteractor(FakeConfigurationRepository()),
- FakeShadeRepository(),
- kosmos.keyguardTransitionInteractor,
- { kosmos.sceneInteractor },
- { kosmos.fromGoneTransitionInteractor },
- { kosmos.sharedNotificationContainerInteractor },
- testScope,
- )
- private val keyguardStatusBarInteractor =
- KeyguardStatusBarInteractor(
- FakeKeyguardStatusBarRepository(),
- )
- private val batteryController = mock<BatteryController>()
+ private val keyguardRepository by lazy { kosmos.fakeKeyguardRepository }
+ private val keyguardInteractor by lazy { kosmos.keyguardInteractor }
+ private val keyguardStatusBarInteractor by lazy { kosmos.keyguardStatusBarInteractor }
+ private val batteryController = kosmos.batteryController
- private val underTest =
- KeyguardStatusBarViewModel(
- testScope.backgroundScope,
- keyguardInteractor,
- keyguardStatusBarInteractor,
- batteryController,
- )
+ lateinit var underTest: KeyguardStatusBarViewModel
+
+ companion object {
+ @JvmStatic
+ @Parameters(name = "{0}")
+ fun getParams(): List<FlagsParameterization> {
+ return FlagsParameterization.allCombinationsOf().andSceneContainer()
+ }
+ }
+
+ init {
+ mSetFlagsRule.setFlagsParameterization(flags!!)
+ }
+
+ @Before
+ fun setup() {
+ underTest =
+ KeyguardStatusBarViewModel(
+ testScope.backgroundScope,
+ keyguardInteractor,
+ keyguardStatusBarInteractor,
+ batteryController,
+ )
+ }
@Test
fun isVisible_dozing_false() =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldHapticsPlayerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldHapticsPlayerTest.kt
index fd513c9..06f1a88 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldHapticsPlayerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldHapticsPlayerTest.kt
@@ -17,13 +17,12 @@
import android.os.VibrationAttributes
import android.os.VibrationEffect
-import android.os.Vibrator
+import android.os.vibrator
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.unfold.util.TestFoldProvider
+import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.mock
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -35,15 +34,20 @@
@SmallTest
class UnfoldHapticsPlayerTest : SysuiTestCase() {
- private val progressProvider = FakeUnfoldTransitionProvider()
- private val vibrator: Vibrator = mock()
- private val testFoldProvider = TestFoldProvider()
+ private val kosmos = testKosmos()
+
+ private val progressProvider = kosmos.fakeUnfoldTransitionProgressProvider
+ private val vibrator = kosmos.vibrator
+ private val transitionConfig = kosmos.unfoldTransitionConfig
+ private val testFoldProvider = kosmos.foldProvider
private lateinit var player: UnfoldHapticsPlayer
@Before
fun before() {
- player = UnfoldHapticsPlayer(progressProvider, testFoldProvider, Runnable::run, vibrator)
+ transitionConfig.isHapticsEnabled = true
+ player = UnfoldHapticsPlayer(progressProvider, testFoldProvider, transitionConfig,
+ Runnable::run, vibrator)
}
@Test
@@ -58,6 +62,21 @@
}
@Test
+ fun testHapticsDisabled_unfoldingTransitionFinishing_doesNotPlayHaptics() {
+ transitionConfig.isHapticsEnabled = false
+ player = UnfoldHapticsPlayer(progressProvider, testFoldProvider, transitionConfig,
+ Runnable::run, vibrator)
+
+ testFoldProvider.onFoldUpdate(isFolded = true)
+ testFoldProvider.onFoldUpdate(isFolded = false)
+ progressProvider.onTransitionStarted()
+ progressProvider.onTransitionProgress(0.5f)
+ progressProvider.onTransitionFinishing()
+
+ verify(vibrator).vibrate(any<VibrationEffect>(), any<VibrationAttributes>())
+ }
+
+ @Test
fun testUnfoldingTransitionFinishingLate_doesNotPlayHaptics() {
testFoldProvider.onFoldUpdate(isFolded = true)
testFoldProvider.onFoldUpdate(isFolded = false)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldTestUtilsKosmos.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldTestUtilsKosmos.kt
new file mode 100644
index 0000000..c073903
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldTestUtilsKosmos.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.unfold
+
+import com.android.systemui.unfold.util.TestFoldProvider
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.unfold.config.TestUnfoldTransitionConfig
+
+var Kosmos.foldProvider: TestFoldProvider by Kosmos.Fixture { TestFoldProvider() }
+
+var Kosmos.unfoldTransitionConfig: TestUnfoldTransitionConfig
+ by Kosmos.Fixture { TestUnfoldTransitionConfig() }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/config/ResourceUnfoldTransitionConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/config/ResourceUnfoldTransitionConfigTest.kt
index ab450e2..3c53997 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/config/ResourceUnfoldTransitionConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/config/ResourceUnfoldTransitionConfigTest.kt
@@ -47,6 +47,12 @@
}
@Test
+ fun testHapticsEnabled() {
+ assertThat(config.isHapticsEnabled).isEqualTo(mContext.resources
+ .getBoolean(com.android.internal.R.bool.config_unfoldTransitionHapticsEnabled))
+ }
+
+ @Test
fun testHalfFoldedTimeout() {
assertThat(config.halfFoldedTimeoutMillis).isEqualTo(mContext.resources
.getInteger(com.android.internal.R.integer.config_unfoldTransitionHalfFoldedTimeout))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/config/TestUnfoldTransitionConfig.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/config/TestUnfoldTransitionConfig.kt
new file mode 100644
index 0000000..0586115
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/config/TestUnfoldTransitionConfig.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.unfold.config
+
+class TestUnfoldTransitionConfig(
+ override var isEnabled: Boolean = false,
+ override var isHingeAngleEnabled: Boolean = false,
+ override var isHapticsEnabled: Boolean = false,
+ override var halfFoldedTimeoutMillis: Int = 1000
+) : UnfoldTransitionConfig
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/service/ObservableServiceConnectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/service/ObservableServiceConnectionTest.java
index 5d34120..8d26c87 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/service/ObservableServiceConnectionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/service/ObservableServiceConnectionTest.java
@@ -16,8 +16,6 @@
package com.android.systemui.util.service;
-import static com.google.common.truth.Truth.assertThat;
-
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations;
@@ -118,6 +116,7 @@
connection.addCallback(mCallback);
mExecutor.runAllReady();
connection.bind();
+ mExecutor.runAllReady();
when(mTransformer.convert(eq(mBinder))).thenReturn(mResult);
@@ -143,8 +142,8 @@
when(mContext.bindServiceAsUser(eq(mIntent), eq(connection), anyInt(),
eq(UserHandle.of(MAIN_USER_ID)))).thenReturn(true);
connection.bind();
+ mExecutor.runAllReady();
connection.onServiceDisconnected(mComponentName);
-
mExecutor.runAllReady();
// Ensure proper disconnect reason reported back
@@ -157,6 +156,7 @@
clearInvocations(mContext);
// Ensure unbind after disconnect has no effect on the connection
connection.unbind();
+ mExecutor.runAllReady();
verify(mContext, never()).unbindService(eq(connection));
}
@@ -197,7 +197,8 @@
// Verify that the exception was caught and that bind returns false, and we properly
// unbind.
- assertThat(connection.bind()).isFalse();
+ connection.bind();
+ mExecutor.runAllReady();
verify(mContext).unbindService(connection);
}
@@ -212,13 +213,15 @@
.thenThrow(new SecurityException());
// Verify that bind returns false and we properly unbind.
- assertThat(connection.bind()).isFalse();
+ connection.bind();
+ mExecutor.runAllReady();
verify(mContext).unbindService(connection);
clearInvocations(mContext);
// Ensure unbind after the failed bind has no effect.
connection.unbind();
+ mExecutor.runAllReady();
verify(mContext, never()).unbindService(eq(connection));
}
}
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 2017954..56e5e29 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -77,10 +77,10 @@
import android.os.UserManager;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.FlagsParameterization;
import android.service.dreams.IDreamManager;
import android.service.notification.NotificationListenerService;
import android.service.notification.ZenModeConfig;
-import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.util.Pair;
import android.util.SparseArray;
@@ -99,46 +99,28 @@
import com.android.launcher3.icons.BubbleIconFactory;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.biometrics.AuthController;
-import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository;
import com.android.systemui.colorextraction.SysuiColorExtractor;
-import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository;
-import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor;
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FakeFeatureFlags;
-import com.android.systemui.flags.FakeFeatureFlagsClassic;
+import com.android.systemui.flags.SceneContainerFlagParameterizationKt;
import com.android.systemui.keyguard.KeyguardViewMediator;
-import com.android.systemui.keyguard.data.repository.FakeCommandQueue;
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository;
-import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor;
-import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor;
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
import com.android.systemui.kosmos.KosmosJavaAdapter;
import com.android.systemui.model.SysUiState;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.power.domain.interactor.PowerInteractor;
import com.android.systemui.scene.FakeWindowRootViewComponent;
-import com.android.systemui.scene.data.repository.SceneContainerRepository;
-import com.android.systemui.scene.domain.interactor.SceneInteractor;
-import com.android.systemui.scene.shared.logger.SceneLogger;
import com.android.systemui.settings.FakeDisplayTracker;
import com.android.systemui.settings.UserTracker;
-import com.android.systemui.shade.LargeScreenHeaderHelper;
import com.android.systemui.shade.NotificationShadeWindowControllerImpl;
import com.android.systemui.shade.NotificationShadeWindowView;
import com.android.systemui.shade.ShadeController;
import com.android.systemui.shade.ShadeWindowLogger;
-import com.android.systemui.shade.data.repository.FakeShadeRepository;
import com.android.systemui.shade.domain.interactor.ShadeInteractor;
-import com.android.systemui.shade.domain.interactor.ShadeInteractorImpl;
-import com.android.systemui.shade.domain.interactor.ShadeInteractorLegacyImpl;
import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.statusbar.NotificationEntryHelper;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.RankingBuilder;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
-import com.android.systemui.statusbar.disableflags.data.repository.FakeDisableFlagsRepository;
import com.android.systemui.statusbar.notification.NotifPipelineFlags;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -154,7 +136,6 @@
import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProviderTestUtil;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
-import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor;
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
@@ -163,13 +144,10 @@
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController;
import com.android.systemui.statusbar.policy.SensitiveNotificationProtectionController;
import com.android.systemui.statusbar.policy.ZenModeController;
import com.android.systemui.statusbar.policy.data.repository.FakeDeviceProvisioningRepository;
-import com.android.systemui.statusbar.policy.data.repository.FakeUserSetupRepository;
import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
-import com.android.systemui.user.domain.interactor.UserSwitcherInteractor;
import com.android.systemui.util.FakeEventLog;
import com.android.systemui.util.settings.FakeGlobalSettings;
import com.android.systemui.util.settings.SystemSettings;
@@ -207,8 +185,6 @@
import com.android.wm.shell.taskview.TaskViewTransitions;
import com.android.wm.shell.transition.Transitions;
-import kotlinx.coroutines.test.TestScope;
-
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -227,8 +203,11 @@
import java.util.Optional;
import java.util.concurrent.Executor;
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
+import platform.test.runner.parameterized.Parameters;
+
@SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(ParameterizedAndroidJunit4.class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
public class BubblesTest extends SysuiTestCase {
@Mock
@@ -355,11 +334,8 @@
private Icon mAppBubbleIcon;
@Mock
private Display mDefaultDisplay;
- @Mock
- private LargeScreenHeaderHelper mLargeScreenHeaderHelper;
private final KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
- private final TestScope mTestScope = mKosmos.getTestScope();
private ShadeInteractor mShadeInteractor;
private ShellTaskOrganizer mShellTaskOrganizer;
private TaskViewTransitions mTaskViewTransitions;
@@ -376,8 +352,16 @@
private UserHandle mUser0;
private FakeBubbleProperties mBubbleProperties;
- private FromLockscreenTransitionInteractor mFromLockscreenTransitionInteractor;
- private FromPrimaryBouncerTransitionInteractor mFromPrimaryBouncerTransitionInteractor;
+
+ @Parameters(name = "{0}")
+ public static List<FlagsParameterization> getParams() {
+ return SceneContainerFlagParameterizationKt.parameterizeSceneContainerFlag();
+ }
+
+ public BubblesTest(FlagsParameterization flags) {
+ mSetFlagsRule.setFlagsParameterization(flags);
+ }
+
@Before
public void setUp() throws Exception {
@@ -402,77 +386,14 @@
FakeDeviceProvisioningRepository deviceProvisioningRepository =
- new FakeDeviceProvisioningRepository();
+ mKosmos.getFakeDeviceProvisioningRepository();
deviceProvisioningRepository.setDeviceProvisioned(true);
- FakeKeyguardRepository keyguardRepository = new FakeKeyguardRepository();
- FakeFeatureFlagsClassic featureFlags = new FakeFeatureFlagsClassic();
- FakeShadeRepository shadeRepository = new FakeShadeRepository();
- FakeConfigurationRepository configurationRepository = new FakeConfigurationRepository();
-
- PowerInteractor powerInteractor = new PowerInteractor(
- mKosmos.getPowerRepository(),
- mKosmos.getFalsingCollector(),
- mock(ScreenOffAnimationController.class),
- mStatusBarStateController);
-
- SceneInteractor sceneInteractor = new SceneInteractor(
- mTestScope.getBackgroundScope(),
- new SceneContainerRepository(
- mTestScope.getBackgroundScope(),
- mKosmos.getFakeSceneContainerConfig(),
- mKosmos.getSceneDataSource()),
- mock(SceneLogger.class),
- mKosmos.getDeviceUnlockedInteractor());
-
- KeyguardTransitionInteractor keyguardTransitionInteractor =
- mKosmos.getKeyguardTransitionInteractor();
- KeyguardInteractor keyguardInteractor = new KeyguardInteractor(
- keyguardRepository,
- new FakeCommandQueue(),
- powerInteractor,
- new FakeKeyguardBouncerRepository(),
- new ConfigurationInteractor(configurationRepository),
- shadeRepository,
- keyguardTransitionInteractor,
- () -> sceneInteractor,
- () -> mKosmos.getFromGoneTransitionInteractor(),
- () -> mKosmos.getSharedNotificationContainerInteractor(),
- mTestScope);
-
- mFromLockscreenTransitionInteractor = mKosmos.getFromLockscreenTransitionInteractor();
- mFromPrimaryBouncerTransitionInteractor =
- mKosmos.getFromPrimaryBouncerTransitionInteractor();
-
- ResourcesSplitShadeStateController splitShadeStateController =
- new ResourcesSplitShadeStateController();
DeviceEntryUdfpsInteractor deviceEntryUdfpsInteractor =
mock(DeviceEntryUdfpsInteractor.class);
when(deviceEntryUdfpsInteractor.isUdfpsSupported()).thenReturn(MutableStateFlow(false));
- mShadeInteractor =
- new ShadeInteractorImpl(
- mTestScope.getBackgroundScope(),
- mKosmos.getDeviceProvisioningInteractor(),
- new FakeDisableFlagsRepository(),
- mDozeParameters,
- keyguardRepository,
- keyguardTransitionInteractor,
- powerInteractor,
- new FakeUserSetupRepository(),
- mock(UserSwitcherInteractor.class),
- new ShadeInteractorLegacyImpl(
- mTestScope.getBackgroundScope(), keyguardRepository,
- new SharedNotificationContainerInteractor(
- configurationRepository,
- mContext,
- splitShadeStateController,
- keyguardInteractor,
- deviceEntryUdfpsInteractor,
- () -> mLargeScreenHeaderHelper),
- shadeRepository
- )
- );
+ mShadeInteractor = mKosmos.getShadeInteractor();
mNotificationShadeWindowController = new NotificationShadeWindowControllerImpl(
mContext,
@@ -2467,6 +2388,10 @@
mStateChangeCalls++;
mLastUpdate = update;
}
+
+ @Override
+ public void animateBubbleBarLocation(BubbleBarLocation location) {
+ }
}
private static class FakeBubbleProperties implements BubbleProperties {
diff --git a/packages/SystemUI/tests/utils/src/android/os/VibratorKosmos.kt b/packages/SystemUI/tests/utils/src/android/os/VibratorKosmos.kt
new file mode 100644
index 0000000..872b25c
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/android/os/VibratorKosmos.kt
@@ -0,0 +1,22 @@
+/*
+ * 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.os
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
+
+var Kosmos.vibrator by Kosmos.Fixture { mock<Vibrator>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/EnableSceneContainer.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/EnableSceneContainer.kt
index e83205c5..1107971 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/EnableSceneContainer.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/EnableSceneContainer.kt
@@ -21,11 +21,9 @@
import com.android.systemui.Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR
import com.android.systemui.Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR
import com.android.systemui.Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR
-import com.android.systemui.Flags.FLAG_MEDIA_IN_SCENE_CONTAINER
import com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT
import com.android.systemui.Flags.FLAG_NOTIFICATIONS_HEADS_UP_REFACTOR
import com.android.systemui.Flags.FLAG_PREDICTIVE_BACK_SYSUI
-import com.android.systemui.Flags.FLAG_REFACTOR_KEYGUARD_DISMISS_INTENT
import com.android.systemui.Flags.FLAG_SCENE_CONTAINER
/**
@@ -36,13 +34,11 @@
FLAG_COMPOSE_LOCKSCREEN,
FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR,
FLAG_KEYGUARD_WM_STATE_REFACTOR,
- FLAG_MEDIA_IN_SCENE_CONTAINER,
FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT,
FLAG_NOTIFICATIONS_HEADS_UP_REFACTOR,
FLAG_PREDICTIVE_BACK_SYSUI,
FLAG_SCENE_CONTAINER,
FLAG_DEVICE_ENTRY_UDFPS_REFACTOR,
- FLAG_REFACTOR_KEYGUARD_DISMISS_INTENT,
)
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardSmartspaceRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardSmartspaceRepository.kt
new file mode 100644
index 0000000..42cb150
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardSmartspaceRepository.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.data.repository
+
+import android.view.View.GONE
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+
+class FakeKeyguardSmartspaceRepository : KeyguardSmartspaceRepository {
+
+ private val _bcSmartspaceVisibility = MutableStateFlow(GONE)
+ override val bcSmartspaceVisibility: StateFlow<Int> = _bcSmartspaceVisibility
+ private val _isWeatherEnabled = MutableStateFlow(true)
+ override val isWeatherEnabled: StateFlow<Boolean> = _isWeatherEnabled
+
+ override fun setBcSmartspaceVisibility(visibility: Int) {
+ _bcSmartspaceVisibility.value = visibility
+ }
+
+ fun setIsWeatherEnabled(isEnabled: Boolean) {
+ _isWeatherEnabled.value = isEnabled
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardSmartspaceRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardSmartspaceRepositoryKosmos.kt
index dc7103f..2949452 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardSmartspaceRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardSmartspaceRepositoryKosmos.kt
@@ -18,4 +18,4 @@
import com.android.systemui.kosmos.Kosmos
-val Kosmos.keyguardSmartspaceRepository by Kosmos.Fixture { KeyguardSmartspaceRepository() }
+val Kosmos.keyguardSmartspaceRepository by Kosmos.Fixture { FakeKeyguardSmartspaceRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt
index 2e751cc..02842cc 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt
@@ -53,6 +53,7 @@
shadeRepository: FakeShadeRepository = FakeShadeRepository(),
sceneInteractor: SceneInteractor = mock(),
fromGoneTransitionInteractor: FromGoneTransitionInteractor = mock(),
+ fromLockscreenTransitionInteractor: FromLockscreenTransitionInteractor = mock(),
sharedNotificationContainerInteractor: SharedNotificationContainerInteractor? = null,
powerInteractor: PowerInteractor = PowerInteractorFactory.create().powerInteractor,
testScope: CoroutineScope = TestScope(),
@@ -98,6 +99,7 @@
keyguardTransitionInteractor = keyguardTransitionInteractor,
sceneInteractorProvider = { sceneInteractor },
fromGoneTransitionInteractor = { fromGoneTransitionInteractor },
+ fromLockscreenTransitionInteractor = { fromLockscreenTransitionInteractor },
sharedNotificationContainerInteractor = { sncInteractor },
applicationScope = testScope,
),
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorKosmos.kt
index 9426718..81d8f0b 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorKosmos.kt
@@ -18,20 +18,20 @@
import com.android.systemui.bouncer.data.repository.keyguardBouncerRepository
import com.android.systemui.common.ui.domain.interactor.configurationInteractor
+import com.android.systemui.keyguard.data.repository.fakeCommandQueue
import com.android.systemui.keyguard.data.repository.keyguardRepository
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testScope
import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.shade.data.repository.shadeRepository
-import com.android.systemui.statusbar.commandQueue
import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor
val Kosmos.keyguardInteractor: KeyguardInteractor by
Kosmos.Fixture {
KeyguardInteractor(
repository = keyguardRepository,
- commandQueue = commandQueue,
+ commandQueue = fakeCommandQueue,
powerInteractor = powerInteractor,
bouncerRepository = keyguardBouncerRepository,
configurationInteractor = configurationInteractor,
@@ -39,6 +39,7 @@
keyguardTransitionInteractor = keyguardTransitionInteractor,
sceneInteractorProvider = { sceneInteractor },
fromGoneTransitionInteractor = { fromGoneTransitionInteractor },
+ fromLockscreenTransitionInteractor = { fromLockscreenTransitionInteractor },
sharedNotificationContainerInteractor = { sharedNotificationContainerInteractor },
applicationScope = testScope.backgroundScope,
)
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 16d08dd..fff3b14 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
@@ -32,7 +32,7 @@
val Kosmos.scenes by Fixture { fakeScenes }
val Kosmos.initialSceneKey by Fixture { Scenes.Lockscreen }
-val Kosmos.sceneContainerConfig by Fixture {
+var Kosmos.sceneContainerConfig by Fixture {
val navigationDistances =
mapOf(
Scenes.Gone to 0,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneContainerStartableKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneContainerStartableKosmos.kt
index c0f5039..ebe591b 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneContainerStartableKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneContainerStartableKosmos.kt
@@ -37,7 +37,7 @@
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.headsUpNotificationInteractor
import com.android.systemui.statusbar.notificationShadeWindowController
-import com.android.systemui.statusbar.phone.centralSurfaces
+import com.android.systemui.statusbar.phone.centralSurfacesOptional
import com.android.systemui.statusbar.policy.domain.interactor.deviceProvisioningInteractor
val Kosmos.sceneContainerStartable by Fixture {
@@ -58,7 +58,7 @@
authenticationInteractor = { authenticationInteractor },
windowController = notificationShadeWindowController,
deviceProvisioningInteractor = deviceProvisioningInteractor,
- centralSurfaces = centralSurfaces,
+ centralSurfacesOptLazy = { centralSurfacesOptional },
headsUpInteractor = headsUpNotificationInteractor,
occlusionInteractor = sceneContainerOcclusionInteractor,
faceUnlockInteractor = deviceEntryFaceAuthInteractor,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorKosmos.kt
index 4221d06..297d1d8 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorKosmos.kt
@@ -19,6 +19,7 @@
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testScope
import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.shade.data.repository.shadeRepository
import com.android.systemui.util.mockito.mock
val Kosmos.shadeLockscreenInteractor by
@@ -28,5 +29,6 @@
shadeInteractor = shadeInteractorImpl,
sceneInteractor = sceneInteractor,
lockIconViewController = mock(),
+ shadeRepository = shadeRepository,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/OverlayShadeViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/OverlayShadeViewModelKosmos.kt
new file mode 100644
index 0000000..45ec032
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/OverlayShadeViewModelKosmos.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.shade.ui.viewmodel
+
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+val Kosmos.overlayShadeViewModel: OverlayShadeViewModel by
+ Kosmos.Fixture {
+ OverlayShadeViewModel(
+ applicationScope = applicationCoroutineScope,
+ sceneInteractor = sceneInteractor,
+ deviceEntryInteractor = deviceEntryInteractor,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/KeyguardStatusBarRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/KeyguardStatusBarRepositoryKosmos.kt
new file mode 100644
index 0000000..da95ee9
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/KeyguardStatusBarRepositoryKosmos.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.data.repository
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.keyguardStatusBarRepository: FakeKeyguardStatusBarRepository by
+ Kosmos.Fixture { fakeKeyguardStatusBarRepository }
+
+val Kosmos.fakeKeyguardStatusBarRepository: FakeKeyguardStatusBarRepository by
+ Kosmos.Fixture { FakeKeyguardStatusBarRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/domain/interactor/KeyguardStatusBarInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/domain/interactor/KeyguardStatusBarInteractorKosmos.kt
new file mode 100644
index 0000000..71ed5f6
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/domain/interactor/KeyguardStatusBarInteractorKosmos.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.domain.interactor
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.statusbar.data.repository.keyguardStatusBarRepository
+
+val Kosmos.keyguardStatusBarInteractor: KeyguardStatusBarInteractor by
+ Kosmos.Fixture {
+ KeyguardStatusBarInteractor(
+ keyguardStatusBarRepository,
+ )
+ }
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 cbba80b..d00eedf 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
@@ -21,6 +21,7 @@
import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.keyguard.ui.viewmodel.alternateBouncerToGoneTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.aodBurnInViewModel
+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.dozingToLockscreenTransitionViewModel
@@ -60,6 +61,7 @@
shadeInteractor = shadeInteractor,
notificationStackAppearanceInteractor = notificationStackAppearanceInteractor,
alternateBouncerToGoneTransitionViewModel = alternateBouncerToGoneTransitionViewModel,
+ aodToGoneTransitionViewModel = aodToGoneTransitionViewModel,
aodToLockscreenTransitionViewModel = aodToLockscreenTransitionViewModel,
aodToOccludedTransitionViewModel = aodToOccludedTransitionViewModel,
dozingToLockscreenTransitionViewModel = dozingToLockscreenTransitionViewModel,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/CentralSurfacesKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/CentralSurfacesKosmos.kt
index 1611f62..f71bf03 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/CentralSurfacesKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/CentralSurfacesKosmos.kt
@@ -19,5 +19,7 @@
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.util.mockito.mock
+import java.util.Optional
+var Kosmos.centralSurfacesOptional by Fixture { Optional.of(centralSurfaces) }
val Kosmos.centralSurfaces by Fixture { mock<CentralSurfaces>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/VolumePanelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/VolumePanelKosmos.kt
index 4a79452..146f109 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/VolumePanelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/VolumePanelKosmos.kt
@@ -17,6 +17,7 @@
package com.android.systemui.volume.panel
import android.content.res.mainResources
+import com.android.systemui.broadcast.broadcastDispatcher
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testScope
import com.android.systemui.statusbar.policy.fakeConfigurationController
@@ -72,5 +73,6 @@
testScope.backgroundScope,
KosmosVolumePanelComponentFactory(this),
fakeConfigurationController,
+ broadcastDispatcher,
)
}
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/config/ResourceUnfoldTransitionConfig.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/config/ResourceUnfoldTransitionConfig.kt
index c513729..ca1daf6 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/config/ResourceUnfoldTransitionConfig.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/config/ResourceUnfoldTransitionConfig.kt
@@ -33,6 +33,12 @@
Resources.getSystem().getBoolean(id)
}
+ override val isHapticsEnabled: Boolean by lazy {
+ val id = Resources.getSystem()
+ .getIdentifier("config_unfoldTransitionHapticsEnabled", "bool", "android")
+ Resources.getSystem().getBoolean(id)
+ }
+
override val halfFoldedTimeoutMillis: Int by lazy {
val id = Resources.getSystem()
.getIdentifier("config_unfoldTransitionHalfFoldedTimeout", "integer", "android")
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/config/UnfoldTransitionConfig.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/config/UnfoldTransitionConfig.kt
index 765e862..1084cb3 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/config/UnfoldTransitionConfig.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/config/UnfoldTransitionConfig.kt
@@ -18,5 +18,6 @@
interface UnfoldTransitionConfig {
val isEnabled: Boolean
val isHingeAngleEnabled: Boolean
+ val isHapticsEnabled: Boolean
val halfFoldedTimeoutMillis: Int
}
diff --git a/ravenwood/Android.bp b/ravenwood/Android.bp
index 3337419..e06f400 100644
--- a/ravenwood/Android.bp
+++ b/ravenwood/Android.bp
@@ -155,6 +155,31 @@
visibility: ["//frameworks/base"],
}
+cc_library_shared {
+ name: "libravenwood_runtime",
+ host_supported: true,
+
+ cflags: [
+ "-Wall",
+ "-Werror",
+ "-Wno-unused-parameter",
+ "-Wthread-safety",
+ ],
+
+ srcs: [
+ "runtime-helper-src/jni/*.cpp",
+ ],
+
+ shared_libs: [
+ "libbase",
+ "liblog",
+ "libnativehelper",
+ "libutils",
+ "libcutils",
+ ],
+ visibility: ["//frameworks/base"],
+}
+
// For collecting the *stats.csv files in a known directory under out/host/linux-x86/testcases/.
// The "test" just shows the available stats filenames.
sh_test_host {
diff --git a/ravenwood/TEST_MAPPING b/ravenwood/TEST_MAPPING
index e77f846f..f6885e1 100644
--- a/ravenwood/TEST_MAPPING
+++ b/ravenwood/TEST_MAPPING
@@ -1,11 +1,18 @@
+// Keep the following two TEST_MAPPINGs in sync:
+// frameworks/base/ravenwood/TEST_MAPPING
+// frameworks/base/tools/hoststubgen/TEST_MAPPING
{
"presubmit": [
+ { "name": "tiny-framework-dump-test" },
+ { "name": "hoststubgentest" },
+ { "name": "hoststubgen-invoke-test" },
{
"name": "RavenwoodMockitoTest_device"
},
{
"name": "RavenwoodBivalentTest_device"
},
+ // The sysui tests should match vendor/unbundled_google/packages/SystemUIGoogle/TEST_MAPPING
{
"name": "SystemUIGoogleTests",
"options": [
@@ -18,6 +25,19 @@
]
}
],
+ "presubmit-large": [
+ {
+ "name": "SystemUITests",
+ "options": [
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ },
+ {
+ "exclude-annotation": "org.junit.Ignore"
+ }
+ ]
+ }
+ ],
"ravenwood-presubmit": [
{
"name": "RavenwoodMinimumTest",
diff --git a/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodClassLoadHook.java b/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodClassLoadHook.java
index 7dc197e..7a3142b 100644
--- a/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodClassLoadHook.java
+++ b/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodClassLoadHook.java
@@ -28,8 +28,10 @@
* Add this with a fully-specified method name (e.g. {@code "com.package.Class.methodName"})
* of a callback to get a callback at the class load time.
*
- * The method must be {@code public static} with a single argument that takes
- * {@link Class}.
+ * The method must be {@code public static} with a single argument that takes {@link Class}.
+ *
+ * Typically, this is used with {@link #LIBANDROID_LOADING_HOOK}, which will load the necessary
+ * native libraries.
*
* @hide
*/
@@ -37,4 +39,10 @@
@Retention(RetentionPolicy.CLASS)
public @interface RavenwoodClassLoadHook {
String value();
+
+ /**
+ * Class load hook that loads <code>libandroid_runtime</code>.
+ */
+ public static String LIBANDROID_LOADING_HOOK
+ = "com.android.platform.test.ravenwood.runtimehelper.ClassLoadHook.onClassLoaded";
}
diff --git a/ravenwood/bivalenttest/Android.bp b/ravenwood/bivalenttest/Android.bp
index a6b6ed9..2d94894 100644
--- a/ravenwood/bivalenttest/Android.bp
+++ b/ravenwood/bivalenttest/Android.bp
@@ -45,7 +45,6 @@
jni_libs: [
"libravenwoodbivalenttest_jni",
],
- sdk_version: "test_current",
auto_gen_config: true,
}
diff --git a/ravenwood/bivalenttest/AndroidTest.xml b/ravenwood/bivalenttest/AndroidTest.xml
index ac4695b..9e5dd11 100644
--- a/ravenwood/bivalenttest/AndroidTest.xml
+++ b/ravenwood/bivalenttest/AndroidTest.xml
@@ -25,5 +25,8 @@
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
<option name="package" value="com.android.ravenwood.bivalenttest" />
<option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+
+ <!-- Need to use MODULE_LIBRARIES APIs, so no hidden API check. -->
+ <option name="hidden-api-checks" value="false" />
</test>
</configuration>
diff --git a/ravenwood/bivalenttest/jni/ravenwood_core_test_jni.cpp b/ravenwood/bivalenttest/jni/ravenwood_core_test_jni.cpp
index 83f756e..956d79c 100644
--- a/ravenwood/bivalenttest/jni/ravenwood_core_test_jni.cpp
+++ b/ravenwood/bivalenttest/jni/ravenwood_core_test_jni.cpp
@@ -15,19 +15,70 @@
*/
#include <nativehelper/JNIHelp.h>
+#include <atomic>
#include "jni.h"
#include "utils/Log.h"
#include "utils/misc.h"
+// JNI methods for RavenwoodJniTest
+
static jint add(JNIEnv* env, jclass clazz, jint a, jint b) {
return a + b;
}
-static const JNINativeMethod sMethods[] =
+static const JNINativeMethod sMethods_JniTest[] =
{
{ "add", "(II)I", (void*)add },
};
+// JNI methods for RavenwoodNativeAllocationRegistryTest
+std::atomic<int> numTotalAlloc = 0;
+
+class NarTestData {
+public:
+ NarTestData(jint v): value(v) {
+ numTotalAlloc++;
+ }
+
+ ~NarTestData() {
+ value = -1;
+ numTotalAlloc--;
+ }
+
+ volatile jint value;
+};
+
+static jlong NarTestData_nMalloc(JNIEnv* env, jclass clazz, jint value) {
+ NarTestData* p = new NarTestData(value);
+ return reinterpret_cast<jlong>(p);
+}
+
+static jint NarTestData_nGet(JNIEnv* env, jclass clazz, jlong ptr) {
+ NarTestData* p = reinterpret_cast<NarTestData*>(ptr);
+ return p->value;
+}
+
+static void NarTestData_free(jlong ptr) {
+ NarTestData* p = reinterpret_cast<NarTestData*>(ptr);
+ delete p;
+}
+
+static jlong NarTestData_nGetNativeFinalizer(JNIEnv* env, jclass clazz) {
+ return reinterpret_cast<jlong>(NarTestData_free);
+}
+
+static jint NarTestData_nGetTotalAlloc(JNIEnv* env, jclass clazz) {
+ return numTotalAlloc;
+}
+
+static const JNINativeMethod sMethods_NarTestData[] =
+{
+ { "nMalloc", "(I)J", (void*)NarTestData_nMalloc },
+ { "nGet", "(J)I", (void*)NarTestData_nGet },
+ { "nGetNativeFinalizer", "()J", (void*)NarTestData_nGetNativeFinalizer },
+ { "nGetTotalAlloc", "()I", (void*)NarTestData_nGetTotalAlloc },
+};
+
extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)
{
JNIEnv* env = NULL;
@@ -43,7 +94,13 @@
int res = jniRegisterNativeMethods(env,
"com/android/ravenwoodtest/bivalenttest/RavenwoodJniTest",
- sMethods, NELEM(sMethods));
+ sMethods_JniTest, NELEM(sMethods_JniTest));
+ if (res < 0) {
+ return res;
+ }
+ res = jniRegisterNativeMethods(env,
+ "com/android/ravenwoodtest/bivalenttest/RavenwoodNativeAllocationRegistryTest$Data",
+ sMethods_NarTestData, NELEM(sMethods_NarTestData));
if (res < 0) {
return res;
}
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodJniTest.java b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodJniTest.java
index 59467e9..0cc2adc 100644
--- a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodJniTest.java
+++ b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodJniTest.java
@@ -29,6 +29,10 @@
@RunWith(AndroidJUnit4.class)
public final class RavenwoodJniTest {
static {
+ initializeJni();
+ }
+
+ public static void initializeJni() {
RavenwoodUtils.loadJniLibrary("ravenwoodbivalenttest_jni");
}
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodNativeAllocationRegistryTest.java b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodNativeAllocationRegistryTest.java
new file mode 100644
index 0000000..415b467
--- /dev/null
+++ b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodNativeAllocationRegistryTest.java
@@ -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.ravenwoodtest.bivalenttest;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import android.platform.test.ravenwood.RavenwoodRule;
+import android.util.Log;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import libcore.util.NativeAllocationRegistry;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class RavenwoodNativeAllocationRegistryTest {
+ private static final String TAG = RavenwoodNativeAllocationRegistryTest.class.getSimpleName();
+ static {
+ RavenwoodJniTest.initializeJni();
+ }
+
+ @Rule
+ public final RavenwoodRule mRavenwoodRule = new RavenwoodRule();
+
+ private static class Data {
+ private final long mNativePtr;
+
+ private static native long nMalloc(int value);
+ private static native int nGet(long ptr);
+ private static native long nGetNativeFinalizer();
+
+ public static native int nGetTotalAlloc();
+
+ public int get() {
+ return nGet(mNativePtr);
+ }
+
+ private static class NarHolder {
+ public static final NativeAllocationRegistry sRegistry =
+ NativeAllocationRegistry.createMalloced(
+ Data.class.getClassLoader(), nGetNativeFinalizer());
+ }
+
+ public Data(int value) {
+ mNativePtr = nMalloc(value);
+ NarHolder.sRegistry.registerNativeAllocation(this, mNativePtr);
+ }
+ }
+
+ @Test
+ public void testNativeAllocationRegistry() {
+
+ final long timeoutTime = mRavenwoodRule.realCurrentTimeMillis() + 10_000;
+
+ final int startAlloc = Data.nGetTotalAlloc();
+
+ int totalAlloc = 0;
+
+ // Keep allocation new objects, until some get released.
+
+ while (true) {
+ for (int i = 0; i < 1000; i++) {
+ totalAlloc++;
+ Data d = new Data(i);
+ assertEquals(i, d.get());
+ }
+ System.gc();
+
+ final int currentAlloc = Data.nGetTotalAlloc() - startAlloc;
+ Log.i(TAG, "# of currently allocated objects=" + currentAlloc);
+
+ if (currentAlloc < totalAlloc) {
+ break; // Good, some objects have been released;
+ }
+ if (mRavenwoodRule.realCurrentTimeMillis() > timeoutTime) {
+ fail("No objects have been released before timeout");
+ }
+ }
+ }
+}
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
index 21d8019..9d12f85 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
@@ -426,4 +426,15 @@
return ENABLE_OPTIONAL_VALIDATION;
}
}
+
+ /**
+ * Returns the "real" result from {@link System#currentTimeMillis()}.
+ *
+ * Currently, it's the same thing as calling {@link System#currentTimeMillis()},
+ * but this one is guaranteeed to return the real value, even when Ravenwood supports
+ * injecting a time to{@link System#currentTimeMillis()}.
+ */
+ public long realCurrentTimeMillis() {
+ return System.currentTimeMillis();
+ }
}
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodUtils.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodUtils.java
index 37ceac6..99ab327 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodUtils.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodUtils.java
@@ -26,6 +26,15 @@
private RavenwoodUtils() {
}
+ private static final String RAVENWOOD_NATIVE_RUNTIME_NAME = "ravenwood_runtime";
+
+ // LibcoreRavenwoodUtils calls it with reflections.
+ public static void loadRavenwoodNativeRuntime() {
+ if (RavenwoodRule.isOnRavenwood()) {
+ RavenwoodUtils.loadJniLibrary(RAVENWOOD_NATIVE_RUNTIME_NAME);
+ }
+ }
+
/**
* Load a JNI library respecting {@code java.library.path}
* (which reflects {@code LD_LIBRARY_PATH}).
diff --git a/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java b/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
index e951351b..773a89a 100644
--- a/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
+++ b/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
@@ -39,4 +39,8 @@
public static void validate(Statement base, Description description,
boolean enableOptionalValidation) {
}
+
+ public static long realCurrentTimeMillis() {
+ return System.currentTimeMillis();
+ }
}
diff --git a/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/runtimehelper/ClassLoadHook.java b/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/runtimehelper/ClassLoadHook.java
index 9057d16..96b7057 100644
--- a/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/runtimehelper/ClassLoadHook.java
+++ b/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/runtimehelper/ClassLoadHook.java
@@ -44,6 +44,14 @@
public static final String LIBANDROID_RUNTIME_NAME = "android_runtime";
+ /**
+ * Extra strings needed to pass to register_android_graphics_classes().
+ *
+ * `android.graphics.Graphics` is not actually a class, so we can't use the same initialization
+ * strategy than the "normal" classes. So we just hardcode it here.
+ */
+ public static final String GRAPHICS_EXTRA_INIT_PARAMS = ",android.graphics.Graphics";
+
private static String sInitialDir = new File("").getAbsolutePath();
static {
@@ -98,7 +106,6 @@
private static void loadFrameworkNativeCode() {
// This is called from class-initializers, so no synchronization is needed.
if (sLoadFrameworkNativeCodeCalled) {
- // This method has already been called before.s
return;
}
sLoadFrameworkNativeCodeCalled = true;
@@ -112,7 +119,8 @@
}
if (SKIP_LOADING_LIBANDROID) {
- log("Skip loading " + LIBANDROID_RUNTIME_NAME);
+ log("Skip loading native runtime.");
+ return;
}
// Make sure these properties are not set.
@@ -121,27 +129,39 @@
ensurePropertyNotSet(KEYBOARD_PATHS);
ensurePropertyNotSet(GRAPHICS_NATIVE_CLASSES);
- // Tell libandroid what JNI to use.
- final var jniClasses = getCoreNativeClassesToUse();
- if (jniClasses.isEmpty()) {
- log("No classes require JNI methods, skip loading " + LIBANDROID_RUNTIME_NAME);
+ // Load the libraries, if needed.
+ final var libanrdoidClasses = getClassesWithNativeMethods(sLibandroidClasses);
+ final var libhwuiClasses = getClassesWithNativeMethods(sLibhwuiClasses);
+ if (libanrdoidClasses.isEmpty() && libhwuiClasses.isEmpty()) {
+ log("No classes require JNI methods, skip loading native runtime.");
return;
}
- setProperty(CORE_NATIVE_CLASSES, jniClasses);
- setProperty(GRAPHICS_NATIVE_CLASSES, "");
+ setProperty(CORE_NATIVE_CLASSES, libanrdoidClasses);
+ setProperty(GRAPHICS_NATIVE_CLASSES, libhwuiClasses + GRAPHICS_EXTRA_INIT_PARAMS);
+ log("Loading " + LIBANDROID_RUNTIME_NAME + " for '" + libanrdoidClasses + "' and '"
+ + libhwuiClasses + "'");
RavenwoodUtils.loadJniLibrary(LIBANDROID_RUNTIME_NAME);
}
/**
- * Classes with native methods that are backed by `libandroid_runtime`.
+ * Classes with native methods that are backed by libandroid_runtime.
*
- * At runtime, we check if these classes have any methods, and if so, we'll have
- * `libandroid_runtime` register the native functions.
+ * See frameworks/base/core/jni/platform/host/HostRuntime.cpp
*/
- private static final Class<?>[] sClassesWithLibandroidNativeMethods = {
+ private static final Class<?>[] sLibandroidClasses = {
android.util.Log.class,
- android.os.Parcel.class,
+ };
+
+ /**
+ * Classes with native methods that are backed by libhwui.
+ *
+ * See frameworks/base/libs/hwui/apex/LayoutlibLoader.cpp
+ */
+ private static final Class<?>[] sLibhwuiClasses = {
+ android.graphics.Interpolator.class,
+ android.graphics.Matrix.class,
+ android.graphics.Path.class,
};
/**
@@ -157,17 +177,15 @@
}
/**
- * Create a list of classes as comma-separated that require JNI methods to be set up.
- *
- * <p>This list is used by frameworks/base/core/jni/LayoutlibLoader.cpp to decide
- * what JNI methods to set up.
+ * Create a list of classes as comma-separated that require JNI methods to be set up from
+ * a given class list, ignoring classes with no native methods.
*/
- private static String getCoreNativeClassesToUse() {
+ private static String getClassesWithNativeMethods(Class<?>[] classes) {
final var coreNativeClassesToLoad = new ArrayList<String>();
- for (var clazz : sClassesWithLibandroidNativeMethods) {
+ for (var clazz : classes) {
if (hasNativeMethod(clazz)) {
- log("Class %s has native methods", clazz);
+ log("Class %s has native methods", clazz.getCanonicalName());
coreNativeClassesToLoad.add(clazz.getName());
}
}
diff --git a/ravenwood/runtime-helper-src/jni/ravenwood_runtime.cpp b/ravenwood/runtime-helper-src/jni/ravenwood_runtime.cpp
new file mode 100644
index 0000000..8e3a21d
--- /dev/null
+++ b/ravenwood/runtime-helper-src/jni/ravenwood_runtime.cpp
@@ -0,0 +1,64 @@
+/*
+ * 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.
+ */
+
+#include <nativehelper/JNIHelp.h>
+#include "jni.h"
+#include "utils/Log.h"
+#include "utils/misc.h"
+
+
+typedef void (*FreeFunction)(void*);
+
+static void NativeAllocationRegistry_applyFreeFunction(JNIEnv*,
+ jclass,
+ jlong freeFunction,
+ jlong ptr) {
+ void* nativePtr = reinterpret_cast<void*>(static_cast<uintptr_t>(ptr));
+ FreeFunction nativeFreeFunction
+ = reinterpret_cast<FreeFunction>(static_cast<uintptr_t>(freeFunction));
+ nativeFreeFunction(nativePtr);
+}
+
+static const JNINativeMethod sMethods_NAR[] =
+{
+ { "applyFreeFunction", "(JJ)V", (void*)NativeAllocationRegistry_applyFreeFunction },
+};
+
+extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)
+{
+ JNIEnv* env = NULL;
+ jint result = -1;
+
+ if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
+ ALOGE("GetEnv failed!");
+ return result;
+ }
+ ALOG_ASSERT(env, "Could not retrieve the env!");
+
+ ALOGI("%s: JNI_OnLoad", __FILE__);
+
+ // Initialize the Ravenwood version of NativeAllocationRegistry.
+ // We don't use this JNI on the device side, but if we ever have to do, skip this part.
+#ifndef __ANDROID__
+ int res = jniRegisterNativeMethods(env, "libcore/util/NativeAllocationRegistry",
+ sMethods_NAR, NELEM(sMethods_NAR));
+ if (res < 0) {
+ return res;
+ }
+#endif
+
+ return JNI_VERSION_1_4;
+}
diff --git a/ravenwood/runtime-helper-src/libcore-fake/libcore/ravenwood/LibcoreRavenwoodUtils.java b/ravenwood/runtime-helper-src/libcore-fake/libcore/ravenwood/LibcoreRavenwoodUtils.java
new file mode 100644
index 0000000..839b62a
--- /dev/null
+++ b/ravenwood/runtime-helper-src/libcore-fake/libcore/ravenwood/LibcoreRavenwoodUtils.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package libcore.ravenwood;
+
+public class LibcoreRavenwoodUtils {
+ private LibcoreRavenwoodUtils() {
+ }
+
+ public static void loadRavenwoodNativeRuntime() {
+ // TODO Stop using reflections.
+ // We need to call RavenwoodUtils.loadRavenwoodNativeRuntime(), but due to the build
+ // structure complexity, we can't refer to to this method directly from here,
+ // so let's use reflections for now...
+ try {
+ final var clazz = Class.forName("android.platform.test.ravenwood.RavenwoodUtils");
+ final var method = clazz.getMethod("loadRavenwoodNativeRuntime");
+ method.invoke(null);
+ } catch (Throwable th) {
+ throw new IllegalStateException("Failed to load Ravenwood native runtime", th);
+ }
+ }
+}
diff --git a/ravenwood/runtime-helper-src/libcore-fake/libcore/util/NativeAllocationRegistry.java b/ravenwood/runtime-helper-src/libcore-fake/libcore/util/NativeAllocationRegistry.java
new file mode 100644
index 0000000..93861e8
--- /dev/null
+++ b/ravenwood/runtime-helper-src/libcore-fake/libcore/util/NativeAllocationRegistry.java
@@ -0,0 +1,88 @@
+/*
+ * 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 libcore.util;
+
+import libcore.ravenwood.LibcoreRavenwoodUtils;
+
+import java.lang.ref.Cleaner;
+import java.lang.ref.Reference;
+
+/**
+ * Re-implementation of ART's NativeAllocationRegistry for Ravenwood.
+ * - We don't track the native allocation size on Ravenwood.
+ * - sun.misc.Cleaner isn't available on the desktop JVM, so we use java.lang.ref.Cleaner.
+ * (Should ART switch to java.lang.ref.Cleaner?)
+ */
+public class NativeAllocationRegistry {
+ static {
+ // Initialize the JNI method.
+ LibcoreRavenwoodUtils.loadRavenwoodNativeRuntime();
+ }
+
+ private final long mFreeFunction;
+ private static final Cleaner sCleaner = Cleaner.create();
+
+ public static NativeAllocationRegistry createNonmalloced(
+ ClassLoader classLoader, long freeFunction, long size) {
+ return new NativeAllocationRegistry(classLoader, freeFunction, size, false);
+ }
+
+ public static NativeAllocationRegistry createMalloced(
+ ClassLoader classLoader, long freeFunction, long size) {
+ return new NativeAllocationRegistry(classLoader, freeFunction, size, true);
+ }
+
+ public static NativeAllocationRegistry createMalloced(
+ ClassLoader classLoader, long freeFunction) {
+ return new NativeAllocationRegistry(classLoader, freeFunction, 0, true);
+ }
+
+ public NativeAllocationRegistry(ClassLoader classLoader, long freeFunction, long size) {
+ this(classLoader, freeFunction, size, size == 0);
+ }
+
+ private NativeAllocationRegistry(ClassLoader classLoader, long freeFunction, long size,
+ boolean mallocAllocation) {
+ if (size < 0) {
+ throw new IllegalArgumentException("Invalid native allocation size: " + size);
+ }
+ mFreeFunction = freeFunction;
+ }
+
+ public Runnable registerNativeAllocation(Object referent, long nativePtr) {
+ if (referent == null) {
+ throw new IllegalArgumentException("referent is null");
+ }
+ if (nativePtr == 0) {
+ throw new IllegalArgumentException("nativePtr is null");
+ }
+
+ final Runnable releaser = () -> {
+ applyFreeFunction(mFreeFunction, nativePtr);
+ };
+ sCleaner.register(referent, releaser);
+
+ // Ensure that cleaner doesn't get invoked before we enable it.
+ Reference.reachabilityFence(referent);
+ return releaser;
+ }
+
+ /**
+ * Calls {@code freeFunction}({@code nativePtr}).
+ */
+ public static native void applyFreeFunction(long freeFunction, long nativePtr);
+}
+
diff --git a/ravenwood/scripts/ravenwood-stats-collector.sh b/ravenwood/scripts/ravenwood-stats-collector.sh
index b5843d0..beacde2 100755
--- a/ravenwood/scripts/ravenwood-stats-collector.sh
+++ b/ravenwood/scripts/ravenwood-stats-collector.sh
@@ -39,14 +39,18 @@
local jar=$1
local file=$2
- sed -e '1d' -e "s/^/$jar,/" $file
+ # Use sed to remove the header + prepend the jar filename.
+ sed -e '1d' -e "s/^/$jar,/" $file
}
collect_stats() {
local out="$1"
{
- echo 'Jar,PackageName,ClassName,SupportedMethods,TotalMethods'
- dump "framework-minus-apex" hoststubgen_framework-minus-apex_stats.csv
+ # Copy the header, with the first column appended.
+ echo -n "Jar,"
+ head -n 1 hoststubgen_framework-minus-apex_stats.csv
+
+ dump "framework-minus-apex" hoststubgen_framework-minus-apex_stats.csv
dump "service.core" hoststubgen_services.core_stats.csv
} > "$out"
@@ -56,7 +60,10 @@
collect_apis() {
local out="$1"
{
- echo 'Jar,PackageName,ClassName,MethodName,Descriptor'
+ # Copy the header, with the first column appended.
+ echo -n "Jar,"
+ head -n 1 hoststubgen_framework-minus-apex_apis.csv
+
dump "framework-minus-apex" hoststubgen_framework-minus-apex_apis.csv
dump "service.core" hoststubgen_services.core_apis.csv
} > "$out"
diff --git a/ravenwood/texts/ravenwood-annotation-allowed-classes.txt b/ravenwood/texts/ravenwood-annotation-allowed-classes.txt
index 243e224..e452299 100644
--- a/ravenwood/texts/ravenwood-annotation-allowed-classes.txt
+++ b/ravenwood/texts/ravenwood-annotation-allowed-classes.txt
@@ -236,7 +236,11 @@
android.accounts.Account
+android.graphics.Bitmap$Config
android.graphics.Insets
+android.graphics.Interpolator
+android.graphics.Matrix
+android.graphics.Path
android.graphics.Point
android.graphics.PointF
android.graphics.Rect
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index ad869a1..2bece6c 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -852,7 +852,7 @@
}
private void registerBroadcastReceivers() {
- mPackageMonitor = new PackageMonitor() {
+ mPackageMonitor = new PackageMonitor(true) {
@Override
public void onSomePackagesChanged() {
if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_PACKAGE_BROADCAST_RECEIVER)) {
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index 522aa67..e830523 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -2292,11 +2292,32 @@
args.arg4 = requestId;
args.argi1 = widget.appWidgetId;
+ if (updateViews != null && updateViews.isLegacyListRemoteViews()) {
+ mCallbackHandler.obtainMessage(
+ CallbackHandler.MSG_NOTIFY_UPDATE_APP_WIDGET_DEFERRED,
+ args).sendToTarget();
+ return;
+ }
+
mCallbackHandler.obtainMessage(
CallbackHandler.MSG_NOTIFY_UPDATE_APP_WIDGET,
args).sendToTarget();
}
+ private void handleNotifyUpdateAppWidgetDeferred(Host host, IAppWidgetHost callbacks,
+ int appWidgetId, long requestId) {
+ try {
+ Slog.d(TAG, "Trying to notify widget update deferred for id: " + appWidgetId);
+ callbacks.updateAppWidgetDeferred(appWidgetId);
+ host.lastWidgetUpdateSequenceNo = requestId;
+ } catch (RemoteException re) {
+ synchronized (mLock) {
+ Slog.e(TAG, "Widget host dead: " + host.id, re);
+ host.callbacks = null;
+ }
+ }
+ }
+
private void handleNotifyUpdateAppWidget(Host host, IAppWidgetHost callbacks,
int appWidgetId, RemoteViews views, long requestId) {
try {
@@ -4277,6 +4298,7 @@
public static final int MSG_NOTIFY_PROVIDERS_CHANGED = 3;
public static final int MSG_NOTIFY_VIEW_DATA_CHANGED = 4;
public static final int MSG_NOTIFY_APP_WIDGET_REMOVED = 5;
+ public static final int MSG_NOTIFY_UPDATE_APP_WIDGET_DEFERRED = 6;
public CallbackHandler(Looper looper) {
super(looper, null, false);
@@ -4340,6 +4362,17 @@
handleNotifyAppWidgetViewDataChanged(host, callbacks, appWidgetId, viewId,
requestId);
} break;
+
+ case MSG_NOTIFY_UPDATE_APP_WIDGET_DEFERRED: {
+ SomeArgs args = (SomeArgs) message.obj;
+ Host host = (Host) args.arg1;
+ IAppWidgetHost callbacks = (IAppWidgetHost) args.arg2;
+ long requestId = (Long) args.arg4;
+ final int appWidgetId = args.argi1;
+ args.recycle();
+
+ handleNotifyUpdateAppWidgetDeferred(host, callbacks, appWidgetId, requestId);
+ } break;
}
}
}
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 300b147..3ff0504 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -242,6 +242,7 @@
"com.android.sysprop.watchdog",
"securebox",
"apache-commons-math",
+ "battery_saver_flag_lib",
"notification_flags_lib",
"power_hint_flags_lib",
"biometrics_flags_lib",
@@ -256,6 +257,7 @@
"stats_flags_lib",
"core_os_flags_lib",
"connectivity_flags_lib",
+ "dreams_flags_lib",
],
javac_shard_size: 50,
javacflags: [
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index 2d1aba4..bc83a0e 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -2691,7 +2691,7 @@
for (Record r : mRecords) {
if (r.matchTelephonyCallbackEvent(
TelephonyCallback.EVENT_RADIO_POWER_STATE_CHANGED)
- && idMatch(r, subId, phoneId)) {
+ && idMatchRelaxed(r, subId, phoneId)) {
try {
r.callback.onRadioPowerStateChanged(state);
} catch (RemoteException ex) {
@@ -4089,6 +4089,45 @@
}
}
+ /**
+ * Match the sub id or phone id of the event to the record with relaxed rules
+ *
+ * We follow the rules below:
+ * 1) If sub id of the event is invalid, phone id should be used.
+ * 2) If record's phoneId is also invalid then allow phone 0 notifications
+ * 3) The event on default sub should be notified to the records
+ * which register the default sub id.
+ * 4) Sub id should be exactly matched for all other cases.
+ * TODO: b/337878785 for longterm fix
+ */
+ boolean idMatchRelaxed(Record r, int subId, int phoneId) {
+ if (!Flags.useRelaxedIdMatch()) {
+ return idMatch(r, subId, phoneId);
+ }
+
+ if (subId < 0) {
+ // Invalid case, we need compare phoneId.
+ // If the record does not have a valid phone Id send phone 0 notifications.
+ // A record's phoneId can get invalid if there is no SIM or modem was restarting
+ // when caller registered.
+ if (r.phoneId == INVALID_SIM_SLOT_INDEX) {
+ return (phoneId == 0);
+ } else {
+ return (r.phoneId == phoneId);
+ }
+ }
+
+ if (r.subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID) {
+ // if the registered record does not have a valid phoneId then use the phone 0
+ if (r.phoneId == INVALID_SIM_SLOT_INDEX) {
+ return (phoneId == 0);
+ }
+ return (subId == mDefaultSubId);
+ } else {
+ return (r.subId == subId);
+ }
+ }
+
private boolean checkFineLocationAccess(Record r) {
return checkFineLocationAccess(r, Build.VERSION_CODES.BASE);
}
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 23891d2..ec0d897 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -3864,10 +3864,12 @@
final long lastTopTime = sr.app.mState.getLastTopTime();
final long constantTimeLimit = getTimeLimitForFgsType(fgsType);
final long nowUptime = SystemClock.uptimeMillis();
- if (constantTimeLimit > (nowUptime - lastTopTime)) {
+ if (lastTopTime != Long.MIN_VALUE && constantTimeLimit > (nowUptime - lastTopTime)) {
+ // Discard any other messages for this service
+ mFGSAnrTimer.discard(sr);
+ mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_FGS_TIMEOUT_MSG, sr);
// The app was in the TOP state after the FGS was started so its time allowance
// should be counted from that time since this is considered a user interaction
- mFGSAnrTimer.discard(sr);
final Message msg = mAm.mHandler.obtainMessage(
ActivityManagerService.SERVICE_FGS_TIMEOUT_MSG, sr);
mAm.mHandler.sendMessageAtTime(msg, lastTopTime + constantTimeLimit);
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index c47e42d..1b59c18 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -5034,7 +5034,7 @@
}
@Override
- public final void finishAttachApplication(long startSeq) {
+ public final void finishAttachApplication(long startSeq, long timestampApplicationOnCreateNs) {
final int pid = Binder.getCallingPid();
final int uid = Binder.getCallingUid();
@@ -5054,6 +5054,11 @@
} finally {
Binder.restoreCallingIdentity(origId);
}
+
+ if (android.app.Flags.appStartInfoTimestamps() && timestampApplicationOnCreateNs > 0) {
+ addStartInfoTimestampInternal(ApplicationStartInfo.START_TIMESTAMP_APPLICATION_ONCREATE,
+ timestampApplicationOnCreateNs, UserHandle.getUserId(uid), uid);
+ }
}
private void handleBindApplicationTimeoutSoft(ProcessRecord app, int softTimeoutMillis) {
@@ -10253,10 +10258,15 @@
mUserController.handleIncomingUser(Binder.getCallingPid(), callingUid, userId, true,
ALLOW_NON_FULL, "addStartInfoTimestamp", null);
- final String packageName = Settings.getPackageNameForUid(mContext, callingUid);
+ addStartInfoTimestampInternal(key, timestampNs, userId, callingUid);
+ }
- mProcessList.getAppStartInfoTracker().addTimestampToStart(packageName,
- UserHandle.getUid(userId, UserHandle.getAppId(callingUid)), timestampNs, key);
+ private void addStartInfoTimestampInternal(int key, long timestampNs, int userId, int uid) {
+ mProcessList.getAppStartInfoTracker().addTimestampToStart(
+ Settings.getPackageNameForUid(mContext, uid),
+ UserHandle.getUid(userId, UserHandle.getAppId(uid)),
+ timestampNs,
+ key);
}
@Override
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index 8b64538..9b83ede 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -35,6 +35,10 @@
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;
/**
* Maps system settings to system properties.
@@ -320,15 +324,30 @@
NAMESPACE_REBOOT_STAGING,
AsyncTask.THREAD_POOL_EXECUTOR,
(DeviceConfig.Properties properties) -> {
- String scope = properties.getNamespace();
- for (String key : properties.getKeyset()) {
- String aconfigPropertyName = makeAconfigFlagStagedPropertyName(key);
- if (aconfigPropertyName == null) {
- log("unable to construct system property for " + scope + "/" + key);
- return;
+
+ HashMap<String, HashMap<String, String>> propsToStage =
+ getStagedFlagsWithValueChange(properties);
+
+ for (HashMap.Entry<String, HashMap<String, String>> entry : propsToStage.entrySet()) {
+ String actualNamespace = entry.getKey();
+ HashMap<String, String> flagValuesToStage = entry.getValue();
+
+ for (String flagName : flagValuesToStage.keySet()) {
+ String stagedValue = flagValuesToStage.get(flagName);
+ String propertyName = "next_boot." + makeAconfigFlagPropertyName(
+ actualNamespace, flagName);
+
+ if (!propertyName.matches(SYSTEM_PROPERTY_VALID_CHARACTERS_REGEX)
+ || propertyName.contains(SYSTEM_PROPERTY_INVALID_SUBSTRING)) {
+ log("unable to construct system property for " + actualNamespace
+ + "/" + flagName);
+ continue;
+ }
+
+ setProperty(propertyName, stagedValue);
}
- setProperty(aconfigPropertyName, properties.getString(key, null));
}
+
});
}
@@ -401,35 +420,6 @@
}
/**
- * system property name constructing rule for staged aconfig flags, the flag name
- * is in the form of [namespace]*[actual flag name], we should push the following
- * to system properties
- * "next_boot.[actual sys prop name]".
- * If the name contains invalid characters or substrings for system property name,
- * will return null.
- * @param flagName
- * @return
- */
- @VisibleForTesting
- static String makeAconfigFlagStagedPropertyName(String flagName) {
- int idx = flagName.indexOf(NAMESPACE_REBOOT_STAGING_DELIMITER);
- if (idx == -1 || idx == flagName.length() - 1 || idx == 0) {
- log("invalid staged flag: " + flagName);
- return null;
- }
-
- String propertyName = "next_boot." + makeAconfigFlagPropertyName(
- flagName.substring(0, idx), flagName.substring(idx+1));
-
- if (!propertyName.matches(SYSTEM_PROPERTY_VALID_CHARACTERS_REGEX)
- || propertyName.contains(SYSTEM_PROPERTY_INVALID_SUBSTRING)) {
- return null;
- }
-
- return propertyName;
- }
-
- /**
* system property name constructing rule for aconfig flags:
* "persist.device_config.aconfig_flags.[category_name].[flag_name]".
* If the name contains invalid characters or substrings for system property name,
@@ -451,6 +441,63 @@
return propertyName;
}
+ /**
+ * Get the flags that need to be staged in sys prop, only these with a real value
+ * change needs to be staged in sys prop. Otherwise, the flag stage is useless and
+ * create performance problem at sys prop side.
+ * @param properties
+ * @return a hash map of namespace name to actual flags to stage
+ */
+ @VisibleForTesting
+ static HashMap<String, HashMap<String, String>> getStagedFlagsWithValueChange(
+ DeviceConfig.Properties properties) {
+
+ // sort flags by actual namespace of the flag
+ HashMap<String, HashMap<String, String>> stagedProps = new HashMap<>();
+ for (String flagName : properties.getKeyset()) {
+ int idx = flagName.indexOf(NAMESPACE_REBOOT_STAGING_DELIMITER);
+ if (idx == -1 || idx == flagName.length() - 1 || idx == 0) {
+ log("invalid staged flag: " + flagName);
+ continue;
+ }
+ String actualNamespace = flagName.substring(0, idx);
+ String actualFlagName = flagName.substring(idx+1);
+ HashMap<String, String> flagStagedValues = stagedProps.get(actualNamespace);
+ if (flagStagedValues == null) {
+ flagStagedValues = new HashMap<String, String>();
+ stagedProps.put(actualNamespace, flagStagedValues);
+ }
+ flagStagedValues.put(actualFlagName, properties.getString(flagName, null));
+ }
+
+ // for each namespace, find flags with real flag value change
+ HashMap<String, HashMap<String, String>> propsToStage = new HashMap<>();
+ for (HashMap.Entry<String, HashMap<String, String>> entry : stagedProps.entrySet()) {
+ String actualNamespace = entry.getKey();
+ HashMap<String, String> flagStagedValues = entry.getValue();
+ Map<String, String> flagCurrentValues = Settings.Config.getStrings(
+ actualNamespace, new ArrayList<String>(flagStagedValues.keySet()));
+
+ HashMap<String, String> flagsToStage = new HashMap<>();
+ for (String flagName : flagStagedValues.keySet()) {
+ String stagedValue = flagStagedValues.get(flagName);
+ String currentValue = flagCurrentValues.get(flagName);
+ if (currentValue == null) {
+ currentValue = new String("false");
+ }
+ if (stagedValue != null && !stagedValue.equalsIgnoreCase(currentValue)) {
+ flagsToStage.put(flagName, stagedValue);
+ }
+ }
+
+ if (!flagsToStage.isEmpty()) {
+ propsToStage.put(actualNamespace, flagsToStage);
+ }
+ }
+
+ return propsToStage;
+ }
+
private void setProperty(String key, String value) {
// Check if need to clear the property
if (value == null) {
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index b703076..c6c1f98 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -155,9 +155,6 @@
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
@@ -225,9 +222,18 @@
private static final int USER_SWITCH_CALLBACKS_TIMEOUT_MS = 5 * 1000;
/**
+ * Amount of time waited for
+ * {@link ActivityTaskManagerInternal.ScreenObserver#onKeyguardStateChanged} callbacks to be
+ * called after calling {@link WindowManagerService#lockDeviceNow}.
+ * Otherwise, we should throw a {@link RuntimeException} and never dismiss the
+ * {@link UserSwitchingDialog}.
+ */
+ static final int SHOW_KEYGUARD_TIMEOUT_MS = 20 * 1000;
+
+ /**
* Amount of time waited for {@link WindowManagerService#dismissKeyguard} callbacks to be
* called after dismissing the keyguard.
- * Otherwise, we should move on to dismiss the dialog {@link #dismissUserSwitchDialog()}
+ * Otherwise, we should move on to dismiss the dialog {@link #dismissUserSwitchDialog}}
* and report user switch is complete {@link #REPORT_USER_SWITCH_COMPLETE_MSG}.
*/
private static final int DISMISS_KEYGUARD_TIMEOUT_MS = 2 * 1000;
@@ -1925,15 +1931,8 @@
updateProfileRelatedCaches();
mInjector.getWindowManager().setCurrentUser(userId);
mInjector.reportCurWakefulnessUsageEvent();
- // Once the internal notion of the active user has switched, we lock the device
- // with the option to show the user switcher on the keyguard.
if (userSwitchUiEnabled) {
mInjector.getWindowManager().setSwitchingUser(true);
- // Only lock if the user has a secure keyguard PIN/Pattern/Pwd
- if (mInjector.getKeyguardManager().isDeviceSecure(userId)) {
- // Make sure the device is locked before moving on with the user switch
- mInjector.lockDeviceNowAndWaitForKeyguardShown();
- }
}
} else {
@@ -2516,32 +2515,54 @@
@VisibleForTesting
void completeUserSwitch(int oldUserId, int newUserId) {
- final boolean isUserSwitchUiEnabled = isUserSwitchUiEnabled();
- // serialize each conditional step
- await(
- // STEP 1 - If there is no challenge set, dismiss the keyguard right away
- isUserSwitchUiEnabled && !mInjector.getKeyguardManager().isDeviceSecure(newUserId),
- mInjector::dismissKeyguard,
- () -> await(
- // STEP 2 - If user switch ui was enabled, dismiss user switch dialog
- isUserSwitchUiEnabled,
- this::dismissUserSwitchDialog,
- () -> {
- // STEP 3 - Send REPORT_USER_SWITCH_COMPLETE_MSG to broadcast
- // ACTION_USER_SWITCHED & call UserSwitchObservers.onUserSwitchComplete
- mHandler.removeMessages(REPORT_USER_SWITCH_COMPLETE_MSG);
- mHandler.sendMessage(mHandler.obtainMessage(
- REPORT_USER_SWITCH_COMPLETE_MSG, oldUserId, newUserId));
- }
- ));
+ final Runnable sendUserSwitchCompleteMessage = () -> {
+ mHandler.removeMessages(REPORT_USER_SWITCH_COMPLETE_MSG);
+ mHandler.sendMessage(mHandler.obtainMessage(
+ REPORT_USER_SWITCH_COMPLETE_MSG, oldUserId, newUserId));
+ };
+ if (isUserSwitchUiEnabled()) {
+ if (mInjector.getKeyguardManager().isDeviceSecure(newUserId)) {
+ this.showKeyguard(() -> dismissUserSwitchDialog(sendUserSwitchCompleteMessage));
+ } else {
+ this.dismissKeyguard(() -> dismissUserSwitchDialog(sendUserSwitchCompleteMessage));
+ }
+ } else {
+ sendUserSwitchCompleteMessage.run();
+ }
}
- private void await(boolean condition, Consumer<Runnable> conditionalStep, Runnable nextStep) {
- if (condition) {
- conditionalStep.accept(nextStep);
- } else {
- nextStep.run();
- }
+ protected void showKeyguard(Runnable runnable) {
+ runWithTimeout(mInjector::showKeyguard, SHOW_KEYGUARD_TIMEOUT_MS, runnable, () -> {
+ throw new RuntimeException(
+ "Keyguard is not shown in " + SHOW_KEYGUARD_TIMEOUT_MS + " ms.");
+ }, "showKeyguard");
+ }
+
+ protected void dismissKeyguard(Runnable runnable) {
+ runWithTimeout(mInjector::dismissKeyguard, DISMISS_KEYGUARD_TIMEOUT_MS, runnable, runnable,
+ "dismissKeyguard");
+ }
+
+ private void runWithTimeout(Consumer<Runnable> task, int timeoutMs, Runnable onSuccess,
+ Runnable onTimeout, String traceMsg) {
+ final AtomicInteger state = new AtomicInteger(0); // state = 0 (RUNNING)
+
+ asyncTraceBegin(traceMsg, 0);
+
+ mHandler.postDelayed(() -> {
+ if (state.compareAndSet(0, 1)) { // state = 1 (TIMEOUT)
+ asyncTraceEnd(traceMsg, 0);
+ Slogf.w(TAG, "Timeout: %s did not finish in %d ms", traceMsg, timeoutMs);
+ onTimeout.run();
+ }
+ }, timeoutMs);
+
+ task.accept(() -> {
+ if (state.compareAndSet(0, 2)) { // state = 2 (SUCCESS)
+ asyncTraceEnd(traceMsg, 0);
+ onSuccess.run();
+ }
+ });
}
private void moveUserToForeground(UserState uss, int newUserId) {
@@ -3977,29 +3998,45 @@
return IStorageManager.Stub.asInterface(ServiceManager.getService("mount"));
}
- protected void dismissKeyguard(Runnable runnable) {
- final AtomicBoolean isFirst = new AtomicBoolean(true);
- final Runnable runOnce = () -> {
- if (isFirst.getAndSet(false)) {
- runnable.run();
- }
- };
+ protected void showKeyguard(Runnable runnable) {
+ if (getWindowManager().isKeyguardLocked()) {
+ runnable.run();
+ return;
+ }
+ getActivityTaskManagerInternal().registerScreenObserver(
+ new ActivityTaskManagerInternal.ScreenObserver() {
+ @Override
+ public void onAwakeStateChanged(boolean isAwake) {
- mHandler.postDelayed(runOnce, DISMISS_KEYGUARD_TIMEOUT_MS);
+ }
+
+ @Override
+ public void onKeyguardStateChanged(boolean isShowing) {
+ if (isShowing) {
+ getActivityTaskManagerInternal().unregisterScreenObserver(this);
+ runnable.run();
+ }
+ }
+ }
+ );
+ getWindowManager().lockDeviceNow();
+ }
+
+ protected void dismissKeyguard(Runnable runnable) {
getWindowManager().dismissKeyguard(new IKeyguardDismissCallback.Stub() {
@Override
public void onDismissError() throws RemoteException {
- mHandler.post(runOnce);
+ runnable.run();
}
@Override
public void onDismissSucceeded() throws RemoteException {
- mHandler.post(runOnce);
+ runnable.run();
}
@Override
public void onDismissCancelled() throws RemoteException {
- mHandler.post(runOnce);
+ runnable.run();
}
}, /* message= */ null);
}
@@ -4025,43 +4062,5 @@
void onSystemUserVisibilityChanged(boolean visible) {
getUserManagerInternal().onSystemUserVisibilityChanged(visible);
}
-
- void lockDeviceNowAndWaitForKeyguardShown() {
- if (getWindowManager().isKeyguardLocked()) {
- return;
- }
-
- final TimingsTraceAndSlog t = new TimingsTraceAndSlog();
- t.traceBegin("lockDeviceNowAndWaitForKeyguardShown");
-
- final CountDownLatch latch = new CountDownLatch(1);
- ActivityTaskManagerInternal.ScreenObserver screenObserver =
- new ActivityTaskManagerInternal.ScreenObserver() {
- @Override
- public void onAwakeStateChanged(boolean isAwake) {
-
- }
-
- @Override
- public void onKeyguardStateChanged(boolean isShowing) {
- if (isShowing) {
- latch.countDown();
- }
- }
- };
-
- getActivityTaskManagerInternal().registerScreenObserver(screenObserver);
- getWindowManager().lockDeviceNow();
- try {
- if (!latch.await(20, TimeUnit.SECONDS)) {
- throw new RuntimeException("Keyguard is not shown in 20 seconds");
- }
- } catch (InterruptedException e) {
- throw new RuntimeException(e);
- } finally {
- getActivityTaskManagerInternal().unregisterScreenObserver(screenObserver);
- t.traceEnd();
- }
- }
}
}
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index e59de6a..798aaee 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -1563,19 +1563,29 @@
private ArrayList<AppOpsManager.OpEntry> collectOps(Ops pkgOps, int[] ops,
String persistentDeviceId) {
ArrayList<AppOpsManager.OpEntry> resOps = null;
+ boolean shouldReturnRestrictedAppOps = mContext.checkPermission(
+ Manifest.permission.GET_APP_OPS_STATS,
+ Binder.getCallingPid(), Binder.getCallingUid())
+ == PackageManager.PERMISSION_GRANTED;
if (ops == null) {
resOps = new ArrayList<>();
- for (int j=0; j<pkgOps.size(); j++) {
+ for (int j = 0; j < pkgOps.size(); j++) {
Op curOp = pkgOps.valueAt(j);
+ if (opRestrictsRead(curOp.op) && !shouldReturnRestrictedAppOps) {
+ continue;
+ }
resOps.add(getOpEntryForResult(curOp, persistentDeviceId));
}
} else {
- for (int j=0; j<ops.length; j++) {
+ for (int j = 0; j < ops.length; j++) {
Op curOp = pkgOps.get(ops[j]);
if (curOp != null) {
if (resOps == null) {
resOps = new ArrayList<>();
}
+ if (opRestrictsRead(curOp.op) && !shouldReturnRestrictedAppOps) {
+ continue;
+ }
resOps.add(getOpEntryForResult(curOp, persistentDeviceId));
}
}
@@ -4244,10 +4254,21 @@
private void verifyIncomingOp(int op) {
if (op >= 0 && op < AppOpsManager._NUM_OP) {
- // Enforce manage appops permission if it's a restricted read op.
+ // Enforce privileged appops permission if it's a restricted read op.
if (opRestrictsRead(op)) {
- mContext.enforcePermission(Manifest.permission.MANAGE_APPOPS,
- Binder.getCallingPid(), Binder.getCallingUid(), "verifyIncomingOp");
+ if (!(mContext.checkPermission(Manifest.permission.MANAGE_APPOPS,
+ Binder.getCallingPid(), Binder.getCallingUid())
+ == PackageManager.PERMISSION_GRANTED || mContext.checkPermission(
+ Manifest.permission.GET_APP_OPS_STATS,
+ Binder.getCallingPid(), Binder.getCallingUid())
+ == PackageManager.PERMISSION_GRANTED || mContext.checkPermission(
+ Manifest.permission.MANAGE_APP_OPS_MODES,
+ Binder.getCallingPid(), Binder.getCallingUid())
+ == PackageManager.PERMISSION_GRANTED)) {
+ throw new SecurityException("verifyIncomingOp: uid " + Binder.getCallingUid()
+ + " does not have any of {MANAGE_APPOPS, GET_APP_OPS_STATS, "
+ + "MANAGE_APP_OPS_MODES}");
+ }
}
return;
}
diff --git a/services/core/java/com/android/server/appop/AttributedOp.java b/services/core/java/com/android/server/appop/AttributedOp.java
index 94baf88..2285826 100644
--- a/services/core/java/com/android/server/appop/AttributedOp.java
+++ b/services/core/java/com/android/server/appop/AttributedOp.java
@@ -24,6 +24,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.AppOpsManager;
+import android.companion.virtual.VirtualDeviceManager;
import android.os.IBinder;
import android.os.Process;
import android.os.RemoteException;
@@ -134,7 +135,7 @@
AppOpsManager.OpEventProxyInfo proxyInfo = null;
if (proxyUid != Process.INVALID_UID) {
proxyInfo = mAppOpsService.mOpEventProxyInfoPool.acquire(proxyUid, proxyPackageName,
- proxyAttributionTag);
+ proxyAttributionTag, VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT);
}
AppOpsManager.NoteOpEvent existingEvent = mAccessEvents.get(key);
@@ -855,7 +856,7 @@
AppOpsManager.OpEventProxyInfo proxyInfo = null;
if (proxyUid != Process.INVALID_UID) {
proxyInfo = mOpEventProxyInfoPool.acquire(proxyUid, proxyPackageName,
- proxyAttributionTag);
+ proxyAttributionTag, VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT);
}
if (recycled != null) {
@@ -881,10 +882,11 @@
AppOpsManager.OpEventProxyInfo acquire(@IntRange(from = 0) int uid,
@Nullable String packageName,
- @Nullable String attributionTag) {
+ @Nullable String attributionTag,
+ @Nullable String deviceId) {
AppOpsManager.OpEventProxyInfo recycled = acquire();
if (recycled != null) {
- recycled.reinit(uid, packageName, attributionTag);
+ recycled.reinit(uid, packageName, attributionTag, deviceId);
return recycled;
}
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 77654d4..da528a2 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -1772,6 +1772,7 @@
@Override
public void handleMessage(Message msg) {
+ int muteCheckDelayMs = BTA2DP_MUTE_CHECK_DELAY_MS;
switch (msg.what) {
case MSG_RESTORE_DEVICES:
synchronized (mSetModeLock) {
@@ -1870,7 +1871,7 @@
btInfo.mDevice, btInfo.mProfile, btInfo.mIsLeOutput,
"MSG_L_BLUETOOTH_DEVICE_CONFIG_CHANGE");
synchronized (mDeviceStateLock) {
- mDeviceInventory.onBluetoothDeviceConfigChange(btInfo,
+ muteCheckDelayMs += mDeviceInventory.onBluetoothDeviceConfigChange(btInfo,
codecAndChanged.first, codecAndChanged.second,
BtHelper.EVENT_DEVICE_CONFIG_CHANGE);
}
@@ -2060,7 +2061,7 @@
// Give some time to Bluetooth service to post a connection message
// in case of active device switch
if (MESSAGES_MUTE_MUSIC.contains(msg.what)) {
- sendMsg(MSG_CHECK_MUTE_MUSIC, SENDMSG_REPLACE, BTA2DP_MUTE_CHECK_DELAY_MS);
+ sendMsg(MSG_CHECK_MUTE_MUSIC, SENDMSG_REPLACE, muteCheckDelayMs);
}
if (isMessageHandledUnderWakelock(msg.what)) {
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index 9bdc51e..c9612ca 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -864,9 +864,25 @@
}
}
+ // Additional delay added to the music mute duration when a codec config change is executed.
+ static final int BT_CONFIG_CHANGE_MUTE_DELAY_MS = 500;
+ /**
+ * Handles a Bluetooth link codec configuration change communicated by the Bluetooth stack.
+ * Called when either A2DP or LE Audio codec encoding or sampling rate changes:
+ * the change is communicated to native audio policy to eventually reconfigure the audio
+ * path.
+ * Also used to notify a change in preferred mode (duplex or output) for Bluetooth profiles.
+ *
+ * @param btInfo contains all information on the Bluetooth device and profile
+ * @param codec the requested audio encoding (e.g SBC)
+ * @param codecChanged true if a codec parameter changed, false for preferred mode change
+ * @param event currently only EVENT_DEVICE_CONFIG_CHANGE
+ * @return an optional additional delay in milliseconds to add to the music mute period in
+ * case of an actual codec reconfiguration.
+ */
@GuardedBy("mDeviceBroker.mDeviceStateLock")
- /*package*/ void onBluetoothDeviceConfigChange(
+ /*package*/ int onBluetoothDeviceConfigChange(
@NonNull AudioDeviceBroker.BtDeviceInfo btInfo,
@AudioSystem.AudioFormatNativeEnumForBtCodec int codec,
boolean codecChanged, int event) {
@@ -874,10 +890,11 @@
+ "onBluetoothDeviceConfigChange")
.set(MediaMetrics.Property.EVENT, BtHelper.deviceEventToString(event));
+ int delayMs = 0;
final BluetoothDevice btDevice = btInfo.mDevice;
if (btDevice == null) {
mmi.set(MediaMetrics.Property.EARLY_RETURN, "btDevice null").record();
- return;
+ return delayMs;
}
if (AudioService.DEBUG_DEVICES) {
Log.d(TAG, "onBluetoothDeviceConfigChange btDevice=" + btDevice);
@@ -899,7 +916,7 @@
.printSlog(EventLogger.Event.ALOGI, TAG));
mmi.set(MediaMetrics.Property.EARLY_RETURN, "A2dp config change ignored")
.record();
- return;
+ return delayMs;
}
final String key = DeviceInfo.makeDeviceListKey(
AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address);
@@ -907,7 +924,7 @@
if (di == null) {
Log.e(TAG, "invalid null DeviceInfo in onBluetoothDeviceConfigChange");
mmi.set(MediaMetrics.Property.EARLY_RETURN, "null DeviceInfo").record();
- return;
+ return delayMs;
}
mmi.set(MediaMetrics.Property.ADDRESS, address)
@@ -915,7 +932,6 @@
.set(MediaMetrics.Property.INDEX, volume)
.set(MediaMetrics.Property.NAME, di.mDeviceName);
-
if (event == BtHelper.EVENT_DEVICE_CONFIG_CHANGE) {
if (btInfo.mProfile == BluetoothProfile.A2DP
|| btInfo.mProfile == BluetoothProfile.LE_AUDIO
@@ -943,6 +959,7 @@
+ address
+ " codec=" + AudioSystem.audioFormatToString(codec))
.printSlog(EventLogger.Event.ALOGI, TAG));
+ delayMs = BT_CONFIG_CHANGE_MUTE_DELAY_MS;
}
}
}
@@ -952,6 +969,7 @@
}
}
mmi.record();
+ return delayMs;
}
/*package*/ void onMakeA2dpDeviceUnavailableNow(String address, int a2dpCodec) {
diff --git a/services/core/java/com/android/server/audio/AudioManagerShellCommand.java b/services/core/java/com/android/server/audio/AudioManagerShellCommand.java
index d669c8d..030ce12 100644
--- a/services/core/java/com/android/server/audio/AudioManagerShellCommand.java
+++ b/services/core/java/com/android/server/audio/AudioManagerShellCommand.java
@@ -61,6 +61,8 @@
return getSoundDoseValue();
case "reset-sound-dose-timeout":
return resetSoundDoseTimeout();
+ case "set-ringer-mode":
+ return setRingerMode();
case "set-volume":
return setVolume();
case "set-device-volume":
@@ -100,6 +102,8 @@
pw.println(" Returns the current sound dose value");
pw.println(" reset-sound-dose-timeout");
pw.println(" Resets the sound dose timeout used for momentary exposure");
+ pw.println(" set-ringer-mode NORMAL|SILENT|VIBRATE");
+ pw.println(" Sets the Ringer mode to one of NORMAL|SILENT|VIBRATE");
pw.println(" set-volume STREAM_TYPE VOLUME_INDEX");
pw.println(" Sets the volume for STREAM_TYPE to VOLUME_INDEX");
pw.println(" set-device-volume STREAM_TYPE VOLUME_INDEX NATIVE_DEVICE_TYPE");
@@ -150,6 +154,34 @@
return 0;
}
+ private int setRingerMode() {
+ String ringerModeText = getNextArg();
+ if (ringerModeText == null) {
+ getErrPrintWriter().println("Error: no ringer mode specified");
+ return 1;
+ }
+
+ final int ringerMode = getRingerMode(ringerModeText);
+ if (!AudioManager.isValidRingerMode(ringerMode)) {
+ getErrPrintWriter().println(
+ "Error: invalid value of ringerMode, should be one of NORMAL|SILENT|VIBRATE");
+ return 1;
+ }
+
+ final AudioManager am = mService.mContext.getSystemService(AudioManager.class);
+ am.setRingerModeInternal(ringerMode);
+ return 0;
+ }
+
+ private int getRingerMode(String ringerModeText) {
+ return switch (ringerModeText) {
+ case "NORMAL" -> AudioManager.RINGER_MODE_NORMAL;
+ case "VIBRATE" -> AudioManager.RINGER_MODE_VIBRATE;
+ case "SILENT" -> AudioManager.RINGER_MODE_SILENT;
+ default -> -1;
+ };
+ }
+
private int getIsSurroundFormatEnabled() {
String surroundFormatText = getNextArg();
diff --git a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
index 28af222..d26ba48 100644
--- a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
+++ b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
@@ -26,6 +26,8 @@
import static android.media.AudioPlaybackConfiguration.PLAYER_PIID_INVALID;
import static android.media.AudioPlaybackConfiguration.PLAYER_UPDATE_MUTED;
+import static com.android.media.audio.Flags.portToPiidSimplification;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
@@ -152,6 +154,8 @@
new HashMap<Integer, AudioPlaybackConfiguration>();
@GuardedBy("mPlayerLock")
+ private final SparseIntArray mPiidToPortId = new SparseIntArray();
+ @GuardedBy("mPlayerLock")
private final SparseIntArray mPortIdToPiid = new SparseIntArray();
private final Context mContext;
@@ -369,7 +373,11 @@
sEventLogger.enqueue(new PlayerEvent(piid, event, eventValue));
if (event == AudioPlaybackConfiguration.PLAYER_UPDATE_PORT_ID) {
- mPortIdToPiid.put(eventValue, piid);
+ if (portToPiidSimplification()) {
+ mPiidToPortId.put(piid, eventValue);
+ } else {
+ mPortIdToPiid.put(eventValue, piid);
+ }
return;
} else if (event == AudioPlaybackConfiguration.PLAYER_STATE_STARTED) {
for (Integer uidInteger: mBannedUids) {
@@ -430,10 +438,20 @@
}
synchronized (mPlayerLock) {
- int piid = mPortIdToPiid.get(portId, PLAYER_PIID_INVALID);
- if (piid == PLAYER_PIID_INVALID) {
- Log.w(TAG, "No piid assigned for invalid/internal port id " + portId);
- return;
+ int piid;
+ if (portToPiidSimplification()) {
+ int idxOfPiid = mPiidToPortId.indexOfValue(portId);
+ if (idxOfPiid < 0) {
+ Log.w(TAG, "No piid assigned for invalid/internal port id " + portId);
+ return;
+ }
+ piid = mPiidToPortId.keyAt(idxOfPiid);
+ } else {
+ piid = mPortIdToPiid.get(portId, PLAYER_PIID_INVALID);
+ if (piid == PLAYER_PIID_INVALID) {
+ Log.w(TAG, "No piid assigned for invalid/internal port id " + portId);
+ return;
+ }
}
final AudioPlaybackConfiguration apc = mPlayers.get(piid);
if (apc == null) {
@@ -489,10 +507,14 @@
change = apc.handleStateEvent(AudioPlaybackConfiguration.PLAYER_STATE_RELEASED,
AudioPlaybackConfiguration.PLAYER_DEVICEID_INVALID);
- // remove all port ids mapped to the released player
- int portIdx;
- while ((portIdx = mPortIdToPiid.indexOfValue(piid)) >= 0) {
- mPortIdToPiid.removeAt(portIdx);
+ if (portToPiidSimplification()) {
+ mPiidToPortId.delete(piid);
+ } else {
+ // remove all port ids mapped to the released player
+ int portIdx;
+ while ((portIdx = mPortIdToPiid.indexOfValue(piid)) >= 0) {
+ mPortIdToPiid.removeAt(portIdx);
+ }
}
if (change && mDoNotLogPiidList.contains(piid)) {
@@ -511,10 +533,17 @@
new EventLogger.StringEvent(
"clear port id to piid map"));
synchronized (mPlayerLock) {
- if (DEBUG) {
- Log.v(TAG, "clear port id to piid map:\n" + mPortIdToPiid);
+ if (portToPiidSimplification()) {
+ if (DEBUG) {
+ Log.v(TAG, "clear piid to portId map:\n" + mPiidToPortId);
+ }
+ mPiidToPortId.clear();
+ } else {
+ if (DEBUG) {
+ Log.v(TAG, "clear port id to piid map:\n" + mPortIdToPiid);
+ }
+ mPortIdToPiid.clear();
}
- mPortIdToPiid.clear();
}
}
@@ -674,12 +703,21 @@
pw.print(" " + piid);
}
pw.println("\n");
- // portId to piid mappings:
- pw.println("\n current portId to piid map:");
- for (int i = 0; i < mPortIdToPiid.size(); ++i) {
- pw.println(
- " portId: " + mPortIdToPiid.keyAt(i) + " piid: " + mPortIdToPiid.valueAt(
- i));
+ if (portToPiidSimplification()) {
+ // portId to piid mappings:
+ pw.println("\n current piid to portId map:");
+ for (int i = 0; i < mPiidToPortId.size(); ++i) {
+ pw.println(
+ " piid: " + mPiidToPortId.keyAt(i) + " portId: "
+ + mPiidToPortId.valueAt(i));
+ }
+ } else {
+ // portId to piid mappings:
+ pw.println("\n current portId to piid map:");
+ for (int i = 0; i < mPortIdToPiid.size(); ++i) {
+ pw.println(" portId: " + mPortIdToPiid.keyAt(i) + " piid: "
+ + mPortIdToPiid.valueAt(i));
+ }
}
pw.println("\n");
// log
diff --git a/services/core/java/com/android/server/biometrics/biometrics.aconfig b/services/core/java/com/android/server/biometrics/biometrics.aconfig
index 92fd9cb..712dcee 100644
--- a/services/core/java/com/android/server/biometrics/biometrics.aconfig
+++ b/services/core/java/com/android/server/biometrics/biometrics.aconfig
@@ -14,3 +14,10 @@
description: "This flag controls whether virtual HAL is used for testing instead of TestHal "
bug: "294254230"
}
+
+flag {
+ name: "mandatory_biometrics"
+ namespace: "biometrics_framework"
+ description: "This flag controls whether LSKF fallback is removed from biometric prompt when the phone is outside trusted locations"
+ bug: "322081563"
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
index 559462a..b0e7575 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
@@ -218,6 +218,7 @@
}
@VisibleForTesting @Nullable protected AidlSession getSessionForUser(int userId) {
+ Slog.d(TAG, "getSessionForUser: mCurrentSession: " + mCurrentSession);
if (mCurrentSession != null && mCurrentSession.getUserId() == userId) {
return mCurrentSession;
} else {
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
index b7e3f70..1c6dfe0 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
@@ -242,6 +242,7 @@
}
@Nullable protected AidlSession getSessionForUser(int userId) {
+ Slog.d(TAG, "getSessionForUser: mCurrentSession: " + mCurrentSession);
if (mCurrentSession != null && mCurrentSession.getUserId() == userId) {
return mCurrentSession;
} else {
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 68e2bd6..7106e89 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -2764,21 +2764,9 @@
display.setHasContentLocked(hasContent);
shouldScheduleTraversal = true;
}
- if (requestedModeId == 0 && requestedRefreshRate != 0) {
- // Scan supported modes returned by display.getInfo() to find a mode with the same
- // size as the default display mode but with the specified refresh rate instead.
- Display.Mode mode = display.getDisplayInfoLocked().findDefaultModeByRefreshRate(
- requestedRefreshRate);
- if (mode != null) {
- requestedModeId = mode.getModeId();
- } else {
- Slog.e(TAG, "Couldn't find a mode for the requestedRefreshRate: "
- + requestedRefreshRate + " on Display: " + displayId);
- }
- }
- mDisplayModeDirector.getAppRequestObserver().setAppRequest(
- displayId, requestedModeId, requestedMinRefreshRate, requestedMaxRefreshRate);
+ mDisplayModeDirector.getAppRequestObserver().setAppRequest(displayId, requestedModeId,
+ requestedRefreshRate, requestedMinRefreshRate, requestedMaxRefreshRate);
// TODO(b/202378408) set minimal post-processing only if it's supported once we have a
// separate API for disabling on-device processing.
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 d3de71e..cd07f5a 100644
--- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
+++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
@@ -159,6 +159,11 @@
Flags::enablePeakRefreshRatePhysicalLimit
);
+ private final FlagState mIgnoreAppPreferredRefreshRate = new FlagState(
+ Flags.FLAG_IGNORE_APP_PREFERRED_REFRESH_RATE_REQUEST,
+ Flags::ignoreAppPreferredRefreshRateRequest
+ );
+
/**
* @return {@code true} if 'port' is allowed in display layout configuration file.
*/
@@ -322,6 +327,13 @@
}
/**
+ * @return Whether to ignore preferredRefreshRate app request or not
+ */
+ public boolean ignoreAppPreferredRefreshRateRequest() {
+ return mIgnoreAppPreferredRefreshRate.isEnabled();
+ }
+
+ /**
* dumps all flagstates
* @param pw printWriter
*/
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 3d64fc2..a15a8e8 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
@@ -255,3 +255,14 @@
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "ignore_app_preferred_refresh_rate_request"
+ namespace: "display_manager"
+ description: "Feature flag for DisplayManager to ignore preferred refresh rate app request. It will be handled by SF only."
+ bug: "330810426"
+ is_fixed_read_only: true
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
index 3084dae..91bd80e 100644
--- a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
@@ -222,7 +222,7 @@
displayManagerFlags.isRefreshRateVotingTelemetryEnabled());
mSupportedModesByDisplay = new SparseArray<>();
mDefaultModeByDisplay = new SparseArray<>();
- mAppRequestObserver = new AppRequestObserver();
+ mAppRequestObserver = new AppRequestObserver(displayManagerFlags);
mConfigParameterProvider = new DeviceConfigParameterProvider(injector.getDeviceConfig());
mDeviceConfigDisplaySettings = new DeviceConfigDisplaySettings();
mSettingsObserver = new SettingsObserver(context, handler, displayManagerFlags);
@@ -1205,17 +1205,32 @@
public final class AppRequestObserver {
private final SparseArray<Display.Mode> mAppRequestedModeByDisplay;
private final SparseArray<RefreshRateRange> mAppPreferredRefreshRateRangeByDisplay;
+ private final boolean mIgnorePreferredRefreshRate;
- AppRequestObserver() {
+ AppRequestObserver(DisplayManagerFlags flags) {
mAppRequestedModeByDisplay = new SparseArray<>();
mAppPreferredRefreshRateRangeByDisplay = new SparseArray<>();
+ mIgnorePreferredRefreshRate = flags.ignoreAppPreferredRefreshRateRequest();
}
/**
* Sets refresh rates from app request
*/
- public void setAppRequest(int displayId, int modeId,
+ public void setAppRequest(int displayId, int modeId, float requestedRefreshRate,
float requestedMinRefreshRateRange, float requestedMaxRefreshRateRange) {
+
+ if (modeId == 0 && requestedRefreshRate != 0 && !mIgnorePreferredRefreshRate) {
+ // Scan supported modes returned to find a mode with the same
+ // size as the default display mode but with the specified refresh rate instead.
+ Display.Mode mode = findDefaultModeByRefreshRate(displayId, requestedRefreshRate);
+ if (mode != null) {
+ modeId = mode.getModeId();
+ } else {
+ Slog.e(TAG, "Couldn't find a mode for the requestedRefreshRate: "
+ + requestedRefreshRate + " on Display: " + displayId);
+ }
+ }
+
synchronized (mLock) {
setAppRequestedModeLocked(displayId, modeId);
setAppPreferredRefreshRateRangeLocked(displayId, requestedMinRefreshRateRange,
@@ -1223,6 +1238,23 @@
}
}
+ @Nullable
+ private Display.Mode findDefaultModeByRefreshRate(int displayId, float refreshRate) {
+ Display.Mode[] modes;
+ Display.Mode defaultMode;
+ synchronized (mLock) {
+ modes = mSupportedModesByDisplay.get(displayId);
+ defaultMode = mDefaultModeByDisplay.get(displayId);
+ }
+ for (int i = 0; i < modes.length; i++) {
+ if (modes[i].matches(defaultMode.getPhysicalWidth(),
+ defaultMode.getPhysicalHeight(), refreshRate)) {
+ return modes[i];
+ }
+ }
+ return null;
+ }
+
private void setAppRequestedModeLocked(int displayId, int modeId) {
final Display.Mode requestedMode = findModeByIdLocked(displayId, modeId);
if (Objects.equals(requestedMode, mAppRequestedModeByDisplay.get(displayId))) {
diff --git a/services/core/java/com/android/server/dreams/Android.bp b/services/core/java/com/android/server/dreams/Android.bp
new file mode 100644
index 0000000..4078a42
--- /dev/null
+++ b/services/core/java/com/android/server/dreams/Android.bp
@@ -0,0 +1,11 @@
+aconfig_declarations {
+ name: "dreams_flags",
+ package: "com.android.server.dreams",
+ container: "system",
+ srcs: ["*.aconfig"],
+}
+
+java_aconfig_library {
+ name: "dreams_flags_lib",
+ aconfig_declarations: "dreams_flags",
+}
diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java
index fc63494..2def5ae 100644
--- a/services/core/java/com/android/server/dreams/DreamManagerService.java
+++ b/services/core/java/com/android/server/dreams/DreamManagerService.java
@@ -45,6 +45,7 @@
import android.hardware.display.AmbientDisplayConfiguration;
import android.net.Uri;
import android.os.BatteryManager;
+import android.os.BatteryManagerInternal;
import android.os.Binder;
import android.os.Build;
import android.os.Handler;
@@ -118,6 +119,7 @@
private final DreamController mController;
private final PowerManager mPowerManager;
private final PowerManagerInternal mPowerManagerInternal;
+ private final BatteryManagerInternal mBatteryManagerInternal;
private final PowerManager.WakeLock mDozeWakeLock;
private final ActivityTaskManagerInternal mAtmInternal;
private final PackageManagerInternal mPmInternal;
@@ -186,7 +188,11 @@
private final BroadcastReceiver mChargingReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- mIsCharging = (BatteryManager.ACTION_CHARGING.equals(intent.getAction()));
+ if (Flags.useBatteryChangedBroadcast()) {
+ mIsCharging = mBatteryManagerInternal.isPowered(BatteryManager.BATTERY_PLUGGED_ANY);
+ } else {
+ mIsCharging = (BatteryManager.ACTION_CHARGING.equals(intent.getAction()));
+ }
}
};
@@ -251,6 +257,12 @@
com.android.internal.R.bool.config_keepDreamingWhenUnplugging);
mDreamsDisabledByAmbientModeSuppressionConfig = mContext.getResources().getBoolean(
com.android.internal.R.bool.config_dreamsDisabledByAmbientModeSuppressionConfig);
+
+ if (Flags.useBatteryChangedBroadcast()) {
+ mBatteryManagerInternal = getLocalService(BatteryManagerInternal.class);
+ } else {
+ mBatteryManagerInternal = null;
+ }
}
@Override
@@ -279,9 +291,15 @@
mContext.registerReceiver(
mDockStateReceiver, new IntentFilter(Intent.ACTION_DOCK_EVENT));
+
IntentFilter chargingIntentFilter = new IntentFilter();
- chargingIntentFilter.addAction(BatteryManager.ACTION_CHARGING);
- chargingIntentFilter.addAction(BatteryManager.ACTION_DISCHARGING);
+ if (Flags.useBatteryChangedBroadcast()) {
+ chargingIntentFilter.addAction(Intent.ACTION_BATTERY_CHANGED);
+ chargingIntentFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
+ } else {
+ chargingIntentFilter.addAction(BatteryManager.ACTION_CHARGING);
+ chargingIntentFilter.addAction(BatteryManager.ACTION_DISCHARGING);
+ }
mContext.registerReceiver(mChargingReceiver, chargingIntentFilter);
mSettingsObserver = new SettingsObserver(mHandler);
diff --git a/services/core/java/com/android/server/dreams/flags.aconfig b/services/core/java/com/android/server/dreams/flags.aconfig
new file mode 100644
index 0000000..5d35ebd
--- /dev/null
+++ b/services/core/java/com/android/server/dreams/flags.aconfig
@@ -0,0 +1,12 @@
+package: "com.android.server.dreams"
+container: "system"
+
+flag {
+ name: "use_battery_changed_broadcast"
+ namespace: "communal"
+ description: "Use ACTION_BATTERY_CHANGED broadcast to track charging state"
+ bug: "329125239"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionBackupHelper.java b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionBackupHelper.java
index d494be5..e91de37 100644
--- a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionBackupHelper.java
+++ b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionBackupHelper.java
@@ -123,8 +123,7 @@
* Returns the system-gender to be backed up as a data-blob.
*/
public byte[] getSystemBackupPayload(int userId) {
- int gender = mGrammaticalGenderService.getSystemGrammaticalGender(mAttributionSource,
- userId);
+ int gender = mGrammaticalGenderService.getSystemGrammaticalGender(userId);
return intToByteArray(gender);
}
@@ -167,7 +166,7 @@
BackupManager.dataChanged(SYSTEM_BACKUP_PACKAGE_KEY);
}
- private byte[] convertToByteArray(HashMap<String, Integer> pkgGenderInfo) {
+ private static byte[] convertToByteArray(HashMap<String, Integer> pkgGenderInfo) {
try (final ByteArrayOutputStream out = new ByteArrayOutputStream();
final ObjectOutputStream objStream = new ObjectOutputStream(out)) {
objStream.writeObject(pkgGenderInfo);
@@ -178,22 +177,22 @@
}
}
- private byte[] intToByteArray(final int gender) {
+ private static byte[] intToByteArray(final int gender) {
ByteBuffer bb = ByteBuffer.allocate(4);
bb.putInt(gender);
return bb.array();
}
- private int convertByteArrayToInt(byte[] intBytes) {
+ private static int convertByteArrayToInt(byte[] intBytes) {
ByteBuffer byteBuffer = ByteBuffer.wrap(intBytes);
return byteBuffer.getInt();
}
- private HashMap<String, Integer> readFromByteArray(byte[] payload) {
+ private static HashMap<String, Integer> readFromByteArray(byte[] payload) {
HashMap<String, Integer> data = new HashMap<>();
- try (ByteArrayInputStream byteIn = new ByteArrayInputStream(payload);
- ObjectInputStream in = new ObjectInputStream(byteIn)) {
+ try (var byteIn = new ByteArrayInputStream(payload);
+ var in = new ObjectInputStream(byteIn)) {
data = (HashMap<String, Integer>) in.readObject();
} catch (IOException | ClassNotFoundException e) {
Log.e(TAG, "cannot convert payload to HashMap.", e);
@@ -205,10 +204,10 @@
private void cleanStagedDataForOldEntries() {
for (int i = 0; i < mCache.size(); i++) {
int userId = mCache.keyAt(i);
- StagedData stagedData = mCache.get(userId);
+ StagedData stagedData = mCache.valueAt(userId);
if (stagedData.mCreationTimeMillis
< mClock.millis() - STAGE_DATA_RETENTION_PERIOD.toMillis()) {
- mCache.remove(userId);
+ mCache.removeAt(i--);
}
}
}
diff --git a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionManagerInternal.java b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionManagerInternal.java
index 2816d08..7eb971c 100644
--- a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionManagerInternal.java
+++ b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionManagerInternal.java
@@ -16,7 +16,6 @@
package com.android.server.grammaticalinflection;
-import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.res.Configuration;
@@ -41,7 +40,7 @@
public abstract void stageAndApplyRestoredPayload(byte[] payload, int userId);
/**
- * Get the current system grammatical gender of privileged application.
+ * Get the current system grammatical gender for the particular user.
*
* @return the value of grammatical gender
*
@@ -50,18 +49,25 @@
public abstract @Configuration.GrammaticalGender int getSystemGrammaticalGender(int userId);
/**
- * Retrieve the system grammatical gender.
+ * Get the final merged value of the global grammatical gender, user- or devsettings-set.
*
* @return the value of grammatical gender
*
*/
- public abstract @Configuration.GrammaticalGender int retrieveSystemGrammaticalGender(
- @NonNull Configuration configuration);
+ public abstract @Configuration.GrammaticalGender int mergedFinalSystemGrammaticalGender();
+
+ /**
+ * Get the grammatical gender from developer settings global override.
+ *
+ * @return the value of grammatical gender
+ */
+ public abstract
+ @Configuration.GrammaticalGender int getGrammaticalGenderFromDeveloperSettings();
/**
* Whether the package can get the system grammatical gender or not.
*/
- public abstract boolean canGetSystemGrammaticalGender(int uid, @Nullable String packageName);
+ public abstract boolean canGetSystemGrammaticalGender(int uid);
/**
@@ -74,4 +80,3 @@
*/
public abstract void applyRestoredSystemPayload(byte[] payload, int userId);
}
-
diff --git a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java
index 93a71b9..e242164 100644
--- a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java
+++ b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java
@@ -17,7 +17,6 @@
package com.android.server.grammaticalinflection;
import static android.app.Flags.systemTermsOfAddressEnabled;
-import static android.content.res.Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED;
import static com.android.server.grammaticalinflection.GrammaticalInflectionUtils.checkSystemGrammaticalGenderPermission;
@@ -43,6 +42,7 @@
import android.util.SparseIntArray;
import android.util.Xml;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.XmlUtils;
import com.android.modules.utils.TypedXmlPullParser;
@@ -126,7 +126,7 @@
@Override
public void setSystemWideGrammaticalGender(int grammaticalGender, int userId) {
- isCallerAllowed();
+ enforceCallerPermissions();
GrammaticalInflectionService.this.setSystemWideGrammaticalGender(grammaticalGender,
userId);
}
@@ -138,18 +138,16 @@
+ " does not have READ_SYSTEM_GRAMMATICAL_GENDER permission.");
}
return checkSystemTermsOfAddressIsEnabled()
- ? GrammaticalInflectionService.this.getSystemGrammaticalGender(
- attributionSource, userId)
- : GRAMMATICAL_GENDER_NOT_SPECIFIED;
+ ? GrammaticalInflectionService.this.getSystemGrammaticalGender(userId)
+ : Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED;
}
@Override
public int peekSystemGrammaticalGenderByUserId(AttributionSource attributionSource,
int userId) {
return canGetSystemGrammaticalGender(attributionSource)
- ? GrammaticalInflectionService.this.getSystemGrammaticalGender(
- attributionSource, userId)
- : GRAMMATICAL_GENDER_NOT_SPECIFIED;
+ ? GrammaticalInflectionService.this.getSystemGrammaticalGender(userId)
+ : Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED;
}
@Override
@@ -163,11 +161,10 @@
private final class GrammaticalInflectionManagerInternalImpl
extends GrammaticalInflectionManagerInternal {
-
@Override
@Nullable
public byte[] getBackupPayload(int userId) {
- isCallerAllowed();
+ enforceCallerPermissions();
return mBackupHelper.getBackupPayload(userId);
}
@@ -179,7 +176,7 @@
@Override
@Nullable
public byte[] getSystemBackupPayload(int userId) {
- isCallerAllowed();
+ enforceCallerPermissions();
return mBackupHelper.getSystemBackupPayload(userId);
}
@@ -191,30 +188,35 @@
@Override
public int getSystemGrammaticalGender(int userId) {
return checkSystemTermsOfAddressIsEnabled()
- ? GrammaticalInflectionService.this.getSystemGrammaticalGender(
- mContext.getAttributionSource(), userId)
- : GRAMMATICAL_GENDER_NOT_SPECIFIED;
+ ? GrammaticalInflectionService.this.getSystemGrammaticalGender(userId)
+ : Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED;
}
@Override
- public int retrieveSystemGrammaticalGender(Configuration configuration) {
+ public int mergedFinalSystemGrammaticalGender() {
int systemGrammaticalGender = getSystemGrammaticalGender(mContext.getUserId());
// Retrieve the grammatical gender from system property, set it into
// configuration which will get updated later if the grammatical gender raw value of
// current configuration is {@link Configuration#GRAMMATICAL_GENDER_UNDEFINED}.
- if (configuration.getGrammaticalGenderRaw()
- == Configuration.GRAMMATICAL_GENDER_UNDEFINED
- || systemGrammaticalGender <= Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED) {
- systemGrammaticalGender = SystemProperties.getInt(GRAMMATICAL_GENDER_PROPERTY,
- Configuration.GRAMMATICAL_GENDER_UNDEFINED);
+ if (systemGrammaticalGender == Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED) {
+ systemGrammaticalGender = getGrammaticalGenderFromDeveloperSettings();
}
- return systemGrammaticalGender;
+ return systemGrammaticalGender == Configuration.GRAMMATICAL_GENDER_UNDEFINED
+ ? Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED : systemGrammaticalGender;
}
@Override
- public boolean canGetSystemGrammaticalGender(int uid, String packageName) {
- AttributionSource attributionSource = new AttributionSource.Builder(
- uid).setPackageName(packageName).build();
+ public int getGrammaticalGenderFromDeveloperSettings() {
+ return SystemProperties.getInt(GRAMMATICAL_GENDER_PROPERTY,
+ Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED);
+ }
+
+ @Override
+ public boolean canGetSystemGrammaticalGender(int uid) {
+ if (uid == Process.SYSTEM_UID) {
+ return true;
+ }
+ var attributionSource = new AttributionSource.Builder(uid).build();
return GrammaticalInflectionService.this.canGetSystemGrammaticalGender(
attributionSource);
}
@@ -225,7 +227,7 @@
mActivityTaskManagerInternal.getApplicationConfig(appPackageName, userId);
if (appConfig == null || appConfig.mGrammaticalGender == null) {
- return GRAMMATICAL_GENDER_NOT_SPECIFIED;
+ return Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED;
} else {
return appConfig.mGrammaticalGender;
}
@@ -239,9 +241,10 @@
userId);
if (!SystemProperties.getBoolean(GRAMMATICAL_INFLECTION_ENABLED, true)) {
- if (preValue != GRAMMATICAL_GENDER_NOT_SPECIFIED) {
+ if (preValue != Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED) {
Log.d(TAG, "Clearing the user's grammatical gender setting");
- updater.setGrammaticalGender(GRAMMATICAL_GENDER_NOT_SPECIFIED).commit();
+ updater.setGrammaticalGender(
+ Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED).commit();
}
return;
}
@@ -250,49 +253,48 @@
FrameworkStatsLog.write(FrameworkStatsLog.APPLICATION_GRAMMATICAL_INFLECTION_CHANGED,
FrameworkStatsLog.APPLICATION_GRAMMATICAL_INFLECTION_CHANGED__SOURCE_ID__OTHERS,
uid,
- gender != GRAMMATICAL_GENDER_NOT_SPECIFIED,
- preValue != GRAMMATICAL_GENDER_NOT_SPECIFIED);
+ gender != Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED,
+ preValue != Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED);
updater.setGrammaticalGender(gender).commit();
}
protected void setSystemWideGrammaticalGender(int grammaticalGender, int userId) {
- Trace.beginSection("GrammaticalInflectionService.setSystemWideGrammaticalGender");
- if (!GrammaticalInflectionManager.VALID_GRAMMATICAL_GENDER_VALUES.contains(
- grammaticalGender)) {
- throw new IllegalArgumentException("Unknown grammatical gender");
- }
-
- if (!checkSystemTermsOfAddressIsEnabled()) {
- if (grammaticalGender == GRAMMATICAL_GENDER_NOT_SPECIFIED) {
- return;
+ try {
+ if (!checkSystemTermsOfAddressIsEnabled()) {
+ return; // Nothing to do, and the flag can't get flipped at the runtime.
}
- Log.d(TAG, "Clearing the system grammatical gender setting");
- grammaticalGender = GRAMMATICAL_GENDER_NOT_SPECIFIED;
- }
- synchronized (mLock) {
+ Trace.beginSection("GrammaticalInflectionService.setSystemWideGrammaticalGender");
+ if (!GrammaticalInflectionManager.VALID_GRAMMATICAL_GENDER_VALUES.contains(
+ grammaticalGender)) {
+ throw new IllegalArgumentException("Unknown grammatical gender");
+ }
+
final File file = getGrammaticalGenderFile(userId);
- final AtomicFile atomicFile = new AtomicFile(file);
- FileOutputStream stream = null;
- try {
- stream = atomicFile.startWrite();
- stream.write(toXmlByteArray(grammaticalGender, stream));
- atomicFile.finishWrite(stream);
- mGrammaticalGenderCache.put(userId, grammaticalGender);
- } catch (IOException e) {
- Log.e(TAG, "Failed to write file " + atomicFile, e);
- if (stream != null) {
- atomicFile.failWrite(stream);
+ synchronized (mLock) {
+ final AtomicFile atomicFile = new AtomicFile(file);
+ FileOutputStream stream = null;
+ try {
+ stream = atomicFile.startWrite();
+ stream.write(toXmlByteArray(grammaticalGender, stream));
+ atomicFile.finishWrite(stream);
+ mGrammaticalGenderCache.put(userId, grammaticalGender);
+ } catch (IOException e) {
+ Log.e(TAG, "Failed to write file " + atomicFile, e);
+ if (stream != null) {
+ atomicFile.failWrite(stream);
+ }
+ throw new RuntimeException(e);
}
- throw new RuntimeException(e);
}
+ updateConfiguration(grammaticalGender, userId);
+ } finally {
+ Trace.endSection();
}
- updateConfiguration(grammaticalGender, userId);
- Trace.endSection();
}
- private void updateConfiguration(int grammaticalGender, int userId) {
+ private static void updateConfiguration(int grammaticalGender, int userId) {
try {
Configuration config = new Configuration();
int preValue = config.getGrammaticalGender();
@@ -301,54 +303,47 @@
FrameworkStatsLog.write(FrameworkStatsLog.SYSTEM_GRAMMATICAL_INFLECTION_CHANGED,
FrameworkStatsLog.SYSTEM_GRAMMATICAL_INFLECTION_CHANGED__SOURCE_ID__SYSTEM,
userId,
- grammaticalGender != GRAMMATICAL_GENDER_NOT_SPECIFIED,
- preValue != GRAMMATICAL_GENDER_NOT_SPECIFIED);
+ grammaticalGender != Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED,
+ preValue != Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED);
GrammaticalInflectionBackupHelper.notifyBackupManager();
} catch (RemoteException e) {
Log.w(TAG, "Can not update configuration", e);
}
}
- public int getSystemGrammaticalGender(AttributionSource attributionSource, int userId) {
- String packageName = attributionSource.getPackageName();
- if (packageName == null) {
- Log.d(TAG, "Package name is null.");
- return GRAMMATICAL_GENDER_NOT_SPECIFIED;
- }
-
+ /**
+ * Returns the system global grammatical gender value for the requested user.
+ */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PROTECTED)
+ public int getSystemGrammaticalGender(int userId) {
synchronized (mLock) {
int grammaticalGender = mGrammaticalGenderCache.get(userId);
- return grammaticalGender < 0 ? GRAMMATICAL_GENDER_NOT_SPECIFIED : grammaticalGender;
+ return grammaticalGender < 0
+ ? Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED : grammaticalGender;
}
}
- private File getGrammaticalGenderFile(int userId) {
+ private static File getGrammaticalGenderFile(int userId) {
final File dir = new File(Environment.getDataSystemCeDirectory(userId),
TAG_GRAMMATICAL_INFLECTION);
return new File(dir, USER_SETTINGS_FILE_NAME);
}
- private byte[] toXmlByteArray(int grammaticalGender, FileOutputStream fileStream) {
-
- try {
- ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
- TypedXmlSerializer out = Xml.resolveSerializer(fileStream);
- out.setOutput(outputStream, StandardCharsets.UTF_8.name());
- out.startDocument(/* encoding= */ null, /* standalone= */ true);
- out.startTag(null, TAG_GRAMMATICAL_INFLECTION);
- out.attributeInt(null, ATTR_NAME, grammaticalGender);
- out.endTag(null, TAG_GRAMMATICAL_INFLECTION);
- out.endDocument();
-
- return outputStream.toByteArray();
- } catch (IOException e) {
- return null;
- }
+ private static byte[] toXmlByteArray(int grammaticalGender, FileOutputStream fileStream)
+ throws IOException {
+ var outputStream = new ByteArrayOutputStream();
+ TypedXmlSerializer out = Xml.resolveSerializer(fileStream);
+ out.setOutput(outputStream, StandardCharsets.UTF_8.name());
+ out.startDocument(/* encoding= */ null, /* standalone= */ true);
+ out.startTag(null, TAG_GRAMMATICAL_INFLECTION);
+ out.attributeInt(null, ATTR_NAME, grammaticalGender);
+ out.endTag(null, TAG_GRAMMATICAL_INFLECTION);
+ out.endDocument();
+ return outputStream.toByteArray();
}
- private int getGrammaticalGenderFromXml(TypedXmlPullParser parser)
+ private static int getGrammaticalGenderFromXml(TypedXmlPullParser parser)
throws IOException, XmlPullParserException {
-
XmlUtils.nextElement(parser);
while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
String tagName = parser.getName();
@@ -359,20 +354,20 @@
}
}
- return GRAMMATICAL_GENDER_NOT_SPECIFIED;
+ return Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED;
}
- private void isCallerAllowed() {
+ private void enforceCallerPermissions() {
int callingUid = Binder.getCallingUid();
if (callingUid != Process.SYSTEM_UID && callingUid != Process.SHELL_UID
&& callingUid != Process.ROOT_UID) {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.CHANGE_CONFIGURATION,
- "Caller must be system, shell, root or has CHANGE_CONFIGURATION permission.");
+ "Caller must be system, shell, root or hold CHANGE_CONFIGURATION permission.");
}
}
- private boolean checkSystemTermsOfAddressIsEnabled() {
+ private static boolean checkSystemTermsOfAddressIsEnabled() {
if (!systemTermsOfAddressEnabled()) {
Log.d(TAG, "The flag must be enabled to allow calling the API.");
return false;
@@ -387,25 +382,31 @@
@Override
public void onUserUnlocked(TargetUser user) {
+ if (!checkSystemTermsOfAddressIsEnabled()) {
+ return;
+ }
IoThread.getHandler().post(() -> {
- int userId = user.getUserIdentifier();
+ final int userId = user.getUserIdentifier();
final File file = getGrammaticalGenderFile(userId);
+ final int grammaticalGender;
synchronized (mLock) {
if (!file.exists()) {
Log.d(TAG, "User " + userId + " doesn't have the grammatical gender file.");
return;
}
- if (mGrammaticalGenderCache.indexOfKey(userId) < 0) {
- try (FileInputStream in = new FileInputStream(file)) {
- final TypedXmlPullParser parser = Xml.resolvePullParser(in);
- int grammaticalGender = getGrammaticalGenderFromXml(parser);
- mGrammaticalGenderCache.put(userId, grammaticalGender);
- updateConfiguration(grammaticalGender, userId);
- } catch (IOException | XmlPullParserException e) {
- Log.e(TAG, "Failed to parse XML configuration from " + file, e);
- }
+ if (mGrammaticalGenderCache.indexOfKey(userId) >= 0) {
+ return;
+ }
+ try (FileInputStream in = new FileInputStream(file)) {
+ final TypedXmlPullParser parser = Xml.resolvePullParser(in);
+ grammaticalGender = getGrammaticalGenderFromXml(parser);
+ mGrammaticalGenderCache.put(userId, grammaticalGender);
+ } catch (IOException | XmlPullParserException e) {
+ Log.e(TAG, "Failed to parse XML configuration from " + file, e);
+ return;
}
}
+ updateConfiguration(grammaticalGender, userId);
});
}
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecController.java b/services/core/java/com/android/server/hdmi/HdmiCecController.java
index f6dfc9e..cd2c037 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecController.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecController.java
@@ -350,7 +350,7 @@
* {@link HdmiCecNetwork} only.
*
* @return CEC physical address of the device. The range of success address
- * is between 0x0000 and 0xFFFF. If failed it returns -1
+ * is between 0x0000 and 0xFFFE. If failed it returns INVALID_PHYSICAL_ADDRESS.
*/
@ServiceThreadOnly
int getPhysicalAddress() {
@@ -1315,7 +1315,7 @@
hdmiPortInfo[i] = new HdmiPortInfo.Builder(
portInfo.portId,
portInfo.type,
- portInfo.physicalAddress)
+ Short.toUnsignedInt(portInfo.physicalAddress))
.setCecSupported(portInfo.cecSupported)
.setMhlSupported(false)
.setArcSupported(portInfo.arcSupported)
@@ -1512,7 +1512,7 @@
hdmiPortInfo[i] = new HdmiPortInfo.Builder(
portInfo.portId,
portInfo.type,
- portInfo.physicalAddress)
+ Short.toUnsignedInt(portInfo.physicalAddress))
.setCecSupported(portInfo.cecSupported)
.setMhlSupported(false)
.setArcSupported(portInfo.arcSupported)
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 54e1217..d10e192 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -867,6 +867,60 @@
}
}
}, mServiceThreadExecutor);
+ mHdmiCecConfig.registerChangeListener(HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_TV,
+ new HdmiCecConfig.SettingChangeListener() {
+ @Override
+ public void onChange(String setting) {
+ reportFeatures(true);
+ }
+ },
+ mServiceThreadExecutor);
+ mHdmiCecConfig.registerChangeListener(
+ HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_ROOT_MENU,
+ new HdmiCecConfig.SettingChangeListener() {
+ @Override
+ public void onChange(String setting) {
+ reportFeatures(false);
+ }
+ },
+ mServiceThreadExecutor);
+ mHdmiCecConfig.registerChangeListener(
+ HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_SETUP_MENU,
+ new HdmiCecConfig.SettingChangeListener() {
+ @Override
+ public void onChange(String setting) {
+ reportFeatures(false);
+ }
+ },
+ mServiceThreadExecutor);
+ mHdmiCecConfig.registerChangeListener(
+ HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_CONTENTS_MENU,
+ new HdmiCecConfig.SettingChangeListener() {
+ @Override
+ public void onChange(String setting) {
+ reportFeatures(false);
+ }
+ },
+ mServiceThreadExecutor);
+ mHdmiCecConfig.registerChangeListener(
+ HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_TOP_MENU,
+ new HdmiCecConfig.SettingChangeListener() {
+ @Override
+ public void onChange(String setting) {
+ reportFeatures(false);
+ }
+ },
+ mServiceThreadExecutor);
+ mHdmiCecConfig.registerChangeListener(
+ HdmiControlManager
+ .CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_MEDIA_CONTEXT_SENSITIVE_MENU,
+ new HdmiCecConfig.SettingChangeListener() {
+ @Override
+ public void onChange(String setting) {
+ reportFeatures(false);
+ }
+ },
+ mServiceThreadExecutor);
if (isTvDevice()) {
mDeviceConfig.addOnPropertiesChangedListener(getContext().getMainExecutor(),
@@ -968,6 +1022,21 @@
}
}
+ /** Helper method for sending feature discovery command */
+ private void reportFeatures(boolean isTvDeviceSetting) {
+ // check if tv device is enabled for tv device specific RC profile setting
+ if (isTvDeviceSetting) {
+ if (isTvDeviceEnabled()) {
+ tv().reportFeatures();
+ }
+ } else { // check for source device setting
+ HdmiCecLocalDeviceSource source = isAudioSystemDevice() ? audioSystem() : playback();
+ if (source != null) {
+ source.reportFeatures();
+ }
+ }
+ }
+
/**
* Returns the initial power status used when the HdmiControlService starts.
*/
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlShellCommand.java b/services/core/java/com/android/server/hdmi/HdmiControlShellCommand.java
index 8949427..bf6633e 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlShellCommand.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlShellCommand.java
@@ -124,8 +124,7 @@
return historySize(pw);
}
- getErrPrintWriter().println("Unhandled command: " + cmd);
- return 1;
+ return handleDefaultCommands(cmd);
}
private int deviceSelect(PrintWriter pw) throws RemoteException {
diff --git a/services/core/java/com/android/server/infra/AbstractMasterSystemService.java b/services/core/java/com/android/server/infra/AbstractMasterSystemService.java
index ef52d2a..e28939b 100644
--- a/services/core/java/com/android/server/infra/AbstractMasterSystemService.java
+++ b/services/core/java/com/android/server/infra/AbstractMasterSystemService.java
@@ -986,7 +986,7 @@
}
private void startTrackingPackageChanges() {
- final PackageMonitor monitor = new PackageMonitor() {
+ final PackageMonitor monitor = new PackageMonitor(true) {
@Override
public void onPackageUpdateStarted(@NonNull String packageName, int uid) {
diff --git a/services/core/java/com/android/server/input/InputShellCommand.java b/services/core/java/com/android/server/input/InputShellCommand.java
index 138186b..4c5a3c2 100644
--- a/services/core/java/com/android/server/input/InputShellCommand.java
+++ b/services/core/java/com/android/server/input/InputShellCommand.java
@@ -333,8 +333,8 @@
out.println();
out.println("The commands and default sources are:");
out.println(" text <string> (Default: keyboard)");
- out.println(" keyevent [--longpress|--doubletap|--async"
- + "|--delay <duration between keycodes in ms>]"
+ out.println(" keyevent [--longpress|--duration <duration to hold key down in ms>]"
+ + " [--doubletap] [--async] [--delay <duration between keycodes in ms>]"
+ " <key code number or name> ..."
+ " (Default: keyboard)");
out.println(" tap <x> <y> (Default: touchscreen)");
@@ -402,6 +402,7 @@
boolean async = false;
boolean doubleTap = false;
long delayMs = 0;
+ long durationMs = 0;
String arg = getNextArgRequired();
do {
@@ -411,9 +412,21 @@
doubleTap = (doubleTap || arg.equals("--doubletap"));
if (arg.equals("--delay")) {
delayMs = Long.parseLong(getNextArgRequired());
+ } else if (arg.equals("--duration")) {
+ durationMs = Long.parseLong(getNextArgRequired());
}
} while ((arg = getNextArg()) != null);
+ if (durationMs > 0 && longPress) {
+ getErrPrintWriter().println(
+ "--duration and --longpress cannot be used at the same time.");
+ throw new IllegalArgumentException(
+ "keyevent args should only contain either durationMs or longPress");
+ }
+ if (longPress) {
+ durationMs = ViewConfiguration.getLongPressTimeout();
+ }
+
boolean firstInput = true;
do {
if (!firstInput && delayMs > 0) {
@@ -422,16 +435,17 @@
firstInput = false;
final int keyCode = KeyEvent.keyCodeFromString(arg);
- sendKeyEvent(inputSource, keyCode, longPress, displayId, async);
+ sendKeyEvent(inputSource, keyCode, durationMs, displayId, async);
if (doubleTap) {
sleep(ViewConfiguration.getDoubleTapMinTime());
- sendKeyEvent(inputSource, keyCode, longPress, displayId, async);
+ sendKeyEvent(inputSource, keyCode, durationMs, displayId, async);
}
} while ((arg = getNextArg()) != null);
}
private void sendKeyEvent(
- int inputSource, int keyCode, boolean longPress, int displayId, boolean async) {
+ int inputSource, int keyCode, long durationMs, int displayId,
+ boolean async) {
final long now = SystemClock.uptimeMillis();
KeyEvent event = new KeyEvent(now, now, KeyEvent.ACTION_DOWN, keyCode, 0 /* repeatCount */,
@@ -440,13 +454,23 @@
event.setDisplayId(displayId);
injectKeyEvent(event, async);
- if (longPress) {
- sleep(ViewConfiguration.getLongPressTimeout());
- // Some long press behavior would check the event time, we set a new event time here.
- final long nextEventTime = now + ViewConfiguration.getLongPressTimeout();
- KeyEvent longPressEvent = KeyEvent.changeTimeRepeat(
- event, nextEventTime, 1 /* repeatCount */, KeyEvent.FLAG_LONG_PRESS);
- injectKeyEvent(longPressEvent, async);
+ long firstSleepDurationMs = Math.min(durationMs, ViewConfiguration.getLongPressTimeout());
+ if (firstSleepDurationMs > 0) {
+ sleep(firstSleepDurationMs);
+ // Send FLAG_LONG_PRESS right after `longPressTimeout`, and resume sleep if needed.
+ if (durationMs >= ViewConfiguration.getLongPressTimeout()) {
+ // Some long press behavior would check the event time, we set a new event time
+ // here.
+ final long nextEventTime = now + ViewConfiguration.getLongPressTimeout();
+ KeyEvent longPressEvent = KeyEvent.changeTimeRepeat(event, nextEventTime,
+ 1 /* repeatCount */, KeyEvent.FLAG_LONG_PRESS);
+ injectKeyEvent(longPressEvent, async);
+
+ long secondSleepDurationMs = durationMs - firstSleepDurationMs;
+ if (secondSleepDurationMs > 0) {
+ sleep(secondSleepDurationMs);
+ }
+ }
}
injectKeyEvent(KeyEvent.changeAction(event, KeyEvent.ACTION_UP), async);
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 44a200e..8985022e 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -382,6 +382,16 @@
@MultiUserUnawareField
Future<?> mImeDrawsImeNavBarResLazyInitFuture;
+ private final ImeTracing.ServiceDumper mDumper = new ImeTracing.ServiceDumper() {
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void dumpToProto(ProtoOutputStream proto, @Nullable byte[] icProto) {
+ dumpDebug(proto, InputMethodManagerServiceTraceProto.INPUT_METHOD_MANAGER_SERVICE);
+ }
+ };
+
static class SessionState {
final ClientState mClient;
final IInputMethodInvoker mMethod;
@@ -471,11 +481,6 @@
return mBindingController.getSelectedMethodId();
}
- @GuardedBy("ImfLock.class")
- private void setSelectedMethodIdLocked(@Nullable String selectedMethodId) {
- mBindingController.setSelectedMethodId(selectedMethodId);
- }
-
/**
* The current binding sequence number, incremented every time there is
* a new bind performed.
@@ -816,6 +821,8 @@
}
} else if (stylusHandwritingEnabledUri.equals(uri)) {
InputMethodManager.invalidateLocalStylusHandwritingAvailabilityCaches();
+ InputMethodManager
+ .invalidateLocalConnectionlessStylusHandwritingAvailabilityCaches();
} else {
boolean enabledChanged = false;
String newEnabled = InputMethodSettingsRepository.get(mCurrentUserId)
@@ -934,6 +941,10 @@
*/
private ArrayList<String> mDataClearedPackages = new ArrayList<>();
+ private MyPackageMonitor() {
+ super(true);
+ }
+
@GuardedBy("ImfLock.class")
void clearKnownImePackageNamesLocked() {
mKnownImePackageNames.clear();
@@ -1526,6 +1537,20 @@
+ " currentUserId=" + mCurrentUserId);
}
+ // Clean up stuff for mCurrentUserId, which soon becomes the previous user.
+
+ // TODO(b/338461930): Check if this is still necessary or not.
+ onUnbindCurrentMethodByReset();
+
+ // Note that in b/197848765 we want to see if we can keep the binding alive for better
+ // profile switching.
+ mBindingController.unbindCurrentMethod();
+ // TODO(b/325515685): No need to do this once BindingController becomes per-user.
+ mBindingController.setSelectedMethodId(null);
+ unbindCurrentClientLocked(UnbindReason.SWITCH_USER);
+
+ // Hereafter we start initializing things for "newUserId".
+
maybeInitImeNavbarConfigLocked(newUserId);
// ContentObserver should be registered again when the user is changed
@@ -1547,10 +1572,6 @@
// IME for that user.
final boolean initialUserSwitch = TextUtils.isEmpty(defaultImiId);
- // The mSystemReady flag is set during boot phase,
- // and user switch would not happen at that time.
- resetCurrentMethodAndClientLocked(UnbindReason.SWITCH_USER);
-
final InputMethodSettings newSettings = InputMethodSettingsRepository.get(newUserId);
postInputMethodSettingUpdatedLocked(initialUserSwitch /* resetDefaultEnabledIme */);
if (TextUtils.isEmpty(newSettings.getSelectedInputMethod())) {
@@ -2497,8 +2518,9 @@
@GuardedBy("ImfLock.class")
void resetCurrentMethodAndClientLocked(@UnbindReason int unbindClientReason) {
- setSelectedMethodIdLocked(null);
+ mBindingController.setSelectedMethodId(null);
// Callback before clean-up binding states.
+ // TODO(b/338461930): Check if this is still necessary or not.
onUnbindCurrentMethodByReset();
mBindingController.unbindCurrentMethod();
unbindCurrentClientLocked(unbindClientReason);
@@ -3077,7 +3099,7 @@
// mCurMethodId should be updated after setSelectedInputMethodAndSubtypeLocked()
// because mCurMethodId is stored as a history in
// setSelectedInputMethodAndSubtypeLocked().
- setSelectedMethodIdLocked(id);
+ mBindingController.setSelectedMethodId(id);
if (mActivityManagerInternal.isSystemReady()) {
Intent intent = new Intent(Intent.ACTION_INPUT_METHOD_CHANGED);
@@ -3099,7 +3121,7 @@
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.showSoftInput");
int uid = Binder.getCallingUid();
ImeTracing.getInstance().triggerManagerServiceDump(
- "InputMethodManagerService#showSoftInput");
+ "InputMethodManagerService#showSoftInput", mDumper);
synchronized (ImfLock.class) {
if (!canInteractWithImeLocked(uid, client, "showSoftInput", statsToken)) {
ImeTracker.forLogging().onFailed(
@@ -3198,7 +3220,7 @@
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.startStylusHandwriting");
try {
ImeTracing.getInstance().triggerManagerServiceDump(
- "InputMethodManagerService#startStylusHandwriting");
+ "InputMethodManagerService#startStylusHandwriting", mDumper);
int uid = Binder.getCallingUid();
synchronized (ImfLock.class) {
if (!acceptingDelegation) {
@@ -3401,7 +3423,14 @@
mBindingController.setCurrentMethodVisible();
final IInputMethodInvoker curMethod = getCurMethodLocked();
ImeTracker.forLogging().onCancelled(mCurStatsToken, ImeTracker.PHASE_SERVER_WAIT_IME);
- if (curMethod != null) {
+ final boolean readyToDispatchToIme;
+ if (Flags.deferShowSoftInputUntilSessionCreation()) {
+ readyToDispatchToIme =
+ curMethod != null && mCurClient != null && mCurClient.mCurSession != null;
+ } else {
+ readyToDispatchToIme = curMethod != null;
+ }
+ if (readyToDispatchToIme) {
ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_SERVER_HAS_IME);
mCurStatsToken = null;
@@ -3426,7 +3455,7 @@
ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
int uid = Binder.getCallingUid();
ImeTracing.getInstance().triggerManagerServiceDump(
- "InputMethodManagerService#hideSoftInput");
+ "InputMethodManagerService#hideSoftInput", mDumper);
synchronized (ImfLock.class) {
if (!canInteractWithImeLocked(uid, client, "hideSoftInput", statsToken)) {
if (isInputShownLocked()) {
@@ -3564,7 +3593,7 @@
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER,
"IMMS.startInputOrWindowGainedFocus");
ImeTracing.getInstance().triggerManagerServiceDump(
- "InputMethodManagerService#startInputOrWindowGainedFocus");
+ "InputMethodManagerService#startInputOrWindowGainedFocus", mDumper);
final InputBindResult result;
synchronized (ImfLock.class) {
// If the system is not yet ready, we shouldn't be running third party code.
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
index 3bd0a9f..326ef7e 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
@@ -98,7 +98,10 @@
final int size = imList.size();
mIms = new InputMethodInfo[size];
mSubtypeIds = new int[size];
- int checkedItem = 0;
+ // No items are checked by default. When we have a list of explicitly enabled subtypes,
+ // the implicit subtype is no longer listed, but if it is still the selected one,
+ // no items will be shown as checked.
+ int checkedItem = -1;
for (int i = 0; i < size; ++i) {
final ImeSubtypeListItem item = imList.get(i);
mIms[i] = item.mImi;
@@ -113,6 +116,12 @@
}
}
+ if (checkedItem == -1) {
+ Slog.w(TAG, "Switching menu shown with no item selected"
+ + ", IME id: " + preferredInputMethodId
+ + ", subtype index: " + preferredInputMethodSubtypeId);
+ }
+
if (mDialogWindowContext == null) {
mDialogWindowContext = new InputMethodDialogWindowContext();
}
diff --git a/services/core/java/com/android/server/net/TEST_MAPPING b/services/core/java/com/android/server/net/TEST_MAPPING
index 4fc1a17..ad6b0ca 100644
--- a/services/core/java/com/android/server/net/TEST_MAPPING
+++ b/services/core/java/com/android/server/net/TEST_MAPPING
@@ -1,7 +1,7 @@
{
"presubmit-large": [
{
- "name": "CtsHostsideNetworkTests",
+ "name": "CtsHostsideNetworkPolicyTests",
"options": [
{
"exclude-annotation": "androidx.test.filters.FlakyTest"
diff --git a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
index 96f32f3..bf49671 100644
--- a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
+++ b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
@@ -16,7 +16,7 @@
package com.android.server.notification;
-import static android.app.Flags.updateRankingTime;
+import static android.app.Flags.sortSectionByTime;
import static android.app.Notification.FLAG_INSISTENT;
import static android.app.Notification.FLAG_ONLY_ALERT_ONCE;
import static android.app.NotificationManager.IMPORTANCE_MIN;
@@ -497,7 +497,7 @@
Slog.v(TAG, "INTERRUPTIVENESS: "
+ record.getKey() + " is interruptive: alerted");
}
- if (updateRankingTime()) {
+ if (sortSectionByTime()) {
if (buzz || beep) {
record.resetRankingTime();
}
@@ -1528,7 +1528,7 @@
// recent conversation
if (record.isConversation()
- && record.getNotification().when > mLastAvalancheTriggerTimestamp) {
+ && record.getNotification().getWhen() > mLastAvalancheTriggerTimestamp) {
return true;
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 9d4ab11..ca6ae63 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -25,6 +25,7 @@
import static android.app.AppOpsManager.MODE_DEFAULT;
import static android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR;
import static android.app.Flags.lifetimeExtensionRefactor;
+import static android.app.Flags.sortSectionByTime;
import static android.app.Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION;
import static android.app.Notification.EXTRA_BUILDER_APPLICATION_INFO;
import static android.app.Notification.EXTRA_LARGE_ICON_BIG;
@@ -8593,7 +8594,7 @@
r.isUpdate = true;
final boolean isInterruptive = isVisuallyInterruptive(old, r);
r.setTextChanged(isInterruptive);
- if (android.app.Flags.updateRankingTime()) {
+ if (sortSectionByTime()) {
if (isInterruptive) {
r.resetRankingTime();
}
@@ -8738,7 +8739,7 @@
return false;
}
- if (android.app.Flags.updateRankingTime()) {
+ if (sortSectionByTime()) {
// Ignore visual interruptions from FGS/UIJs because users
// consider them one 'session'. Count them for everything else.
if (r.getSbn().getNotification().isFgsOrUij()) {
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index 38c95f7..0c6a6c8 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -18,7 +18,7 @@
import static android.app.Flags.restrictAudioAttributesAlarm;
import static android.app.Flags.restrictAudioAttributesCall;
import static android.app.Flags.restrictAudioAttributesMedia;
-import static android.app.Flags.updateRankingTime;
+import static android.app.Flags.sortSectionByTime;
import static android.app.NotificationChannel.USER_LOCKED_IMPORTANCE;
import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
import static android.app.NotificationManager.IMPORTANCE_HIGH;
@@ -580,7 +580,7 @@
pw.println(prefix + "deleteIntent=" + notification.deleteIntent);
pw.println(prefix + "number=" + notification.number);
pw.println(prefix + "groupAlertBehavior=" + notification.getGroupAlertBehavior());
- pw.println(prefix + "when=" + notification.when);
+ pw.println(prefix + "when=" + notification.when + "/" + notification.getWhen());
pw.print(prefix + "tickerText=");
if (!TextUtils.isEmpty(notification.tickerText)) {
@@ -1092,9 +1092,9 @@
private long calculateRankingTimeMs(long previousRankingTimeMs) {
Notification n = getNotification();
// Take developer provided 'when', unless it's in the future.
- if (updateRankingTime()) {
- if (n.hasAppProvidedWhen() && n.when <= getSbn().getPostTime()){
- return n.when;
+ if (sortSectionByTime()) {
+ if (n.hasAppProvidedWhen() && n.getWhen() <= getSbn().getPostTime()){
+ return n.getWhen();
}
} else {
if (n.when != 0 && n.when <= getSbn().getPostTime()) {
@@ -1211,7 +1211,7 @@
}
public void resetRankingTime() {
- if (updateRankingTime()) {
+ if (sortSectionByTime()) {
mRankingTimeMs = calculateRankingTimeMs(getSbn().getPostTime());
}
}
diff --git a/services/core/java/com/android/server/notification/NotificationRecordLogger.java b/services/core/java/com/android/server/notification/NotificationRecordLogger.java
index 9a6ea2c..65ef53f 100644
--- a/services/core/java/com/android/server/notification/NotificationRecordLogger.java
+++ b/services/core/java/com/android/server/notification/NotificationRecordLogger.java
@@ -542,7 +542,7 @@
this.is_locked = p.r.isLocked();
this.age_in_minutes = NotificationRecordLogger.getAgeInMinutes(
- p.r.getSbn().getPostTime(), p.r.getSbn().getNotification().when);
+ p.r.getSbn().getPostTime(), p.r.getSbn().getNotification().getWhen());
}
}
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 1f2ad07e..309e945 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -26,6 +26,7 @@
import static android.app.NotificationManager.IMPORTANCE_NONE;
import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
+import static android.os.UserHandle.USER_SYSTEM;
import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_CHANNEL_GROUP_PREFERENCES;
import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES;
import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES;
@@ -139,6 +140,8 @@
private static final String ATT_VERSION = "version";
private static final String ATT_NAME = "name";
private static final String ATT_UID = "uid";
+
+ private static final String ATT_USERID = "userid";
private static final String ATT_ID = "id";
private static final String ATT_ALLOW_BUBBLE = "allow_bubble";
private static final String ATT_PRIORITY = "priority";
@@ -268,7 +271,7 @@
}
if (type == XmlPullParser.START_TAG) {
if (TAG_STATUS_ICONS.equals(tag)) {
- if (forRestore && userId != UserHandle.USER_SYSTEM) {
+ if (forRestore && userId != USER_SYSTEM) {
continue;
}
mHideSilentStatusBarIcons = parser.getAttributeBoolean(null,
@@ -311,8 +314,16 @@
: parser.getAttributeInt(null, ATT_ALLOW_BUBBLE, DEFAULT_BUBBLE_PREFERENCE);
int appImportance = parser.getAttributeInt(null, ATT_IMPORTANCE, DEFAULT_IMPORTANCE);
+ // when data is loaded from disk it's loaded as USER_ALL, but restored data that
+ // is pending app install needs the user id that the data was restored to
+ int fixedUserId = userId;
+ if (Flags.persistIncompleteRestoreData()) {
+ if (!forRestore && uid == UNKNOWN_UID) {
+ fixedUserId = parser.getAttributeInt(null, ATT_USERID, USER_SYSTEM);
+ }
+ }
PackagePreferences r = getOrCreatePackagePreferencesLocked(
- name, userId, uid,
+ name, fixedUserId, uid,
appImportance,
parser.getAttributeInt(null, ATT_PRIORITY, DEFAULT_PRIORITY),
parser.getAttributeInt(null, ATT_VISIBILITY, DEFAULT_VISIBILITY),
@@ -504,6 +515,9 @@
}
if (r.uid == UNKNOWN_UID) {
+ if (Flags.persistIncompleteRestoreData()) {
+ r.userId = userId;
+ }
mRestoredWithoutUids.put(unrestoredPackageKey(pkg, userId), r);
} else {
mPackagePreferences.put(key, r);
@@ -674,6 +688,7 @@
if (Flags.persistIncompleteRestoreData() && r.uid == UNKNOWN_UID) {
out.attributeLong(null, ATT_CREATION_TIME, r.creationTime);
+ out.attributeInt(null, ATT_USERID, r.userId);
}
if (!forBackup) {
@@ -2947,6 +2962,8 @@
boolean migrateToPm = false;
long creationTime;
+ @UserIdInt int userId;
+
Delegate delegate = null;
ArrayMap<String, NotificationChannel> channels = new ArrayMap<>();
Map<String, NotificationChannelGroup> groups = new ConcurrentHashMap<>();
diff --git a/services/core/java/com/android/server/notification/RankingHelper.java b/services/core/java/com/android/server/notification/RankingHelper.java
index 7756801..03dd935 100644
--- a/services/core/java/com/android/server/notification/RankingHelper.java
+++ b/services/core/java/com/android/server/notification/RankingHelper.java
@@ -147,11 +147,9 @@
if (sortSectionByTime()) {
final String groupKey = record.getGroupKey();
NotificationRecord existingProxy = mProxyByGroupTmp.get(groupKey);
- // summaries are mostly hidden in systemui - if there is a child notification
- // with better information, use its rank
- if (existingProxy == null
- || (existingProxy.getNotification().isGroupSummary()
- && !existingProxy.getNotification().hasAppProvidedWhen())) {
+ // summaries are mostly hidden in systemui - if there is a child notification,
+ // use its rank
+ if (existingProxy == null || existingProxy.getNotification().isGroupSummary()) {
mProxyByGroupTmp.put(groupKey, record);
}
} else {
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 4747689..143bc5c 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -1725,7 +1725,28 @@
synchronized (mConfigLock) {
if (policy == null || mConfig == null) return;
final ZenModeConfig newConfig = mConfig.copy();
- newConfig.applyNotificationPolicy(policy);
+ if (Flags.modesApi() && !Flags.modesUi()) {
+ // Fix for b/337193321 -- propagate changes to notificationPolicy to rules where
+ // the user cannot edit zen policy to emulate the previous "inheritance".
+ ZenPolicy previousPolicy = ZenAdapters.notificationPolicyToZenPolicy(
+ newConfig.toNotificationPolicy());
+ ZenPolicy newPolicy = ZenAdapters.notificationPolicyToZenPolicy(policy);
+
+ newConfig.applyNotificationPolicy(policy);
+
+ if (!previousPolicy.equals(newPolicy)) {
+ for (ZenRule rule : newConfig.automaticRules.values()) {
+ if (!SystemZenRules.isSystemOwnedRule(rule)
+ && rule.zenMode == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS
+ && (rule.zenPolicy == null || rule.zenPolicy.equals(previousPolicy)
+ || rule.zenPolicy.equals(getDefaultZenPolicy()))) {
+ rule.zenPolicy = newPolicy;
+ }
+ }
+ }
+ } else {
+ newConfig.applyNotificationPolicy(policy);
+ }
setConfigLocked(newConfig, null, origin, "setNotificationPolicy", callingUid);
}
}
diff --git a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
index c8fd7e4..8a85328 100644
--- a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
+++ b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
@@ -19,6 +19,7 @@
import static android.app.admin.flags.Flags.onboardingBugreportV2Enabled;
import android.Manifest;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.app.AppOpsManager;
@@ -68,6 +69,7 @@
import java.io.InputStream;
import java.io.PrintWriter;
import java.util.HashSet;
+import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.OptionalInt;
@@ -335,14 +337,22 @@
}
static class Injector {
+ class RoleManagerWrapper {
+ List<String> getRoleHolders(@NonNull String roleName) {
+ return mContext.getSystemService(RoleManager.class).getRoleHolders(roleName);
+ }
+ }
+
Context mContext;
ArraySet<String> mAllowlistedPackages;
AtomicFile mMappingFile;
+ RoleManagerWrapper mRoleManagerWrapper;
Injector(Context context, ArraySet<String> allowlistedPackages, AtomicFile mappingFile) {
mContext = context;
mAllowlistedPackages = allowlistedPackages;
mMappingFile = mappingFile;
+ mRoleManagerWrapper = new RoleManagerWrapper();
}
Context getContext() {
@@ -368,6 +378,10 @@
void setSystemProperty(String key, String value) {
SystemProperties.set(key, value);
}
+
+ RoleManagerWrapper getRoleManagerWrapper() {
+ return mRoleManagerWrapper;
+ }
}
BugreportManagerServiceImpl(Context context) {
@@ -546,7 +560,7 @@
if (!allowlisted) {
final long token = Binder.clearCallingIdentity();
try {
- allowlisted = mContext.getSystemService(RoleManager.class).getRoleHolders(
+ allowlisted = mInjector.getRoleManagerWrapper().getRoleHolders(
ROLE_SYSTEM_AUTOMOTIVE_PROJECTION).contains(callingPackage);
} finally {
Binder.restoreCallingIdentity(token);
diff --git a/services/core/java/com/android/server/pdb/PersistentDataBlockService.java b/services/core/java/com/android/server/pdb/PersistentDataBlockService.java
index 2c14532..c5e2bb8 100644
--- a/services/core/java/com/android/server/pdb/PersistentDataBlockService.java
+++ b/services/core/java/com/android/server/pdb/PersistentDataBlockService.java
@@ -291,9 +291,15 @@
private void setOldSettingForBackworkCompatibility(boolean isActive) {
// Set the SECURE_FRP_MODE flag, for backward compatibility with clients who use it.
- // They should switch to calling #isFrpActive().
- Settings.Global.putInt(mContext.getContentResolver(),
- Settings.Global.SECURE_FRP_MODE, isActive ? 1 : 0);
+ // They should switch to calling #isFrpActive(). Clear calling ID since this can happen
+ // during an app call.
+ final long callingId = Binder.clearCallingIdentity();
+ try {
+ Settings.Global.putInt(mContext.getContentResolver(),
+ Settings.Global.SECURE_FRP_MODE, isActive ? 1 : 0);
+ } finally {
+ Binder.restoreCallingIdentity(callingId);
+ }
}
private void setOemUnlockEnabledProperty(boolean oemUnlockEnabled) {
diff --git a/services/core/java/com/android/server/pm/DefaultCrossProfileIntentFiltersUtils.java b/services/core/java/com/android/server/pm/DefaultCrossProfileIntentFiltersUtils.java
index 306f77d..3862b79 100644
--- a/services/core/java/com/android/server/pm/DefaultCrossProfileIntentFiltersUtils.java
+++ b/services/core/java/com/android/server/pm/DefaultCrossProfileIntentFiltersUtils.java
@@ -734,11 +734,11 @@
.addCategory(Intent.CATEGORY_BROWSABLE)
.build();
- /** SMS and MMS can be handled by the private profile or by the parent user. */
+ /** SMS and MMS are always handled in the main user. */
private static final DefaultCrossProfileIntentFilter SMS_MMS_PRIVATE_PROFILE =
new DefaultCrossProfileIntentFilter.Builder(
DefaultCrossProfileIntentFilter.Direction.TO_PARENT,
- ONLY_IF_NO_MATCH_FOUND,
+ SKIP_CURRENT_PROFILE,
/* letsPersonalDataIntoProfile= */ false)
.addAction(Intent.ACTION_VIEW)
.addAction(Intent.ACTION_SENDTO)
diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java
index b2c6c49..47ee1d0 100644
--- a/services/core/java/com/android/server/pm/DeletePackageHelper.java
+++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java
@@ -513,7 +513,11 @@
// Legacy behavior to report appId as UID here.
// The final broadcasts will contain a per-user UID.
outInfo.mUid = ps.getAppId();
- outInfo.mIsAppIdRemoved = true;
+ // Only send Intent.ACTION_UID_REMOVED when flag & DELETE_KEEP_DATA is 0
+ // i.e. the mDataRemoved is true
+ if (outInfo.mDataRemoved) {
+ outInfo.mIsAppIdRemoved = true;
+ }
mPm.scheduleWritePackageRestrictions(user);
return;
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 0f4e482..ae485ed 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -3984,6 +3984,8 @@
// packageName -> list of components to send broadcasts now
final ArrayMap<String, ArrayList<String>> sendNowBroadcasts = new ArrayMap<>(targetSize);
+ final List<PackageMetrics.ComponentStateMetrics> componentStateMetricsList =
+ new ArrayList<PackageMetrics.ComponentStateMetrics>();
synchronized (mLock) {
Computer computer = snapshotComputer();
boolean scheduleBroadcastMessage = false;
@@ -3997,11 +3999,17 @@
// update enabled settings
final ComponentEnabledSetting setting = settings.get(i);
final String packageName = setting.getPackageName();
- if (!setEnabledSettingInternalLocked(computer, pkgSettings.get(packageName),
- setting, userId, callingPackage)) {
+ final PackageSetting packageSetting = pkgSettings.get(packageName);
+ final PackageMetrics.ComponentStateMetrics componentStateMetrics =
+ new PackageMetrics.ComponentStateMetrics(setting,
+ UserHandle.getUid(userId, packageSetting.getAppId()),
+ packageSetting.getEnabled(userId));
+ if (!setEnabledSettingInternalLocked(computer, packageSetting, setting, userId,
+ callingPackage)) {
continue;
}
anyChanged = true;
+ componentStateMetricsList.add(componentStateMetrics);
if ((setting.getEnabledFlags() & PackageManager.SYNCHRONOUS) != 0) {
isSynchronous = true;
@@ -4029,6 +4037,9 @@
return;
}
+ // Log the metrics when the component state is changed.
+ PackageMetrics.reportComponentStateChanged(computer, componentStateMetricsList, userId);
+
if (isSynchronous) {
flushPackageRestrictionsAsUserInternalLocked(userId);
} else {
diff --git a/services/core/java/com/android/server/pm/PackageMetrics.java b/services/core/java/com/android/server/pm/PackageMetrics.java
index a0b6897..20598f9 100644
--- a/services/core/java/com/android/server/pm/PackageMetrics.java
+++ b/services/core/java/com/android/server/pm/PackageMetrics.java
@@ -19,12 +19,21 @@
import static android.os.Process.INVALID_UID;
import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.admin.SecurityLog;
+import android.content.ComponentName;
+import android.content.pm.ActivityInfo;
+import android.content.pm.Flags;
import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
import android.content.pm.parsing.ApkLiteParseUtils;
import android.os.UserHandle;
+import android.text.TextUtils;
import android.util.Pair;
+import android.util.Slog;
import android.util.SparseArray;
import com.android.internal.util.FrameworkStatsLog;
@@ -41,12 +50,14 @@
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
+import java.util.List;
import java.util.concurrent.atomic.AtomicLong;
/**
* Metrics class for reporting stats to logging infrastructures like statsd
*/
final class PackageMetrics {
+ private static final String TAG = "PackageMetrics";
public static final int STEP_PREPARE = 1;
public static final int STEP_SCAN = 2;
public static final int STEP_RECONCILE = 3;
@@ -344,4 +355,76 @@
SecurityLog.writeEvent(SecurityLog.TAG_PACKAGE_UNINSTALLED, packageName, versionCode,
userId);
}
+
+ public static class ComponentStateMetrics {
+ public int mUid;
+ public int mComponentOldState;
+ public int mComponentNewState;
+ public boolean mIsForWholeApp;
+ @NonNull private String mPackageName;
+ @Nullable private String mClassName;
+
+ ComponentStateMetrics(@NonNull PackageManager.ComponentEnabledSetting setting, int uid,
+ int componentOldState) {
+ mUid = uid;
+ mComponentOldState = componentOldState;
+ mComponentNewState = setting.getEnabledState();
+ mIsForWholeApp = !setting.isComponent();
+ mPackageName = setting.getPackageName();
+ mClassName = setting.getClassName();
+ }
+
+ public boolean isSameComponent(ActivityInfo activityInfo) {
+ if (activityInfo == null) {
+ return false;
+ }
+ return mIsForWholeApp ? TextUtils.equals(activityInfo.packageName, mPackageName)
+ : activityInfo.getComponentName().equals(
+ new ComponentName(mPackageName, mClassName));
+ }
+ }
+
+ public static void reportComponentStateChanged(@NonNull Computer computer,
+ List<ComponentStateMetrics> componentStateMetricsList, @UserIdInt int userId) {
+ if (!Flags.componentStateChangedMetrics()) {
+ return;
+ }
+ if (componentStateMetricsList == null || componentStateMetricsList.isEmpty()) {
+ Slog.d(TAG, "Fail to report component state due to metrics is empty");
+ return;
+ }
+ boolean isLauncher = false;
+ final List<ResolveInfo> resolveInfosForLauncher = getHomeActivitiesResolveInfoAsUser(
+ computer, userId);
+ final int resolveInfosForLauncherSize =
+ resolveInfosForLauncher != null ? resolveInfosForLauncher.size() : 0;
+ final int metricsSize = componentStateMetricsList.size();
+ for (int i = 0; i < metricsSize; i++) {
+ final ComponentStateMetrics componentStateMetrics = componentStateMetricsList.get(i);
+ for (int j = 0; j < resolveInfosForLauncherSize; j++) {
+ ResolveInfo resolveInfo = resolveInfosForLauncher.get(j);
+ if (componentStateMetrics.isSameComponent(resolveInfo.activityInfo)) {
+ isLauncher = true;
+ break;
+ }
+ }
+ reportComponentStateChanged(componentStateMetrics.mUid,
+ componentStateMetrics.mComponentOldState,
+ componentStateMetrics.mComponentNewState,
+ isLauncher,
+ componentStateMetrics.mIsForWholeApp);
+ }
+ }
+
+ private static void reportComponentStateChanged(int uid, int componentOldState,
+ int componentNewState, boolean isLauncher, boolean isForWholeApp) {
+ FrameworkStatsLog.write(FrameworkStatsLog.COMPONENT_STATE_CHANGED_REPORTED,
+ uid, componentOldState, componentNewState, isLauncher, isForWholeApp);
+ }
+
+ private static List<ResolveInfo> getHomeActivitiesResolveInfoAsUser(@NonNull Computer computer,
+ @UserIdInt int userId) {
+ return computer.queryIntentActivitiesInternal(computer.getHomeIntent(), /* resolvedType */
+ null, /* flags */ 0, userId);
+ }
}
diff --git a/services/core/java/com/android/server/pm/ScanPackageUtils.java b/services/core/java/com/android/server/pm/ScanPackageUtils.java
index 2e67b2f..9ab6016 100644
--- a/services/core/java/com/android/server/pm/ScanPackageUtils.java
+++ b/services/core/java/com/android/server/pm/ScanPackageUtils.java
@@ -170,6 +170,7 @@
}
}
+ boolean isPendingRestoreBefore = false;
if (pkgSetting != null && oldSharedUserSetting != sharedUserSetting) {
PackageManagerService.reportSettingsProblem(Log.WARN,
"Package " + parsedPackage.getPackageName() + " shared user changed from "
@@ -178,6 +179,9 @@
+ " to "
+ (sharedUserSetting != null ? sharedUserSetting.name : "<nothing>")
+ "; replacing with new");
+ // Preserve the value of isPendingRestore. We need to set it to the new PackageSetting
+ // if the value is true to restore the app
+ isPendingRestoreBefore = pkgSetting.isPendingRestore();
pkgSetting = null;
}
@@ -224,6 +228,11 @@
parsedPackage.getUsesStaticLibrariesVersions(), parsedPackage.getMimeGroups(),
newDomainSetId,
parsedPackage.getTargetSdkVersion(), parsedPackage.getRestrictUpdateHash());
+
+ // If isPendingRestore is true before, set the value true to the PackageSetting
+ if (isPendingRestoreBefore) {
+ pkgSetting.setPendingRestore(true);
+ }
} else {
// make a deep copy to avoid modifying any existing system state.
pkgSetting = new PackageSetting(pkgSetting);
diff --git a/services/core/java/com/android/server/pm/ShortcutLauncher.java b/services/core/java/com/android/server/pm/ShortcutLauncher.java
index 5e24673..00582bf 100644
--- a/services/core/java/com/android/server/pm/ShortcutLauncher.java
+++ b/services/core/java/com/android/server/pm/ShortcutLauncher.java
@@ -46,7 +46,7 @@
/**
* Launcher information used by {@link ShortcutService}.
*
- * All methods should be guarded by {@code #mShortcutUser.mService.mLock}.
+ * All methods should be guarded by {@code ShortcutPackageItem#mPackageItemLock}.
*/
class ShortcutLauncher extends ShortcutPackageItem {
private static final String TAG = ShortcutService.TAG;
@@ -66,7 +66,7 @@
/**
* Package name -> IDs.
*/
- @GuardedBy("mLock")
+ @GuardedBy("mPackageItemLock")
private final ArrayMap<UserPackage, ArraySet<String>> mPinnedShortcuts = new ArrayMap<>();
private ShortcutLauncher(@NonNull ShortcutUser shortcutUser,
@@ -99,7 +99,7 @@
*/
private void onRestoreBlocked() {
final ArrayList<UserPackage> pinnedPackages;
- synchronized (mLock) {
+ synchronized (mPackageItemLock) {
pinnedPackages = new ArrayList<>(mPinnedShortcuts.keySet());
mPinnedShortcuts.clear();
}
@@ -138,7 +138,7 @@
final int idSize = ids.size();
if (idSize == 0) {
- synchronized (mLock) {
+ synchronized (mPackageItemLock) {
mPinnedShortcuts.remove(up);
}
} else {
@@ -165,7 +165,7 @@
floatingSet.add(id);
}
}
- synchronized (mLock) {
+ synchronized (mPackageItemLock) {
final ArraySet<String> prevSet = mPinnedShortcuts.get(up);
if (prevSet != null) {
for (String id : floatingSet) {
@@ -187,7 +187,7 @@
@Nullable
public ArraySet<String> getPinnedShortcutIds(@NonNull String packageName,
@UserIdInt int packageUserId) {
- synchronized (mLock) {
+ synchronized (mPackageItemLock) {
final ArraySet<String> pinnedShortcuts = mPinnedShortcuts.get(
UserPackage.of(packageUserId, packageName));
return pinnedShortcuts == null ? null : new ArraySet<>(pinnedShortcuts);
@@ -198,7 +198,7 @@
* Return true if the given shortcut is pinned by this launcher.<code></code>
*/
public boolean hasPinned(ShortcutInfo shortcut) {
- synchronized (mLock) {
+ synchronized (mPackageItemLock) {
final ArraySet<String> pinned = mPinnedShortcuts.get(
UserPackage.of(shortcut.getUserId(), shortcut.getPackage()));
return (pinned != null) && pinned.contains(shortcut.getId());
@@ -211,7 +211,7 @@
public void addPinnedShortcut(@NonNull String packageName, @UserIdInt int packageUserId,
String id, boolean forPinRequest) {
final ArrayList<String> pinnedList;
- synchronized (mLock) {
+ synchronized (mPackageItemLock) {
final ArraySet<String> pinnedSet = mPinnedShortcuts.get(
UserPackage.of(packageUserId, packageName));
if (pinnedSet != null) {
@@ -227,7 +227,7 @@
}
boolean cleanUpPackage(String packageName, @UserIdInt int packageUserId) {
- synchronized (mLock) {
+ synchronized (mPackageItemLock) {
return mPinnedShortcuts.remove(UserPackage.of(packageUserId, packageName)) != null;
}
}
@@ -253,7 +253,7 @@
return;
}
final ArrayMap<UserPackage, ArraySet<String>> pinnedShortcuts;
- synchronized (mLock) {
+ synchronized (mPackageItemLock) {
pinnedShortcuts = new ArrayMap<>(mPinnedShortcuts);
}
final int size = pinnedShortcuts.size();
@@ -366,7 +366,7 @@
: ShortcutService.parseIntAttribute(parser,
ATTR_PACKAGE_USER_ID, ownerUserId);
ids = new ArraySet<>();
- synchronized (ret.mLock) {
+ synchronized (ret.mPackageItemLock) {
ret.mPinnedShortcuts.put(
UserPackage.of(packageUserId, packageName), ids);
}
@@ -407,7 +407,7 @@
pw.println();
final ArrayMap<UserPackage, ArraySet<String>> pinnedShortcuts;
- synchronized (mLock) {
+ synchronized (mPackageItemLock) {
pinnedShortcuts = new ArrayMap<>(mPinnedShortcuts);
}
final int size = pinnedShortcuts.size();
diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java
index 449e9ab..c929c1f 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackage.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackage.java
@@ -163,20 +163,20 @@
/**
* An in-memory copy of shortcuts for this package that was loaded from xml, keyed on IDs.
*/
- @GuardedBy("mLock")
+ @GuardedBy("mPackageItemLock")
private final ArrayMap<String, ShortcutInfo> mShortcuts = new ArrayMap<>();
/**
* A temporary copy of shortcuts that are to be cleared once persisted into AppSearch, keyed on
* IDs.
*/
- @GuardedBy("mLock")
+ @GuardedBy("mPackageItemLock")
private final ArrayMap<String, ShortcutInfo> mTransientShortcuts = new ArrayMap<>(0);
/**
* All the share targets from the package
*/
- @GuardedBy("mLock")
+ @GuardedBy("mPackageItemLock")
private final ArrayList<ShareTargetInfo> mShareTargets = new ArrayList<>(0);
/**
@@ -193,10 +193,10 @@
private long mLastKnownForegroundElapsedTime;
- @GuardedBy("mLock")
+ @GuardedBy("mPackageItemLock")
private long mLastReportedTime;
- @GuardedBy("mLock")
+ @GuardedBy("mPackageItemLock")
private boolean mIsAppSearchSchemaUpToDate;
private ShortcutPackage(ShortcutUser shortcutUser,
@@ -233,7 +233,7 @@
}
public int getShortcutCount() {
- synchronized (mLock) {
+ synchronized (mPackageItemLock) {
return mShortcuts.size();
}
}
@@ -276,7 +276,7 @@
@Nullable
public ShortcutInfo findShortcutById(@Nullable final String id) {
if (id == null) return null;
- synchronized (mLock) {
+ synchronized (mPackageItemLock) {
return mShortcuts.get(id);
}
}
@@ -354,7 +354,7 @@
*/
private ShortcutInfo forceDeleteShortcutInner(@NonNull String id) {
final ShortcutInfo shortcut;
- synchronized (mLock) {
+ synchronized (mPackageItemLock) {
shortcut = mShortcuts.remove(id);
if (shortcut != null) {
removeIcon(shortcut);
@@ -409,7 +409,7 @@
if (newShortcut.isExcludedFromSurfaces(ShortcutInfo.SURFACE_LAUNCHER)) {
if (isAppSearchEnabled()) {
- synchronized (mLock) {
+ synchronized (mPackageItemLock) {
mTransientShortcuts.put(newShortcut.getId(), newShortcut);
}
}
@@ -482,7 +482,7 @@
if (newShortcut.isExcludedFromSurfaces(ShortcutInfo.SURFACE_LAUNCHER)) {
if (isAppSearchEnabled()) {
- synchronized (mLock) {
+ synchronized (mPackageItemLock) {
mTransientShortcuts.put(newShortcut.getId(), newShortcut);
}
}
@@ -506,7 +506,7 @@
final ShortcutService service = mShortcutUser.mService;
// Ensure the total number of shortcuts doesn't exceed the hard limit per app.
final int maxShortcutPerApp = service.getMaxAppShortcuts();
- synchronized (mLock) {
+ synchronized (mPackageItemLock) {
final List<ShortcutInfo> appShortcuts = mShortcuts.values().stream().filter(si ->
!si.isPinned()).collect(Collectors.toList());
if (appShortcuts.size() >= maxShortcutPerApp) {
@@ -555,7 +555,7 @@
public List<ShortcutInfo> deleteAllDynamicShortcuts() {
final long now = mShortcutUser.mService.injectCurrentTimeMillis();
boolean changed = false;
- synchronized (mLock) {
+ synchronized (mPackageItemLock) {
for (int i = mShortcuts.size() - 1; i >= 0; i--) {
ShortcutInfo si = mShortcuts.valueAt(i);
if (si.isDynamic() && si.isVisibleToPublisher()) {
@@ -914,7 +914,7 @@
List<ShortcutManager.ShareShortcutInfo> getMatchingShareTargets(
@NonNull final IntentFilter filter, @Nullable final String pkgName) {
- synchronized (mLock) {
+ synchronized (mPackageItemLock) {
final List<ShareTargetInfo> matchedTargets = new ArrayList<>();
for (int i = 0; i < mShareTargets.size(); i++) {
final ShareTargetInfo target = mShareTargets.get(i);
@@ -967,7 +967,7 @@
}
public boolean hasShareTargets() {
- synchronized (mLock) {
+ synchronized (mPackageItemLock) {
return !mShareTargets.isEmpty();
}
}
@@ -978,7 +978,7 @@
* the app's Xml resource.
*/
int getSharingShortcutCount() {
- synchronized (mLock) {
+ synchronized (mPackageItemLock) {
if (mShareTargets.isEmpty()) {
return 0;
}
@@ -1017,7 +1017,7 @@
/**
* Return the filenames (excluding path names) of icon bitmap files from this package.
*/
- @GuardedBy("mLock")
+ @GuardedBy("mPackageItemLock")
private ArraySet<String> getUsedBitmapFilesLocked() {
final ArraySet<String> usedFiles = new ArraySet<>(1);
forEachShortcut(si -> {
@@ -1029,7 +1029,7 @@
}
public void cleanupDanglingBitmapFiles(@NonNull File path) {
- synchronized (mLock) {
+ synchronized (mPackageItemLock) {
mShortcutBitmapSaver.waitForAllSavesLocked();
final ArraySet<String> usedFiles = getUsedBitmapFilesLocked();
@@ -1136,7 +1136,7 @@
// Now prepare to publish manifest shortcuts.
List<ShortcutInfo> newManifestShortcutList = null;
int shareTargetSize = 0;
- synchronized (mLock) {
+ synchronized (mPackageItemLock) {
try {
shareTargetSize = mShareTargets.size();
newManifestShortcutList = ShortcutParser.parseShortcuts(mShortcutUser.mService,
@@ -1680,7 +1680,7 @@
void reportShortcutUsed(@NonNull final UsageStatsManagerInternal usageStatsManagerInternal,
@NonNull final String shortcutId) {
- synchronized (mLock) {
+ synchronized (mPackageItemLock) {
final long currentTS = SystemClock.elapsedRealtime();
final ShortcutService s = mShortcutUser.mService;
if (currentTS - mLastReportedTime > s.mSaveDelayMillis) {
@@ -1757,7 +1757,7 @@
pw.println(")");
pw.println();
- synchronized (mLock) {
+ synchronized (mPackageItemLock) {
mShortcutBitmapSaver.dumpLocked(pw, " ");
}
}
@@ -1827,7 +1827,7 @@
@Override
public void saveToXml(@NonNull TypedXmlSerializer out, boolean forBackup)
throws IOException, XmlPullParserException {
- synchronized (mLock) {
+ synchronized (mPackageItemLock) {
final int size = mShortcuts.size();
final int shareTargetSize = mShareTargets.size();
@@ -2037,7 +2037,7 @@
final ShortcutPackage ret = new ShortcutPackage(shortcutUser,
shortcutUser.getUserId(), packageName);
- synchronized (ret.mLock) {
+ synchronized (ret.mPackageItemLock) {
ret.mIsAppSearchSchemaUpToDate = ShortcutService.parseIntAttribute(
parser, ATTR_SCHEMA_VERSON, 0) == AppSearchShortcutInfo.SCHEMA_VERSION;
@@ -2283,7 +2283,7 @@
@VisibleForTesting
List<ShareTargetInfo> getAllShareTargetsForTest() {
- synchronized (mLock) {
+ synchronized (mPackageItemLock) {
return new ArrayList<>(mShareTargets);
}
}
@@ -2404,7 +2404,7 @@
@NonNull final Consumer<ShortcutInfo> transform) {
Objects.requireNonNull(id);
Objects.requireNonNull(transform);
- synchronized (mLock) {
+ synchronized (mPackageItemLock) {
if (shortcut != null) {
transform.accept(shortcut);
}
@@ -2424,7 +2424,7 @@
private void saveShortcut(@NonNull final Collection<ShortcutInfo> shortcuts) {
Objects.requireNonNull(shortcuts);
- synchronized (mLock) {
+ synchronized (mPackageItemLock) {
for (ShortcutInfo si : shortcuts) {
mShortcuts.put(si.getId(), si);
}
@@ -2433,7 +2433,7 @@
@Nullable
List<ShortcutInfo> findAll(@NonNull final Collection<String> ids) {
- synchronized (mLock) {
+ synchronized (mPackageItemLock) {
return ids.stream().map(mShortcuts::get)
.filter(Objects::nonNull).collect(Collectors.toList());
}
@@ -2455,7 +2455,7 @@
private void forEachShortcutStopWhen(
@NonNull final Function<ShortcutInfo, Boolean> cb) {
- synchronized (mLock) {
+ synchronized (mPackageItemLock) {
for (int i = mShortcuts.size() - 1; i >= 0; i--) {
final ShortcutInfo si = mShortcuts.valueAt(i);
if (cb.apply(si)) {
@@ -2600,7 +2600,7 @@
})));
}
- @GuardedBy("mLock")
+ @GuardedBy("mPackageItemLock")
@Override
void scheduleSaveToAppSearchLocked() {
final Map<String, ShortcutInfo> copy = new ArrayMap<>(mShortcuts);
@@ -2684,7 +2684,7 @@
.penaltyLog() // TODO: change this to penaltyDeath to fix the call-site
.build());
future = mShortcutUser.getAppSearch(searchContext);
- synchronized (mLock) {
+ synchronized (mPackageItemLock) {
if (!mIsAppSearchSchemaUpToDate) {
future = future.thenCompose(this::setupSchema);
}
diff --git a/services/core/java/com/android/server/pm/ShortcutPackageItem.java b/services/core/java/com/android/server/pm/ShortcutPackageItem.java
index 12115af..dfd2e08 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackageItem.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackageItem.java
@@ -39,7 +39,7 @@
import java.util.Objects;
/**
- * All methods should be either guarded by {@code #mShortcutUser.mService.mLock} or {@code #mLock}.
+ * All methods should be either guarded by {@code #mPackageItemLock}.
*/
abstract class ShortcutPackageItem {
private static final String TAG = ShortcutService.TAG;
@@ -52,10 +52,10 @@
protected ShortcutUser mShortcutUser;
- @GuardedBy("mLock")
+ @GuardedBy("mPackageItemLock")
protected final ShortcutBitmapSaver mShortcutBitmapSaver;
- protected final Object mLock = new Object();
+ protected final Object mPackageItemLock = new Object();
protected ShortcutPackageItem(@NonNull ShortcutUser shortcutUser,
int packageUserId, @NonNull String packageName,
@@ -157,7 +157,7 @@
public abstract void saveToXml(@NonNull TypedXmlSerializer out, boolean forBackup)
throws IOException, XmlPullParserException;
- @GuardedBy("mLock")
+ @GuardedBy("mPackageItemLock")
public void saveToFileLocked(File path, boolean forBackup) {
try (ResilientAtomicFile file = getResilientFile(path)) {
FileOutputStream os = null;
@@ -187,7 +187,7 @@
}
}
- @GuardedBy("mLock")
+ @GuardedBy("mPackageItemLock")
void scheduleSaveToAppSearchLocked() {
}
@@ -219,7 +219,7 @@
if (ShortcutService.DEBUG || ShortcutService.DEBUG_REBOOT) {
Slog.d(TAG, "Saving package item " + getPackageName() + " to " + path);
}
- synchronized (mLock) {
+ synchronized (mPackageItemLock) {
path.getParentFile().mkdirs();
// TODO: Since we are persisting shortcuts into AppSearch, we should read from/write to
// AppSearch as opposed to maintaining a separate XML file.
@@ -229,14 +229,14 @@
}
public boolean waitForBitmapSaves() {
- synchronized (mLock) {
+ synchronized (mPackageItemLock) {
return mShortcutBitmapSaver.waitForAllSavesLocked();
}
}
public void saveBitmap(ShortcutInfo shortcut,
int maxDimension, Bitmap.CompressFormat format, int quality) {
- synchronized (mLock) {
+ synchronized (mPackageItemLock) {
mShortcutBitmapSaver.saveBitmapLocked(shortcut, maxDimension, format, quality);
}
}
@@ -246,19 +246,19 @@
*/
@Nullable
public String getBitmapPathMayWait(ShortcutInfo shortcut) {
- synchronized (mLock) {
+ synchronized (mPackageItemLock) {
return mShortcutBitmapSaver.getBitmapPathMayWaitLocked(shortcut);
}
}
public void removeIcon(ShortcutInfo shortcut) {
- synchronized (mLock) {
+ synchronized (mPackageItemLock) {
mShortcutBitmapSaver.removeIcon(shortcut);
}
}
void removeShortcutPackageItem() {
- synchronized (mLock) {
+ synchronized (mPackageItemLock) {
getResilientFile(getShortcutPackageItemFile()).delete();
}
}
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index fe9c3f2..82902d4 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -50,7 +50,6 @@
import android.content.pm.IPackageManager;
import android.content.pm.IShortcutService;
import android.content.pm.LauncherApps;
-import android.content.pm.LauncherApps.ShortcutChangeCallback;
import android.content.pm.LauncherApps.ShortcutQuery;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
@@ -152,7 +151,6 @@
import java.util.Collections;
import java.util.List;
import java.util.Objects;
-import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Consumer;
@@ -295,7 +293,7 @@
final Context mContext;
- private final Object mLock = new Object();
+ private final Object mServiceLock = new Object();
private final Object mNonPersistentUsersLock = new Object();
private final Object mWtfLock = new Object();
@@ -322,18 +320,19 @@
private final Handler mHandler;
- private final CopyOnWriteArrayList<ShortcutChangeListener> mListeners =
- new CopyOnWriteArrayList<>();
+ @GuardedBy("mServiceLock")
+ private final ArrayList<ShortcutChangeListener> mListeners = new ArrayList<>(1);
- private final CopyOnWriteArrayList<ShortcutChangeCallback> mShortcutChangeCallbacks =
- new CopyOnWriteArrayList<>();
+ @GuardedBy("mServiceLock")
+ private final ArrayList<LauncherApps.ShortcutChangeCallback> mShortcutChangeCallbacks =
+ new ArrayList<>(1);
private final AtomicLong mRawLastResetTime = new AtomicLong(0);
/**
* User ID -> UserShortcuts
*/
- @GuardedBy("mLock")
+ @GuardedBy("mServiceLock")
private final SparseArray<ShortcutUser> mUsers = new SparseArray<>();
/**
@@ -388,13 +387,13 @@
private final ShortcutRequestPinProcessor mShortcutRequestPinProcessor;
private final ShortcutDumpFiles mShortcutDumpFiles;
- @GuardedBy("mLock")
+ @GuardedBy("mServiceLock")
final SparseIntArray mUidState = new SparseIntArray();
- @GuardedBy("mLock")
+ @GuardedBy("mServiceLock")
final SparseLongArray mUidLastForegroundElapsedTime = new SparseLongArray();
- @GuardedBy("mLock")
+ @GuardedBy("mServiceLock")
private List<Integer> mDirtyUserIds = new ArrayList<>();
private final AtomicBoolean mBootCompleted = new AtomicBoolean();
@@ -473,7 +472,7 @@
@GuardedBy("mWtfLock")
private Exception mLastWtfStacktrace;
- @GuardedBy("mLock")
+ @GuardedBy("mServiceLock")
private final MetricsLogger mMetricsLogger = new MetricsLogger();
private final boolean mIsAppSearchEnabled;
@@ -518,7 +517,7 @@
mUriPermissionOwner = mUriGrantsManagerInternal.newUriPermissionOwner(TAG);
mRoleManager = Objects.requireNonNull(mContext.getSystemService(RoleManager.class));
- mShortcutRequestPinProcessor = new ShortcutRequestPinProcessor(this, mLock);
+ mShortcutRequestPinProcessor = new ShortcutRequestPinProcessor(this, mServiceLock);
mShortcutDumpFiles = new ShortcutDumpFiles(this);
mIsAppSearchEnabled = DeviceConfig.getBoolean(NAMESPACE_SYSTEMUI,
SystemUiDeviceConfigFlags.SHORTCUT_APPSEARCH_INTEGRATION, false)
@@ -595,7 +594,7 @@
// Default launcher is removed or changed, revoke all URI permissions.
mUriGrantsManagerInternal.revokeUriPermissionFromOwner(mUriPermissionOwner, null, ~0, 0);
- synchronized (mLock) {
+ synchronized (mServiceLock) {
// Clear the launcher cache for this user. It will be set again next time the default
// launcher is read from RoleManager.
if (isUserLoadedLocked(userId)) {
@@ -622,7 +621,7 @@
Slog.d(TAG, "onUidStateChanged: uid=" + uid + " state=" + procState);
}
Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "shortcutHandleOnUidStateChanged");
- synchronized (mLock) {
+ synchronized (mServiceLock) {
mUidState.put(uid, procState);
// We need to keep track of last time an app comes to foreground.
@@ -639,7 +638,7 @@
return processState <= PROCESS_STATE_FOREGROUND_THRESHOLD;
}
- @GuardedBy("mLock")
+ @GuardedBy("mServiceLock")
boolean isUidForegroundLocked(int uid) {
if (uid == Process.SYSTEM_UID) {
// IUidObserver doesn't report the state of SYSTEM, but it always has bound services,
@@ -655,7 +654,7 @@
return isProcessStateForeground(mActivityManagerInternal.getUidProcessState(uid));
}
- @GuardedBy("mLock")
+ @GuardedBy("mServiceLock")
long getUidLastForegroundElapsedTimeLocked(int uid) {
return mUidLastForegroundElapsedTime.get(uid);
}
@@ -729,7 +728,7 @@
final long start = getStatStartTime();
injectRunOnNewThread(() -> {
Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "shortcutHandleUnlockUser");
- synchronized (mLock) {
+ synchronized (mServiceLock) {
logDurationStat(Stats.ASYNC_PRELOAD_USER_DELAY, start);
getUserShortcutsLocked(userId);
}
@@ -743,7 +742,7 @@
Slog.d(TAG, "handleStopUser: user=" + userId);
}
Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "shortcutHandleStopUser");
- synchronized (mLock) {
+ synchronized (mServiceLock) {
unloadUserLocked(userId);
synchronized (mUnlockedUsers) {
@@ -753,7 +752,7 @@
Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
}
- @GuardedBy("mLock")
+ @GuardedBy("mServiceLock")
private void unloadUserLocked(int userId) {
if (DEBUG || DEBUG_REBOOT) {
Slog.d(TAG, "unloadUserLocked: user=" + userId);
@@ -784,7 +783,7 @@
* Init the instance. (load the state file, etc)
*/
private void initialize() {
- synchronized (mLock) {
+ synchronized (mServiceLock) {
loadConfigurationLocked();
loadBaseStateLocked();
}
@@ -1003,7 +1002,7 @@
FileOutputStream outs = null;
try {
- synchronized (mLock) {
+ synchronized (mServiceLock) {
outs = file.startWrite();
}
@@ -1029,7 +1028,7 @@
}
}
- @GuardedBy("mLock")
+ @GuardedBy("mServiceLock")
private void loadBaseStateLocked() {
mRawLastResetTime.set(0);
@@ -1104,7 +1103,7 @@
Slog.d(TAG, "Saving to " + file);
}
- synchronized (mLock) {
+ synchronized (mServiceLock) {
os = file.startWrite();
saveUserInternalLocked(userId, os, /* forBackup= */ false);
}
@@ -1122,7 +1121,7 @@
getUserShortcutsLocked(userId).logSharingShortcutStats(mMetricsLogger);
}
- @GuardedBy("mLock")
+ @GuardedBy("mServiceLock")
private void saveUserInternalLocked(@UserIdInt int userId, OutputStream os,
boolean forBackup) throws IOException, XmlPullParserException {
@@ -1224,7 +1223,7 @@
if (DEBUG || DEBUG_REBOOT) {
Slog.d(TAG, "Scheduling to save for " + userId);
}
- synchronized (mLock) {
+ synchronized (mServiceLock) {
if (!mDirtyUserIds.contains(userId)) {
mDirtyUserIds.add(userId);
}
@@ -1245,7 +1244,7 @@
try {
Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "shortcutSaveDirtyInfo");
List<Integer> dirtyUserIds = new ArrayList<>();
- synchronized (mLock) {
+ synchronized (mServiceLock) {
List<Integer> tmp = mDirtyUserIds;
mDirtyUserIds = dirtyUserIds;
dirtyUserIds = tmp;
@@ -1266,14 +1265,14 @@
}
/** Return the last reset time. */
- @GuardedBy("mLock")
+ @GuardedBy("mServiceLock")
long getLastResetTimeLocked() {
updateTimesLocked();
return mRawLastResetTime.get();
}
/** Return the next reset time. */
- @GuardedBy("mLock")
+ @GuardedBy("mServiceLock")
long getNextResetTimeLocked() {
updateTimesLocked();
return mRawLastResetTime.get() + mResetInterval;
@@ -1286,7 +1285,7 @@
/**
* Update the last reset time.
*/
- @GuardedBy("mLock")
+ @GuardedBy("mServiceLock")
private void updateTimesLocked() {
final long now = injectCurrentTimeMillis();
@@ -1315,7 +1314,7 @@
}
}
- // Requires mLock held, but "Locked" prefix would look weired so we just say "L".
+ // Requires mServiceLock held, but "Locked" prefix would look weird so we just say "L".
protected boolean isUserUnlockedL(@UserIdInt int userId) {
// First, check the local copy.
synchronized (mUnlockedUsers) {
@@ -1331,14 +1330,14 @@
return mUserManagerInternal.isUserUnlockingOrUnlocked(userId);
}
- // Requires mLock held, but "Locked" prefix would look weired so we jsut say "L".
+ // Requires mServiceLock held, but "Locked" prefix would look weird so we just say "L".
void throwIfUserLockedL(@UserIdInt int userId) {
if (!isUserUnlockedL(userId)) {
throw new IllegalStateException("User " + userId + " is locked or not running");
}
}
- @GuardedBy("mLock")
+ @GuardedBy("mServiceLock")
@NonNull
private boolean isUserLoadedLocked(@UserIdInt int userId) {
return mUsers.get(userId) != null;
@@ -1347,7 +1346,7 @@
private int mLastLockedUser = -1;
/** Return the per-user state. */
- @GuardedBy("mLock")
+ @GuardedBy("mServiceLock")
@NonNull
ShortcutUser getUserShortcutsLocked(@UserIdInt int userId) {
if (!isUserUnlockedL(userId)) {
@@ -1386,7 +1385,7 @@
return ret;
}
- @GuardedBy("mLock")
+ @GuardedBy("mServiceLock")
void forEachLoadedUserLocked(@NonNull Consumer<ShortcutUser> c) {
for (int i = mUsers.size() - 1; i >= 0; i--) {
c.accept(mUsers.valueAt(i));
@@ -1397,7 +1396,7 @@
* Return the per-user per-package state. If the caller is a publisher, use
* {@link #getPackageShortcutsForPublisherLocked} instead.
*/
- @GuardedBy("mLock")
+ @GuardedBy("mServiceLock")
@NonNull
ShortcutPackage getPackageShortcutsLocked(
@NonNull String packageName, @UserIdInt int userId) {
@@ -1405,7 +1404,7 @@
}
/** Return the per-user per-package state. Use this when the caller is a publisher. */
- @GuardedBy("mLock")
+ @GuardedBy("mServiceLock")
@NonNull
ShortcutPackage getPackageShortcutsForPublisherLocked(
@NonNull String packageName, @UserIdInt int userId) {
@@ -1414,7 +1413,7 @@
return ret;
}
- @GuardedBy("mLock")
+ @GuardedBy("mServiceLock")
@NonNull
ShortcutLauncher getLauncherShortcutsLocked(
@NonNull String packageName, @UserIdInt int ownerUserId,
@@ -1443,7 +1442,7 @@
* {@link ShortcutBitmapSaver#waitForAllSavesLocked()} to make sure there's no pending bitmap
* saves are going on.
*/
- @GuardedBy("mLock")
+ @GuardedBy("mServiceLock")
private void cleanupDanglingBitmapDirectoriesLocked(@UserIdInt int userId) {
if (DEBUG) {
Slog.d(TAG, "cleanupDanglingBitmaps: userId=" + userId);
@@ -1780,7 +1779,7 @@
void injectPostToHandlerDebounced(@NonNull final Object token, @NonNull final Runnable r) {
Objects.requireNonNull(token);
Objects.requireNonNull(r);
- synchronized (mLock) {
+ synchronized (mServiceLock) {
mHandler.removeCallbacksAndMessages(token);
mHandler.postDelayed(r, token, CALLBACK_DELAY);
}
@@ -1842,11 +1841,18 @@
@UserIdInt final int userId) {
return () -> {
try {
- if (!isUserUnlockedL(userId)) {
- return;
+ final ArrayList<ShortcutChangeListener> copy;
+ synchronized (mServiceLock) {
+ if (!isUserUnlockedL(userId)) {
+ return;
+ }
+
+ copy = new ArrayList<>(mListeners);
}
// Note onShortcutChanged() needs to be called with the system service permissions.
- mListeners.forEach(listener -> listener.onShortcutChanged(packageName, userId));
+ for (int i = copy.size() - 1; i >= 0; i--) {
+ copy.get(i).onShortcutChanged(packageName, userId);
+ }
} catch (Exception ignore) {
}
};
@@ -1861,17 +1867,22 @@
final UserHandle user = UserHandle.of(userId);
injectPostToHandler(() -> {
try {
- if (!isUserUnlockedL(userId)) {
- return;
+ final ArrayList<LauncherApps.ShortcutChangeCallback> copy;
+ synchronized (mServiceLock) {
+ if (!isUserUnlockedL(userId)) {
+ return;
+ }
+
+ copy = new ArrayList<>(mShortcutChangeCallbacks);
}
- mShortcutChangeCallbacks.forEach(callback -> {
+ for (int i = copy.size() - 1; i >= 0; i--) {
if (!CollectionUtils.isEmpty(changedList)) {
- callback.onShortcutsAddedOrUpdated(packageName, changedList, user);
+ copy.get(i).onShortcutsAddedOrUpdated(packageName, changedList, user);
}
if (!CollectionUtils.isEmpty(removedList)) {
- callback.onShortcutsRemoved(packageName, removedList, user);
+ copy.get(i).onShortcutsRemoved(packageName, removedList, user);
}
- });
+ }
} catch (Exception ignore) {
}
});
@@ -2015,7 +2026,7 @@
List<ShortcutInfo> removedShortcuts = null;
final ShortcutPackage ps;
- synchronized (mLock) {
+ synchronized (mServiceLock) {
throwIfUserLockedL(userId);
ps = getPackageShortcutsForPublisherLocked(packageName, userId);
@@ -2084,7 +2095,7 @@
final List<ShortcutInfo> changedShortcuts = new ArrayList<>(1);
final ShortcutPackage ps;
- synchronized (mLock) {
+ synchronized (mServiceLock) {
throwIfUserLockedL(userId);
ps = getPackageShortcutsForPublisherLocked(packageName, userId);
@@ -2184,7 +2195,7 @@
List<ShortcutInfo> changedShortcuts = null;
final ShortcutPackage ps;
- synchronized (mLock) {
+ synchronized (mServiceLock) {
throwIfUserLockedL(userId);
ps = getPackageShortcutsForPublisherLocked(packageName, userId);
@@ -2241,7 +2252,7 @@
List<ShortcutInfo> removedShortcuts = null;
final ShortcutPackage ps;
- synchronized (mLock) {
+ synchronized (mServiceLock) {
throwIfUserLockedL(userId);
ps = getPackageShortcutsForPublisherLocked(packageName, userId);
@@ -2306,7 +2317,7 @@
verifyCaller(packageName, userId);
verifyShortcutInfoPackage(packageName, shortcut);
final Intent intent;
- synchronized (mLock) {
+ synchronized (mServiceLock) {
throwIfUserLockedL(userId);
// Send request to the launcher, if supported.
intent = mShortcutRequestPinProcessor.createShortcutResultIntent(shortcut, userId);
@@ -2337,7 +2348,7 @@
}
final boolean ret;
- synchronized (mLock) {
+ synchronized (mServiceLock) {
throwIfUserLockedL(userId);
Preconditions.checkState(isUidForegroundLocked(callingUid),
@@ -2378,7 +2389,7 @@
List<ShortcutInfo> changedShortcuts = null;
List<ShortcutInfo> removedShortcuts = null;
final ShortcutPackage ps;
- synchronized (mLock) {
+ synchronized (mServiceLock) {
throwIfUserLockedL(userId);
ps = getPackageShortcutsForPublisherLocked(packageName, userId);
ps.ensureImmutableShortcutsNotIncludedWithIds((List<String>) shortcutIds,
@@ -2419,7 +2430,7 @@
Objects.requireNonNull(shortcutIds, "shortcutIds must be provided");
List<ShortcutInfo> changedShortcuts = null;
final ShortcutPackage ps;
- synchronized (mLock) {
+ synchronized (mServiceLock) {
throwIfUserLockedL(userId);
ps = getPackageShortcutsForPublisherLocked(packageName, userId);
ps.ensureImmutableShortcutsNotIncludedWithIds((List<String>) shortcutIds,
@@ -2449,7 +2460,7 @@
List<ShortcutInfo> changedShortcuts = null;
List<ShortcutInfo> removedShortcuts = null;
final ShortcutPackage ps;
- synchronized (mLock) {
+ synchronized (mServiceLock) {
throwIfUserLockedL(userId);
ps = getPackageShortcutsForPublisherLocked(packageName, userId);
ps.ensureImmutableShortcutsNotIncludedWithIds((List<String>) shortcutIds,
@@ -2487,7 +2498,7 @@
List<ShortcutInfo> changedShortcuts = new ArrayList<>();
List<ShortcutInfo> removedShortcuts = null;
final ShortcutPackage ps;
- synchronized (mLock) {
+ synchronized (mServiceLock) {
throwIfUserLockedL(userId);
ps = getPackageShortcutsForPublisherLocked(packageName, userId);
// Dynamic shortcuts that are either cached or pinned will not get deleted.
@@ -2511,7 +2522,7 @@
List<ShortcutInfo> changedShortcuts = null;
List<ShortcutInfo> removedShortcuts = null;
final ShortcutPackage ps;
- synchronized (mLock) {
+ synchronized (mServiceLock) {
throwIfUserLockedL(userId);
ps = getPackageShortcutsForPublisherLocked(packageName, userId);
ps.ensureImmutableShortcutsNotIncludedWithIds((List<String>) shortcutIds,
@@ -2545,7 +2556,7 @@
public ParceledListSlice<ShortcutInfo> getShortcuts(String packageName,
@ShortcutManager.ShortcutMatchFlags int matchFlags, @UserIdInt int userId) {
verifyCaller(packageName, userId);
- synchronized (mLock) {
+ synchronized (mServiceLock) {
throwIfUserLockedL(userId);
final boolean matchDynamic = (matchFlags & ShortcutManager.FLAG_MATCH_DYNAMIC) != 0;
final boolean matchPinned = (matchFlags & ShortcutManager.FLAG_MATCH_PINNED) != 0;
@@ -2575,7 +2586,7 @@
"getShareTargets");
final ComponentName chooser = injectChooserActivity();
final String pkg = chooser != null ? chooser.getPackageName() : mContext.getPackageName();
- synchronized (mLock) {
+ synchronized (mServiceLock) {
throwIfUserLockedL(userId);
final List<ShortcutManager.ShareShortcutInfo> shortcutInfoList = new ArrayList<>();
final ShortcutUser user = getUserShortcutsLocked(userId);
@@ -2592,7 +2603,7 @@
enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APP_PREDICTIONS,
"hasShareTargets");
- synchronized (mLock) {
+ synchronized (mServiceLock) {
throwIfUserLockedL(userId);
return getPackageShortcutsLocked(packageToCheck, userId).hasShareTargets();
@@ -2606,7 +2617,7 @@
enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APP_PREDICTIONS,
"isSharingShortcut");
- synchronized (mLock) {
+ synchronized (mServiceLock) {
throwIfUserLockedL(userId);
throwIfUserLockedL(callingUserId);
@@ -2623,7 +2634,7 @@
return false;
}
- @GuardedBy("mLock")
+ @GuardedBy("mServiceLock")
private ParceledListSlice<ShortcutInfo> getShortcutsWithQueryLocked(@NonNull String packageName,
@UserIdInt int userId, int cloneFlags, @NonNull Predicate<ShortcutInfo> filter) {
@@ -2649,7 +2660,7 @@
final boolean unlimited = injectHasUnlimitedShortcutsApiCallsPermission(
injectBinderCallingPid(), injectBinderCallingUid());
- synchronized (mLock) {
+ synchronized (mServiceLock) {
throwIfUserLockedL(userId);
final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId);
@@ -2661,7 +2672,7 @@
public long getRateLimitResetTime(String packageName, @UserIdInt int userId) {
verifyCaller(packageName, userId);
- synchronized (mLock) {
+ synchronized (mServiceLock) {
throwIfUserLockedL(userId);
return getNextResetTimeLocked();
@@ -2672,7 +2683,7 @@
public int getIconMaxDimensions(String packageName, int userId) {
verifyCaller(packageName, userId);
- synchronized (mLock) {
+ synchronized (mServiceLock) {
return mMaxIconDimension;
}
}
@@ -2686,7 +2697,7 @@
shortcutId, packageName, userId));
}
final ShortcutPackage ps;
- synchronized (mLock) {
+ synchronized (mServiceLock) {
throwIfUserLockedL(userId);
ps = getPackageShortcutsForPublisherLocked(packageName, userId);
if (ps.findShortcutById(shortcutId) == null) {
@@ -2723,7 +2734,7 @@
}
void resetThrottlingInner(@UserIdInt int userId) {
- synchronized (mLock) {
+ synchronized (mServiceLock) {
if (!isUserUnlockedL(userId)) {
Log.w(TAG, "User " + userId + " is locked or not running");
return;
@@ -2747,7 +2758,7 @@
Slog.d(TAG, "onApplicationActive: package=" + packageName + " userid=" + userId);
}
enforceResetThrottlingPermission();
- synchronized (mLock) {
+ synchronized (mServiceLock) {
if (!isUserUnlockedL(userId)) {
// This is called by system UI, so no need to throw. Just ignore.
return;
@@ -2804,7 +2815,7 @@
// even when hasShortcutPermission() is overridden.
@VisibleForTesting
boolean hasShortcutHostPermissionInner(@NonNull String packageName, int userId) {
- synchronized (mLock) {
+ synchronized (mServiceLock) {
throwIfUserLockedL(userId);
final String defaultLauncher = getDefaultLauncher(userId);
@@ -2830,7 +2841,7 @@
final long token = injectClearCallingIdentity();
boolean isSupported;
try {
- synchronized (mLock) {
+ synchronized (mServiceLock) {
isSupported = !mUserManagerInternal.getUserProperties(userId)
.areItemsRestrictedOnHomeScreen();
}
@@ -2846,7 +2857,7 @@
final long start = getStatStartTime();
final long token = injectClearCallingIdentity();
try {
- synchronized (mLock) {
+ synchronized (mServiceLock) {
throwIfUserLockedL(userId);
final ShortcutUser user = getUserShortcutsLocked(userId);
@@ -2890,7 +2901,7 @@
private void cleanUpPackageForAllLoadedUsers(String packageName, @UserIdInt int packageUserId,
boolean appStillExists) {
- synchronized (mLock) {
+ synchronized (mServiceLock) {
forEachLoadedUserLocked(user ->
cleanUpPackageLocked(packageName, user.getUserId(), packageUserId,
appStillExists));
@@ -2904,7 +2915,7 @@
*
* This is called when an app is uninstalled, or an app gets "clear data"ed.
*/
- @GuardedBy("mLock")
+ @GuardedBy("mServiceLock")
@VisibleForTesting
void cleanUpPackageLocked(String packageName, int owningUserId, int packageUserId,
boolean appStillExists) {
@@ -2979,7 +2990,7 @@
shortcutIds = null; // LauncherAppsService already threw for it though.
}
- synchronized (mLock) {
+ synchronized (mServiceLock) {
throwIfUserLockedL(userId);
throwIfUserLockedL(launcherUserId);
@@ -3005,7 +3016,7 @@
return setReturnedByServer(ret);
}
- @GuardedBy("ShortcutService.this.mLock")
+ @GuardedBy("ShortcutService.this.mServiceLock")
private void getShortcutsInnerLocked(int launcherUserId, @NonNull String callingPackage,
@Nullable String packageName, @Nullable List<String> shortcutIds,
@Nullable List<LocusId> locusIds, long changedSince,
@@ -3095,7 +3106,7 @@
return;
}
final ShortcutPackage p;
- synchronized (mLock) {
+ synchronized (mServiceLock) {
p = getUserShortcutsLocked(userId).getPackageShortcutsIfExists(packageName);
}
if (p == null) {
@@ -3129,7 +3140,7 @@
Preconditions.checkStringNotEmpty(packageName, "packageName");
Preconditions.checkStringNotEmpty(shortcutId, "shortcutId");
- synchronized (mLock) {
+ synchronized (mServiceLock) {
throwIfUserLockedL(userId);
throwIfUserLockedL(launcherUserId);
@@ -3143,7 +3154,7 @@
}
}
- @GuardedBy("ShortcutService.this.mLock")
+ @GuardedBy("ShortcutService.this.mServiceLock")
private ShortcutInfo getShortcutInfoLocked(
int launcherUserId, @NonNull String callingPackage,
@NonNull String packageName, @NonNull String shortcutId, int userId,
@@ -3176,7 +3187,7 @@
throwIfUserLockedL(launcherUserId);
final ShortcutPackage p;
- synchronized (mLock) {
+ synchronized (mServiceLock) {
p = getUserShortcutsLocked(userId).getPackageShortcutsIfExists(packageName);
}
if (p == null) {
@@ -3198,7 +3209,7 @@
List<ShortcutInfo> changedShortcuts = null;
List<ShortcutInfo> removedShortcuts = null;
final ShortcutPackage sp;
- synchronized (mLock) {
+ synchronized (mServiceLock) {
throwIfUserLockedL(userId);
throwIfUserLockedL(launcherUserId);
@@ -3284,7 +3295,7 @@
List<ShortcutInfo> changedShortcuts = null;
List<ShortcutInfo> removedShortcuts = null;
final ShortcutPackage sp;
- synchronized (mLock) {
+ synchronized (mServiceLock) {
throwIfUserLockedL(userId);
throwIfUserLockedL(launcherUserId);
@@ -3346,7 +3357,7 @@
Preconditions.checkStringNotEmpty(packageName, "packageName can't be empty");
Preconditions.checkStringNotEmpty(shortcutId, "shortcutId can't be empty");
- synchronized (mLock) {
+ synchronized (mServiceLock) {
throwIfUserLockedL(userId);
throwIfUserLockedL(launcherUserId);
@@ -3380,7 +3391,7 @@
Preconditions.checkStringNotEmpty(shortcutId, "shortcutId can't be empty");
// Check in memory shortcut first
- synchronized (mLock) {
+ synchronized (mServiceLock) {
throwIfUserLockedL(userId);
throwIfUserLockedL(launcherUserId);
@@ -3414,13 +3425,17 @@
@Override
public void addListener(@NonNull ShortcutChangeListener listener) {
- mListeners.add(Objects.requireNonNull(listener));
+ synchronized (mServiceLock) {
+ mListeners.add(Objects.requireNonNull(listener));
+ }
}
@Override
public void addShortcutChangeCallback(
@NonNull LauncherApps.ShortcutChangeCallback callback) {
- mShortcutChangeCallbacks.add(Objects.requireNonNull(callback));
+ synchronized (mServiceLock) {
+ mShortcutChangeCallbacks.add(Objects.requireNonNull(callback));
+ }
}
@Override
@@ -3430,7 +3445,7 @@
Objects.requireNonNull(packageName, "packageName");
Objects.requireNonNull(shortcutId, "shortcutId");
- synchronized (mLock) {
+ synchronized (mServiceLock) {
throwIfUserLockedL(userId);
throwIfUserLockedL(launcherUserId);
@@ -3458,7 +3473,7 @@
Objects.requireNonNull(packageName, "packageName");
Objects.requireNonNull(shortcutId, "shortcutId");
- synchronized (mLock) {
+ synchronized (mServiceLock) {
throwIfUserLockedL(userId);
throwIfUserLockedL(launcherUserId);
@@ -3484,7 +3499,7 @@
Objects.requireNonNull(packageName, "packageName");
Objects.requireNonNull(shortcutId, "shortcutId");
- synchronized (mLock) {
+ synchronized (mServiceLock) {
throwIfUserLockedL(userId);
throwIfUserLockedL(launcherUserId);
@@ -3515,7 +3530,7 @@
// Checks shortcuts in memory first
final ShortcutPackage p;
- synchronized (mLock) {
+ synchronized (mServiceLock) {
throwIfUserLockedL(userId);
throwIfUserLockedL(launcherUserId);
@@ -3568,7 +3583,7 @@
Objects.requireNonNull(packageName, "packageName");
Objects.requireNonNull(shortcutId, "shortcutId");
- synchronized (mLock) {
+ synchronized (mServiceLock) {
throwIfUserLockedL(userId);
throwIfUserLockedL(launcherUserId);
@@ -3599,7 +3614,7 @@
Objects.requireNonNull(shortcutId, "shortcutId");
// Checks shortcuts in memory first
- synchronized (mLock) {
+ synchronized (mServiceLock) {
throwIfUserLockedL(userId);
throwIfUserLockedL(launcherUserId);
@@ -3702,7 +3717,7 @@
if (!callingPackage.equals(defaultLauncher)) {
return false;
}
- synchronized (mLock) {
+ synchronized (mServiceLock) {
if (!isUidForegroundLocked(callingUid)) {
return false;
}
@@ -3733,7 +3748,7 @@
}
scheduleSaveBaseState();
- synchronized (mLock) {
+ synchronized (mServiceLock) {
final long token = injectClearCallingIdentity();
try {
forEachLoadedUserLocked(user -> user.detectLocaleChange());
@@ -3762,7 +3777,7 @@
// but we still check it in unit tests.
final long token = injectClearCallingIdentity();
try {
- synchronized (mLock) {
+ synchronized (mServiceLock) {
if (!isUserUnlockedL(userId)) {
if (DEBUG) {
Slog.d(TAG, "Ignoring package broadcast " + action
@@ -3821,7 +3836,7 @@
// Since it cleans up the shortcut directory and rewrite the ShortcutPackageItems
// in odrder during saveToXml(), it could lead to shortcuts missing when shutdown.
// We need it so that it can finish up saving before shutdown.
- synchronized (mLock) {
+ synchronized (mServiceLock) {
if (mHandler.hasCallbacks(mSaveDirtyInfoRunner)) {
mHandler.removeCallbacks(mSaveDirtyInfoRunner);
forEachLoadedUserLocked(ShortcutUser::cancelAllInFlightTasks);
@@ -3852,7 +3867,7 @@
try {
final ArrayList<UserPackage> gonePackages = new ArrayList<>();
- synchronized (mLock) {
+ synchronized (mServiceLock) {
final ShortcutUser user = getUserShortcutsLocked(ownerUserId);
// Find packages that have been uninstalled.
@@ -3885,7 +3900,7 @@
verifyStates();
}
- @GuardedBy("mLock")
+ @GuardedBy("mServiceLock")
private void rescanUpdatedPackagesLocked(@UserIdInt int userId, long lastScanTime) {
if (DEBUG_REBOOT) {
Slog.d(TAG, "rescan updated package user=" + userId + " last scanned=" + lastScanTime);
@@ -3916,7 +3931,7 @@
if (DEBUG || DEBUG_REBOOT) {
Slog.d(TAG, String.format("handlePackageAdded: %s user=%d", packageName, userId));
}
- synchronized (mLock) {
+ synchronized (mServiceLock) {
final ShortcutUser user = getUserShortcutsLocked(userId);
user.attemptToRestoreIfNeededAndSave(this, packageName, userId);
user.rescanPackageIfNeeded(packageName, /* forceRescan=*/ true);
@@ -3929,7 +3944,7 @@
Slog.d(TAG, String.format("handlePackageUpdateFinished: %s user=%d",
packageName, userId));
}
- synchronized (mLock) {
+ synchronized (mServiceLock) {
final ShortcutUser user = getUserShortcutsLocked(userId);
user.attemptToRestoreIfNeededAndSave(this, packageName, userId);
@@ -3972,7 +3987,7 @@
}
// Activities may be disabled or enabled. Just rescan the package.
- synchronized (mLock) {
+ synchronized (mServiceLock) {
final ShortcutUser user = getUserShortcutsLocked(packageUserId);
user.rescanPackageIfNeeded(packageName, /* forceRescan=*/ true);
@@ -4474,7 +4489,7 @@
if (DEBUG) {
Slog.d(TAG, "Backing up user " + userId);
}
- synchronized (mLock) {
+ synchronized (mServiceLock) {
if (!isUserUnlockedL(userId)) {
wtf("Can't backup: user " + userId + " is locked or not running");
return null;
@@ -4524,7 +4539,7 @@
if (DEBUG || DEBUG_REBOOT) {
Slog.d(TAG, "Restoring user " + userId);
}
- synchronized (mLock) {
+ synchronized (mServiceLock) {
if (!isUserUnlockedL(userId)) {
wtf("Can't restore: user " + userId + " is locked or not running");
return;
@@ -4762,7 +4777,7 @@
}
private void dumpInner(PrintWriter pw, DumpFilter filter) {
- synchronized (mLock) {
+ synchronized (mServiceLock) {
if (filter.shouldDumpDetails()) {
final long now = injectCurrentTimeMillis();
pw.print("Now: [");
@@ -4841,7 +4856,7 @@
}
private void dumpUid(PrintWriter pw) {
- synchronized (mLock) {
+ synchronized (mServiceLock) {
pw.println("** SHORTCUT MANAGER UID STATES (dumpsys shortcut -n -u)");
for (int i = 0; i < mUidState.size(); i++) {
@@ -4876,7 +4891,7 @@
* behavior but shortcut service doesn't for now.
*/
private void dumpCheckin(PrintWriter pw, boolean clear) {
- synchronized (mLock) {
+ synchronized (mServiceLock) {
try {
final JSONArray users = new JSONArray();
@@ -4898,7 +4913,7 @@
}
private void dumpDumpFiles(PrintWriter pw) {
- synchronized (mLock) {
+ synchronized (mServiceLock) {
pw.println("** SHORTCUT MANAGER FILES (dumpsys shortcut -n -f)");
mShortcutDumpFiles.dumpAll(pw);
}
@@ -5051,7 +5066,7 @@
}
private void handleResetThrottling() throws CommandException {
- synchronized (mLock) {
+ synchronized (mServiceLock) {
parseOptionsLocked(/* takeUser =*/ true);
Slog.i(TAG, "cmd: handleResetThrottling: user=" + mUserId);
@@ -5071,7 +5086,7 @@
Slog.i(TAG, "cmd: handleOverrideConfig: " + config);
- synchronized (mLock) {
+ synchronized (mServiceLock) {
if (!updateConfigurationLocked(config)) {
throw new CommandException("override-config failed. See logcat for details.");
}
@@ -5081,7 +5096,7 @@
private void handleResetConfig() {
Slog.i(TAG, "cmd: handleResetConfig");
- synchronized (mLock) {
+ synchronized (mServiceLock) {
loadConfigurationLocked();
}
}
@@ -5090,7 +5105,7 @@
// should query this information directly from RoleManager instead. Keeping the old behavior
// by returning the result from package manager.
private void handleGetDefaultLauncher() throws CommandException {
- synchronized (mLock) {
+ synchronized (mServiceLock) {
parseOptionsLocked(/* takeUser =*/ true);
final String defaultLauncher = getDefaultLauncher(mUserId);
@@ -5114,7 +5129,7 @@
}
private void handleUnloadUser() throws CommandException {
- synchronized (mLock) {
+ synchronized (mServiceLock) {
parseOptionsLocked(/* takeUser =*/ true);
Slog.i(TAG, "cmd: handleUnloadUser: user=" + mUserId);
@@ -5124,7 +5139,7 @@
}
private void handleClearShortcuts() throws CommandException {
- synchronized (mLock) {
+ synchronized (mServiceLock) {
parseOptionsLocked(/* takeUser =*/ true);
final String packageName = getNextArgRequired();
@@ -5136,7 +5151,7 @@
}
private void handleGetShortcuts() throws CommandException {
- synchronized (mLock) {
+ synchronized (mServiceLock) {
parseOptionsLocked(/* takeUser =*/ true);
final String packageName = getNextArgRequired();
@@ -5162,7 +5177,7 @@
}
private void handleHasShortcutAccess() throws CommandException {
- synchronized (mLock) {
+ synchronized (mServiceLock) {
parseOptionsLocked(/* takeUser =*/ true);
final String packageName = getNextArgRequired();
@@ -5318,7 +5333,7 @@
@VisibleForTesting
ShortcutPackage getPackageShortcutForTest(String packageName, int userId) {
- synchronized (mLock) {
+ synchronized (mServiceLock) {
final ShortcutUser user = mUsers.get(userId);
if (user == null) return null;
@@ -5328,7 +5343,7 @@
@VisibleForTesting
ShortcutInfo getPackageShortcutForTest(String packageName, String shortcutId, int userId) {
- synchronized (mLock) {
+ synchronized (mServiceLock) {
final ShortcutPackage pkg = getPackageShortcutForTest(packageName, userId);
if (pkg == null) return null;
@@ -5339,7 +5354,7 @@
@VisibleForTesting
void updatePackageShortcutForTest(String packageName, String shortcutId, int userId,
Consumer<ShortcutInfo> cb) {
- synchronized (mLock) {
+ synchronized (mServiceLock) {
final ShortcutPackage pkg = getPackageShortcutForTest(packageName, userId);
if (pkg == null) return;
cb.accept(pkg.findShortcutById(shortcutId));
@@ -5348,7 +5363,7 @@
@VisibleForTesting
ShortcutLauncher getLauncherShortcutForTest(String packageName, int userId) {
- synchronized (mLock) {
+ synchronized (mServiceLock) {
final ShortcutUser user = mUsers.get(userId);
if (user == null) return null;
@@ -5385,14 +5400,14 @@
}
private void verifyStatesInner() {
- synchronized (mLock) {
+ synchronized (mServiceLock) {
forEachLoadedUserLocked(u -> u.forAllPackageItems(ShortcutPackageItem::verifyStates));
}
}
@VisibleForTesting
void waitForBitmapSavesForTest() {
- synchronized (mLock) {
+ synchronized (mServiceLock) {
forEachLoadedUserLocked(u ->
u.forAllPackageItems(ShortcutPackageItem::waitForBitmapSaves));
}
diff --git a/services/core/java/com/android/server/policy/PermissionPolicyService.java b/services/core/java/com/android/server/policy/PermissionPolicyService.java
index 98499417..deaa8d8 100644
--- a/services/core/java/com/android/server/policy/PermissionPolicyService.java
+++ b/services/core/java/com/android/server/policy/PermissionPolicyService.java
@@ -494,6 +494,7 @@
PhoneCarrierPrivilegesCallback(int phoneId) {
mPhoneId = phoneId;
}
+
@Override
public void onCarrierPrivilegesChanged(
@NonNull Set<String> privilegedPackageNames,
@@ -563,7 +564,11 @@
final TimingsTraceAndSlog t = new TimingsTraceAndSlog();
t.traceBegin("Permission_grant_default_permissions-" + userId);
- grantOrUpgradeDefaultRuntimePermissionsIfNeeded(userId);
+ if (mPackageManagerInternal.isPermissionUpgradeNeeded(userId)) {
+ grantOrUpgradeDefaultRuntimePermissions(userId);
+ updateUserSensitive(userId);
+ mPackageManagerInternal.updateRuntimePermissionsFingerprint(userId);
+ }
t.traceEnd();
final OnInitializedCallback callback;
@@ -595,59 +600,56 @@
}
}
- private void grantOrUpgradeDefaultRuntimePermissionsIfNeeded(@UserIdInt int userId) {
+ private void grantOrUpgradeDefaultRuntimePermissions(@UserIdInt int userId) {
if (PermissionManager.USE_ACCESS_CHECKING_SERVICE) {
return;
}
- if (DEBUG) Slog.i(LOG_TAG, "grantOrUpgradeDefaultPermsIfNeeded(" + userId + ")");
+ if (DEBUG) Slog.i(LOG_TAG, "grantOrUpgradeDefaultPerms(" + userId + ")");
final TimingsTraceAndSlog t = new TimingsTraceAndSlog();
- final PackageManagerInternal packageManagerInternal =
- LocalServices.getService(PackageManagerInternal.class);
- final PermissionManagerServiceInternal permissionManagerInternal =
- LocalServices.getService(PermissionManagerServiceInternal.class);
- if (packageManagerInternal.isPermissionUpgradeNeeded(userId)) {
- if (DEBUG) Slog.i(LOG_TAG, "defaultPermsWereGrantedSinceBoot(" + userId + ")");
+ // Now call into the permission controller to apply policy around permissions
+ final AndroidFuture<Boolean> future = new AndroidFuture<>();
- // Now call into the permission controller to apply policy around permissions
- final AndroidFuture<Boolean> future = new AndroidFuture<>();
-
- // We need to create a local manager that does not schedule work on the main
- // there as we are on the main thread and want to block until the work is
- // completed or we time out.
- final PermissionControllerManager permissionControllerManager =
- new PermissionControllerManager(
- getUserContext(getContext(), UserHandle.of(userId)),
- PermissionThread.getHandler());
- permissionControllerManager.grantOrUpgradeDefaultRuntimePermissions(
- PermissionThread.getExecutor(), successful -> {
- if (successful) {
- future.complete(null);
- } else {
- // We are in an undefined state now, let us crash and have
- // rescue party suggest a wipe to recover to a good one.
- final String message = "Error granting/upgrading runtime permissions"
- + " for user " + userId;
- Slog.wtf(LOG_TAG, message);
- future.completeExceptionally(new IllegalStateException(message));
- }
- });
- try {
- t.traceBegin("Permission_callback_waiting-" + userId);
- future.get();
- } catch (InterruptedException | ExecutionException e) {
- throw new IllegalStateException(e);
- } finally {
- t.traceEnd();
- }
-
- permissionControllerManager.updateUserSensitive();
-
- packageManagerInternal.updateRuntimePermissionsFingerprint(userId);
+ // We need to create a local manager that does not schedule work on the main
+ // there as we are on the main thread and want to block until the work is
+ // completed or we time out.
+ final PermissionControllerManager permissionControllerManager =
+ new PermissionControllerManager(
+ getUserContext(getContext(), UserHandle.of(userId)),
+ PermissionThread.getHandler());
+ permissionControllerManager.grantOrUpgradeDefaultRuntimePermissions(
+ PermissionThread.getExecutor(), successful -> {
+ if (successful) {
+ future.complete(null);
+ } else {
+ // We are in an undefined state now, let us crash and have
+ // rescue party suggest a wipe to recover to a good one.
+ final String message = "Error granting/upgrading runtime permissions"
+ + " for user " + userId;
+ Slog.wtf(LOG_TAG, message);
+ future.completeExceptionally(new IllegalStateException(message));
+ }
+ });
+ try {
+ t.traceBegin("Permission_callback_waiting-" + userId);
+ future.get();
+ } catch (InterruptedException | ExecutionException e) {
+ throw new IllegalStateException(e);
+ } finally {
+ t.traceEnd();
}
}
+ private void updateUserSensitive(@UserIdInt int userId) {
+ if (DEBUG) Slog.i(LOG_TAG, "updateUserSensitive(" + userId + ")");
+ final PermissionControllerManager permissionControllerManager =
+ new PermissionControllerManager(
+ getUserContext(getContext(), UserHandle.of(userId)),
+ PermissionThread.getHandler());
+ permissionControllerManager.updateUserSensitive();
+ }
+
private static @Nullable Context getUserContext(@NonNull Context context,
@Nullable UserHandle user) {
if (context.getUser().equals(user)) {
@@ -695,12 +697,10 @@
if (DEBUG) Slog.i(LOG_TAG, "synchronizePermissionsAndAppOpsForUser(" + userId + ")");
final TimingsTraceAndSlog t = new TimingsTraceAndSlog();
- final PackageManagerInternal packageManagerInternal = LocalServices.getService(
- PackageManagerInternal.class);
final PermissionToOpSynchroniser synchronizer = new PermissionToOpSynchroniser(
getUserContext(getContext(), UserHandle.of(userId)));
t.traceBegin("Permission_synchronize_addPackages-" + userId);
- packageManagerInternal.forEachPackage(
+ mPackageManagerInternal.forEachPackage(
(pkg) -> synchronizer.addPackage(pkg.getPackageName()));
t.traceEnd();
t.traceBegin("Permission_syncPackages-" + userId);
@@ -1052,13 +1052,11 @@
* @param pkgName The package to add for later processing.
*/
void addPackage(@NonNull String pkgName) {
- PackageManagerInternal pmInternal =
- LocalServices.getService(PackageManagerInternal.class);
final PackageInfo pkgInfo;
final AndroidPackage pkg;
try {
pkgInfo = mPackageManager.getPackageInfo(pkgName, GET_PERMISSIONS);
- pkg = pmInternal.getPackage(pkgName);
+ pkg = mPackageManagerInternal.getPackage(pkgName);
} catch (NameNotFoundException e) {
return;
}
diff --git a/services/core/java/com/android/server/policy/SideFpsEventHandler.java b/services/core/java/com/android/server/policy/SideFpsEventHandler.java
index 2d76c50..4ad4353 100644
--- a/services/core/java/com/android/server/policy/SideFpsEventHandler.java
+++ b/services/core/java/com/android/server/policy/SideFpsEventHandler.java
@@ -38,6 +38,7 @@
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
+import android.view.accessibility.AccessibilityManager;
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
@@ -66,6 +67,7 @@
private final int mDismissDialogTimeout;
@Nullable
private SideFpsToast mDialog;
+ private final AccessibilityManager mAccessibilityManager;
private final Runnable mTurnOffDialog =
() -> {
dismissDialog("mTurnOffDialog");
@@ -96,6 +98,7 @@
DialogProvider provider) {
mContext = context;
mHandler = handler;
+ mAccessibilityManager = mContext.getSystemService(AccessibilityManager.class);
mPowerManager = powerManager;
mBiometricState = STATE_IDLE;
mSideFpsEventHandlerReady = new AtomicBoolean(false);
@@ -157,7 +160,9 @@
mHandler.removeCallbacks(mTurnOffDialog);
}
showDialog(eventTime, "Enroll Power Press");
- mHandler.postDelayed(mTurnOffDialog, mDismissDialogTimeout);
+ if (!mAccessibilityManager.isEnabled()) {
+ mHandler.postDelayed(mTurnOffDialog, mDismissDialogTimeout);
+ }
});
return true;
case STATE_BP_AUTH:
@@ -231,6 +236,10 @@
public void onBiometricAction(
@BiometricStateListener.Action int action) {
Log.d(TAG, "onBiometricAction " + action);
+ if (mAccessibilityManager != null
+ && mAccessibilityManager.isEnabled()) {
+ dismissDialog("mTurnOffDialog");
+ }
}
});
mSideFpsEventHandlerReady.set(true);
@@ -256,6 +265,9 @@
mLastPowerPressTime = time;
mDialog.show();
mDialog.setOnClickListener(this);
+ if (mAccessibilityManager.isEnabled()) {
+ mDialog.addAccessibilityDelegate();
+ }
}
interface DialogProvider {
diff --git a/services/core/java/com/android/server/policy/SideFpsToast.java b/services/core/java/com/android/server/policy/SideFpsToast.java
index db07467..c27753c 100644
--- a/services/core/java/com/android/server/policy/SideFpsToast.java
+++ b/services/core/java/com/android/server/policy/SideFpsToast.java
@@ -16,6 +16,7 @@
package com.android.server.policy;
+import android.annotation.NonNull;
import android.app.Dialog;
import android.content.Context;
import android.os.Bundle;
@@ -23,6 +24,7 @@
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
+import android.view.accessibility.AccessibilityEvent;
import android.widget.Button;
import com.android.internal.R;
@@ -34,7 +36,6 @@
* This dialog is used by {@link SideFpsEventHandler}
*/
public class SideFpsToast extends Dialog {
-
SideFpsToast(Context context) {
super(context);
}
@@ -66,4 +67,27 @@
turnOffScreen.setOnClickListener(listener);
}
}
+
+ /**
+ * When accessibility mode is on, add AccessibilityDelegate to dismiss dialog when focus is
+ * moved away from the dialog.
+ */
+ public void addAccessibilityDelegate() {
+ final Button turnOffScreen = findViewById(R.id.turn_off_screen);
+ if (turnOffScreen != null) {
+ turnOffScreen.setAccessibilityDelegate(new View.AccessibilityDelegate() {
+ @Override
+ public void onInitializeAccessibilityEvent(@NonNull View host,
+ @NonNull AccessibilityEvent event) {
+ if (event.getEventType()
+ == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED
+ && isShowing()) {
+ dismiss();
+ }
+ super.onInitializeAccessibilityEvent(host, event);
+ }
+ });
+
+ }
+ }
}
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index bbb59ce..76cedd8 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -444,6 +444,9 @@
// Refer to autosuspend.h.
private boolean mHalAutoSuspendModeEnabled;
+ // True if the device uses auto-suspend mode.
+ private final boolean mUseAutoSuspend;
+
// True if interactive mode is enabled.
// Refer to power.h.
private boolean mHalInteractiveModeEnabled;
@@ -1203,6 +1206,9 @@
mPowerGroupWakefulnessChangeListener = new PowerGroupWakefulnessChangeListener();
+ mUseAutoSuspend = mContext.getResources().getBoolean(com.android.internal.R.bool
+ .config_useAutoSuspend);
+
// Save brightness values:
// Get float values from config.
// Store float if valid
@@ -3918,6 +3924,9 @@
@GuardedBy("mLock")
private void setHalAutoSuspendModeLocked(boolean enable) {
+ if (!mUseAutoSuspend) {
+ return;
+ }
if (enable != mHalAutoSuspendModeEnabled) {
if (DEBUG) {
Slog.d(TAG, "Setting HAL auto-suspend mode to " + enable);
@@ -4661,6 +4670,7 @@
pw.println(" mEnhancedDischargePredictionIsPersonalized="
+ mEnhancedDischargePredictionIsPersonalized);
}
+ pw.println(" mUseAutoSuspend=" + mUseAutoSuspend);
pw.println(" mHalAutoSuspendModeEnabled=" + mHalAutoSuspendModeEnabled);
pw.println(" mHalInteractiveModeEnabled=" + mHalInteractiveModeEnabled);
pw.println(" mWakeLockSummary=0x" + Integer.toHexString(mWakeLockSummary));
diff --git a/services/core/java/com/android/server/power/batterysaver/Android.bp b/services/core/java/com/android/server/power/batterysaver/Android.bp
new file mode 100644
index 0000000..0b04345
--- /dev/null
+++ b/services/core/java/com/android/server/power/batterysaver/Android.bp
@@ -0,0 +1,14 @@
+aconfig_declarations {
+ name: "battery_saver_flag",
+ package: "com.android.server.power.batterysaver",
+ container: "system",
+ srcs: [
+ "*.aconfig",
+ ],
+}
+
+java_aconfig_library {
+ name: "battery_saver_flag_lib",
+ aconfig_declarations: "battery_saver_flag",
+ sdk_version: "system_current",
+}
diff --git a/services/core/java/com/android/server/power/batterysaver/BatterySaverStateMachine.java b/services/core/java/com/android/server/power/batterysaver/BatterySaverStateMachine.java
index c8cb92b..9a4c60d 100644
--- a/services/core/java/com/android/server/power/batterysaver/BatterySaverStateMachine.java
+++ b/services/core/java/com/android/server/power/batterysaver/BatterySaverStateMachine.java
@@ -29,6 +29,7 @@
import android.content.res.Resources;
import android.database.ContentObserver;
import android.os.BatterySaverPolicyConfig;
+import android.os.Bundle;
import android.os.Handler;
import android.os.PowerManager;
import android.os.SystemClock;
@@ -97,6 +98,9 @@
private static final String TAG = "BatterySaverStateMachine";
private static final String DYNAMIC_MODE_NOTIF_CHANNEL_ID = "dynamic_mode_notification";
private static final String BATTERY_SAVER_NOTIF_CHANNEL_ID = "battery_saver_channel";
+ private static final String EXTRA_FRAGMENT_ARG_KEY = ":settings:fragment_args_key";
+ private static final String EXTRA_SHOW_FRAGMENT_TITLE = ":settings:show_fragment_args";
+ private static final String PREFERENCE_KEY_BATTERY_SAVER_SCHEDULER = "battery_saver_schedule";
private static final int DYNAMIC_MODE_NOTIFICATION_ID = 1992;
private static final int STICKY_AUTO_DISABLED_NOTIFICATION_ID = 1993;
private final Object mLock;
@@ -831,7 +835,11 @@
// Handle triggering the notification to show/hide when appropriate
if (intReason == BatterySaverController.REASON_DYNAMIC_POWER_SAVINGS_AUTOMATIC_ON
|| intReason == BatterySaverController.REASON_PERCENTAGE_AUTOMATIC_ON) {
- triggerDynamicModeNotification();
+ if (Flags.updateAutoTurnOnNotificationStringAndAction()) {
+ triggerDynamicModeNotificationV2();
+ } else {
+ triggerDynamicModeNotification();
+ }
} else if (!enable) {
hideDynamicModeNotification();
}
@@ -862,6 +870,31 @@
}
@VisibleForTesting
+ void triggerDynamicModeNotificationV2() {
+ // The current lock is the PowerManager lock, which sits very low in the service lock
+ // hierarchy. We shouldn't call out to NotificationManager with the PowerManager lock.
+ runOnBgThread(() -> {
+ NotificationManager manager = mContext.getSystemService(NotificationManager.class);
+ ensureNotificationChannelExists(manager, DYNAMIC_MODE_NOTIF_CHANNEL_ID,
+ R.string.dynamic_mode_notification_channel_name);
+
+ // The bundle is used for highlighting a settings item when launching the settings page.
+ final var highlightBundle = new Bundle(1 /* capacity */);
+ highlightBundle.putString(
+ EXTRA_FRAGMENT_ARG_KEY, PREFERENCE_KEY_BATTERY_SAVER_SCHEDULER);
+
+ manager.notifyAsUser(TAG, DYNAMIC_MODE_NOTIFICATION_ID,
+ buildNotificationV2(DYNAMIC_MODE_NOTIF_CHANNEL_ID,
+ R.string.dynamic_mode_notification_title_v2,
+ R.string.dynamic_mode_notification_summary_v2,
+ Settings.ACTION_BATTERY_SAVER_SETTINGS,
+ 0L /* timeoutMs */,
+ highlightBundle),
+ UserHandle.ALL);
+ });
+ }
+
+ @VisibleForTesting
void triggerStickyDisabledNotification() {
if (!mBatterySaverTurnedOffNotificationEnabled) {
return;
@@ -915,6 +948,32 @@
.build();
}
+ private Notification buildNotificationV2(@NonNull String channelId, @StringRes int titleId,
+ @StringRes int summaryId, @NonNull String intentAction, long timeoutMs,
+ @NonNull Bundle highlightBundle) {
+ Resources res = mContext.getResources();
+ Intent intent = new Intent(intentAction)
+ .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK)
+ .putExtra(EXTRA_SHOW_FRAGMENT_TITLE, highlightBundle);
+
+ PendingIntent batterySaverIntent = PendingIntent.getActivity(
+ mContext, 0 /* requestCode */, intent,
+ PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT);
+ final String title = res.getString(titleId);
+ final String summary = res.getString(summaryId);
+
+ return new Notification.Builder(mContext, channelId)
+ .setSmallIcon(R.drawable.ic_battery)
+ .setContentTitle(title)
+ .setContentText(summary)
+ .setContentIntent(batterySaverIntent)
+ .setStyle(new Notification.BigTextStyle().bigText(summary))
+ .setOnlyAlertOnce(true)
+ .setAutoCancel(true)
+ .setTimeoutAfter(timeoutMs)
+ .build();
+ }
+
private void hideDynamicModeNotification() {
hideNotification(DYNAMIC_MODE_NOTIFICATION_ID);
}
diff --git a/services/core/java/com/android/server/power/batterysaver/flags.aconfig b/services/core/java/com/android/server/power/batterysaver/flags.aconfig
new file mode 100644
index 0000000..fa29dc1
--- /dev/null
+++ b/services/core/java/com/android/server/power/batterysaver/flags.aconfig
@@ -0,0 +1,12 @@
+package: "com.android.server.power.batterysaver"
+container: "system"
+
+flag {
+ name: "update_auto_turn_on_notification_string_and_action"
+ namespace: "battery_saver"
+ description: "Improve the string and hightligh settings item for battery saver auto-turn-on notification"
+ bug: "336960905"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/services/core/java/com/android/server/utils/AnrTimer.java b/services/core/java/com/android/server/utils/AnrTimer.java
index 592d039..12db21d 100644
--- a/services/core/java/com/android/server/utils/AnrTimer.java
+++ b/services/core/java/com/android/server/utils/AnrTimer.java
@@ -40,6 +40,7 @@
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.ArrayList;
+import java.util.Comparator;
import java.util.Objects;
/**
@@ -211,10 +212,6 @@
@GuardedBy("mLock")
private int mTotalStarted = 0;
- /** The total number of timers that were restarted without an explicit cancel. */
- @GuardedBy("mLock")
- private int mTotalRestarted = 0;
-
/** The total number of errors detected. */
@GuardedBy("mLock")
private int mTotalErrors = 0;
@@ -368,7 +365,7 @@
abstract boolean enabled();
- abstract void dump(PrintWriter pw, boolean verbose);
+ abstract void dump(IndentingPrintWriter pw, boolean verbose);
abstract void close();
}
@@ -410,9 +407,14 @@
return false;
}
- /** dump() is a no-op when the feature is disabled. */
+ /** Dump the limited statistics captured when the feature is disabled. */
@Override
- void dump(PrintWriter pw, boolean verbose) {
+ void dump(IndentingPrintWriter pw, boolean verbose) {
+ synchronized (mLock) {
+ pw.format("started=%d maxStarted=%d running=%d expired=%d errors=%d\n",
+ mTotalStarted, mMaxStarted, mTimerIdMap.size(),
+ mTotalExpired, mTotalErrors);
+ }
}
/** close() is a no-op when the feature is disabled. */
@@ -441,6 +443,10 @@
*/
private long mNative = 0;
+ /** The total number of timers that were restarted without an explicit cancel. */
+ @GuardedBy("mLock")
+ private int mTotalRestarted = 0;
+
/** Fetch the native tag (an integer) for the given label. */
FeatureEnabled() {
mNative = nativeAnrTimerCreate(mLabel);
@@ -537,13 +543,22 @@
/** Dump statistics from the native layer. */
@Override
- void dump(PrintWriter pw, boolean verbose) {
+ void dump(IndentingPrintWriter pw, boolean verbose) {
synchronized (mLock) {
- if (mNative != 0) {
- nativeAnrTimerDump(mNative, verbose);
- } else {
+ if (mNative == 0) {
pw.println("closed");
+ return;
}
+ String[] nativeDump = nativeAnrTimerDump(mNative);
+ if (nativeDump == null) {
+ pw.println("no-data");
+ return;
+ }
+ for (String s : nativeDump) {
+ pw.println(s);
+ }
+ // The following counter is only available at the Java level.
+ pw.println("restarted:" + mTotalRestarted);
}
}
@@ -690,11 +705,8 @@
synchronized (mLock) {
pw.format("timer: %s\n", mLabel);
pw.increaseIndent();
- pw.format("started=%d maxStarted=%d restarted=%d running=%d expired=%d errors=%d\n",
- mTotalStarted, mMaxStarted, mTotalRestarted, mTimerIdMap.size(),
- mTotalExpired, mTotalErrors);
- pw.decreaseIndent();
mFeature.dump(pw, false);
+ pw.decreaseIndent();
}
}
@@ -755,6 +767,14 @@
recordErrorLocked(operation, "notFound", arg);
}
+ /** Compare two AnrTimers in display order. */
+ private static final Comparator<AnrTimer> sComparator =
+ Comparator.nullsLast(new Comparator<>() {
+ @Override
+ public int compare(AnrTimer o1, AnrTimer o2) {
+ return o1.mLabel.compareTo(o2.mLabel);
+ }});
+
/** Dumpsys output, allowing for overrides. */
@VisibleForTesting
static void dump(@NonNull PrintWriter pw, boolean verbose, @NonNull Injector injector) {
@@ -764,11 +784,18 @@
ipw.println("AnrTimer statistics");
ipw.increaseIndent();
synchronized (sAnrTimerList) {
+ // Find the currently live instances and sort them by their label. The goal is to
+ // have consistent output ordering.
final int size = sAnrTimerList.size();
- ipw.println("reporting " + size + " timers");
+ AnrTimer[] active = new AnrTimer[size];
+ int valid = 0;
for (int i = 0; i < size; i++) {
AnrTimer a = sAnrTimerList.valueAt(i).get();
- if (a != null) a.dump(ipw);
+ if (a != null) active[valid++] = a;
+ }
+ Arrays.sort(active, 0, valid, sComparator);
+ for (int i = 0; i < valid; i++) {
+ if (active[i] != null) active[i].dump(ipw);
}
}
if (verbose) dumpErrors(ipw);
@@ -827,6 +854,6 @@
/** Discard an expired timer by ID. Return true if the timer was found. */
private static native boolean nativeAnrTimerDiscard(long service, int timerId);
- /** Prod the native library to log a few statistics. */
- private static native void nativeAnrTimerDump(long service, boolean verbose);
+ /** Retrieve runtime dump information from the native layer. */
+ private static native String[] nativeAnrTimerDump(long service);
}
diff --git a/services/core/java/com/android/server/vr/EnabledComponentsObserver.java b/services/core/java/com/android/server/vr/EnabledComponentsObserver.java
index 7126cb5..7c2ce64 100644
--- a/services/core/java/com/android/server/vr/EnabledComponentsObserver.java
+++ b/services/core/java/com/android/server/vr/EnabledComponentsObserver.java
@@ -110,7 +110,7 @@
final EnabledComponentsObserver o = new EnabledComponentsObserver(context, settingName,
servicePermission, serviceName, lock, listeners);
- PackageMonitor packageMonitor = new PackageMonitor() {
+ PackageMonitor packageMonitor = new PackageMonitor(true) {
@Override
public void onSomePackagesChanged() {
o.onPackagesChanged();
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index f1ba755..d20b3b2 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -1346,6 +1346,10 @@
}
class MyPackageMonitor extends PackageMonitor {
+ private MyPackageMonitor() {
+ super(true);
+ }
+
@Override
public void onPackageUpdateFinished(String packageName, int uid) {
synchronized (mLock) {
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 1f320da..e814f17 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -7846,10 +7846,12 @@
@Override
void prepareSurfaces() {
+ final boolean isDecorSurfaceBoosted =
+ getTask() != null && getTask().isDecorSurfaceBoosted();
final boolean show = (isVisible()
// Ensure that the activity content is hidden when the decor surface is boosted to
// prevent UI redressing attack.
- && !getTask().isDecorSurfaceBoosted())
+ && !isDecorSurfaceBoosted)
|| isAnimating(PARENTS, ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_RECENTS
| ANIMATION_TYPE_PREDICT_BACK);
@@ -9492,6 +9494,12 @@
return false;
}
+ @Override
+ protected boolean setOverrideGender(Configuration requestsTmpConfig, int gender) {
+ return WindowProcessController.applyConfigGenderOverride(
+ requestsTmpConfig, gender, mAtmService.mGrammaticalManagerInternal, getUid());
+ }
+
@VisibleForTesting
@Override
Rect getAnimationBounds(int appRootTaskClipMode) {
@@ -9817,10 +9825,10 @@
if (mLetterboxUiController.shouldApplyUserMinAspectRatioOverride()) {
return mLetterboxUiController.getUserMinAspectRatio();
}
- if (!mLetterboxUiController.shouldOverrideMinAspectRatio()) {
+ if (!mLetterboxUiController.shouldOverrideMinAspectRatio()
+ && !mLetterboxUiController.shouldOverrideMinAspectRatioForCamera()) {
return info.getMinAspectRatio();
}
-
if (info.isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY)
&& !ActivityInfo.isFixedOrientationPortrait(
getOverrideOrientation())) {
@@ -9941,6 +9949,16 @@
return updateReportedConfigurationAndSend();
}
+ /**
+ * @return {@code true} if the Camera is active for the current activity
+ */
+ boolean isCameraActive() {
+ return mDisplayContent != null
+ && mDisplayContent.getDisplayRotationCompatPolicy() != null
+ && mDisplayContent.getDisplayRotationCompatPolicy()
+ .isCameraActive(this, /* mustBeFullscreen */ true);
+ }
+
boolean updateReportedConfigurationAndSend() {
if (isConfigurationDispatchPaused()) {
Slog.wtf(TAG, "trying to update reported(client) config while dispatch is paused");
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 5bd845f..3303367 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -2167,7 +2167,7 @@
// We don't need to start a new activity, and the client said not to do anything
// if that is the case, so this is it! And for paranoia, make sure we have
// correctly resumed the top activity.
- if (!mMovedToFront && mDoResume && !avoidMoveToFront()) {
+ if (!mMovedToFront && mDoResume) {
ProtoLog.d(WM_DEBUG_TASKS, "Bring to front target: %s from %s", mTargetRootTask,
targetTaskTop);
mTargetRootTask.moveToFront("intentActivityFound");
@@ -2196,7 +2196,7 @@
if (mMovedToFront) {
// We moved the task to front, use starting window to hide initial drawn delay.
targetTaskTop.showStartingWindow(true /* taskSwitch */);
- } else if (mDoResume && !avoidMoveToFront()) {
+ } else if (mDoResume) {
// Make sure the root task and its belonging display are moved to topmost.
mTargetRootTask.moveToFront("intentActivityFound");
}
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index fc05d17..f3e1dfb 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -949,7 +949,7 @@
}
configuration.setGrammaticalGender(
- mGrammaticalManagerInternal.retrieveSystemGrammaticalGender(configuration));
+ mGrammaticalManagerInternal.mergedFinalSystemGrammaticalGender());
synchronized (mGlobalLock) {
mForceResizableActivities = forceResizable;
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index 90a53ee..c9703d8 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -303,8 +303,7 @@
removedWindowContainer = currentTask;
backType = BackNavigationInfo.TYPE_RETURN_TO_HOME;
final ActivityRecord ar = prevTask.getTopNonFinishingActivity();
- mShowWallpaper =
- ar != null && ar.forAllWindows(WindowState::hasWallpaper, true);
+ mShowWallpaper = ar != null && ar.hasWallpaper();
} else {
// If it reaches the top activity, we will check the below task from parent.
// If it's null or multi-window and has different parent task, fallback the type
diff --git a/services/core/java/com/android/server/wm/ConfigurationContainer.java b/services/core/java/com/android/server/wm/ConfigurationContainer.java
index 31754bf..efd5202 100644
--- a/services/core/java/com/android/server/wm/ConfigurationContainer.java
+++ b/services/core/java/com/android/server/wm/ConfigurationContainer.java
@@ -534,8 +534,8 @@
nightMode);
boolean newLocalesSet = (locales != null) && setOverrideLocales(mRequestsTmpConfig,
locales);
- boolean newGenderSet = (gender != null) && setOverrideGender(mRequestsTmpConfig,
- gender);
+ boolean newGenderSet = setOverrideGender(mRequestsTmpConfig,
+ gender == null ? Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED : gender);
if (newNightModeSet || newLocalesSet || newGenderSet) {
onRequestedOverrideConfigurationChanged(mRequestsTmpConfig);
}
@@ -577,14 +577,11 @@
*
* @return true if the grammatical gender has been changed.
*/
- private boolean setOverrideGender(Configuration requestsTmpConfig,
+ protected boolean setOverrideGender(Configuration requestsTmpConfig,
@Configuration.GrammaticalGender int gender) {
- if (mRequestedOverrideConfiguration.getGrammaticalGender() == gender) {
- return false;
- } else {
- requestsTmpConfig.setGrammaticalGender(gender);
- return true;
- }
+ // Noop, only ActivityRecord and WindowProcessController have enough knowledge about the
+ // app to apply gender correctly.
+ return false;
}
public boolean isActivityTypeDream() {
diff --git a/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java b/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java
index 8069a93..1c59977 100644
--- a/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java
+++ b/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java
@@ -48,15 +48,8 @@
/**
* Flag to indicate whether to restrict desktop mode to supported devices.
*/
- @VisibleForTesting
- static final String ENFORCE_DEVICE_RESTRICTIONS_KEY =
- "persist.wm.debug.desktop_mode_enforce_device_restrictions";
-
- /**
- * Flag to indicate whether to restrict desktop mode to supported devices.
- */
private static final boolean ENFORCE_DEVICE_RESTRICTIONS = SystemProperties.getBoolean(
- ENFORCE_DEVICE_RESTRICTIONS_KEY, true);
+ "persist.wm.debug.desktop_mode_enforce_device_restrictions", true);
private StringBuilder mLogBuilder;
@@ -118,19 +111,7 @@
}
if (phase == PHASE_WINDOWING_MODE) {
- return RESULT_DONE;
- }
-
- // TODO(b/336998072) - Find a better solution to this that makes use of the logic from
- // TaskLaunchParamsModifier. Put logic in common utils, return RESULT_CONTINUE, inherit
- // from parent class, etc.
- if (outParams.mPreferredTaskDisplayArea == null && task.getRootTask() != null) {
- appendLog("display-from-task=" + task.getRootTask().getDisplayId());
- outParams.mPreferredTaskDisplayArea = task.getRootTask().getDisplayArea();
- }
-
- if (phase == PHASE_DISPLAY_AREA) {
- return RESULT_DONE;
+ return RESULT_CONTINUE;
}
if (!currentParams.mBounds.isEmpty()) {
@@ -142,7 +123,7 @@
appendLog("setting desktop mode task bounds to %s", outParams.mBounds);
- return RESULT_DONE;
+ return RESULT_CONTINUE;
}
/**
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index f7e5dd8..2f37e88 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -1318,6 +1318,15 @@
}
}
+ /**
+ * @return The {@link DisplayRotationCompatPolicy} for this DisplayContent
+ */
+ // TODO(b/335387481) Allow access to DisplayRotationCompatPolicy only with getters
+ @Nullable
+ DisplayRotationCompatPolicy getDisplayRotationCompatPolicy() {
+ return mDisplayRotationCompatPolicy;
+ }
+
@Override
void migrateToNewSurfaceControl(Transaction t) {
t.remove(mSurfaceControl);
@@ -2021,12 +2030,11 @@
}
// Update directly because the app which will change the orientation of display is ready.
if (mDisplayRotation.updateOrientation(getOrientation(), false /* forceUpdate */)) {
- // Run rotation change on display thread. See Transition#shouldApplyOnDisplayThread().
- mWmService.mH.post(() -> {
- synchronized (mWmService.mGlobalLock) {
- sendNewConfiguration();
- }
- });
+ // If a transition is collecting, let the transition apply the rotation change on
+ // display thread. See Transition#shouldApplyOnDisplayThread().
+ if (!mTransitionController.isCollecting(this)) {
+ sendNewConfiguration();
+ }
return;
}
if (mRemoteDisplayChangeController.isWaitingForRemoteDisplayChange()) {
diff --git a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
index d5f8df3..eacf9a3 100644
--- a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
@@ -64,7 +64,7 @@
* R.bool.config_isWindowManagerCameraCompatTreatmentEnabled} is {@code true}.
*/
// TODO(b/261444714): Consider moving Camera-specific logic outside of the WM Core path
-final class DisplayRotationCompatPolicy implements CameraStateMonitor.CameraCompatStateListener {
+class DisplayRotationCompatPolicy implements CameraStateMonitor.CameraCompatStateListener {
// Delay for updating display rotation after Camera connection is closed. Needed to avoid
// rotation flickering when an app is flipping between front and rear cameras or when size
@@ -306,7 +306,8 @@
boolean isActivityEligibleForOrientationOverride(@NonNull ActivityRecord activity) {
return isTreatmentEnabledForDisplay()
- && isCameraActive(activity, /* mustBeFullscreen */ true);
+ && isCameraActive(activity, /* mustBeFullscreen */ true)
+ && activity.mLetterboxUiController.shouldForceRotateForCameraCompat();
}
@@ -324,6 +325,13 @@
return isTreatmentEnabledForActivity(activity, /* mustBeFullscreen */ true);
}
+ boolean isCameraActive(@NonNull ActivityRecord activity, boolean mustBeFullscreen) {
+ // Checking windowing mode on activity level because we don't want to
+ // apply treatment in case of activity embedding.
+ return (!mustBeFullscreen || !activity.inMultiWindowMode())
+ && mCameraStateMonitor.isCameraRunningForActivity(activity);
+ }
+
private boolean isTreatmentEnabledForActivity(@Nullable ActivityRecord activity,
boolean mustBeFullscreen) {
return activity != null && isCameraActive(activity, mustBeFullscreen)
@@ -331,14 +339,7 @@
// "locked" and "nosensor" values are often used by camera apps that can't
// handle dynamic changes so we shouldn't force rotate them.
&& activity.getOverrideOrientation() != SCREEN_ORIENTATION_NOSENSOR
- && activity.getOverrideOrientation() != SCREEN_ORIENTATION_LOCKED;
- }
-
- private boolean isCameraActive(@NonNull ActivityRecord activity, boolean mustBeFullscreen) {
- // Checking windowing mode on activity level because we don't want to
- // apply treatment in case of activity embedding.
- return (!mustBeFullscreen || !activity.inMultiWindowMode())
- && mCameraStateMonitor.isCameraRunningForActivity(activity)
+ && activity.getOverrideOrientation() != SCREEN_ORIENTATION_LOCKED
&& activity.mLetterboxUiController.shouldForceRotateForCameraCompat();
}
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index 683efde..b38e666 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -30,6 +30,7 @@
import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION;
import static android.content.pm.ActivityInfo.OVERRIDE_LANDSCAPE_ORIENTATION_TO_REVERSE_LANDSCAPE;
import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO;
+import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA;
import static android.content.pm.ActivityInfo.OVERRIDE_ORIENTATION_ONLY_FOR_CAMERA;
import static android.content.pm.ActivityInfo.OVERRIDE_RESPECT_REQUESTED_ORIENTATION;
import static android.content.pm.ActivityInfo.OVERRIDE_UNDEFINED_ORIENTATION_TO_NOSENSOR;
@@ -510,6 +511,26 @@
}
/**
+ * Whether we should apply the min aspect ratio per-app override only when an app is connected
+ * to the camera.
+ * When this override is applied the min aspect ratio given in the app's manifest will be
+ * overridden to the largest enabled aspect ratio treatment unless the app's manifest value
+ * is higher. The treatment will also apply if no value is provided in the manifest.
+ *
+ * <p>This method returns {@code true} when the following conditions are met:
+ * <ul>
+ * <li>Opt-out component property isn't enabled
+ * <li>Per-app override is enabled
+ * </ul>
+ */
+ boolean shouldOverrideMinAspectRatioForCamera() {
+ return mActivityRecord.isCameraActive()
+ && mAllowMinAspectRatioOverrideOptProp
+ .shouldEnableWithOptInOverrideAndOptOutProperty(
+ isCompatChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA));
+ }
+
+ /**
* Whether we should apply the force resize per-app override. When this override is applied it
* forces the packages it is applied to to be resizable. It won't change whether the app can be
* put into multi-windowing mode, but allow the app to resize without going into size-compat
@@ -962,7 +983,8 @@
void recomputeConfigurationForCameraCompatIfNeeded() {
if (isOverrideOrientationOnlyForCameraEnabled()
- || isCameraCompatSplitScreenAspectRatioAllowed()) {
+ || isCameraCompatSplitScreenAspectRatioAllowed()
+ || shouldOverrideMinAspectRatioForCamera()) {
mActivityRecord.recomputeConfiguration();
}
}
diff --git a/services/core/java/com/android/server/wm/PackageConfigPersister.java b/services/core/java/com/android/server/wm/PackageConfigPersister.java
index 23127ac..9d597ea 100644
--- a/services/core/java/com/android/server/wm/PackageConfigPersister.java
+++ b/services/core/java/com/android/server/wm/PackageConfigPersister.java
@@ -169,6 +169,8 @@
LocaleOverlayHelper.combineLocalesIfOverlayExists(
modifiedRecord.mLocales, mAtm.getGlobalConfiguration().getLocales()),
modifiedRecord.mGrammaticalGender);
+ } else {
+ container.applyAppSpecificConfig(null, null, null);
}
}
}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 9b98380..56e5d76 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -43,12 +43,10 @@
import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE;
import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE_AND_PIPABLE_DEPRECATED;
import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION;
-import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.provider.Settings.Secure.USER_SETUP_COMPLETE;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;
-import static android.view.InsetsSource.FLAG_INSETS_ROUNDED_CORNER;
import static android.view.SurfaceControl.METADATA_TASK_ID;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
@@ -171,12 +169,10 @@
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
import android.view.DisplayInfo;
-import android.view.InsetsSource;
import android.view.InsetsState;
import android.view.RemoteAnimationAdapter;
import android.view.Surface;
import android.view.SurfaceControl;
-import android.view.TaskTransitionSpec;
import android.view.WindowManager;
import android.view.WindowManager.TransitionOldType;
import android.window.ITaskOrganizer;
@@ -641,8 +637,6 @@
mLastTaskSnapshotData = _lastSnapshotData != null
? _lastSnapshotData
: new PersistedTaskSnapshotData();
- // Tasks have no set orientation value (including SCREEN_ORIENTATION_UNSPECIFIED).
- setOrientation(SCREEN_ORIENTATION_UNSET);
affinityIntent = _affinityIntent;
affinity = _affinity;
rootAffinity = _rootAffinity;
@@ -2942,33 +2936,6 @@
return;
}
- /**
- * Account for specified insets to crop the animation bounds by to avoid the animation
- * occurring over "out of bounds" regions
- *
- * For example this is used to make sure the tasks are cropped to be fully above the expanded
- * taskbar when animating.
- *
- * TEMPORARY FIELD (b/202383002)
- * TODO: Remove once we use surfaceflinger rounded corners on tasks rather than taskbar overlays
- * or when shell transitions are fully enabled
- *
- * @param animationBounds The animation bounds to adjust to account for the custom spec insets.
- */
- void adjustAnimationBoundsForTransition(Rect animationBounds) {
- TaskTransitionSpec spec = mWmService.mTaskTransitionSpec;
- if (spec != null) {
- final InsetsState state =
- getDisplayContent().getInsetsStateController().getRawInsetsState();
- for (int i = state.sourceSize() - 1; i >= 0; i--) {
- final InsetsSource source = state.sourceAt(i);
- if (source.hasFlags(FLAG_INSETS_ROUNDED_CORNER)) {
- animationBounds.inset(source.calculateVisibleInsets(animationBounds));
- }
- }
- }
- }
-
void setDragResizing(boolean dragResizing) {
if (mDragResizing != dragResizing) {
// No need to check if allowed if it's leaving dragResize
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index a437914..d70ca02 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -44,7 +44,6 @@
import static com.android.server.wm.AppTransition.MAX_APP_TRANSITION_DURATION;
import static com.android.server.wm.AppTransition.isActivityTransitOld;
import static com.android.server.wm.AppTransition.isTaskFragmentTransitOld;
-import static com.android.server.wm.AppTransition.isTaskTransitOld;
import static com.android.server.wm.DisplayContent.IME_TARGET_LAYERING;
import static com.android.server.wm.IdentifierProto.HASH_CODE;
import static com.android.server.wm.IdentifierProto.TITLE;
@@ -72,7 +71,6 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.pm.ActivityInfo.ScreenOrientation;
import android.content.res.Configuration;
@@ -103,7 +101,6 @@
import android.view.SurfaceControl.Builder;
import android.view.SurfaceControlViewHost;
import android.view.SurfaceSession;
-import android.view.TaskTransitionSpec;
import android.view.WindowManager;
import android.view.WindowManager.TransitionOldType;
import android.view.animation.Animation;
@@ -186,7 +183,7 @@
// The specified orientation for this window container.
// Shouldn't be accessed directly since subclasses can override getOverrideOrientation.
@ScreenOrientation
- private int mOverrideOrientation = SCREEN_ORIENTATION_UNSPECIFIED;
+ private int mOverrideOrientation = SCREEN_ORIENTATION_UNSET;
/**
* The window container which decides its orientation since the last time
@@ -1683,8 +1680,6 @@
for (int i = mChildren.size() - 1; i >= 0; --i) {
final WindowContainer wc = mChildren.get(i);
- // TODO: Maybe mOverrideOrientation should default to SCREEN_ORIENTATION_UNSET vs.
- // SCREEN_ORIENTATION_UNSPECIFIED?
final int orientation = wc.getOrientation(candidate == SCREEN_ORIENTATION_BEHIND
? SCREEN_ORIENTATION_BEHIND : SCREEN_ORIENTATION_UNSET);
if (orientation == SCREEN_ORIENTATION_BEHIND) {
@@ -1700,7 +1695,7 @@
continue;
}
- if (wc.providesOrientation() || orientation != SCREEN_ORIENTATION_UNSPECIFIED) {
+ if (orientation != SCREEN_ORIENTATION_UNSPECIFIED || wc.providesOrientation()) {
// Use the orientation if the container can provide or requested an explicit
// orientation that isn't SCREEN_ORIENTATION_UNSPECIFIED.
ProtoLog.v(WM_DEBUG_ORIENTATION, "%s is requesting orientation %d (%s)",
@@ -3148,9 +3143,6 @@
// Separate position and size for use in animators.
final Rect screenBounds = getAnimationBounds(appRootTaskClipMode);
mTmpRect.set(screenBounds);
- if (this.asTask() != null && isTaskTransitOld(transit)) {
- this.asTask().adjustAnimationBoundsForTransition(mTmpRect);
- }
getAnimationPosition(mTmpPoint);
mTmpRect.offsetTo(0, 0);
@@ -3285,10 +3277,6 @@
AnimationRunnerBuilder animationRunnerBuilder = new AnimationRunnerBuilder();
- if (isTaskTransitOld(transit) && getWindowingMode() == WINDOWING_MODE_FULLSCREEN) {
- animationRunnerBuilder.setTaskBackgroundColor(getTaskAnimationBackgroundColor());
- }
-
// Check if the animation requests to show background color for Activity and embedded
// TaskFragment.
final ActivityRecord activityRecord = asActivityRecord();
@@ -3342,18 +3330,6 @@
}
}
- private @ColorInt int getTaskAnimationBackgroundColor() {
- Context uiContext = mDisplayContent.getDisplayPolicy().getSystemUiContext();
- TaskTransitionSpec customSpec = mWmService.mTaskTransitionSpec;
- @ColorInt int defaultFallbackColor = uiContext.getColor(R.color.overview_background);
-
- if (customSpec != null && customSpec.backgroundColor != 0) {
- return customSpec.backgroundColor;
- }
-
- return defaultFallbackColor;
- }
-
final SurfaceAnimationRunner getSurfaceAnimationRunner() {
return mWmService.mSurfaceAnimationRunner;
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index b19f0be..feede01 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -20,7 +20,6 @@
import static android.Manifest.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS;
import static android.Manifest.permission.INPUT_CONSUMER;
import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW;
-import static android.Manifest.permission.MANAGE_ACTIVITY_TASKS;
import static android.Manifest.permission.MANAGE_APP_TOKENS;
import static android.Manifest.permission.MODIFY_TOUCH_MODE_STATE;
import static android.Manifest.permission.READ_FRAME_BUFFER;
@@ -295,7 +294,6 @@
import android.view.SurfaceControl;
import android.view.SurfaceControlViewHost;
import android.view.SurfaceSession;
-import android.view.TaskTransitionSpec;
import android.view.View;
import android.view.View.FocusDirection;
import android.view.ViewDebug;
@@ -763,11 +761,6 @@
*/
final Handler mAnimationHandler = new Handler(AnimationThread.getHandler().getLooper());
- /**
- * Used during task transitions to allow SysUI and launcher to customize task transitions.
- */
- TaskTransitionSpec mTaskTransitionSpec;
-
boolean mHardKeyboardAvailable;
WindowManagerInternal.OnHardKeyboardStatusChangeListener mHardKeyboardStatusChangeListener;
@@ -9932,24 +9925,6 @@
}
@Override
- public void setTaskTransitionSpec(TaskTransitionSpec spec) {
- if (!checkCallingPermission(MANAGE_ACTIVITY_TASKS, "setTaskTransitionSpec()")) {
- throw new SecurityException("Requires MANAGE_ACTIVITY_TASKS permission");
- }
-
- mTaskTransitionSpec = spec;
- }
-
- @Override
- public void clearTaskTransitionSpec() {
- if (!checkCallingPermission(MANAGE_ACTIVITY_TASKS, "clearTaskTransitionSpec()")) {
- throw new SecurityException("Requires MANAGE_ACTIVITY_TASKS permission");
- }
-
- mTaskTransitionSpec = null;
- }
-
- @Override
@RequiresPermission(Manifest.permission.ACCESS_FPS_COUNTER)
public void registerTaskFpsCallback(@IntRange(from = 0) int taskId,
ITaskFpsCallback callback) {
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index fd1b5be..1c00fbb 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -84,6 +84,7 @@
import com.android.internal.protolog.common.ProtoLog;
import com.android.internal.util.function.pooled.PooledLambda;
import com.android.server.Watchdog;
+import com.android.server.grammaticalinflection.GrammaticalInflectionManagerInternal;
import com.android.server.wm.ActivityTaskManagerService.HotPath;
import java.io.IOException;
@@ -324,8 +325,6 @@
*/
private volatile int mActivityStateFlags = ACTIVITY_STATE_FLAG_MASK_MIN_TASK_LAYER;
- private final boolean mCanUseSystemGrammaticalGender;
-
public WindowProcessController(@NonNull ActivityTaskManagerService atm,
@NonNull ApplicationInfo info, String name, int uid, int userId, Object owner,
@NonNull WindowProcessListener listener) {
@@ -349,9 +348,6 @@
mUseFifoUiScheduling = com.android.window.flags.Flags.fifoPriorityForMajorUiProcesses()
&& (isSysUiPackage || mAtm.isCallerRecents(uid));
- mCanUseSystemGrammaticalGender = mAtm.mGrammaticalManagerInternal != null
- && mAtm.mGrammaticalManagerInternal.canGetSystemGrammaticalGender(mUid,
- mInfo.packageName);
onConfigurationChanged(atm.getGlobalConfiguration());
mAtm.mPackageConfigPersister.updateConfigIfNeeded(this, mUserId, mInfo.packageName);
}
@@ -1612,11 +1608,6 @@
return;
}
- if (mCanUseSystemGrammaticalGender) {
- config.setGrammaticalGender(
- mAtm.mGrammaticalManagerInternal.getSystemGrammaticalGender(mUserId));
- }
-
if (mPauseConfigurationDispatchCount > 0) {
mHasPendingConfigurationChange = true;
return;
@@ -2139,4 +2130,34 @@
void dumpDebug(ProtoOutputStream proto, long fieldId) {
mListener.dumpDebug(proto, fieldId);
}
+
+ @Override
+ protected boolean setOverrideGender(Configuration requestsTmpConfig, int gender) {
+ return applyConfigGenderOverride(requestsTmpConfig, gender,
+ mAtm.mGrammaticalManagerInternal, mUid);
+ }
+
+ static boolean applyConfigGenderOverride(@NonNull Configuration overrideConfig,
+ @Configuration.GrammaticalGender int override,
+ GrammaticalInflectionManagerInternal service, int uid) {
+ final boolean canGetSystemValue = service != null
+ && service.canGetSystemGrammaticalGender(uid);
+
+ // The priority here is as follows:
+ // - app-specific override if set
+ // - system value if allowed to see it
+ // - global configuration otherwise
+ final int targetValue = (override != Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED)
+ ? override
+ : canGetSystemValue
+ ? Configuration.GRAMMATICAL_GENDER_UNDEFINED
+ : service != null
+ ? service.getGrammaticalGenderFromDeveloperSettings()
+ : Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED;
+ if (overrideConfig.getGrammaticalGenderRaw() == targetValue) {
+ return false;
+ }
+ overrideConfig.setGrammaticalGender(targetValue);
+ return true;
+ }
}
diff --git a/services/core/jni/OWNERS b/services/core/jni/OWNERS
index 25c5db1..64cbb0d1 100644
--- a/services/core/jni/OWNERS
+++ b/services/core/jni/OWNERS
@@ -12,6 +12,7 @@
per-file com_android_server_Usb* = file:/services/usb/OWNERS
per-file com_android_server_Vibrator* = file:/services/core/java/com/android/server/vibrator/OWNERS
per-file com_android_server_accessibility_* = file:/services/accessibility/OWNERS
+per-file com_android_server_adb_* = file:/services/core/java/com/android/server/adb/OWNERS
per-file com_android_server_display_* = file:/services/core/java/com/android/server/display/OWNERS
per-file com_android_server_hdmi_* = file:/core/java/android/hardware/hdmi/OWNERS
per-file com_android_server_lights_* = file:/services/core/java/com/android/server/lights/OWNERS
diff --git a/services/core/jni/com_android_server_adb_AdbDebuggingManager.cpp b/services/core/jni/com_android_server_adb_AdbDebuggingManager.cpp
index 9c834aa..c7b6852 100644
--- a/services/core/jni/com_android_server_adb_AdbDebuggingManager.cpp
+++ b/services/core/jni/com_android_server_adb_AdbDebuggingManager.cpp
@@ -18,58 +18,22 @@
#define LOG_NDEBUG 0
-#include <algorithm>
#include <condition_variable>
#include <mutex>
#include <optional>
-#include <random>
-#include <string>
-#include <vector>
#include <adb/pairing/pairing_server.h>
#include <android-base/properties.h>
-#include <utils/Log.h>
-
+#include <jni.h>
#include <nativehelper/JNIHelp.h>
-#include "jni.h"
+#include <nativehelper/utils.h>
+#include <utils/Log.h>
namespace android {
// ----------------------------------------------------------------------------
namespace {
-template <class T, class N>
-class JSmartWrapper {
-public:
- JSmartWrapper(JNIEnv* env, T* jData) : mEnv(env), mJData(jData) {}
-
- virtual ~JSmartWrapper() = default;
-
- const N* data() const { return mRawData; }
-
- jsize size() const { return mSize; }
-
-protected:
- N* mRawData = nullptr;
- JNIEnv* mEnv = nullptr;
- T* mJData = nullptr;
- jsize mSize = 0;
-}; // JSmartWrapper
-
-class JStringUTFWrapper : public JSmartWrapper<jstring, const char> {
-public:
- explicit JStringUTFWrapper(JNIEnv* env, jstring* str) : JSmartWrapper(env, str) {
- mRawData = env->GetStringUTFChars(*str, NULL);
- mSize = env->GetStringUTFLength(*str);
- }
-
- virtual ~JStringUTFWrapper() {
- if (data()) {
- mEnv->ReleaseStringUTFChars(*mJData, mRawData);
- }
- }
-}; // JStringUTFWrapper
-
struct ServerDeleter {
void operator()(PairingServerCtx* p) { pairing_server_destroy(p); }
};
@@ -97,19 +61,19 @@
std::unique_ptr<PairingResultWaiter> sWaiter;
} // namespace
-static jint native_pairing_start(JNIEnv* env, jobject thiz, jstring guid, jstring password) {
+static jint native_pairing_start(JNIEnv* env, jobject thiz, jstring javaGuid, jstring javaPassword) {
// Server-side only sends its GUID on success.
- PeerInfo system_info = {};
- system_info.type = ADB_DEVICE_GUID;
- JStringUTFWrapper guidWrapper(env, &guid);
- memcpy(system_info.data, guidWrapper.data(), guidWrapper.size());
+ PeerInfo system_info = { .type = ADB_DEVICE_GUID };
- JStringUTFWrapper passwordWrapper(env, &password);
+ ScopedUtfChars guid = GET_UTF_OR_RETURN(env, javaGuid);
+ memcpy(system_info.data, guid.c_str(), guid.size());
+
+ ScopedUtfChars password = GET_UTF_OR_RETURN(env, javaPassword);
// Create the pairing server
sServer = PairingServerPtr(
- pairing_server_new_no_cert(reinterpret_cast<const uint8_t*>(passwordWrapper.data()),
- passwordWrapper.size(), &system_info, 0));
+ pairing_server_new_no_cert(reinterpret_cast<const uint8_t*>(password.c_str()),
+ password.size(), &system_info, 0));
sWaiter.reset(new PairingResultWaiter);
uint16_t port = pairing_server_start(sServer.get(), sWaiter->ResultCallback, sWaiter.get());
@@ -137,11 +101,16 @@
return JNI_FALSE;
}
- std::string peer_public_key = reinterpret_cast<char*>(sWaiter->peer_info_.data);
- // Write to PairingThread's member variables
+ // Create a Java string for the public key.
+ char* peer_public_key = reinterpret_cast<char*>(sWaiter->peer_info_.data);
+ jstring jpublickey = env->NewStringUTF(peer_public_key);
+ if (jpublickey == nullptr) {
+ return JNI_FALSE;
+ }
+
+ // Write to PairingThread.mPublicKey.
jclass clazz = env->GetObjectClass(thiz);
jfieldID mPublicKey = env->GetFieldID(clazz, "mPublicKey", "Ljava/lang/String;");
- jstring jpublickey = env->NewStringUTF(peer_public_key.c_str());
env->SetObjectField(thiz, mPublicKey, jpublickey);
return JNI_TRUE;
}
@@ -157,12 +126,9 @@
};
int register_android_server_AdbDebuggingManager(JNIEnv* env) {
- int res = jniRegisterNativeMethods(env,
- "com/android/server/adb/AdbDebuggingManager$PairingThread",
- gPairingThreadMethods, NELEM(gPairingThreadMethods));
- (void)res; // Faked use when LOG_NDEBUG.
- LOG_FATAL_IF(res < 0, "Unable to register native methods.");
- return 0;
+ return jniRegisterNativeMethods(env,
+ "com/android/server/adb/AdbDebuggingManager$PairingThread",
+ gPairingThreadMethods, NELEM(gPairingThreadMethods));
}
} /* namespace android */
diff --git a/services/core/jni/com_android_server_utils_AnrTimer.cpp b/services/core/jni/com_android_server_utils_AnrTimer.cpp
index da95666..6509958 100644
--- a/services/core/jni/com_android_server_utils_AnrTimer.cpp
+++ b/services/core/jni/com_android_server_utils_AnrTimer.cpp
@@ -161,14 +161,14 @@
// A timer has expired.
void expire(timer_id_t);
- // Dump a small amount of state to the log file.
- void dump(bool verbose) const;
-
// Return the Java object associated with this instance.
jweak jtimer() const {
return notifierObject_;
}
+ // Return the per-instance statistics.
+ std::vector<std::string> getDump() const;
+
private:
// The service cannot be copied.
AnrTimerService(AnrTimerService const &) = delete;
@@ -199,7 +199,7 @@
std::set<Timer> running_;
// The maximum number of active timers.
- size_t maxActive_;
+ size_t maxRunning_;
// Simple counters
struct Counters {
@@ -209,6 +209,7 @@
size_t accepted;
size_t discarded;
size_t expired;
+ size_t extended;
// The number of times there were zero active timers.
size_t drained;
@@ -437,7 +438,9 @@
// Construct the ticker. This creates the timerfd file descriptor and starts the monitor
// thread. The monitor thread is given a unique name.
- Ticker() {
+ Ticker() :
+ id_(idGen_.fetch_add(1))
+ {
timerFd_ = timer_create();
if (timerFd_ < 0) {
ALOGE("failed to create timerFd: %s", strerror(errno));
@@ -502,6 +505,11 @@
}
}
+ // The unique ID of this particular ticker. Used for debug and logging.
+ size_t id() const {
+ return id_;
+ }
+
// Return the number of timers still running.
size_t running() const {
AutoMutex _l(lock_);
@@ -617,8 +625,16 @@
// The list of timers that are scheduled. This set is sorted by timeout and then by timer
// ID. A set is sufficient (as opposed to a multiset) because timer IDs are unique.
std::set<Entry> running_;
+
+ // A unique ID assigned to this instance.
+ const size_t id_;
+
+ // The ID generator.
+ static std::atomic<size_t> idGen_;
};
+std::atomic<size_t> AnrTimerService::Ticker::idGen_;
+
AnrTimerService::AnrTimerService(char const* label,
notifier_t notifier, void* cookie, jweak jtimer, Ticker* ticker) :
@@ -629,7 +645,7 @@
ticker_(ticker) {
// Zero the statistics
- maxActive_ = 0;
+ maxRunning_ = 0;
memset(&counters_, 0, sizeof(counters_));
ALOGI_IF(DEBUG, "initialized %s", label);
@@ -739,6 +755,12 @@
elapsed = now() - t.started;
}
+ if (expired) {
+ counters_.expired++;
+ } else {
+ counters_.extended++;
+ }
+
// Deliver the notification outside of the lock.
if (expired) {
if (!notifier_(timerId, pid, uid, elapsed, notifierCookie_, notifierObject_)) {
@@ -756,8 +778,8 @@
if (t.status == Running) {
// Only forward running timers to the ticker. Expired timers are handled separately.
ticker_->insert(t.scheduled, t.id, this);
- maxActive_ = std::max(maxActive_, running_.size());
}
+ maxRunning_ = std::max(maxRunning_, running_.size());
}
AnrTimerService::Timer AnrTimerService::remove(timer_id_t timerId) {
@@ -767,29 +789,32 @@
Timer result = *found;
running_.erase(found);
ticker_->remove(result.scheduled, result.id);
+ if (running_.size() == 0) counters_.drained++;
return result;
}
return Timer();
}
-void AnrTimerService::dump(bool verbose) const {
+std::vector<std::string> AnrTimerService::getDump() const {
+ std::vector<std::string> r;
AutoMutex _l(lock_);
- ALOGI("timer %s ops started=%zu canceled=%zu accepted=%zu discarded=%zu expired=%zu",
- label_.c_str(),
- counters_.started, counters_.canceled, counters_.accepted,
- counters_.discarded, counters_.expired);
- ALOGI("timer %s stats max-active=%zu/%zu running=%zu/%zu errors=%zu",
- label_.c_str(),
- maxActive_, ticker_->maxRunning(), running_.size(), ticker_->running(),
- counters_.error);
-
- if (verbose) {
- nsecs_t time = now();
- for (auto i = running_.begin(); i != running_.end(); i++) {
- Timer t = *i;
- ALOGI(" running %s", t.toString(time).c_str());
- }
- }
+ r.push_back(StringPrintf("started:%zu canceled:%zu accepted:%zu discarded:%zu expired:%zu",
+ counters_.started,
+ counters_.canceled,
+ counters_.accepted,
+ counters_.discarded,
+ counters_.expired));
+ r.push_back(StringPrintf("extended:%zu drained:%zu error:%zu running:%zu maxRunning:%zu",
+ counters_.extended,
+ counters_.drained,
+ counters_.error,
+ running_.size(),
+ maxRunning_));
+ r.push_back(StringPrintf("ticker:%zu ticking:%zu maxTicking:%zu",
+ ticker_->id(),
+ ticker_->running(),
+ ticker_->maxRunning()));
+ return r;
}
/**
@@ -894,21 +919,26 @@
return toService(ptr)->discard(timerId);
}
-jint anrTimerDump(JNIEnv *env, jclass, jlong ptr, jboolean verbose) {
- if (!nativeSupportEnabled) return -1;
- toService(ptr)->dump(verbose);
- return 0;
+jobjectArray anrTimerDump(JNIEnv *env, jclass, jlong ptr) {
+ if (!nativeSupportEnabled) return nullptr;
+ std::vector<std::string> stats = toService(ptr)->getDump();
+ jclass sclass = env->FindClass("java/lang/String");
+ jobjectArray r = env->NewObjectArray(stats.size(), sclass, nullptr);
+ for (size_t i = 0; i < stats.size(); i++) {
+ env->SetObjectArrayElement(r, i, env->NewStringUTF(stats[i].c_str()));
+ }
+ return r;
}
static const JNINativeMethod methods[] = {
{"nativeAnrTimerSupported", "()Z", (void*) anrTimerSupported},
- {"nativeAnrTimerCreate", "(Ljava/lang/String;)J", (void*) anrTimerCreate},
- {"nativeAnrTimerClose", "(J)I", (void*) anrTimerClose},
- {"nativeAnrTimerStart", "(JIIJZ)I", (void*) anrTimerStart},
- {"nativeAnrTimerCancel", "(JI)Z", (void*) anrTimerCancel},
- {"nativeAnrTimerAccept", "(JI)Z", (void*) anrTimerAccept},
- {"nativeAnrTimerDiscard", "(JI)Z", (void*) anrTimerDiscard},
- {"nativeAnrTimerDump", "(JZ)V", (void*) anrTimerDump},
+ {"nativeAnrTimerCreate", "(Ljava/lang/String;)J", (void*) anrTimerCreate},
+ {"nativeAnrTimerClose", "(J)I", (void*) anrTimerClose},
+ {"nativeAnrTimerStart", "(JIIJZ)I", (void*) anrTimerStart},
+ {"nativeAnrTimerCancel", "(JI)Z", (void*) anrTimerCancel},
+ {"nativeAnrTimerAccept", "(JI)Z", (void*) anrTimerAccept},
+ {"nativeAnrTimerDiscard", "(JI)Z", (void*) anrTimerDiscard},
+ {"nativeAnrTimerDump", "(J)[Ljava/lang/String;", (void*) anrTimerDump},
};
} // anonymous namespace
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index d555f1a..85ab562 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -21610,7 +21610,8 @@
== HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER;
}
- if (Flags.headlessSingleUserFixes() && isSingleUserMode && !mInjector.isChangeEnabled(
+ if (Flags.headlessSingleUserFixes() && mInjector.userManagerIsHeadlessSystemUserMode()
+ && isSingleUserMode && !mInjector.isChangeEnabled(
PROVISION_SINGLE_USER_MODE, deviceAdmin.getPackageName(), caller.getUserId())) {
throw new IllegalStateException("Device admin is not targeting Android V.");
}
@@ -21629,7 +21630,7 @@
setLocale(provisioningParams.getLocale());
int deviceOwnerUserId = Flags.headlessDeviceOwnerSingleUserEnabled()
- && isSingleUserMode
+ && isSingleUserMode && mInjector.userManagerIsHeadlessSystemUserMode()
? mUserManagerInternal.getMainUserId() : UserHandle.USER_SYSTEM;
if (!removeNonRequiredAppsForManagedDevice(
diff --git a/services/print/java/com/android/server/print/PrintManagerService.java b/services/print/java/com/android/server/print/PrintManagerService.java
index 4a8d73d2..07cda37 100644
--- a/services/print/java/com/android/server/print/PrintManagerService.java
+++ b/services/print/java/com/android/server/print/PrintManagerService.java
@@ -875,7 +875,7 @@
}
private void registerBroadcastReceivers() {
- PackageMonitor monitor = new PackageMonitor() {
+ PackageMonitor monitor = new PackageMonitor(true) {
/**
* Checks if the package contains a print service.
*
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/AppRequestObserverTest.kt b/services/tests/displayservicetests/src/com/android/server/display/mode/AppRequestObserverTest.kt
new file mode 100644
index 0000000..f0abcd2
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/AppRequestObserverTest.kt
@@ -0,0 +1,122 @@
+/*
+ * 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.display.mode
+
+import android.content.Context
+import android.util.SparseArray
+import android.view.Display
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.filters.SmallTest
+import com.android.server.display.feature.DisplayManagerFlags
+import com.android.server.display.mode.DisplayModeDirector.DisplayDeviceConfigProvider
+import com.android.server.display.mode.RefreshRateVote.RenderVote
+import com.android.server.testutils.TestHandler
+import com.google.common.truth.Truth.assertThat
+import com.google.testing.junit.testparameterinjector.TestParameter
+import com.google.testing.junit.testparameterinjector.TestParameterInjector
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
+
+@SmallTest
+@RunWith(TestParameterInjector::class)
+class AppRequestObserverTest {
+
+ private lateinit var context: Context
+ private val mockInjector = mock<DisplayModeDirector.Injector>()
+ private val mockFlags = mock<DisplayManagerFlags>()
+ private val mockDisplayDeviceConfigProvider = mock<DisplayDeviceConfigProvider>()
+ private val testHandler = TestHandler(null)
+
+ @Before
+ fun setUp() {
+ context = ApplicationProvider.getApplicationContext()
+ }
+
+ @Test
+ fun `test app request votes`(@TestParameter testCase: AppRequestTestCase) {
+ whenever(mockFlags.ignoreAppPreferredRefreshRateRequest())
+ .thenReturn(testCase.ignoreRefreshRateRequest)
+ val displayModeDirector = DisplayModeDirector(
+ context, testHandler, mockInjector, mockFlags, mockDisplayDeviceConfigProvider)
+ val modes = arrayOf(
+ Display.Mode(1, 1000, 1000, 60f),
+ Display.Mode(2, 1000, 1000, 90f),
+ Display.Mode(3, 1000, 1000, 120f)
+ )
+ displayModeDirector.injectSupportedModesByDisplay(SparseArray<Array<Display.Mode>>().apply {
+ append(Display.DEFAULT_DISPLAY, modes)
+ })
+ displayModeDirector.injectDefaultModeByDisplay(SparseArray<Display.Mode>().apply {
+ append(Display.DEFAULT_DISPLAY, modes[0])
+ })
+
+ displayModeDirector.appRequestObserver.setAppRequest(Display.DEFAULT_DISPLAY,
+ testCase.modeId,
+ testCase.requestedRefreshRates,
+ testCase.requestedMinRefreshRates,
+ testCase.requestedMaxRefreshRates)
+
+ val baseModeVote = displayModeDirector.getVote(Display.DEFAULT_DISPLAY,
+ Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE)
+ assertThat(baseModeVote).isEqualTo(testCase.expectedBaseModeVote)
+
+ val sizeVote = displayModeDirector.getVote(Display.DEFAULT_DISPLAY,
+ Vote.PRIORITY_APP_REQUEST_SIZE)
+ assertThat(sizeVote).isEqualTo(testCase.expectedSizeVote)
+
+ val renderRateVote = displayModeDirector.getVote(Display.DEFAULT_DISPLAY,
+ Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE)
+ assertThat(renderRateVote).isEqualTo(testCase.expectedRenderRateVote)
+ }
+
+ enum class AppRequestTestCase(
+ val ignoreRefreshRateRequest: Boolean,
+ val modeId: Int,
+ val requestedRefreshRates: Float,
+ val requestedMinRefreshRates: Float,
+ val requestedMaxRefreshRates: Float,
+ internal val expectedBaseModeVote: Vote?,
+ internal val expectedSizeVote: Vote?,
+ internal val expectedRenderRateVote: Vote?,
+ ) {
+ BASE_MODE_60(true, 1, 0f, 0f, 0f,
+ BaseModeRefreshRateVote(60f), SizeVote(1000, 1000, 1000, 1000), null),
+ BASE_MODE_90(true, 2, 0f, 0f, 0f,
+ BaseModeRefreshRateVote(90f), SizeVote(1000, 1000, 1000, 1000), null),
+ MIN_REFRESH_RATE_60(true, 0, 0f, 60f, 0f,
+ null, null, RenderVote(60f, Float.POSITIVE_INFINITY)),
+ MIN_REFRESH_RATE_120(true, 0, 0f, 120f, 0f,
+ null, null, RenderVote(120f, Float.POSITIVE_INFINITY)),
+ MAX_REFRESH_RATE_60(true, 0, 0f, 0f, 60f,
+ null, null, RenderVote(0f, 60f)),
+ MAX_REFRESH_RATE_120(true, 0, 0f, 0f, 120f,
+ null, null, RenderVote(0f, 120f)),
+ INVALID_MIN_MAX_REFRESH_RATE(true, 0, 0f, 90f, 60f,
+ null, null, null),
+ BASE_MODE_MIN_MAX(true, 1, 0f, 60f, 90f,
+ BaseModeRefreshRateVote(60f), SizeVote(1000, 1000, 1000, 1000), RenderVote(60f, 90f)),
+ PREFERRED_REFRESH_RATE(false, 0, 60f, 0f, 0f,
+ BaseModeRefreshRateVote(60f), SizeVote(1000, 1000, 1000, 1000), null),
+ PREFERRED_REFRESH_RATE_IGNORED(true, 0, 60f, 0f, 0f,
+ null, null, null),
+ PREFERRED_REFRESH_RATE_INVALID(false, 0, 45f, 0f, 0f,
+ null, null, null),
+ }
+}
\ No newline at end of file
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
index 52e157b..cd1e9e8 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
@@ -31,7 +31,6 @@
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -2262,162 +2261,6 @@
}
@Test
- public void testAppRequestObserver_modeId() {
- DisplayModeDirector director = createDirectorFromFpsRange(60, 90);
- director.getAppRequestObserver().setAppRequest(DISPLAY_ID, 60, 0, 0);
-
- Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE);
- assertNotNull(vote);
- assertThat(vote).isInstanceOf(BaseModeRefreshRateVote.class);
- BaseModeRefreshRateVote baseModeVote = (BaseModeRefreshRateVote) vote;
- assertThat(baseModeVote.mAppRequestBaseModeRefreshRate).isWithin(FLOAT_TOLERANCE).of(60);
-
- vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_SIZE);
- assertNotNull(vote);
- assertThat(vote).isInstanceOf(SizeVote.class);
- SizeVote sizeVote = (SizeVote) vote;
- assertThat(sizeVote.mHeight).isEqualTo(1000);
- assertThat(sizeVote.mWidth).isEqualTo(1000);
- assertThat(sizeVote.mMinHeight).isEqualTo(1000);
- assertThat(sizeVote.mMinWidth).isEqualTo(1000);
-
- vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE);
- assertNull(vote);
-
- director.getAppRequestObserver().setAppRequest(DISPLAY_ID, 90, 0, 0);
-
- vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE);
- assertNotNull(vote);
- assertThat(vote).isInstanceOf(BaseModeRefreshRateVote.class);
- baseModeVote = (BaseModeRefreshRateVote) vote;
- assertThat(baseModeVote.mAppRequestBaseModeRefreshRate).isWithin(FLOAT_TOLERANCE).of(90);
-
- vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_SIZE);
- assertNotNull(vote);
- assertThat(vote).isInstanceOf(SizeVote.class);
- sizeVote = (SizeVote) vote;
- assertThat(sizeVote.mHeight).isEqualTo(1000);
- assertThat(sizeVote.mWidth).isEqualTo(1000);
- assertThat(sizeVote.mMinHeight).isEqualTo(1000);
- assertThat(sizeVote.mMinWidth).isEqualTo(1000);
-
- vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE);
- assertNull(vote);
- }
-
- @Test
- public void testAppRequestObserver_minRefreshRate() {
- DisplayModeDirector director = createDirectorFromFpsRange(60, 90);
- director.getAppRequestObserver().setAppRequest(DISPLAY_ID, -1, 60, 0);
- Vote appRequestRefreshRate =
- director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE);
- assertNull(appRequestRefreshRate);
-
- Vote appRequestSize = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_SIZE);
- assertNull(appRequestSize);
-
- Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE);
- assertNotNull(vote);
- assertThat(vote).isInstanceOf(RefreshRateVote.RenderVote.class);
- RefreshRateVote.RenderVote renderVote = (RefreshRateVote.RenderVote) vote;
- assertThat(renderVote.mMinRefreshRate).isWithin(FLOAT_TOLERANCE).of(60);
- assertThat(renderVote.mMaxRefreshRate).isAtLeast(90);
-
- director.getAppRequestObserver().setAppRequest(DISPLAY_ID, -1, 90, 0);
- appRequestRefreshRate =
- director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE);
- assertNull(appRequestRefreshRate);
-
- appRequestSize = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_SIZE);
- assertNull(appRequestSize);
-
- vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE);
- assertNotNull(vote);
- assertThat(vote).isInstanceOf(RefreshRateVote.RenderVote.class);
- renderVote = (RefreshRateVote.RenderVote) vote;
- assertThat(renderVote.mMinRefreshRate).isWithin(FLOAT_TOLERANCE).of(90);
- assertThat(renderVote.mMaxRefreshRate).isAtLeast(90);
- }
-
- @Test
- public void testAppRequestObserver_maxRefreshRate() {
- DisplayModeDirector director = createDirectorFromFpsRange(60, 90);
- director.getAppRequestObserver().setAppRequest(DISPLAY_ID, -1, 0, 90);
- Vote appRequestRefreshRate =
- director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE);
- assertNull(appRequestRefreshRate);
-
- Vote appRequestSize = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_SIZE);
- assertNull(appRequestSize);
-
- Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE);
- assertNotNull(vote);
- assertThat(vote).isInstanceOf(RefreshRateVote.RenderVote.class);
- RefreshRateVote.RenderVote renderVote = (RefreshRateVote.RenderVote) vote;
- assertThat(renderVote.mMinRefreshRate).isZero();
- assertThat(renderVote.mMaxRefreshRate).isWithin(FLOAT_TOLERANCE).of(90);
-
- director.getAppRequestObserver().setAppRequest(DISPLAY_ID, -1, 0, 60);
- appRequestRefreshRate =
- director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE);
- assertNull(appRequestRefreshRate);
-
- appRequestSize = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_SIZE);
- assertNull(appRequestSize);
-
- vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE);
- assertNotNull(vote);
- assertThat(vote).isInstanceOf(RefreshRateVote.RenderVote.class);
- renderVote = (RefreshRateVote.RenderVote) vote;
- assertThat(renderVote.mMinRefreshRate).isZero();
- assertThat(renderVote.mMaxRefreshRate).isWithin(FLOAT_TOLERANCE).of(60);
- }
-
- @Test
- public void testAppRequestObserver_invalidRefreshRateRange() {
- DisplayModeDirector director = createDirectorFromFpsRange(60, 90);
- director.getAppRequestObserver().setAppRequest(DISPLAY_ID, -1, 90, 60);
- Vote appRequestRefreshRate =
- director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE);
- assertNull(appRequestRefreshRate);
-
- Vote appRequestSize = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_SIZE);
- assertNull(appRequestSize);
-
- Vote appRequestRefreshRateRange =
- director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE);
- assertNull(appRequestRefreshRateRange);
- }
-
- @Test
- public void testAppRequestObserver_modeIdAndRefreshRateRange() {
- DisplayModeDirector director = createDirectorFromFpsRange(60, 90);
- director.getAppRequestObserver().setAppRequest(DISPLAY_ID, 60, 90, 90);
-
- Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE);
- assertNotNull(vote);
- assertThat(vote).isInstanceOf(BaseModeRefreshRateVote.class);
- BaseModeRefreshRateVote baseModeVote = (BaseModeRefreshRateVote) vote;
- assertThat(baseModeVote.mAppRequestBaseModeRefreshRate).isWithin(FLOAT_TOLERANCE).of(60);
-
- vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_SIZE);
- assertNotNull(vote);
- assertThat(vote).isInstanceOf(SizeVote.class);
- SizeVote sizeVote = (SizeVote) vote;
- assertThat(sizeVote.mHeight).isEqualTo(1000);
- assertThat(sizeVote.mWidth).isEqualTo(1000);
- assertThat(sizeVote.mMinHeight).isEqualTo(1000);
- assertThat(sizeVote.mMinWidth).isEqualTo(1000);
-
- vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE);
- assertNotNull(vote);
- assertThat(vote).isInstanceOf(RefreshRateVote.RenderVote.class);
- RefreshRateVote.RenderVote renderVote = (RefreshRateVote.RenderVote) vote;
- assertThat(renderVote.mMinRefreshRate).isWithin(FLOAT_TOLERANCE).of(90);
- assertThat(renderVote.mMaxRefreshRate).isWithin(FLOAT_TOLERANCE).of(90);
- }
-
- @Test
public void testAppRequestsIsTheDefaultMode() {
Display.Mode[] modes = new Display.Mode[2];
modes[0] = new Display.Mode(
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/SettingsObserverTest.kt b/services/tests/displayservicetests/src/com/android/server/display/mode/SettingsObserverTest.kt
index 3e0677c..4d910ce 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/SettingsObserverTest.kt
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/SettingsObserverTest.kt
@@ -146,8 +146,8 @@
val modes = arrayOf(
Display.Mode(1, 1000, 1000, 60f),
- Display.Mode(1, 1000, 1000, 90f),
- Display.Mode(1, 1000, 1000, 120f)
+ Display.Mode(2, 1000, 1000, 90f),
+ Display.Mode(3, 1000, 1000, 120f)
)
displayModeDirector.injectSupportedModesByDisplay(SparseArray<Array<Display.Mode>>().apply {
append(Display.DEFAULT_DISPLAY, modes)
diff --git a/services/tests/dreamservicetests/src/com/android/server/dreams/DreamServiceTest.java b/services/tests/dreamservicetests/src/com/android/server/dreams/DreamServiceTest.java
index 23314cd..1322545 100644
--- a/services/tests/dreamservicetests/src/com/android/server/dreams/DreamServiceTest.java
+++ b/services/tests/dreamservicetests/src/com/android/server/dreams/DreamServiceTest.java
@@ -20,12 +20,17 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
+import android.content.res.TypedArray;
import android.os.Looper;
import android.platform.test.annotations.EnableFlags;
import android.service.dreams.DreamService;
@@ -41,6 +46,7 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mockito;
@SmallTest
@RunWith(AndroidJUnit4.class)
@@ -83,6 +89,18 @@
assertThat(metadata.dreamCategory).isEqualTo(DreamService.DREAM_CATEGORY_DEFAULT);
}
+ @Test
+ public void testMetadataParsing_exceptionReading() {
+ final PackageManager packageManager = Mockito.mock(PackageManager.class);
+ final ServiceInfo serviceInfo = Mockito.mock(ServiceInfo.class);
+ final TypedArray rawMetadata = Mockito.mock(TypedArray.class);
+ when(packageManager.extractPackageItemInfoAttributes(eq(serviceInfo), any(), any(), any()))
+ .thenReturn(rawMetadata);
+ when(rawMetadata.getString(anyInt())).thenThrow(new RuntimeException("failure"));
+
+ assertThat(DreamService.getDreamMetadata(packageManager, serviceInfo)).isNull();
+ }
+
private DreamService.DreamMetadata getDreamMetadata(String dreamClassName)
throws PackageManager.NameNotFoundException {
final Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java b/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java
index a8b792e..80f7a06 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java
@@ -197,7 +197,7 @@
+ Arrays.toString(invocation.getArguments()));
if (!wedge) {
if (mRealAms.mConstants.mEnableWaitForFinishAttachApplication) {
- mRealAms.finishAttachApplication(0);
+ mRealAms.finishAttachApplication(0, 0);
}
}
return null;
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ProcessObserverTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ProcessObserverTest.java
index 67be93b..89c67d5 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/ProcessObserverTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ProcessObserverTest.java
@@ -200,7 +200,7 @@
Log.v(TAG, "Intercepting bindApplication() for "
+ Arrays.toString(invocation.getArguments()));
if (mRealAms.mConstants.mEnableWaitForFinishAttachApplication) {
- mRealAms.finishAttachApplication(0);
+ mRealAms.finishAttachApplication(0, 0);
}
return null;
}).when(thread).bindApplication(
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/SettingsToPropertiesMapperTest.java b/services/tests/mockingservicestests/src/com/android/server/am/SettingsToPropertiesMapperTest.java
index e672928..599b9cd 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/SettingsToPropertiesMapperTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/SettingsToPropertiesMapperTest.java
@@ -24,6 +24,7 @@
import android.content.ContentResolver;
import android.os.SystemProperties;
import android.provider.Settings;
+import android.provider.DeviceConfig.Properties;
import android.text.TextUtils;
import com.android.dx.mockito.inline.extended.ExtendedMockito;
@@ -42,6 +43,7 @@
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
+import java.util.Map;
/**
* Test SettingsToPropertiesMapper.
@@ -61,6 +63,7 @@
private HashMap<String, String> mSystemSettingsMap;
private HashMap<String, String> mGlobalSettingsMap;
+ private HashMap<String, String> mConfigSettingsMap;
@Before
public void setUp() throws Exception {
@@ -71,9 +74,11 @@
.spyStatic(SystemProperties.class)
.spyStatic(Settings.Global.class)
.spyStatic(SettingsToPropertiesMapper.class)
+ .spyStatic(Settings.Config.class)
.startMocking();
mSystemSettingsMap = new HashMap<>();
mGlobalSettingsMap = new HashMap<>();
+ mConfigSettingsMap = new HashMap<>();
// Mock SystemProperties setter and various getters
doAnswer((Answer<Void>) invocationOnMock -> {
@@ -93,7 +98,7 @@
}
).when(() -> SystemProperties.get(anyString()));
- // Mock Settings.Global methods
+ // Mock Settings.Global method
doAnswer((Answer<String>) invocationOnMock -> {
String key = invocationOnMock.getArgument(1);
@@ -101,6 +106,21 @@
}
).when(() -> Settings.Global.getString(any(), anyString()));
+ // Mock Settings.Config getstrings method
+ doAnswer((Answer<Map<String, String>>) invocationOnMock -> {
+ String namespace = invocationOnMock.getArgument(0);
+ List<String> flags = invocationOnMock.getArgument(1);
+ HashMap<String, String> values = new HashMap<>();
+ for (String flag : flags) {
+ String value = mConfigSettingsMap.get(namespace + "/" + flag);
+ if (value != null) {
+ values.put(flag, value);
+ }
+ }
+ return values;
+ }
+ ).when(() -> Settings.Config.getStrings(anyString(), any()));
+
mTestMapper = new SettingsToPropertiesMapper(
mMockContentResolver, TEST_MAPPING, new String[] {}, new String[] {});
}
@@ -239,4 +259,43 @@
Assert.assertTrue(categories.contains("category2"));
Assert.assertTrue(categories.contains("category3"));
}
+
+ @Test
+ public void testGetStagedFlagsWithValueChange() {
+ // mock up what is in the setting already
+ mConfigSettingsMap.put("namespace_1/flag_1", "true");
+ mConfigSettingsMap.put("namespace_1/flag_2", "true");
+
+ // mock up input
+ String namespace = "staged";
+ Map<String, String> keyValueMap = new HashMap<>();
+ // case 1: existing prop, stage the same value
+ keyValueMap.put("namespace_1*flag_1", "true");
+ // case 2: existing prop, stage a different value
+ keyValueMap.put("namespace_1*flag_2", "false");
+ // case 3: new prop, stage the non default value
+ keyValueMap.put("namespace_2*flag_1", "true");
+ // case 4: new prop, stage the default value
+ keyValueMap.put("namespace_2*flag_2", "false");
+ Properties props = new Properties(namespace, keyValueMap);
+
+ HashMap<String, HashMap<String, String>> toStageProps =
+ SettingsToPropertiesMapper.getStagedFlagsWithValueChange(props);
+
+ HashMap<String, String> namespace_1_to_stage = toStageProps.get("namespace_1");
+ HashMap<String, String> namespace_2_to_stage = toStageProps.get("namespace_2");
+ Assert.assertTrue(namespace_1_to_stage != null);
+ Assert.assertTrue(namespace_2_to_stage != null);
+
+ String namespace_1_flag_1 = namespace_1_to_stage.get("flag_1");
+ String namespace_1_flag_2 = namespace_1_to_stage.get("flag_2");
+ String namespace_2_flag_1 = namespace_2_to_stage.get("flag_1");
+ String namespace_2_flag_2 = namespace_2_to_stage.get("flag_2");
+ Assert.assertTrue(namespace_1_flag_1 == null);
+ Assert.assertTrue(namespace_1_flag_2 != null);
+ Assert.assertTrue(namespace_2_flag_1 != null);
+ Assert.assertTrue(namespace_2_flag_2 == null);
+ Assert.assertTrue(namespace_1_flag_2.equals("false"));
+ Assert.assertTrue(namespace_2_flag_1.equals("true"));
+ }
}
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 5c74a80..7f165e0 100644
--- a/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java
+++ b/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java
@@ -3241,6 +3241,48 @@
}
}
+ @Test
+ public void testHalAutoSuspendMode_enabledByConfiguration() {
+ AtomicReference<DisplayManagerInternal.DisplayPowerCallbacks> callback =
+ new AtomicReference<>();
+ doAnswer((Answer<Void>) invocation -> {
+ callback.set(invocation.getArgument(0));
+ return null;
+ }).when(mDisplayManagerInternalMock).initPowerManagement(any(), any(), any());
+ when(mResourcesSpy.getBoolean(
+ com.android.internal.R.bool.config_powerDecoupleAutoSuspendModeFromDisplay))
+ .thenReturn(false);
+ when(mResourcesSpy.getBoolean(com.android.internal.R.bool.config_useAutoSuspend))
+ .thenReturn(true);
+
+ createService();
+ startSystem();
+ callback.get().onDisplayStateChange(/* allInactive= */ true, /* allOff= */ true);
+
+ verify(mNativeWrapperMock).nativeSetAutoSuspend(true);
+ }
+
+ @Test
+ public void testHalAutoSuspendMode_disabledByConfiguration() {
+ AtomicReference<DisplayManagerInternal.DisplayPowerCallbacks> callback =
+ new AtomicReference<>();
+ doAnswer((Answer<Void>) invocation -> {
+ callback.set(invocation.getArgument(0));
+ return null;
+ }).when(mDisplayManagerInternalMock).initPowerManagement(any(), any(), any());
+ when(mResourcesSpy.getBoolean(
+ com.android.internal.R.bool.config_powerDecoupleAutoSuspendModeFromDisplay))
+ .thenReturn(false);
+ when(mResourcesSpy.getBoolean(com.android.internal.R.bool.config_useAutoSuspend))
+ .thenReturn(false);
+
+ createService();
+ startSystem();
+ callback.get().onDisplayStateChange(/* allInactive= */ true, /* allOff= */ true);
+
+ verify(mNativeWrapperMock, never()).nativeSetAutoSuspend(true);
+ }
+
private void setCachedUidProcState(int uid) {
mService.updateUidProcStateInternal(uid, PROCESS_STATE_TOP_SLEEPING);
}
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index 94a71be..753db12 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -279,7 +279,7 @@
test_module_config {
name: "FrameworksServicesTests_contentprotection",
base: "FrameworksServicesTests",
- test_suites: ["general-tests"],
+ test_suites: ["device-tests"],
include_filters: ["com.android.server.contentprotection"],
exclude_annotations: FLAKY_AND_IGNORED,
}
@@ -287,7 +287,7 @@
test_module_config {
name: "FrameworksServicesTests_om",
base: "FrameworksServicesTests",
- test_suites: ["general-tests"],
+ test_suites: ["device-tests"],
include_filters: ["com.android.server.om."],
exclude_annotations: FLAKY_AND_IGNORED,
}
@@ -296,7 +296,7 @@
test_module_config {
name: "FrameworksServicesTests_contexthub_presubmit",
base: "FrameworksServicesTests",
- test_suites: ["general-tests"],
+ test_suites: ["device-tests"],
include_filters: ["com.android.server.location.contexthub."],
// TODO(ron): are these right, does it run anything?
include_annotations: ["android.platform.test.annotations.Presubmit"],
@@ -306,7 +306,7 @@
test_module_config {
name: "FrameworksServicesTests_contexthub_postsubmit",
base: "FrameworksServicesTests",
- test_suites: ["general-tests"],
+ test_suites: ["device-tests"],
include_filters: ["com.android.server.location.contexthub."],
// TODO(ron): are these right, does it run anything?
include_annotations: ["android.platform.test.annotations.Postsubmit"],
@@ -317,7 +317,7 @@
test_module_config {
name: "FrameworksServicesTests_contentcapture",
base: "FrameworksServicesTests",
- test_suites: ["general-tests"],
+ test_suites: ["device-tests"],
include_filters: ["com.android.server.contentcapture"],
exclude_annotations: FLAKY_AND_IGNORED,
}
@@ -325,7 +325,7 @@
test_module_config {
name: "FrameworksServicesTests_recoverysystem",
base: "FrameworksServicesTests",
- test_suites: ["general-tests"],
+ test_suites: ["device-tests"],
include_filters: ["com.android.server.recoverysystem."],
exclude_annotations: ["androidx.test.filters.FlakyTest"],
}
@@ -334,7 +334,7 @@
test_module_config {
name: "FrameworksServicesTests_pm_presubmit",
base: "FrameworksServicesTests",
- test_suites: ["general-tests"],
+ test_suites: ["device-tests"],
include_annotations: ["android.platform.test.annotations.Presubmit"],
include_filters: ["com.android.server.pm."],
exclude_annotations: FLAKY_AND_IGNORED,
@@ -343,7 +343,7 @@
test_module_config {
name: "FrameworksServicesTests_pm_postsubmit",
base: "FrameworksServicesTests",
- test_suites: ["general-tests"],
+ test_suites: ["device-tests"],
include_annotations: ["android.platform.test.annotations.Postsubmit"],
include_filters: ["com.android.server.pm."],
exclude_annotations: FLAKY_AND_IGNORED,
@@ -353,6 +353,6 @@
test_module_config {
name: "FrameworksServicesTests_os",
base: "FrameworksServicesTests",
- test_suites: ["general-tests"],
+ test_suites: ["device-tests"],
include_filters: ["com.android.server.os."],
}
diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
index c6f3eb3..30e3b18 100644
--- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
@@ -58,6 +58,7 @@
import static org.mockito.Matchers.anyBoolean;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doCallRealMethod;
import static org.mockito.Mockito.doNothing;
@@ -90,6 +91,7 @@
import android.os.Message;
import android.os.PowerManagerInternal;
import android.os.RemoteException;
+import android.os.SystemClock;
import android.os.UserHandle;
import android.os.UserManager;
import android.os.storage.IStorageManager;
@@ -203,7 +205,10 @@
doNothing().when(mInjector).activityManagerOnUserStopped(anyInt());
doNothing().when(mInjector).clearBroadcastQueueForUser(anyInt());
doNothing().when(mInjector).taskSupervisorRemoveUser(anyInt());
- doNothing().when(mInjector).lockDeviceNowAndWaitForKeyguardShown();
+ doAnswer(invocation -> {
+ ((Runnable) invocation.getArgument(0)).run();
+ return null;
+ }).when(mInjector).showKeyguard(any());
mockIsUsersOnSecondaryDisplaysEnabled(false);
// All UserController params are set to default.
@@ -540,7 +545,6 @@
expectedCodes.add(REPORT_USER_SWITCH_COMPLETE_MSG);
if (backgroundUserStopping) {
expectedCodes.add(CLEAR_USER_JOURNEY_SESSION_MSG);
- expectedCodes.add(0); // this is for directly posting in stopping.
}
if (expectScheduleBackgroundUserStopping) {
expectedCodes.add(SCHEDULED_STOP_BACKGROUND_USER_MSG);
@@ -1419,21 +1423,13 @@
// mock the device to be secure in order to expect the keyguard to be shown
when(mInjector.mKeyguardManagerMock.isDeviceSecure(anyInt())).thenReturn(true);
- // call real lockDeviceNowAndWaitForKeyguardShown method for this test
- doCallRealMethod().when(mInjector).lockDeviceNowAndWaitForKeyguardShown();
+ // call real showKeyguard method for this test
+ doCallRealMethod().when(mInjector).showKeyguard(any());
- // call startUser on a thread because we're expecting it to be blocked
- Thread threadStartUser = new Thread(()-> {
- mUserController.startUser(TEST_USER_ID, USER_START_MODE_FOREGROUND);
- });
- threadStartUser.start();
+ mUserController.completeUserSwitch(TEST_USER_ID1, TEST_USER_ID2);
- // make sure the switch is stalled...
- Thread.sleep(2000);
- // by checking REPORT_USER_SWITCH_MSG is not sent yet
- assertNull(mInjector.mHandler.getMessageForCode(REPORT_USER_SWITCH_MSG));
- // and the thread is still alive
- assertTrue(threadStartUser.isAlive());
+ // make sure the switch is stalled by checking the UserSwitchingDialog is not dismissed yet
+ verify(mInjector, never()).dismissUserSwitchingDialog(any());
// mock send the keyguard shown event
ArgumentCaptor<ActivityTaskManagerInternal.ScreenObserver> captor = ArgumentCaptor.forClass(
@@ -1441,12 +1437,42 @@
verify(mInjector.mActivityTaskManagerInternal).registerScreenObserver(captor.capture());
captor.getValue().onKeyguardStateChanged(true);
- // verify the switch now moves on...
- Thread.sleep(1000);
- // by checking REPORT_USER_SWITCH_MSG is sent
- assertNotNull(mInjector.mHandler.getMessageForCode(REPORT_USER_SWITCH_MSG));
- // and the thread is finished
- assertFalse(threadStartUser.isAlive());
+ // verify the switch now moves on by checking the UserSwitchingDialog is dismissed
+ verify(mInjector, atLeastOnce()).dismissUserSwitchingDialog(any());
+
+ // verify that SHOW_KEYGUARD_TIMEOUT is ignored and does not crash the system
+ try {
+ mInjector.mHandler.processPostDelayedCallbacksWithin(
+ UserController.SHOW_KEYGUARD_TIMEOUT_MS);
+ } catch (RuntimeException e) {
+ throw new AssertionError(
+ "SHOW_KEYGUARD_TIMEOUT is not ignored and crashed the system", e);
+ }
+ }
+
+ @Test
+ public void testRuntimeExceptionIsThrownIfTheKeyguardIsNotShown() throws Exception {
+ // enable user switch ui, because keyguard is only shown then
+ mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
+ /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false,
+ /* backgroundUserScheduledStopTimeSecs= */ -1);
+
+ // mock the device to be secure in order to expect the keyguard to be shown
+ when(mInjector.mKeyguardManagerMock.isDeviceSecure(anyInt())).thenReturn(true);
+
+ // suppress showKeyguard method for this test
+ doNothing().when(mInjector).showKeyguard(any());
+
+ mUserController.completeUserSwitch(TEST_USER_ID1, TEST_USER_ID2);
+
+ // verify that the system has crashed
+ assertThrows("Should have thrown RuntimeException", RuntimeException.class, () -> {
+ mInjector.mHandler.processPostDelayedCallbacksWithin(
+ UserController.SHOW_KEYGUARD_TIMEOUT_MS);
+ });
+
+ // make sure the UserSwitchingDialog is not dismissed
+ verify(mInjector, never()).dismissUserSwitchingDialog(any());
}
private void setUpAndStartUserInBackground(int userId) throws Exception {
@@ -1793,7 +1819,9 @@
Set<Integer> getMessageCodes() {
Set<Integer> result = new LinkedHashSet<>();
for (Message msg : mMessages) {
- result.add(msg.what);
+ if (msg.what != 0) { // ignore mHandle.post and mHandler.postDelayed messages
+ result.add(msg.what);
+ }
}
return result;
}
@@ -1817,14 +1845,28 @@
@Override
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
+ final Runnable cb = msg.getCallback();
+ if (cb != null && uptimeMillis <= SystemClock.uptimeMillis()) {
+ // run mHandler.post calls immediately
+ cb.run();
+ return true;
+ }
Message copy = new Message();
copy.copyFrom(msg);
+ copy.setCallback(cb);
mMessages.add(copy);
- if (msg.getCallback() != null) {
- msg.getCallback().run();
- msg.setCallback(null);
- }
return super.sendMessageAtTime(msg, uptimeMillis);
}
+
+ public void processPostDelayedCallbacksWithin(long millis) {
+ final long whenMax = SystemClock.uptimeMillis() + millis;
+ for (Message msg : mMessages) {
+ final Runnable cb = msg.getCallback();
+ if (cb != null && msg.getWhen() <= whenMax) {
+ msg.setCallback(null);
+ cb.run();
+ }
+ }
+ }
}
}
diff --git a/services/tests/servicestests/src/com/android/server/autofill/OWNERS b/services/tests/servicestests/src/com/android/server/autofill/OWNERS
new file mode 100644
index 0000000..70106d1
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/autofill/OWNERS
@@ -0,0 +1 @@
+include /core/java/android/view/autofill/OWNERS
\ No newline at end of file
diff --git a/services/tests/servicestests/src/com/android/server/grammaticalinflection/GrammaticalInflectionBackupTest.java b/services/tests/servicestests/src/com/android/server/grammaticalinflection/GrammaticalInflectionBackupTest.java
index af6f6f2..608b306 100644
--- a/services/tests/servicestests/src/com/android/server/grammaticalinflection/GrammaticalInflectionBackupTest.java
+++ b/services/tests/servicestests/src/com/android/server/grammaticalinflection/GrammaticalInflectionBackupTest.java
@@ -112,7 +112,7 @@
public void testSystemBackupPayload_returnsGender()
throws IOException, ClassNotFoundException {
doReturn(Configuration.GRAMMATICAL_GENDER_MASCULINE).when(mGrammaticalInflectionService)
- .getSystemGrammaticalGender(any(), eq(DEFAULT_USER_ID));
+ .getSystemGrammaticalGender(eq(DEFAULT_USER_ID));
int gender = convertByteArrayToInt(mBackupHelper.getSystemBackupPayload(DEFAULT_USER_ID));
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
index 902ffed..4faeea5 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
@@ -937,6 +937,94 @@
}
@Test
+ public void onHotplug_doNotSend_systemAudioModeRequestWithParameter(){
+ // Add a device to the network and assert that this device is included in the list of
+ // devices.
+ HdmiDeviceInfo infoAudioSystem = HdmiDeviceInfo.cecDeviceBuilder()
+ .setLogicalAddress(ADDR_AUDIO_SYSTEM)
+ .setPhysicalAddress(0x2000)
+ .setPortId(2)
+ .setDeviceType(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM)
+ .setVendorId(0x1000)
+ .setDisplayName("Audio System")
+ .build();
+ mHdmiControlService.getHdmiCecNetwork().addCecDevice(infoAudioSystem);
+ mTestLooper.dispatchAll();
+ assertThat(mHdmiControlService.getHdmiCecNetwork().getDeviceInfoList(false))
+ .hasSize(1);
+ mDeviceEventListeners.clear();
+ assertThat(mDeviceEventListeners.size()).isEqualTo(0);
+
+ // Connect port 2 (ARC port)
+ mNativeWrapper.setPortConnectionStatus(2, true);
+
+ // AVR connection
+ HdmiCecMessage initiateArc = HdmiCecMessageBuilder.buildInitiateArc(
+ ADDR_AUDIO_SYSTEM,
+ ADDR_TV);
+
+ mNativeWrapper.onCecMessage(initiateArc);
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage reportArcInitiated = HdmiCecMessageBuilder.buildReportArcInitiated(
+ ADDR_TV,
+ ADDR_AUDIO_SYSTEM);
+ // <Report ARC Initiated> should only be sent after SAD querying is done
+ assertThat(mNativeWrapper.getResultMessages()).doesNotContain(reportArcInitiated);
+ // Finish querying SADs
+ assertThat(mNativeWrapper.getResultMessages()).contains(SAD_QUERY);
+ mNativeWrapper.clearResultMessages();
+ mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS);
+ mTestLooper.dispatchAll();
+ assertThat(mNativeWrapper.getResultMessages()).contains(SAD_QUERY);
+ mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS);
+ mTestLooper.dispatchAll();
+
+ assertThat(mNativeWrapper.getResultMessages()).contains(reportArcInitiated);
+ mNativeWrapper.clearResultMessages();
+
+ // Audio System still acking polls. Allowing detection by HotplugDetectionAction
+ mNativeWrapper.setPollAddressResponse(ADDR_AUDIO_SYSTEM, SendMessageResult.SUCCESS);
+ mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS);
+ mTestLooper.dispatchAll();
+
+ // Hotplug event
+ mHdmiCecLocalDeviceTv.onHotplug(2, true);
+
+ // Audio System replies to <Give System Audio Mode> with <System Audio Mode Status>[On]
+ HdmiCecMessage reportSystemAudioModeOn =
+ HdmiCecMessageBuilder.buildReportSystemAudioMode(
+ ADDR_AUDIO_SYSTEM,
+ mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(),
+ true);
+ mHdmiControlService.handleCecCommand(reportSystemAudioModeOn);
+ mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS);
+ mTestLooper.dispatchAll();
+
+ // Hotplug event when turn off the audio system
+ mHdmiCecLocalDeviceTv.onHotplug(2, false);
+ mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS);
+ mTestLooper.dispatchAll();
+
+ // Some audio systems (eg. Sony) might trigger 5V status from false to true when the
+ // devices are off
+ mHdmiCecLocalDeviceTv.onHotplug(2, true);
+
+ // Audio System replies to <Give System Audio Mode> with <System Audio Mode Status>
+ HdmiCecMessage reportSystemAudioMode =
+ HdmiCecMessageBuilder.buildReportSystemAudioMode(
+ ADDR_AUDIO_SYSTEM,
+ mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(),
+ true);
+ mHdmiControlService.handleCecCommand(reportSystemAudioMode);
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage systemAudioModeRequest = HdmiCecMessageBuilder.buildSystemAudioModeRequest(
+ mTvLogicalAddress, ADDR_AUDIO_SYSTEM, mTvPhysicalAddress, true);
+ assertThat(mNativeWrapper.getResultMessages()).doesNotContain(systemAudioModeRequest);
+ }
+
+ @Test
public void listenerInvokedIfPhysicalAddressReported() {
mHdmiControlService.getHdmiCecNetwork().clearDeviceList();
assertThat(mHdmiControlService.getHdmiCecNetwork().getDeviceInfoList(false))
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
index 1d3dacc..9a92c70 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
@@ -408,6 +408,60 @@
}
@Test
+ public void setRcProfileRootMenu_reportFeatureBroadcast() {
+ setRcProfileSourceDeviceTestHelper(
+ HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_ROOT_MENU,
+ HdmiControlManager.RC_PROFILE_SOURCE_MENU_HANDLED);
+ }
+
+ @Test
+ public void setRcProfileSetupMenu_reportFeatureBroadcast() {
+ setRcProfileSourceDeviceTestHelper(
+ HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_SETUP_MENU,
+ HdmiControlManager.RC_PROFILE_SOURCE_MENU_HANDLED);
+ }
+
+ @Test
+ public void setRcProfileContentMenu_reportFeatureBroadcast() {
+ setRcProfileSourceDeviceTestHelper(
+ HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_CONTENTS_MENU,
+ HdmiControlManager.RC_PROFILE_SOURCE_MENU_HANDLED);
+ }
+
+ @Test
+ public void setRcProfileTopMenu_reportFeatureBroadcast() {
+ setRcProfileSourceDeviceTestHelper(
+ HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_TOP_MENU,
+ HdmiControlManager.RC_PROFILE_SOURCE_MENU_HANDLED);
+ }
+
+ @Test
+ public void setRcProfileMediaSensitiveMenu_reportFeatureBroadcast() {
+ setRcProfileSourceDeviceTestHelper(
+ HdmiControlManager
+ .CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_MEDIA_CONTEXT_SENSITIVE_MENU,
+ HdmiControlManager.RC_PROFILE_SOURCE_MENU_HANDLED);
+ }
+
+ /** Helper method to test if feature discovery message sent given RCProfile change */
+ private void setRcProfileSourceDeviceTestHelper(final String setting, final int val) {
+ mNativeWrapper.clearResultMessages();
+
+ mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
+ HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
+ HdmiControlManager.HDMI_CEC_VERSION_2_0);
+ mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(setting, val);
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage reportFeatures = ReportFeaturesMessage.build(Constants.ADDR_PLAYBACK_1,
+ HdmiControlManager.HDMI_CEC_VERSION_2_0,
+ Arrays.asList(DEVICE_PLAYBACK, DEVICE_AUDIO_SYSTEM),
+ mPlaybackDeviceSpy.getRcProfile(), mPlaybackDeviceSpy.getRcFeatures(),
+ mPlaybackDeviceSpy.getDeviceFeatures());
+ assertThat(mNativeWrapper.getResultMessages()).contains(reportFeatures);
+ }
+
+ @Test
public void disableAndReenableCec_volumeControlReturnsToOriginalValue_enabled() {
int volumeControlEnabled = HdmiControlManager.VOLUME_CONTROL_ENABLED;
mHdmiControlServiceSpy.setHdmiCecVolumeControlEnabledInternal(volumeControlEnabled);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTvTest.java
index 920c376..eed9975 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTvTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTvTest.java
@@ -16,11 +16,14 @@
package com.android.server.hdmi;
+import static android.hardware.hdmi.HdmiDeviceInfo.DEVICE_TV;
+
import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
import static com.google.common.truth.Truth.assertThat;
import android.content.Context;
+import android.hardware.hdmi.HdmiControlManager;
import android.hardware.hdmi.HdmiDeviceInfo;
import android.os.Looper;
import android.os.test.TestLooper;
@@ -35,6 +38,7 @@
import org.junit.runners.JUnit4;
import java.util.Collections;
+import java.util.List;
/**
* TV specific tests for {@link HdmiControlService} class.
@@ -47,6 +51,7 @@
private static final String TAG = "HdmiControlServiceTvTest";
private HdmiControlService mHdmiControlService;
private HdmiCecController mHdmiCecController;
+ private HdmiCecLocalDeviceTv mHdmiCecLocalDeviceTv;
private FakeNativeWrapper mNativeWrapper;
private HdmiEarcController mHdmiEarcController;
private FakeEarcNativeWrapper mEarcNativeWrapper;
@@ -90,6 +95,8 @@
mHdmiControlService.initService();
mTestLooper.dispatchAll();
+
+ mHdmiCecLocalDeviceTv = mHdmiControlService.tv();
}
@Test
@@ -139,4 +146,23 @@
assertThat(mHdmiControlService
.verifyPhysicalAddresses(HdmiUtils.buildMessage("4F:82:10"))).isFalse();
}
+
+ @Test
+ public void setRcProfileTv_reportFeatureBroadcast() {
+ mNativeWrapper.clearResultMessages();
+
+ mHdmiControlService.getHdmiCecConfig().setIntValue(
+ HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
+ HdmiControlManager.HDMI_CEC_VERSION_2_0);
+ mHdmiControlService.getHdmiCecConfig().setIntValue(
+ HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_TV,
+ HdmiControlManager.RC_PROFILE_TV_NONE);
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage reportFeatures = ReportFeaturesMessage.build(Constants.ADDR_TV,
+ HdmiControlManager.HDMI_CEC_VERSION_2_0, List.of(DEVICE_TV),
+ mHdmiCecLocalDeviceTv.getRcProfile(), mHdmiCecLocalDeviceTv.getRcFeatures(),
+ mHdmiCecLocalDeviceTv.getDeviceFeatures());
+ assertThat(mNativeWrapper.getResultMessages()).contains(reportFeatures);
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java b/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java
index a6f2196..9862663 100644
--- a/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java
@@ -16,8 +16,6 @@
package com.android.server.os;
-import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
-
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;
@@ -25,9 +23,9 @@
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.when;
+import android.annotation.NonNull;
import android.app.admin.DevicePolicyManager;
import android.app.admin.flags.Flags;
-import android.app.role.RoleManager;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
@@ -61,6 +59,8 @@
import org.mockito.MockitoAnnotations;
import java.io.FileDescriptor;
+import java.util.Collections;
+import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -104,7 +104,7 @@
ArraySet<String> mAllowlistedPackages = new ArraySet<>();
mAllowlistedPackages.add(mContext.getPackageName());
mInjector = new TestInjector(mContext, mAllowlistedPackages, mMappingFile,
- mMockUserManager, mMockDevicePolicyManager);
+ mMockUserManager, mMockDevicePolicyManager, null);
mService = new BugreportManagerServiceImpl(mInjector);
mBugreportFileManager = new BugreportManagerServiceImpl.BugreportFileManager(mMappingFile);
when(mPackageManager.getPackageUidAsUser(anyString(), anyInt())).thenReturn(mCallingUid);
@@ -114,24 +114,8 @@
@After
public void tearDown() throws Exception {
- // Changes to RoleManager persist between tests, so we need to clear out any funny
- // business we did in previous tests.
+ // Clean up the mapping file between tests since it would otherwise persist.
mMappingFile.delete();
- RoleManager roleManager = mContext.getSystemService(RoleManager.class);
- CallbackFuture future = new CallbackFuture();
- runWithShellPermissionIdentity(
- () -> {
- roleManager.setBypassingRoleQualification(false);
- roleManager.removeRoleHolderAsUser(
- "android.app.role.SYSTEM_AUTOMOTIVE_PROJECTION",
- mContext.getPackageName(),
- /* flags= */ 0,
- Process.myUserHandle(),
- mContext.getMainExecutor(),
- future);
- });
-
- assertThat(future.get()).isEqualTo(true);
}
@Test
@@ -267,7 +251,10 @@
@Test
public void testCancelBugreportWithoutRole() {
- clearAllowlist();
+ // Create a new service to clear the allowlist
+ mService = new BugreportManagerServiceImpl(
+ new TestInjector(mContext, new ArraySet<>(), mMappingFile,
+ mMockUserManager, mMockDevicePolicyManager, null));
assertThrows(SecurityException.class, () -> mService.cancelBugreport(
Binder.getCallingUid(), mContext.getPackageName()));
@@ -275,29 +262,13 @@
@Test
public void testCancelBugreportWithRole() throws Exception {
- clearAllowlist();
- RoleManager roleManager = mContext.getSystemService(RoleManager.class);
- CallbackFuture future = new CallbackFuture();
- runWithShellPermissionIdentity(
- () -> {
- roleManager.setBypassingRoleQualification(true);
- roleManager.addRoleHolderAsUser(
- "android.app.role.SYSTEM_AUTOMOTIVE_PROJECTION",
- mContext.getPackageName(),
- /* flags= */ 0,
- Process.myUserHandle(),
- mContext.getMainExecutor(),
- future);
- });
-
- assertThat(future.get()).isEqualTo(true);
- mService.cancelBugreport(Binder.getCallingUid(), mContext.getPackageName());
- }
-
- private void clearAllowlist() {
+ // Create a new service to clear the allowlist, but override the role manager
mService = new BugreportManagerServiceImpl(
new TestInjector(mContext, new ArraySet<>(), mMappingFile,
- mMockUserManager, mMockDevicePolicyManager));
+ mMockUserManager, mMockDevicePolicyManager,
+ "android.app.role.SYSTEM_AUTOMOTIVE_PROJECTION"));
+
+ mService.cancelBugreport(Binder.getCallingUid(), mContext.getPackageName());
}
private static class Listener implements IDumpstateListener {
@@ -359,10 +330,22 @@
private boolean mBugreportStarted = false;
TestInjector(Context context, ArraySet<String> allowlistedPackages, AtomicFile mappingFile,
- UserManager um, DevicePolicyManager dpm) {
+ UserManager um, DevicePolicyManager dpm, String grantedRole) {
super(context, allowlistedPackages, mappingFile);
mUserManager = um;
mDevicePolicyManager = dpm;
+
+ if (grantedRole != null) {
+ mRoleManagerWrapper =
+ new BugreportManagerServiceImpl.Injector.RoleManagerWrapper() {
+ @Override
+ List<String> getRoleHolders(@NonNull String roleName) {
+ return roleName.equals(grantedRole)
+ ? Collections.singletonList(mContext.getPackageName())
+ : Collections.emptyList();
+ }
+ };
+ }
}
@Override
diff --git a/services/tests/servicestests/src/com/android/server/policy/SideFpsEventHandlerTest.java b/services/tests/servicestests/src/com/android/server/policy/SideFpsEventHandlerTest.java
index 669eedf..dabf531 100644
--- a/services/tests/servicestests/src/com/android/server/policy/SideFpsEventHandlerTest.java
+++ b/services/tests/servicestests/src/com/android/server/policy/SideFpsEventHandlerTest.java
@@ -31,11 +31,13 @@
import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback;
import android.os.Handler;
import android.os.PowerManager;
+import android.os.RemoteException;
import android.os.test.TestLooper;
import android.testing.AndroidTestingRunner;
import android.testing.TestableContext;
import android.testing.TestableResources;
import android.view.Window;
+import android.view.accessibility.AccessibilityManager;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
@@ -48,7 +50,8 @@
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
import java.util.List;
@@ -72,9 +75,15 @@
private static final Integer AUTO_DISMISS_DIALOG = 500;
@Rule
+ public MockitoRule rule = MockitoJUnit.rule();
+
+ @Rule
public TestableContext mContext =
new TestableContext(InstrumentationRegistry.getContext(), null);
+ private final AccessibilityManager mAccessibilityManager =
+ mContext.getSystemService(AccessibilityManager.class);
+
@Mock
private PackageManager mPackageManager;
@Mock
@@ -89,9 +98,8 @@
private BiometricStateListener mBiometricStateListener;
@Before
- public void setup() {
- MockitoAnnotations.initMocks(this);
-
+ public void setup() throws RemoteException {
+ disableAccessibility();
mContext.addMockSystemService(PackageManager.class, mPackageManager);
mContext.addMockSystemService(FingerprintManager.class, mFingerprintManager);
TestableResources resources = mContext.getOrCreateTestableResources();
@@ -192,9 +200,8 @@
}
@Test
- public void dialogDismissesAfterTime() throws Exception {
+ public void dialogDismissesAfterTime_accessibilityDisabled() throws Exception {
setupWithSensor(true /* hasSfps */, true /* initialized */);
-
setBiometricState(BiometricStateListener.STATE_ENROLLING);
when(mDialog.isShowing()).thenReturn(true);
assertThat(mEventHandler.shouldConsumeSinglePress(80000L)).isTrue();
@@ -207,9 +214,23 @@
}
@Test
- public void dialogDoesNotDismissOnSensorTouch() throws Exception {
+ public void dialogDoesNotDismissAfterTime_accessibilityEnabled() throws Exception {
+ enableAccessibility();
setupWithSensor(true /* hasSfps */, true /* initialized */);
+ setBiometricState(BiometricStateListener.STATE_ENROLLING);
+ when(mDialog.isShowing()).thenReturn(true);
+ assertThat(mEventHandler.shouldConsumeSinglePress(80000L)).isTrue();
+ mLooper.dispatchAll();
+ verify(mDialog).show();
+ mLooper.moveTimeForward(AUTO_DISMISS_DIALOG);
+ mLooper.dispatchAll();
+ verify(mDialog, never()).dismiss();
+ }
+
+ @Test
+ public void dialogDoesNotDismissOnSensorTouch_accessibilityDisabled() throws Exception {
+ setupWithSensor(true /* hasSfps */, true /* initialized */);
setBiometricState(BiometricStateListener.STATE_ENROLLING);
when(mDialog.isShowing()).thenReturn(true);
assertThat(mEventHandler.shouldConsumeSinglePress(80000L)).isTrue();
@@ -218,12 +239,26 @@
verify(mDialog).show();
mBiometricStateListener.onBiometricAction(BiometricStateListener.ACTION_SENSOR_TOUCH);
- mLooper.moveTimeForward(AUTO_DISMISS_DIALOG - 1);
mLooper.dispatchAll();
-
verify(mDialog, never()).dismiss();
}
+ @Test
+ public void dialogDismissesOnSensorTouch_accessibilityEnabled() throws Exception {
+ enableAccessibility();
+ setupWithSensor(true /* hasSfps */, true /* initialized */);
+ setBiometricState(BiometricStateListener.STATE_ENROLLING);
+ when(mDialog.isShowing()).thenReturn(true);
+ assertThat(mEventHandler.shouldConsumeSinglePress(80000L)).isTrue();
+
+ mLooper.dispatchAll();
+ verify(mDialog).show();
+
+ mBiometricStateListener.onBiometricAction(BiometricStateListener.ACTION_SENSOR_TOUCH);
+ mLooper.dispatchAll();
+ verify(mDialog).dismiss();
+ }
+
private void setBiometricState(@BiometricStateListener.State int newState) {
if (mBiometricStateListener != null) {
mBiometricStateListener.onStateChanged(newState);
@@ -231,6 +266,20 @@
}
}
+ private void enableAccessibility() throws RemoteException {
+ if (mAccessibilityManager != null) {
+ mAccessibilityManager.getClient().setState(1);
+ mLooper.dispatchAll();
+ }
+ }
+
+ private void disableAccessibility() throws RemoteException {
+ if (mAccessibilityManager != null) {
+ mAccessibilityManager.getClient().setState(0);
+ mLooper.dispatchAll();
+ }
+ }
+
private void setupWithSensor(boolean hasSfps, boolean initialized) throws Exception {
when(mPackageManager.hasSystemFeature(eq(PackageManager.FEATURE_FINGERPRINT)))
.thenReturn(true);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
index 0d6fdc9..4af20a9 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
@@ -2638,7 +2638,7 @@
@Test
public void testSoundResetsRankingTime() throws Exception {
- mSetFlagsRule.enableFlags(android.app.Flags.FLAG_UPDATE_RANKING_TIME);
+ mSetFlagsRule.enableFlags(android.app.Flags.FLAG_SORT_SECTION_BY_TIME);
TestableFlagResolver flagResolver = new TestableFlagResolver();
initAttentionHelper(flagResolver);
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 74d8433..3a0eba1 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -24,7 +24,7 @@
import static android.app.ActivityManagerInternal.ServiceNotificationPolicy.SHOW_IMMEDIATELY;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.app.Flags.FLAG_KEYGUARD_PRIVATE_NOTIFICATIONS;
-import static android.app.Flags.FLAG_UPDATE_RANKING_TIME;
+import static android.app.Flags.FLAG_SORT_SECTION_BY_TIME;
import static android.app.Notification.EXTRA_ALLOW_DURING_SETUP;
import static android.app.Notification.EXTRA_PICTURE;
import static android.app.Notification.EXTRA_PICTURE_ICON;
@@ -7376,7 +7376,7 @@
}
@Test
- @EnableFlags({android.app.Flags.FLAG_UPDATE_RANKING_TIME})
+ @EnableFlags({android.app.Flags.FLAG_SORT_SECTION_BY_TIME})
public void testVisualDifference_userInitiatedJob() {
Notification.Builder nb1 = new Notification.Builder(mContext, "")
.setContentTitle("foo");
@@ -15283,7 +15283,7 @@
}
@Test
- @EnableFlags(FLAG_UPDATE_RANKING_TIME)
+ @EnableFlags(FLAG_SORT_SECTION_BY_TIME)
public void rankingTime_newNotification_noisy_matchesSbn() throws Exception {
NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel, mUserId);
@@ -15297,7 +15297,7 @@
}
@Test
- @EnableFlags(FLAG_UPDATE_RANKING_TIME)
+ @EnableFlags(FLAG_SORT_SECTION_BY_TIME)
public void rankingTime_newNotification_silent_matchesSbn() throws Exception {
NotificationChannel low = new NotificationChannel("low", "low", IMPORTANCE_LOW);
NotificationRecord nr = generateNotificationRecord(low, mUserId);
@@ -15312,7 +15312,7 @@
}
@Test
- @EnableFlags(FLAG_UPDATE_RANKING_TIME)
+ @EnableFlags(FLAG_SORT_SECTION_BY_TIME)
public void rankingTime_updatedNotification_silentSameText_originalPostTime() throws Exception {
NotificationChannel low = new NotificationChannel("low", "low", IMPORTANCE_LOW);
NotificationRecord nr = generateNotificationRecord(low, mUserId);
@@ -15332,7 +15332,7 @@
}
@Test
- @EnableFlags(FLAG_UPDATE_RANKING_TIME)
+ @EnableFlags(FLAG_SORT_SECTION_BY_TIME)
public void rankingTime_updatedNotification_silentNewText_newPostTime() throws Exception {
NotificationChannel low = new NotificationChannel("low", "low", IMPORTANCE_LOW);
NotificationRecord nr = generateNotificationRecord(low, 0, mUserId);
@@ -15357,7 +15357,7 @@
}
@Test
- @EnableFlags(FLAG_UPDATE_RANKING_TIME)
+ @EnableFlags(FLAG_SORT_SECTION_BY_TIME)
public void rankingTime_updatedNotification_noisySameText_newPostTime() throws Exception {
NotificationChannel low = new NotificationChannel("low", "low", IMPORTANCE_LOW);
NotificationRecord nr = generateNotificationRecord(low, mUserId);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index 9a58594..d1880d2 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -1561,14 +1561,14 @@
mXmlHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
false, mClock);
- loadByteArrayXml(baos.toByteArray(), false, USER_SYSTEM);
+ loadByteArrayXml(baos.toByteArray(), false, USER_ALL);
// Trigger 2nd restore pass
- when(mPm.getPackageUidAsUser(PKG_R, USER_SYSTEM)).thenReturn(UID_P);
+ when(mPm.getPackageUidAsUser(PKG_R, USER_SYSTEM)).thenReturn(UID_R);
mXmlHelper.onPackagesChanged(false, USER_SYSTEM, new String[]{PKG_R},
- new int[]{UID_P});
+ new int[]{UID_R});
- NotificationChannel channel = mXmlHelper.getNotificationChannel(PKG_R, UID_P, id,
+ NotificationChannel channel = mXmlHelper.getNotificationChannel(PKG_R, UID_R, id,
false);
assertThat(channel.getImportance()).isEqualTo(2);
assertThat(channel.canShowBadge()).isTrue();
@@ -1616,7 +1616,7 @@
mXmlHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
false, mClock);
- loadByteArrayXml(xml.getBytes(), false, USER_SYSTEM);
+ loadByteArrayXml(xml.getBytes(), false, USER_ALL);
// Trigger 2nd restore pass
mXmlHelper.onPackagesChanged(false, USER_SYSTEM, new String[]{PKG_R},
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java
index 527001d..9a6e818 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java
@@ -378,7 +378,7 @@
}
@Test
- @EnableFlags({android.app.Flags.FLAG_SORT_SECTION_BY_TIME, Flags.FLAG_UPDATE_RANKING_TIME})
+ @EnableFlags(android.app.Flags.FLAG_SORT_SECTION_BY_TIME)
public void testSort_oldWhenChildren_unspecifiedSummary() {
NotificationRecord child1 = new NotificationRecord(mContext,
new StatusBarNotification(
@@ -430,7 +430,7 @@
}
@Test
- @EnableFlags({android.app.Flags.FLAG_SORT_SECTION_BY_TIME, Flags.FLAG_UPDATE_RANKING_TIME})
+ @EnableFlags(android.app.Flags.FLAG_SORT_SECTION_BY_TIME)
public void testSort_oldChildren_unspecifiedSummary() {
NotificationRecord child1 = new NotificationRecord(mContext,
new StatusBarNotification(
@@ -480,7 +480,7 @@
}
@Test
- @EnableFlags({android.app.Flags.FLAG_SORT_SECTION_BY_TIME, Flags.FLAG_UPDATE_RANKING_TIME})
+ @EnableFlags(android.app.Flags.FLAG_SORT_SECTION_BY_TIME)
public void testSort_oldChildren_oldSummary() {
NotificationRecord child1 = new NotificationRecord(mContext,
new StatusBarNotification(
@@ -517,10 +517,10 @@
mUser, null, System.currentTimeMillis()), getLowChannel());
ArrayList<NotificationRecord> expected = new ArrayList<>();
+ expected.add(unrelated);
expected.add(summary);
expected.add(child2);
expected.add(child1);
- expected.add(unrelated);
ArrayList<NotificationRecord> actual = new ArrayList<>();
actual.addAll(expected);
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 9559a25..5fdb396 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -6095,6 +6095,67 @@
assertThat(readPolicy.allowConversations()).isFalse();
}
+ @Test
+ @EnableFlags(Flags.FLAG_MODES_API)
+ @DisableFlags(Flags.FLAG_MODES_UI)
+ public void setNotificationPolicy_updatesRulePolicies_ifRulePolicyIsDefaultOrGlobalPolicy() {
+ ZenPolicy defaultZenPolicy = mZenModeHelper.getDefaultZenPolicy();
+ Policy previousManualPolicy = mZenModeHelper.mConfig.toNotificationPolicy();
+ ZenPolicy previousManualZenPolicy = ZenAdapters.notificationPolicyToZenPolicy(
+ previousManualPolicy);
+ ZenPolicy customZenPolicy = new ZenPolicy.Builder(defaultZenPolicy).allowConversations(
+ CONVERSATION_SENDERS_ANYONE).build();
+
+ mZenModeHelper.mConfig.automaticRules.clear();
+ addZenRule(mZenModeHelper.mConfig, "appWithDefault", "app.pkg",
+ ZEN_MODE_IMPORTANT_INTERRUPTIONS, defaultZenPolicy);
+ addZenRule(mZenModeHelper.mConfig, "appWithSameAsManual", "app.pkg",
+ ZEN_MODE_IMPORTANT_INTERRUPTIONS, previousManualZenPolicy);
+ addZenRule(mZenModeHelper.mConfig, "appWithCustom", "app.pkg",
+ ZEN_MODE_IMPORTANT_INTERRUPTIONS, customZenPolicy);
+ addZenRule(mZenModeHelper.mConfig, "appWithOtherFilter", "app.pkg",
+ ZEN_MODE_ALARMS, null);
+ addZenRule(mZenModeHelper.mConfig, "systemWithDefault", "android",
+ ZEN_MODE_IMPORTANT_INTERRUPTIONS, defaultZenPolicy);
+ addZenRule(mZenModeHelper.mConfig, "systemWithSameAsManual", "android",
+ ZEN_MODE_IMPORTANT_INTERRUPTIONS, previousManualZenPolicy);
+
+ Policy newManualPolicy = new Policy(PRIORITY_CATEGORY_EVENTS, 0, 0);
+ mZenModeHelper.setNotificationPolicy(newManualPolicy, UPDATE_ORIGIN_USER, 0);
+ ZenPolicy newManualZenPolicy = ZenAdapters.notificationPolicyToZenPolicy(newManualPolicy);
+
+ // Only app rules with default or same-as-manual policies were updated.
+ assertThat(mZenModeHelper.mConfig.automaticRules.get("appWithDefault").zenPolicy)
+ .isEqualTo(newManualZenPolicy);
+ assertThat(mZenModeHelper.mConfig.automaticRules.get("appWithSameAsManual").zenPolicy)
+ .isEqualTo(newManualZenPolicy);
+
+ assertThat(mZenModeHelper.mConfig.automaticRules.get("appWithCustom").zenPolicy)
+ .isEqualTo(customZenPolicy);
+ assertThat(mZenModeHelper.mConfig.automaticRules.get("appWithOtherFilter").zenPolicy)
+ .isNull();
+ assertThat(mZenModeHelper.mConfig.automaticRules.get("systemWithDefault").zenPolicy)
+ .isEqualTo(defaultZenPolicy);
+ assertThat(mZenModeHelper.mConfig.automaticRules.get("systemWithSameAsManual").zenPolicy)
+ .isEqualTo(previousManualZenPolicy);
+ }
+
+ private static void addZenRule(ZenModeConfig config, String id, String ownerPkg, int zenMode,
+ @Nullable ZenPolicy zenPolicy) {
+ ZenRule rule = new ZenRule();
+ rule.id = id;
+ rule.pkg = ownerPkg;
+ rule.enabled = true;
+ rule.zenMode = zenMode;
+ rule.zenPolicy = zenPolicy;
+ // Plus stuff so that isValidAutomaticRule() passes
+ rule.name = String.format("Rule %s from %s with mode=%s and policy=%s", id, ownerPkg,
+ zenMode, zenPolicy);
+ rule.conditionId = Uri.parse(rule.name);
+
+ config.automaticRules.put(id, rule);
+ }
+
private static final Correspondence<ZenRule, ZenRule> IGNORE_METADATA =
Correspondence.transforming(zr -> {
Parcel p = Parcel.obtain();
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 30eb5ef..b0a3374 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -116,6 +116,7 @@
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.when;
import android.app.ActivityOptions;
import android.app.AppOpsManager;
@@ -524,10 +525,13 @@
// The activity shouldn't start relaunching since it doesn't have any desk resources.
assertFalse(activity.isRelaunching());
+ // The activity configuration ui mode should match.
+ final var activityConfig = activity.getConfiguration();
+ assertEquals(newConfig.uiMode, activityConfig.uiMode);
// The configuration change is still sent to the activity, even if it doesn't relaunch.
final ActivityConfigurationChangeItem expected =
- ActivityConfigurationChangeItem.obtain(activity.token, newConfig,
+ ActivityConfigurationChangeItem.obtain(activity.token, activityConfig,
activity.getActivityWindowInfo());
verify(mClientLifecycleManager).scheduleTransactionItem(
eq(activity.app.getThread()), eq(expected));
@@ -3507,6 +3511,23 @@
}
@Test
+ public void testIsCameraActive() {
+ final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
+ final DisplayRotationCompatPolicy displayRotationCompatPolicy = mock(
+ DisplayRotationCompatPolicy.class);
+ when(mDisplayContent.getDisplayRotationCompatPolicy()).thenReturn(
+ displayRotationCompatPolicy);
+
+ when(displayRotationCompatPolicy.isCameraActive(any(ActivityRecord.class),
+ anyBoolean())).thenReturn(false);
+ assertFalse(app.mActivityRecord.isCameraActive());
+
+ when(displayRotationCompatPolicy.isCameraActive(any(ActivityRecord.class),
+ anyBoolean())).thenReturn(true);
+ assertTrue(app.mActivityRecord.isCameraActive());
+ }
+
+ @Test
public void testUpdateCameraCompatStateFromUser_clickedOnDismiss() throws RemoteException {
final ActivityRecord activity = createActivityWithTask();
// Mock a flag being enabled.
diff --git a/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java b/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java
index 89ae802..f92387c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java
@@ -21,30 +21,24 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.server.wm.DesktopModeLaunchParamsModifier.DESKTOP_MODE_INITIAL_BOUNDS_SCALE;
-import static com.android.server.wm.DesktopModeLaunchParamsModifier.ENFORCE_DEVICE_RESTRICTIONS_KEY;
import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.PHASE_BOUNDS;
import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.PHASE_DISPLAY;
import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.RESULT_CONTINUE;
-import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.RESULT_DONE;
import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.RESULT_SKIP;
import static org.junit.Assert.assertEquals;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doReturn;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-import android.content.res.Resources;
import android.graphics.Rect;
-import android.os.SystemProperties;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
import androidx.test.filters.SmallTest;
-import com.android.internal.R;
import com.android.server.wm.LaunchParamsController.LaunchParamsModifier.Result;
import com.android.window.flags.Flags;
@@ -52,7 +46,6 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
-import org.mockito.Mockito;
/**
* Tests for desktop mode task bounds.
@@ -80,6 +73,8 @@
mCurrent.reset();
mResult = new LaunchParamsController.LaunchParams();
mResult.reset();
+
+ mTarget = new DesktopModeLaunchParamsModifier(mContext);
}
@Test
@@ -101,12 +96,12 @@
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
- public void testReturnsDoneIfDesktopWindowingIsEnabledAndUnsupportedDeviceOverridden() {
+ public void testReturnsContinueIfDesktopWindowingIsEnabledAndUnsupportedDeviceOverridden() {
setupDesktopModeLaunchParamsModifier(/*isDesktopModeSupported=*/ true,
/*enforceDeviceRestrictions=*/ false);
final Task task = new TaskBuilder(mSupervisor).build();
- assertEquals(RESULT_DONE, new CalculateRequestBuilder().setTask(task).calculate());
+ assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task).calculate());
}
@Test
@@ -139,22 +134,22 @@
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
- public void testReturnsDoneIfTaskUsingActivityTypeStandard() {
+ public void testReturnsContinueIfTaskUsingActivityTypeStandard() {
setupDesktopModeLaunchParamsModifier();
final Task task = new TaskBuilder(mSupervisor).setActivityType(
ACTIVITY_TYPE_STANDARD).build();
- assertEquals(RESULT_DONE, new CalculateRequestBuilder().setTask(task).calculate());
+ assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task).calculate());
}
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
- public void testReturnsDoneIfTaskUsingActivityTypeUndefined() {
+ public void testReturnsContinueIfTaskUsingActivityTypeUndefined() {
setupDesktopModeLaunchParamsModifier();
final Task task = new TaskBuilder(mSupervisor).setActivityType(
ACTIVITY_TYPE_UNDEFINED).build();
- assertEquals(RESULT_DONE, new CalculateRequestBuilder().setTask(task).calculate());
+ assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task).calculate());
}
@Test
@@ -180,7 +175,7 @@
task.getDisplayArea().setBounds(new Rect(0, 0, displayWidth, displayHeight));
final int desiredWidth = (int) (displayWidth * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
final int desiredHeight = (int) (displayHeight * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
- assertEquals(RESULT_DONE, new CalculateRequestBuilder().setTask(task).calculate());
+ assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task).calculate());
assertEquals(desiredWidth, mResult.mBounds.width());
assertEquals(desiredHeight, mResult.mBounds.height());
}
@@ -196,7 +191,7 @@
mCurrent.mPreferredTaskDisplayArea = mockTaskDisplayArea;
mCurrent.mWindowingMode = WINDOWING_MODE_FREEFORM;
- assertEquals(RESULT_DONE, new CalculateRequestBuilder().setTask(task).calculate());
+ assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task).calculate());
assertEquals(mockTaskDisplayArea, mResult.mPreferredTaskDisplayArea);
assertEquals(WINDOWING_MODE_FREEFORM, mResult.mWindowingMode);
}
@@ -208,15 +203,10 @@
private void setupDesktopModeLaunchParamsModifier(boolean isDesktopModeSupported,
boolean enforceDeviceRestrictions) {
- Resources mockResources = Mockito.mock(Resources.class);
- when(mockResources.getBoolean(eq(R.bool.config_isDesktopModeSupported)))
- .thenReturn(isDesktopModeSupported);
- doReturn(mockResources).when(mContext).getResources();
-
- SystemProperties.set(ENFORCE_DEVICE_RESTRICTIONS_KEY,
- String.valueOf(enforceDeviceRestrictions));
-
- mTarget = new DesktopModeLaunchParamsModifier(mContext);
+ doReturn(isDesktopModeSupported)
+ .when(() -> DesktopModeLaunchParamsModifier.isDesktopModeSupported(any()));
+ doReturn(enforceDeviceRestrictions)
+ .when(DesktopModeLaunchParamsModifier::enforceDeviceRestrictions);
}
private class CalculateRequestBuilder {
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 7356b43..27d9d13 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -1071,6 +1071,7 @@
@Test
public void testAllowsTopmostFullscreenOrientation() {
final DisplayContent dc = createNewDisplay();
+ assertEquals(SCREEN_ORIENTATION_UNSPECIFIED, dc.getOrientation());
dc.getDisplayRotation().setFixedToUserRotation(
IWindowManager.FIXED_TO_USER_ROTATION_DISABLED);
@@ -1717,7 +1718,6 @@
// The display should be rotated after the launch is finished.
doReturn(false).when(app).isAnimating(anyInt(), anyInt());
mDisplayContent.mAppTransition.notifyAppTransitionFinishedLocked(app.token);
- waitHandlerIdle(mWm.mH);
mStatusBarWindow.finishSeamlessRotation(t);
mNavBarWindow.finishSeamlessRotation(t);
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 507140d..262ba8b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
@@ -38,6 +38,7 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -538,6 +539,42 @@
assertActivityRefreshRequested(/* refreshRequested */ true, /* cycleThroughStop */ false);
}
+ @Test
+ public void testIsCameraActiveWhenCallbackInvokedNoMultiWindow_returnTrue() {
+ configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+ mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+
+ assertTrue(
+ mDisplayRotationCompatPolicy.isCameraActive(mActivity, /* mustBeFullscreen*/ true));
+ }
+
+ @Test
+ public void testIsCameraActiveWhenCallbackNotInvokedNoMultiWindow_returnFalse() {
+ configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+
+ assertFalse(
+ mDisplayRotationCompatPolicy.isCameraActive(mActivity, /* mustBeFullscreen*/ true));
+ }
+
+ @Test
+ public void testIsCameraActiveWhenCallbackNotInvokedMultiWindow_returnFalse() {
+ configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+ when(mActivity.inMultiWindowMode()).thenReturn(true);
+
+ assertFalse(
+ mDisplayRotationCompatPolicy.isCameraActive(mActivity, /* mustBeFullscreen*/ true));
+ }
+
+ @Test
+ public void testIsCameraActiveWhenCallbackInvokedMultiWindow_returnFalse() {
+ configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+ when(mActivity.inMultiWindowMode()).thenReturn(true);
+ mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+
+ assertFalse(
+ mDisplayRotationCompatPolicy.isCameraActive(mActivity, /* mustBeFullscreen*/ true));
+ }
+
private void configureActivity(@ScreenOrientation int activityOrientation) {
configureActivityAndDisplay(activityOrientation, ORIENTATION_PORTRAIT);
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
index b41db31..b74da1a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
@@ -29,6 +29,7 @@
import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION;
import static android.content.pm.ActivityInfo.OVERRIDE_LANDSCAPE_ORIENTATION_TO_REVERSE_LANDSCAPE;
import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO;
+import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA;
import static android.content.pm.ActivityInfo.OVERRIDE_ORIENTATION_ONLY_FOR_CAMERA;
import static android.content.pm.ActivityInfo.OVERRIDE_UNDEFINED_ORIENTATION_TO_NOSENSOR;
import static android.content.pm.ActivityInfo.OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT;
@@ -73,6 +74,8 @@
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import android.annotation.Nullable;
@@ -1086,6 +1089,39 @@
assertFalse(mController.shouldApplyUserMinAspectRatioOverride());
}
+ @Test
+ public void testRecomputeConfigurationForCameraCompatIfNeeded() {
+ spyOn(mController);
+ doReturn(false).when(mController).isOverrideOrientationOnlyForCameraEnabled();
+ doReturn(false).when(mController).isCameraCompatSplitScreenAspectRatioAllowed();
+ doReturn(false).when(mController).shouldOverrideMinAspectRatioForCamera();
+ clearInvocations(mActivity);
+
+ mController.recomputeConfigurationForCameraCompatIfNeeded();
+
+ verify(mActivity, never()).recomputeConfiguration();
+
+ // isOverrideOrientationOnlyForCameraEnabled
+ doReturn(true).when(mController).isOverrideOrientationOnlyForCameraEnabled();
+ clearInvocations(mActivity);
+ mController.recomputeConfigurationForCameraCompatIfNeeded();
+ verify(mActivity).recomputeConfiguration();
+
+ // isCameraCompatSplitScreenAspectRatioAllowed
+ doReturn(false).when(mController).isOverrideOrientationOnlyForCameraEnabled();
+ doReturn(true).when(mController).isCameraCompatSplitScreenAspectRatioAllowed();
+ clearInvocations(mActivity);
+ mController.recomputeConfigurationForCameraCompatIfNeeded();
+ verify(mActivity).recomputeConfiguration();
+
+ // shouldOverrideMinAspectRatioForCamera
+ doReturn(false).when(mController).isCameraCompatSplitScreenAspectRatioAllowed();
+ doReturn(true).when(mController).shouldOverrideMinAspectRatioForCamera();
+ clearInvocations(mActivity);
+ mController.recomputeConfigurationForCameraCompatIfNeeded();
+ verify(mActivity).recomputeConfiguration();
+ }
+
private void prepareActivityForShouldApplyUserMinAspectRatioOverride(
boolean orientationRequest) {
spyOn(mController);
@@ -1280,6 +1316,78 @@
}
@Test
+ @EnableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA})
+ public void shouldOverrideMinAspectRatioForCamera_overrideEnabled_returnsTrue() {
+ doReturn(true).when(mActivity).isCameraActive();
+ mController = new LetterboxUiController(mWm, mActivity);
+
+ assertTrue(mController.shouldOverrideMinAspectRatioForCamera());
+ }
+
+ @Test
+ @EnableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA})
+ public void shouldOverrideMinAspectRatioForCamera_propertyTrue_overrideEnabled_returnsTrue()
+ throws Exception {
+ doReturn(true).when(mActivity).isCameraActive();
+ mockThatProperty(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE, /* value */ true);
+ mController = new LetterboxUiController(mWm, mActivity);
+
+ assertTrue(mController.shouldOverrideMinAspectRatioForCamera());
+ }
+
+ @Test
+ @EnableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA})
+ public void shouldOverrideMinAspectRatioForCamera_propertyTrue_overrideEnabled_returnsFalse()
+ throws Exception {
+ doReturn(false).when(mActivity).isCameraActive();
+ mockThatProperty(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE, /* value */ true);
+ mController = new LetterboxUiController(mWm, mActivity);
+
+ assertFalse(mController.shouldOverrideMinAspectRatioForCamera());
+ }
+
+ @Test
+ @DisableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA})
+ public void shouldOverrideMinAspectRatioForCamera_propertyTrue_overrideDisabled_returnsFalse()
+ throws Exception {
+ doReturn(true).when(mActivity).isCameraActive();
+ mockThatProperty(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE, /* value */ true);
+ mController = new LetterboxUiController(mWm, mActivity);
+
+ assertFalse(mController.shouldOverrideMinAspectRatioForCamera());
+ }
+
+ @Test
+ @DisableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA})
+ public void shouldOverrideMinAspectRatioForCamera_overrideDisabled_returnsFalse() {
+ doReturn(true).when(mActivity).isCameraActive();
+ mController = new LetterboxUiController(mWm, mActivity);
+
+ assertFalse(mController.shouldOverrideMinAspectRatioForCamera());
+ }
+
+ @Test
+ @EnableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA})
+ public void shouldOverrideMinAspectRatioForCamera_propertyFalse_overrideEnabled_returnsFalse()
+ throws Exception {
+ mockThatProperty(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE, /* value */ false);
+ mController = new LetterboxUiController(mWm, mActivity);
+
+ assertFalse(mController.shouldOverrideMinAspectRatioForCamera());
+ }
+
+ @Test
+ @DisableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA})
+ public void shouldOverrideMinAspectRatioForCamera_propertyFalse_noOverride_returnsFalse()
+ throws Exception {
+ mockThatProperty(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE, /* value */ false);
+ doReturn(true).when(mActivity).isCameraActive();
+ mController = new LetterboxUiController(mWm, mActivity);
+
+ assertFalse(mController.shouldOverrideMinAspectRatioForCamera());
+ }
+
+ @Test
@EnableCompatChanges({FORCE_RESIZE_APP})
public void testshouldOverrideForceResizeApp_overrideEnabled_returnsTrue() {
mController = new LetterboxUiController(mWm, mActivity);
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 64adff8..c45c86c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
@@ -56,6 +56,7 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManagerInternal;
+import android.content.res.Configuration;
import android.database.ContentObserver;
import android.hardware.devicestate.DeviceStateManager;
import android.hardware.display.DisplayManagerGlobal;
@@ -86,6 +87,7 @@
import com.android.server.display.DisplayControl;
import com.android.server.display.color.ColorDisplayService;
import com.android.server.firewall.IntentFirewall;
+import com.android.server.grammaticalinflection.GrammaticalInflectionManagerInternal;
import com.android.server.input.InputManagerService;
import com.android.server.pm.UserManagerInternal;
import com.android.server.pm.UserManagerService;
@@ -200,6 +202,7 @@
.mockStatic(DisplayControl.class, mockStubOnly)
.mockStatic(LockGuard.class, mockStubOnly)
.mockStatic(Watchdog.class, mockStubOnly)
+ .spyStatic(DesktopModeLaunchParamsModifier.class)
.strictness(Strictness.LENIENT)
.startMocking();
@@ -207,6 +210,11 @@
setUpLocalServices();
setUpActivityTaskManagerService();
setUpWindowManagerService();
+
+ // We never load the system settings in the tests, thus need to setup the grammatical
+ // gender configuration explicitly.
+ mAtmService.getGlobalConfiguration().setGrammaticalGender(
+ Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED);
}
private void setUpSystemCore() {
@@ -336,6 +344,18 @@
};
when(umi.isUserVisible(anyInt())).thenAnswer(isUserVisibleAnswer);
when(umi.isUserVisible(anyInt(), anyInt())).thenAnswer(isUserVisibleAnswer);
+
+ final var gimi = mock(
+ GrammaticalInflectionManagerInternal.class, withSettings().stubOnly());
+ doReturn(Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED).when(
+ gimi).getGrammaticalGenderFromDeveloperSettings();
+ doReturn(Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED).when(
+ gimi).getSystemGrammaticalGender(anyInt());
+ doReturn(Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED).when(
+ gimi).mergedFinalSystemGrammaticalGender();
+ doReturn(false).when(gimi).canGetSystemGrammaticalGender(anyInt());
+ doReturn(gimi).when(
+ () -> LocalServices.getService(GrammaticalInflectionManagerInternal.class));
}
private void setUpActivityTaskManagerService() {
@@ -474,6 +494,7 @@
LocalServices.removeServiceForTest(StatusBarManagerInternal.class);
LocalServices.removeServiceForTest(UserManagerInternal.class);
LocalServices.removeServiceForTest(ImeTargetVisibilityPolicy.class);
+ LocalServices.removeServiceForTest(GrammaticalInflectionManagerInternal.class);
}
Description getDescription() {
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
index 48fc2dc..9f85acb 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
@@ -565,36 +565,17 @@
@Test
public void testGetOrientation_childSpecified() {
- testGetOrientation_childSpecifiedConfig(false, SCREEN_ORIENTATION_LANDSCAPE,
- SCREEN_ORIENTATION_LANDSCAPE);
- testGetOrientation_childSpecifiedConfig(false, SCREEN_ORIENTATION_UNSET,
- SCREEN_ORIENTATION_UNSPECIFIED);
- }
-
- private void testGetOrientation_childSpecifiedConfig(boolean childVisible, int childOrientation,
- int expectedOrientation) {
final TestWindowContainerBuilder builder = new TestWindowContainerBuilder(mWm);
- final TestWindowContainer root = builder.setLayer(0).build();
+ final TestWindowContainer root = builder.build();
root.setFillsParent(true);
+ assertEquals(SCREEN_ORIENTATION_UNSET, root.getOrientation());
- builder.setIsVisible(childVisible);
+ final TestWindowContainer child = root.addChildWindow();
+ child.setFillsParent(true);
+ assertEquals(SCREEN_ORIENTATION_UNSET, root.getOrientation());
- if (childOrientation != SCREEN_ORIENTATION_UNSET) {
- builder.setOrientation(childOrientation);
- }
-
- final TestWindowContainer child1 = root.addChildWindow(builder);
- child1.setFillsParent(true);
-
- assertEquals(expectedOrientation, root.getOrientation());
- }
-
- @Test
- public void testGetOrientation_Unset() {
- final TestWindowContainerBuilder builder = new TestWindowContainerBuilder(mWm);
- final TestWindowContainer root = builder.setLayer(0).setIsVisible(true).build();
- // Unspecified well because we didn't specify anything...
- assertEquals(SCREEN_ORIENTATION_UNSPECIFIED, root.getOrientation());
+ child.setOverrideOrientation(SCREEN_ORIENTATION_LANDSCAPE);
+ assertEquals(SCREEN_ORIENTATION_LANDSCAPE, root.getOrientation());
}
@Test
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index a35a35a..9d14290 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -1241,7 +1241,7 @@
break;
case Event.SHORTCUT_INVOCATION:
case Event.CHOOSER_ACTION:
- case Event.STANDBY_BUCKET_CHANGED:
+ // case Event.STANDBY_BUCKET_CHANGED:
case Event.FOREGROUND_SERVICE_START:
case Event.FOREGROUND_SERVICE_STOP:
logAppUsageEventReportedAtomLocked(event.mEventType, uid, event.mPackage);
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index e5d7b40e..1d01420 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -2538,7 +2538,8 @@
}
}
- PackageMonitor mPackageMonitor = new PackageMonitor() {
+ PackageMonitor mPackageMonitor = new PackageMonitor(
+ /* supportsPackageRestartQuery= */ true) {
@Override
public boolean onHandleForceStop(Intent intent, String[] packages, int uid,
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 7db4180..4c719dd 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -6007,7 +6007,7 @@
defaults.putBoolean(KEY_ENABLE_PRESENCE_CAPABILITY_EXCHANGE_BOOL, false);
defaults.putBoolean(KEY_RCS_BULK_CAPABILITY_EXCHANGE_BOOL, false);
defaults.putBoolean(KEY_ENABLE_PRESENCE_GROUP_SUBSCRIBE_BOOL, false);
- defaults.putInt(KEY_SUBSCRIBE_RETRY_DURATION_MILLIS_LONG, -1);
+ defaults.putLong(KEY_SUBSCRIBE_RETRY_DURATION_MILLIS_LONG, -1);
defaults.putBoolean(KEY_USE_SIP_URI_FOR_PRESENCE_SUBSCRIBE_BOOL, false);
defaults.putInt(KEY_NON_RCS_CAPABILITIES_CACHE_EXPIRATION_SEC_INT, 30 * 24 * 60 * 60);
defaults.putBoolean(KEY_RCS_REQUEST_FORBIDDEN_BY_SIP_489_BOOL, false);
diff --git a/telephony/java/android/telephony/PreciseDataConnectionState.java b/telephony/java/android/telephony/PreciseDataConnectionState.java
index b568f07..e6515f13 100644
--- a/telephony/java/android/telephony/PreciseDataConnectionState.java
+++ b/telephony/java/android/telephony/PreciseDataConnectionState.java
@@ -65,6 +65,7 @@
public final class PreciseDataConnectionState implements Parcelable {
private final @TransportType int mTransportType;
private final int mId;
+ private final int mNetId;
private final @DataState int mState;
private final @NetworkType int mNetworkType;
private final @DataFailureCause int mFailCause;
@@ -134,7 +135,7 @@
@ApnType int apnTypes, @NonNull String apn,
@Nullable LinkProperties linkProperties,
@DataFailureCause int failCause) {
- this(AccessNetworkConstants.TRANSPORT_TYPE_INVALID, -1, state, networkType,
+ this(AccessNetworkConstants.TRANSPORT_TYPE_INVALID, -1, -1, state, networkType,
linkProperties, failCause, new ApnSetting.Builder()
.setApnTypeBitmask(apnTypes)
.setApnName(apn)
@@ -158,13 +159,14 @@
* @param defaultQos If there is a valid QoS for the default bearer supporting this data call,
* (supported for LTE and NR), then this is specified. Otherwise it should be null.
*/
- private PreciseDataConnectionState(@TransportType int transportType, int id,
+ private PreciseDataConnectionState(@TransportType int transportType, int id, int netId,
@DataState int state, @NetworkType int networkType,
@Nullable LinkProperties linkProperties, @DataFailureCause int failCause,
@Nullable ApnSetting apnSetting, @Nullable Qos defaultQos,
@NetworkValidationStatus int networkValidationStatus) {
mTransportType = transportType;
mId = id;
+ mNetId = netId;
mState = state;
mNetworkType = networkType;
mLinkProperties = linkProperties;
@@ -182,6 +184,7 @@
private PreciseDataConnectionState(Parcel in) {
mTransportType = in.readInt();
mId = in.readInt();
+ mNetId = in.readInt();
mState = in.readInt();
mNetworkType = in.readInt();
mLinkProperties = in.readParcelable(
@@ -244,6 +247,14 @@
}
/**
+ * @return the current TelephonyNetworkAgent ID. {@code -1} if no network agent.
+ * @hide
+ */
+ public int getNetId() {
+ return mNetId;
+ }
+
+ /**
* @return The high-level state of this data connection.
*/
public @DataState int getState() {
@@ -363,6 +374,7 @@
public void writeToParcel(@NonNull Parcel out, int flags) {
out.writeInt(mTransportType);
out.writeInt(mId);
+ out.writeInt(mNetId);
out.writeInt(mState);
out.writeInt(mNetworkType);
out.writeParcelable(mLinkProperties, flags);
@@ -386,7 +398,7 @@
@Override
public int hashCode() {
- return Objects.hash(mTransportType, mId, mState, mNetworkType, mFailCause,
+ return Objects.hash(mTransportType, mId, mNetId, mState, mNetworkType, mFailCause,
mLinkProperties, mApnSetting, mDefaultQos, mNetworkValidationStatus);
}
@@ -398,6 +410,7 @@
PreciseDataConnectionState that = (PreciseDataConnectionState) o;
return mTransportType == that.mTransportType
&& mId == that.mId
+ && mNetId == that.mNetId
&& mState == that.mState
&& mNetworkType == that.mNetworkType
&& mFailCause == that.mFailCause
@@ -412,17 +425,18 @@
public String toString() {
StringBuilder sb = new StringBuilder();
- sb.append(" state: " + TelephonyUtils.dataStateToString(mState));
- sb.append(", transport: "
- + AccessNetworkConstants.transportTypeToString(mTransportType));
- sb.append(", id: " + mId);
- sb.append(", network type: " + TelephonyManager.getNetworkTypeName(mNetworkType));
- sb.append(", APN Setting: " + mApnSetting);
- sb.append(", link properties: " + mLinkProperties);
- sb.append(", default QoS: " + mDefaultQos);
- sb.append(", fail cause: " + DataFailCause.toString(mFailCause));
- sb.append(", network validation status: "
- + networkValidationStatusToString(mNetworkValidationStatus));
+ sb.append(" state: ").append(TelephonyUtils.dataStateToString(mState));
+ sb.append(", transport: ").append(
+ AccessNetworkConstants.transportTypeToString(mTransportType));
+ sb.append(", id: ").append(mId);
+ sb.append(", netId: ").append(mNetId);
+ sb.append(", network type: ").append(TelephonyManager.getNetworkTypeName(mNetworkType));
+ sb.append(", APN Setting: ").append(mApnSetting);
+ sb.append(", link properties: ").append(mLinkProperties);
+ sb.append(", default QoS: ").append(mDefaultQos);
+ sb.append(", fail cause: ").append(DataFailCause.toString(mFailCause));
+ sb.append(", network validation status: ").append(
+ networkValidationStatusToString(mNetworkValidationStatus));
return sb.toString();
}
@@ -463,6 +477,11 @@
*/
private int mId = -1;
+ /**
+ * The current TelephonyNetworkAgent ID. {@code -1} if no network agent.
+ */
+ private int mNetworkAgentId = -1;
+
/** The state of the data connection */
private @DataState int mState = TelephonyManager.DATA_UNKNOWN;
@@ -511,6 +530,17 @@
}
/**
+ * Set the id of the data connection.
+ *
+ * @param agentId The id of the data connection
+ * @return The builder
+ */
+ public @NonNull Builder setNetworkAgentId(int agentId) {
+ mNetworkAgentId = agentId;
+ return this;
+ }
+
+ /**
* Set the state of the data connection.
*
* @param state The state of the data connection
@@ -598,8 +628,8 @@
* @return The {@link PreciseDataConnectionState} instance
*/
public PreciseDataConnectionState build() {
- return new PreciseDataConnectionState(mTransportType, mId, mState, mNetworkType,
- mLinkProperties, mFailCause, mApnSetting, mDefaultQos,
+ return new PreciseDataConnectionState(mTransportType, mId, mNetworkAgentId, mState,
+ mNetworkType, mLinkProperties, mFailCause, mApnSetting, mDefaultQos,
mNetworkValidationStatus);
}
}
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index f076de3..25e2d82 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -19266,6 +19266,26 @@
}
/**
+ * Returns whether the AOSP domain selection service is supported.
+ *
+ * @return {@code true} if the AOSP domain selection service is supported.
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+ @RequiresFeature(PackageManager.FEATURE_TELEPHONY_CALLING)
+ public boolean isAospDomainSelectionService() {
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony != null) {
+ return telephony.isAospDomainSelectionService();
+ }
+ } catch (RemoteException ex) {
+ Rlog.w(TAG, "RemoteException", ex);
+ }
+ return false;
+ }
+
+ /**
* Returns the primary IMEI (International Mobile Equipment Identity) of the device as
* mentioned in GSMA TS.37. {@link #getImei(int)} returns the IMEI that belongs to the selected
* slotID whereas this API {@link #getPrimaryImei()} returns primary IMEI of the device.
diff --git a/telephony/java/android/telephony/satellite/ISatelliteCommunicationAllowedStateCallback.aidl b/telephony/java/android/telephony/satellite/ISatelliteCommunicationAllowedStateCallback.aidl
new file mode 100644
index 0000000..a7eda48
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/ISatelliteCommunicationAllowedStateCallback.aidl
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.satellite;
+
+/**
+ * Interface for satellite communication allowed state callback.
+ * @hide
+ */
+oneway interface ISatelliteCommunicationAllowedStateCallback {
+ /**
+ * Telephony does not guarantee that whenever there is a change in communication allowed
+ * state, this API will be called. Telephony does its best to detect the changes and notify
+ * its listners accordingly.
+ *
+ * @param allowed whether satellite communication state or not
+ */
+ void onSatelliteCommunicationAllowedStateChanged(in boolean isAllowed);
+}
diff --git a/telephony/java/android/telephony/satellite/SatelliteCommunicationAllowedStateCallback.java b/telephony/java/android/telephony/satellite/SatelliteCommunicationAllowedStateCallback.java
new file mode 100644
index 0000000..1a87020
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/SatelliteCommunicationAllowedStateCallback.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.satellite;
+
+import android.annotation.FlaggedApi;
+
+import com.android.internal.telephony.flags.Flags;
+
+
+/**
+ * A callback class for monitoring satellite communication allowed state changed events.
+ *
+ * @hide
+ */
+@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+public interface SatelliteCommunicationAllowedStateCallback {
+
+ /**
+ * Telephony does not guarantee that whenever there is a change in communication allowed state,
+ * this API will be called. Telephony does its best to detect the changes and notify its
+ * listeners accordingly.
+ *
+ * @param isAllowed {@code true} means satellite allow state is changed,
+ * {@code false} satellite allow state is not changed
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+ void onSatelliteCommunicationAllowedStateChanged(boolean isAllowed);
+}
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index 40ad312..0bb5fd5 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -91,6 +91,11 @@
ISatelliteSupportedStateCallback> sSatelliteSupportedStateCallbackMap =
new ConcurrentHashMap<>();
+ private static final ConcurrentHashMap<SatelliteCommunicationAllowedStateCallback,
+ ISatelliteCommunicationAllowedStateCallback>
+ sSatelliteCommunicationAllowedStateCallbackMap =
+ new ConcurrentHashMap<>();
+
private final int mSubId;
/**
@@ -2393,7 +2398,89 @@
}
}
- @Nullable private static ITelephony getITelephony() {
+ /**
+ * Registers for the satellite communication allowed state changed.
+ *
+ * @param executor The executor on which the callback will be called.
+ * @param callback The callback to handle satellite communication allowed state changed event.
+ * @return The {@link SatelliteResult} result of the operation.
+ * @throws SecurityException if the caller doesn't have required permission.
+ * @throws IllegalStateException if the Telephony process is not currently available.
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
+ @SatelliteResult
+ public int registerForCommunicationAllowedStateChanged(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull SatelliteCommunicationAllowedStateCallback callback) {
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(callback);
+
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony != null) {
+ ISatelliteCommunicationAllowedStateCallback internalCallback =
+ new ISatelliteCommunicationAllowedStateCallback.Stub() {
+ @Override
+ public void onSatelliteCommunicationAllowedStateChanged(
+ boolean isAllowed) {
+ executor.execute(() -> Binder.withCleanCallingIdentity(
+ () -> callback.onSatelliteCommunicationAllowedStateChanged(
+ isAllowed)));
+ }
+ };
+ sSatelliteCommunicationAllowedStateCallbackMap.put(callback, internalCallback);
+ return telephony.registerForCommunicationAllowedStateChanged(
+ mSubId, internalCallback);
+ } else {
+ throw new IllegalStateException("telephony service is null.");
+ }
+ } catch (RemoteException ex) {
+ loge("registerForCommunicationAllowedStateChanged() RemoteException: " + ex);
+ ex.rethrowAsRuntimeException();
+ }
+ return SATELLITE_RESULT_REQUEST_FAILED;
+ }
+
+ /**
+ * Unregisters for the satellite communication allowed state changed.
+ * If callback was not registered before, the request will be ignored.
+ *
+ * @param callback The callback that was passed to
+ * {@link #registerForCommunicationAllowedStateChanged(Executor,
+ * SatelliteCommunicationAllowedStateCallback)}
+ * @throws SecurityException if the caller doesn't have required permission.
+ * @throws IllegalStateException if the Telephony process is not currently available.
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
+ @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+ public void unregisterForCommunicationAllowedStateChanged(
+ @NonNull SatelliteCommunicationAllowedStateCallback callback) {
+ Objects.requireNonNull(callback);
+ ISatelliteCommunicationAllowedStateCallback internalCallback =
+ sSatelliteCommunicationAllowedStateCallbackMap.remove(callback);
+
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony != null) {
+ if (internalCallback != null) {
+ telephony.unregisterForCommunicationAllowedStateChanged(mSubId,
+ internalCallback);
+ } else {
+ loge("unregisterForCommunicationAllowedStateChanged: No internal callback.");
+ }
+ } else {
+ throw new IllegalStateException("telephony service is null.");
+ }
+ } catch (RemoteException ex) {
+ loge("unregisterForCommunicationAllowedStateChanged() RemoteException: " + ex);
+ ex.rethrowAsRuntimeException();
+ }
+ }
+
+ @Nullable
+ private static ITelephony getITelephony() {
ITelephony binder = ITelephony.Stub.asInterface(TelephonyFrameworkInitializer
.getTelephonyServiceManager()
.getTelephonyServiceRegisterer()
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index f25fc36..d4da736 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -69,6 +69,7 @@
import android.telephony.ims.aidl.IRcsConfigCallback;
import android.telephony.satellite.INtnSignalStrengthCallback;
import android.telephony.satellite.ISatelliteCapabilitiesCallback;
+import android.telephony.satellite.ISatelliteCommunicationAllowedStateCallback;
import android.telephony.satellite.ISatelliteDatagramCallback;
import android.telephony.satellite.ISatelliteTransmissionUpdateCallback;
import android.telephony.satellite.ISatelliteProvisionStateCallback;
@@ -3244,6 +3245,12 @@
boolean clearDomainSelectionServiceOverride();
/**
+ * @return {@code true} if the AOSP domain selection service is supported,
+ * {@code false} otherwise.
+ */
+ boolean isAospDomainSelectionService();
+
+ /**
* Enable or disable notifications sent for cellular identifier disclosure events.
*
* Disclosure events are defined as instances where a device has sent a cellular identifier
@@ -3341,4 +3348,29 @@
+ "android.Manifest.permission.SATELLITE_COMMUNICATION)")
void unregisterForSatelliteSupportedStateChanged(int subId,
in ISatelliteSupportedStateCallback callback);
+
+ /**
+ * Registers for satellite communication allowed state changed.
+ *
+ * @param subId The subId of the subscription to register for communication allowed state.
+ * @param callback The callback to handle the communication allowed state changed event.
+ *
+ * @return The {@link SatelliteError} result of the operation.
+ */
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+ + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
+ int registerForCommunicationAllowedStateChanged(int subId,
+ in ISatelliteCommunicationAllowedStateCallback callback);
+
+ /**
+ * Unregisters for satellite communication allowed state.
+ * If callback was not registered before, the request will be ignored.
+ *
+ * @param subId The subId of the subscription to unregister for supported state changed.
+ * @param callback The callback that was passed to registerForCommunicationAllowedStateChanged.
+ */
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+ + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
+ void unregisterForCommunicationAllowedStateChanged(int subId,
+ in ISatelliteCommunicationAllowedStateCallback callback);
}
diff --git a/tests/FlickerTests/ActivityEmbedding/AndroidTestTemplate.xml b/tests/FlickerTests/ActivityEmbedding/AndroidTestTemplate.xml
index 439cf13..1dc1037 100644
--- a/tests/FlickerTests/ActivityEmbedding/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/ActivityEmbedding/AndroidTestTemplate.xml
@@ -8,6 +8,8 @@
<option name="isolated-storage" value="false"/>
<target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
+ <!-- disable DeprecatedTargetSdk warning -->
+ <option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/>
<!-- keeps the screen on during tests -->
<option name="screen-always-on" value="on"/>
<!-- prevents the phone from restarting -->
diff --git a/tests/FlickerTests/AppClose/AndroidTestTemplate.xml b/tests/FlickerTests/AppClose/AndroidTestTemplate.xml
index 4b6224e..57a58c8 100644
--- a/tests/FlickerTests/AppClose/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/AppClose/AndroidTestTemplate.xml
@@ -8,6 +8,8 @@
<option name="isolated-storage" value="false"/>
<target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
+ <!-- disable DeprecatedTargetSdk warning -->
+ <option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/>
<!-- keeps the screen on during tests -->
<option name="screen-always-on" value="on"/>
<!-- prevents the phone from restarting -->
diff --git a/tests/FlickerTests/AppLaunch/AndroidTestTemplate.xml b/tests/FlickerTests/AppLaunch/AndroidTestTemplate.xml
index 583bcb7..2cb86e0 100644
--- a/tests/FlickerTests/AppLaunch/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/AppLaunch/AndroidTestTemplate.xml
@@ -8,6 +8,8 @@
<option name="isolated-storage" value="false"/>
<target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
+ <!-- disable DeprecatedTargetSdk warning -->
+ <option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/>
<!-- keeps the screen on during tests -->
<option name="screen-always-on" value="on"/>
<!-- prevents the phone from restarting -->
diff --git a/tests/FlickerTests/FlickerService/AndroidTestTemplate.xml b/tests/FlickerTests/FlickerService/AndroidTestTemplate.xml
index d6ae2b3..2cf85fa 100644
--- a/tests/FlickerTests/FlickerService/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/FlickerService/AndroidTestTemplate.xml
@@ -8,6 +8,8 @@
<option name="isolated-storage" value="false"/>
<target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
+ <!-- disable DeprecatedTargetSdk warning -->
+ <option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/>
<!-- keeps the screen on during tests -->
<option name="screen-always-on" value="on"/>
<!-- prevents the phone from restarting -->
diff --git a/tests/FlickerTests/IME/AndroidTestTemplate.xml b/tests/FlickerTests/IME/AndroidTestTemplate.xml
index 38442db..b93e1be 100644
--- a/tests/FlickerTests/IME/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/IME/AndroidTestTemplate.xml
@@ -8,6 +8,8 @@
<option name="isolated-storage" value="false"/>
<target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
+ <!-- disable DeprecatedTargetSdk warning -->
+ <option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/>
<!-- keeps the screen on during tests -->
<option name="screen-always-on" value="on"/>
<!-- enable AOD -->
diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnUnlockScreenTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnUnlockScreenTest.kt
index 31506b5..d22bdcf 100644
--- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnUnlockScreenTest.kt
+++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnUnlockScreenTest.kt
@@ -24,6 +24,7 @@
import android.tools.flicker.legacy.LegacyFlickerTest
import android.tools.flicker.legacy.LegacyFlickerTestFactory
import android.tools.traces.component.ComponentNameMatcher
+import androidx.test.filters.FlakyTest
import com.android.server.wm.flicker.BaseTest
import com.android.server.wm.flicker.helpers.ImeAppHelper
import org.junit.FixMethodOrder
@@ -90,6 +91,11 @@
@Ignore("Not applicable to this CUJ. Display turns off during transition")
override fun taskBarWindowIsAlwaysVisible() {}
+ @FlakyTest(bugId = 338178020)
+ @Test
+ override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
+ super.visibleWindowsShownMoreThanOneConsecutiveEntry()
+
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
diff --git a/tests/FlickerTests/Notification/AndroidTestTemplate.xml b/tests/FlickerTests/Notification/AndroidTestTemplate.xml
index 4036858..9c6a17d3 100644
--- a/tests/FlickerTests/Notification/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/Notification/AndroidTestTemplate.xml
@@ -8,6 +8,8 @@
<option name="isolated-storage" value="false"/>
<target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
+ <!-- disable DeprecatedTargetSdk warning -->
+ <option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/>
<!-- keeps the screen on during tests -->
<option name="screen-always-on" value="on"/>
<!-- prevents the phone from restarting -->
diff --git a/tests/FlickerTests/QuickSwitch/AndroidTestTemplate.xml b/tests/FlickerTests/QuickSwitch/AndroidTestTemplate.xml
index 797ca4e..ecbed28 100644
--- a/tests/FlickerTests/QuickSwitch/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/QuickSwitch/AndroidTestTemplate.xml
@@ -8,6 +8,8 @@
<option name="isolated-storage" value="false"/>
<target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
+ <!-- disable DeprecatedTargetSdk warning -->
+ <option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/>
<!-- keeps the screen on during tests -->
<option name="screen-always-on" value="on"/>
<!-- prevents the phone from restarting -->
diff --git a/tests/FlickerTests/Rotation/AndroidTestTemplate.xml b/tests/FlickerTests/Rotation/AndroidTestTemplate.xml
index b5ea739..1eacdfd 100644
--- a/tests/FlickerTests/Rotation/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/Rotation/AndroidTestTemplate.xml
@@ -8,6 +8,8 @@
<option name="isolated-storage" value="false"/>
<target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
+ <!-- disable DeprecatedTargetSdk warning -->
+ <option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/>
<!-- keeps the screen on during tests -->
<option name="screen-always-on" value="on"/>
<!-- prevents the phone from restarting -->
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 461dfec..9a5e88b 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
@@ -16,6 +16,7 @@
package com.android.server.wm.flicker.helpers
+import android.graphics.Rect
import android.tools.device.apphelpers.IStandardAppHelper
import android.tools.helpers.SYSTEMUI_PACKAGE
import android.tools.traces.parsers.WindowManagerStateHelper
@@ -32,6 +33,14 @@
*/
open class DesktopModeAppHelper(private val innerHelper: IStandardAppHelper) :
IStandardAppHelper by innerHelper {
+
+ enum class Corners {
+ LEFT_TOP,
+ RIGHT_TOP,
+ LEFT_BOTTOM,
+ RIGHT_BOTTOM
+ }
+
private val TIMEOUT_MS = 3_000L
private val CAPTION = "desktop_mode_caption"
private val CAPTION_HANDLE = "caption_handle"
@@ -121,4 +130,39 @@
wmHelper.getWindowRegion(innerHelper).bounds.contains(it.visibleBounds)
}
}
+
+ /** Resize a desktop app from its corners. */
+ fun cornerResize(
+ wmHelper: WindowManagerStateHelper,
+ device: UiDevice,
+ corner: Corners,
+ horizontalChange: Int,
+ verticalChange: Int
+ ) {
+ val windowRect = wmHelper.getWindowRegion(innerHelper).bounds
+ val (startX, startY) = getStartCoordinatesForCornerResize(windowRect, corner)
+
+ // The position we want to drag to
+ val endY = startY + verticalChange
+ val endX = startX + horizontalChange
+
+ // drag the specified corner of the window to the end coordinate.
+ device.drag(startX, startY, endX, endY, 100)
+ wmHelper
+ .StateSyncBuilder()
+ .withAppTransitionIdle()
+ .waitForAndVerify()
+ }
+
+ private fun getStartCoordinatesForCornerResize(
+ windowRect: Rect,
+ corner: Corners
+ ): Pair<Int, Int> {
+ return when (corner) {
+ Corners.LEFT_TOP -> Pair(windowRect.left, windowRect.top)
+ Corners.RIGHT_TOP -> Pair(windowRect.right, windowRect.top)
+ Corners.LEFT_BOTTOM -> Pair(windowRect.left, windowRect.bottom)
+ Corners.RIGHT_BOTTOM -> Pair(windowRect.right, windowRect.bottom)
+ }
+ }
}
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingMainActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingMainActivity.java
index 23fa91c..2df3da6 100644
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingMainActivity.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingMainActivity.java
@@ -16,7 +16,6 @@
package com.android.server.wm.flicker.testapp;
-import androidx.annotation.NonNull;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
@@ -103,18 +102,13 @@
}
private static SplitPairRule createSplitPairRules(@NonNull String layoutDirection) {
- final Set<SplitPairFilter> pairFilters = new HashSet<>();
- final SplitPairFilter activitiesPair = new SplitPairFilter(
- ActivityOptions.ActivityEmbedding.MainActivity.COMPONENT,
- ActivityOptions.ActivityEmbedding.SecondaryActivity.COMPONENT,
- null /* secondaryActivityIntentAction */);
- pairFilters.add(activitiesPair);
+ final Set<SplitPairFilter> pairFilters = getSplitPairFilters();
final SplitAttributes splitAttributes = new SplitAttributes.Builder()
.setSplitType(SplitAttributes.SplitType.SPLIT_TYPE_EQUAL)
.setLayoutDirection(parseLayoutDirection(layoutDirection))
.build();
// Setting thresholds to ALWAYS_ALLOW values to make it easy for running on all devices.
- final SplitPairRule rule = new SplitPairRule.Builder(pairFilters)
+ return new SplitPairRule.Builder(pairFilters)
.setDefaultSplitAttributes(splitAttributes)
.setMinWidthDp(SplitRule.SPLIT_MIN_DIMENSION_ALWAYS_ALLOW)
.setMinHeightDp(SplitRule.SPLIT_MIN_DIMENSION_ALWAYS_ALLOW)
@@ -122,7 +116,22 @@
.setMaxAspectRatioInPortrait(EmbeddingAspectRatio.ALWAYS_ALLOW)
.setMaxAspectRatioInLandscape(EmbeddingAspectRatio.ALWAYS_ALLOW)
.build();
- return rule;
+ }
+
+ @NonNull
+ private static Set<SplitPairFilter> getSplitPairFilters() {
+ final Set<SplitPairFilter> pairFilters = new HashSet<>();
+ final SplitPairFilter mainAndSecondaryActivitiesPair = new SplitPairFilter(
+ ActivityOptions.ActivityEmbedding.MainActivity.COMPONENT,
+ ActivityOptions.ActivityEmbedding.SecondaryActivity.COMPONENT,
+ null /* secondaryActivityIntentAction */);
+ pairFilters.add(mainAndSecondaryActivitiesPair);
+ final SplitPairFilter mainAndTrampolineActivitiesPair = new SplitPairFilter(
+ ActivityOptions.ActivityEmbedding.MainActivity.COMPONENT,
+ ActivityOptions.ActivityEmbedding.TrampolineActivity.COMPONENT,
+ null /* secondaryActivityIntentAction */);
+ pairFilters.add(mainAndTrampolineActivitiesPair);
+ return pairFilters;
}
private static SplitPlaceholderRule createSplitPlaceholderRules(
diff --git a/tests/Input/src/com/android/server/input/InputShellCommandTest.java b/tests/Input/src/com/android/server/input/InputShellCommandTest.java
index f4845a5..11f4633 100644
--- a/tests/Input/src/com/android/server/input/InputShellCommandTest.java
+++ b/tests/Input/src/com/android/server/input/InputShellCommandTest.java
@@ -125,6 +125,14 @@
assertThat(mInputEventInjector.mInjectedEvents).isEmpty();
}
+ @Test
+ public void testInvalidKeyEventCommandArgsCombination() {
+ // --duration and --longpress must not be sent together
+ runCommand("keyevent --duration 1000 --longpress KEYCODE_A");
+
+ assertThat(mInputEventInjector.mInjectedEvents).isEmpty();
+ }
+
private InputEvent getSingleInjectedInputEvent() {
assertThat(mInputEventInjector.mInjectedEvents).hasSize(1);
return mInputEventInjector.mInjectedEvents.get(0);
diff --git a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeStressTestUtil.java b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeStressTestUtil.java
index c0c60ef..d033389 100644
--- a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeStressTestUtil.java
+++ b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeStressTestUtil.java
@@ -393,6 +393,7 @@
mEditText.setFocusableInTouchMode(false);
}
rootView.addView(mEditText, new LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT));
+ rootView.setFitsSystemWindows(true);
setContentView(rootView);
if (requestFocus) {
diff --git a/tests/inputmethod/ConcurrentMultiSessionImeTest/Android.bp b/tests/inputmethod/ConcurrentMultiSessionImeTest/Android.bp
index 0e0d212..4c531b8 100644
--- a/tests/inputmethod/ConcurrentMultiSessionImeTest/Android.bp
+++ b/tests/inputmethod/ConcurrentMultiSessionImeTest/Android.bp
@@ -20,9 +20,12 @@
android_test {
name: "ConcurrentMultiSessionImeTest",
srcs: ["src/**/*.java"],
+ resource_dirs: ["res"],
libs: ["android.test.runner"],
static_libs: [
"androidx.test.ext.junit",
+ "androidx.test.rules",
+ "compatibility-device-util-axt",
"platform-test-annotations",
"platform-test-rules",
"truth",
@@ -35,7 +38,11 @@
test_suites: [
"general-tests",
],
- sdk_version: "current",
+ sdk_version: "test_current",
+
+ data: [
+ ":CtsMockInputMethod",
+ ],
// Store test artifacts in separated directories for easier debugging.
per_testcase_directory: true,
diff --git a/tests/inputmethod/ConcurrentMultiSessionImeTest/AndroidManifest.xml b/tests/inputmethod/ConcurrentMultiSessionImeTest/AndroidManifest.xml
index 0defe5b..2e336ca 100644
--- a/tests/inputmethod/ConcurrentMultiSessionImeTest/AndroidManifest.xml
+++ b/tests/inputmethod/ConcurrentMultiSessionImeTest/AndroidManifest.xml
@@ -17,6 +17,17 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.server.inputmethod.multisessiontest">
+ <application>
+ <uses-library android:name="android.test.runner" />
+ <activity android:name=".MainActivity"
+ android:theme="@android:style/Theme.Material.NoActionBar"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ </intent-filter>
+ </activity>
+ </application>
+
<instrumentation
android:name="androidx.test.runner.AndroidJUnitRunner"
android:targetPackage="com.android.server.inputmethod.multisessiontest"></instrumentation>
diff --git a/tests/inputmethod/ConcurrentMultiSessionImeTest/AndroidTest.xml b/tests/inputmethod/ConcurrentMultiSessionImeTest/AndroidTest.xml
index fd598c5..d5ed203 100644
--- a/tests/inputmethod/ConcurrentMultiSessionImeTest/AndroidTest.xml
+++ b/tests/inputmethod/ConcurrentMultiSessionImeTest/AndroidTest.xml
@@ -17,13 +17,28 @@
<configuration description="Config for Concurrent Multi-Session IME tests">
<object class="com.android.tradefed.testtype.suite.module.DeviceFeatureModuleController"
type="module_controller">
- <option name="required-feature" value="android.software.input_methods" />
+ <!-- TODO(b/323372972): require this feature once the bug is fixed. -->
+ <!-- option name="required-feature" value="android.software.input_methods" -->
<!-- Currently enabled to automotive only -->
<option name="required-feature" value="android.hardware.type.automotive" />
</object>
<option name="test-suite-tag" value="apct" />
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="force-install-mode" value="FULL" />
+ <option name="test-file-name" value="ConcurrentMultiSessionImeTest.apk" />
+ <option name="test-file-name" value="CtsMockInputMethod.apk" />
+ </target_preparer>
+
+ <!-- RunOnSecondaryUserTargetPreparer must run after SuiteApkInstaller. -->
+ <target_preparer class="com.android.tradefed.targetprep.RunOnSecondaryUserTargetPreparer">
+ <option name="start-background-user" value="true" />
+ <option name="test-package-name" value="com.android.server.inputmethod.multisessiontest" />
+ <option name="test-package-name" value="com.android.cts.mockime" />
+ </target_preparer>
+
<target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
<option name="run-command" value="setprop debug.wm.disable_deprecated_abi_dialog 1" />
<option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1" />
@@ -31,12 +46,6 @@
value="settings delete secure show_ime_with_hard_keyboard" />
</target_preparer>
- <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
- <option name="cleanup-apks" value="true" />
- <option name="force-install-mode" value="FULL" />
- <option name="test-file-name" value="ConcurrentMultiSessionImeTest.apk" />
- </target_preparer>
-
<test class="com.android.tradefed.testtype.AndroidJUnitTest">
<option name="package" value="com.android.server.inputmethod.multisessiontest" />
</test>
diff --git a/tests/inputmethod/ConcurrentMultiSessionImeTest/res/layout/main_activity.xml b/tests/inputmethod/ConcurrentMultiSessionImeTest/res/layout/main_activity.xml
new file mode 100644
index 0000000..e16d286
--- /dev/null
+++ b/tests/inputmethod/ConcurrentMultiSessionImeTest/res/layout/main_activity.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:fitsSystemWindows="true">
+ <EditText
+ android:id="@+id/edit_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:hint="Input text here"/>
+</FrameLayout>
diff --git a/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/ConcurrentMultiUserTest.java b/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/ConcurrentMultiUserTest.java
index a5ce69d..56dbde0 100644
--- a/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/ConcurrentMultiUserTest.java
+++ b/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/ConcurrentMultiUserTest.java
@@ -16,16 +16,27 @@
package com.android.server.inputmethod.multisessiontest;
-import static com.google.common.truth.Truth.assertThat;
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
-import android.content.Context;
-import android.content.pm.PackageManager;
+import static com.android.compatibility.common.util.concurrentuser.ConcurrentUserActivityUtils.getResponderUserId;
+import static com.android.compatibility.common.util.concurrentuser.ConcurrentUserActivityUtils.launchActivityAsUserSync;
+import static com.android.compatibility.common.util.concurrentuser.ConcurrentUserActivityUtils.sendBundleAndWaitForReply;
+import static com.android.server.inputmethod.multisessiontest.TestRequestConstants.KEY_REQUEST_CODE;
+import static com.android.server.inputmethod.multisessiontest.TestRequestConstants.KEY_RESULT_CODE;
+import static com.android.server.inputmethod.multisessiontest.TestRequestConstants.REPLY_IME_HIDDEN;
+import static com.android.server.inputmethod.multisessiontest.TestRequestConstants.REQUEST_IME_STATUS;
-import androidx.test.platform.app.InstrumentationRegistry;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.content.ComponentName;
+import android.os.Bundle;
+
+import androidx.test.core.app.ActivityScenario;
import com.android.bedstead.harrier.BedsteadJUnit4;
import com.android.bedstead.harrier.DeviceState;
+import org.junit.After;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Rule;
@@ -39,19 +50,56 @@
@Rule
public static final DeviceState sDeviceState = new DeviceState();
+ private static final ComponentName TEST_ACTIVITY = new ComponentName(
+ getInstrumentation().getTargetContext().getPackageName(),
+ MainActivity.class.getName());
+
+ private ActivityScenario<MainActivity> mActivityScenario;
+ private MainActivity mActivity;
+ private int mPeerUserId;
+
@Before
- public void doBeforeEachTest() {
- // No op
+ public void setUp() {
+ // Launch passenger activity.
+ mPeerUserId = getResponderUserId();
+ launchActivityAsUserSync(TEST_ACTIVITY, mPeerUserId);
+
+ // Launch driver activity.
+ mActivityScenario = ActivityScenario.launch(MainActivity.class);
+ mActivityScenario.onActivity(activity -> mActivity = activity);
+ }
+
+ @After
+ public void tearDown() {
+ if (mActivityScenario != null) {
+ mActivityScenario.close();
+ }
}
@Test
- public void behaviorBeingTested_expectedResult() {
- // Sample test
- Context context =
- InstrumentationRegistry.getInstrumentation().getTargetContext();
- assertThat(context.getPackageManager().hasSystemFeature(
- PackageManager.FEATURE_AUTOMOTIVE)).isTrue();
- assertThat(context.getPackageManager().hasSystemFeature(
- PackageManager.FEATURE_INPUT_METHODS)).isTrue();
+ public void driverShowImeNotAffectPassenger() {
+ assertDriverImeHidden();
+ assertPassengerImeHidden();
+
+ showDriverImeAndAssert();
+ assertPassengerImeHidden();
+ }
+
+ private void assertDriverImeHidden() {
+ assertWithMessage("Driver IME should be hidden")
+ .that(mActivity.isMyImeVisible()).isFalse();
+ }
+
+ private void assertPassengerImeHidden() {
+ final Bundle bundleToSend = new Bundle();
+ bundleToSend.putInt(KEY_REQUEST_CODE, REQUEST_IME_STATUS);
+ Bundle receivedBundle = sendBundleAndWaitForReply(TEST_ACTIVITY.getPackageName(),
+ mPeerUserId, bundleToSend);
+ assertWithMessage("Passenger IME should be hidden")
+ .that(receivedBundle.getInt(KEY_RESULT_CODE)).isEqualTo(REPLY_IME_HIDDEN);
+ }
+
+ private void showDriverImeAndAssert() {
+ mActivity.showMyImeAndWait();
}
}
diff --git a/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/MainActivity.java b/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/MainActivity.java
new file mode 100644
index 0000000..e3f84c1
--- /dev/null
+++ b/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/MainActivity.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.inputmethod.multisessiontest;
+
+import static com.android.server.inputmethod.multisessiontest.TestRequestConstants.KEY_REQUEST_CODE;
+import static com.android.server.inputmethod.multisessiontest.TestRequestConstants.KEY_RESULT_CODE;
+import static com.android.server.inputmethod.multisessiontest.TestRequestConstants.REPLY_IME_HIDDEN;
+import static com.android.server.inputmethod.multisessiontest.TestRequestConstants.REPLY_IME_SHOWN;
+import static com.android.server.inputmethod.multisessiontest.TestRequestConstants.REQUEST_IME_STATUS;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.EditText;
+
+import androidx.core.view.ViewCompat;
+import androidx.core.view.WindowInsetsCompat;
+
+import com.android.compatibility.common.util.PollingCheck;
+import com.android.compatibility.common.util.concurrentuser.ConcurrentUserActivityBase;
+
+/**
+ * An {@link Activity} to test multiple concurrent session IME.
+ */
+public final class MainActivity extends ConcurrentUserActivityBase {
+ private static final String TAG = ConcurrentMultiUserTest.class.getSimpleName();
+ private static final long WAIT_IME_TIMEOUT_MS = 3000;
+
+ private EditText mEditor;
+ private InputMethodManager mImm;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ Log.v(TAG, "Create MainActivity as user " + getUserId() + " on display "
+ + getDisplayId());
+ setContentView(R.layout.main_activity);
+ mImm = getSystemService(InputMethodManager.class);
+ mEditor = requireViewById(R.id.edit_text);
+ }
+
+ @Override
+ protected Bundle onBundleReceived(Bundle receivedBundle) {
+ final int requestCode = receivedBundle.getInt(KEY_REQUEST_CODE);
+ Log.v(TAG, "onBundleReceived() with request code:" + requestCode);
+ final Bundle replyBundle = new Bundle();
+ switch (requestCode) {
+ case REQUEST_IME_STATUS:
+ replyBundle.putInt(KEY_RESULT_CODE,
+ isMyImeVisible() ? REPLY_IME_SHOWN : REPLY_IME_HIDDEN);
+ break;
+ default:
+ throw new RuntimeException("Received undefined request code:" + requestCode);
+ }
+ return replyBundle;
+ }
+
+ boolean isMyImeVisible() {
+ final WindowInsetsCompat insets = ViewCompat.getRootWindowInsets(mEditor);
+ return insets == null ? false : insets.isVisible(WindowInsetsCompat.Type.ime());
+ }
+
+ void showMyImeAndWait() {
+ Log.v(TAG, "showSoftInput");
+ runOnUiThread(() -> {
+ // requestFocus() must run on UI thread.
+ if (!mEditor.requestFocus()) {
+ Log.e(TAG, "Failed to focus on mEditor");
+ return;
+ }
+ if (!mImm.showSoftInput(mEditor, /* flags= */ 0)) {
+ Log.e(TAG, String.format("Failed to show my IME as user %d, "
+ + "mEditor:focused=%b,hasWindowFocus=%b", getUserId(),
+ mEditor.isFocused(), mEditor.hasWindowFocus()));
+ }
+ });
+ PollingCheck.waitFor(WAIT_IME_TIMEOUT_MS, () -> isMyImeVisible(),
+ String.format("My IME (user %d) didn't show up", getUserId()));
+ }
+}
diff --git a/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/TestRequestConstants.java b/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/TestRequestConstants.java
new file mode 100644
index 0000000..1501bfb
--- /dev/null
+++ b/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/TestRequestConstants.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.inputmethod.multisessiontest;
+
+final class TestRequestConstants {
+ private TestRequestConstants() {
+ }
+
+ public static final String KEY_REQUEST_CODE = "key_request_code";
+ public static final String KEY_RESULT_CODE = "key_result_code";
+
+ public static final int REQUEST_IME_STATUS = 1;
+ public static final int REPLY_IME_SHOWN = 2;
+ public static final int REPLY_IME_HIDDEN = 3;
+}
diff --git a/tools/hoststubgen/TEST_MAPPING b/tools/hoststubgen/TEST_MAPPING
index eca258c..f6885e1 100644
--- a/tools/hoststubgen/TEST_MAPPING
+++ b/tools/hoststubgen/TEST_MAPPING
@@ -1,13 +1,63 @@
+// Keep the following two TEST_MAPPINGs in sync:
+// frameworks/base/ravenwood/TEST_MAPPING
+// frameworks/base/tools/hoststubgen/TEST_MAPPING
{
"presubmit": [
{ "name": "tiny-framework-dump-test" },
{ "name": "hoststubgentest" },
- { "name": "hoststubgen-invoke-test" }
+ { "name": "hoststubgen-invoke-test" },
+ {
+ "name": "RavenwoodMockitoTest_device"
+ },
+ {
+ "name": "RavenwoodBivalentTest_device"
+ },
+ // The sysui tests should match vendor/unbundled_google/packages/SystemUIGoogle/TEST_MAPPING
+ {
+ "name": "SystemUIGoogleTests",
+ "options": [
+ {
+ "exclude-annotation": "org.junit.Ignore"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ }
+ ]
+ }
+ ],
+ "presubmit-large": [
+ {
+ "name": "SystemUITests",
+ "options": [
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ },
+ {
+ "exclude-annotation": "org.junit.Ignore"
+ }
+ ]
+ }
],
"ravenwood-presubmit": [
{
+ "name": "RavenwoodMinimumTest",
+ "host": true
+ },
+ {
+ "name": "RavenwoodMockitoTest",
+ "host": true
+ },
+ {
"name": "CtsUtilTestCasesRavenwood",
"host": true
+ },
+ {
+ "name": "RavenwoodCoreTest",
+ "host": true
+ },
+ {
+ "name": "RavenwoodBivalentTest",
+ "host": true
}
]
}
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
index 2f432cc..7212beb 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
@@ -16,6 +16,7 @@
package com.android.hoststubgen
import com.android.hoststubgen.asm.ClassNodes
+import com.android.hoststubgen.dumper.ApiDumper
import com.android.hoststubgen.filters.AnnotationBasedFilter
import com.android.hoststubgen.filters.ClassWidePolicyPropagatingFilter
import com.android.hoststubgen.filters.ConstantFilter
@@ -89,7 +90,11 @@
log.i("Dump file created at $it")
}
options.apiListFile.ifSet {
- PrintWriter(it).use { pw -> stats.dumpApis(pw) }
+ PrintWriter(it).use { pw ->
+ // TODO, when dumping a jar that's not framework-minus-apex.jar, we need to feed
+ // framework-minus-apex.jar so that we can dump inherited methods from it.
+ ApiDumper(pw, allClasses, null, filter).dump()
+ }
log.i("API list file created at $it")
}
}
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenStats.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenStats.kt
index da61469..9045db2 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenStats.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenStats.kt
@@ -15,7 +15,8 @@
*/
package com.android.hoststubgen
-import com.android.hoststubgen.asm.toHumanReadableClassName
+import com.android.hoststubgen.asm.getOuterClassNameFromFullClassName
+import com.android.hoststubgen.asm.getPackageNameFromFullClassName
import com.android.hoststubgen.filters.FilterPolicyWithReason
import org.objectweb.asm.Opcodes
import java.io.PrintWriter
@@ -55,8 +56,8 @@
// Ignore methods where policy isn't relevant
if (policy.isIgnoredForStats) return
- val packageName = resolvePackageName(fullClassName)
- val className = resolveOuterClassName(fullClassName)
+ val packageName = getPackageNameFromFullClassName(fullClassName)
+ val className = getOuterClassNameFromFullClassName(fullClassName)
// Ignore methods for certain generated code
if (className.endsWith("Proto")
@@ -88,42 +89,4 @@
}
}
}
-
- fun dumpApis(pw: PrintWriter) {
- pw.printf("PackageName,ClassName,MethodName,MethodDesc\n")
- apis.sortedWith(compareBy({ it.fullClassName }, { it.methodName }, { it.methodDesc }))
- .forEach { api ->
- pw.printf(
- "%s,%s,%s,%s\n",
- csvEscape(resolvePackageName(api.fullClassName)),
- csvEscape(resolveClassName(api.fullClassName)),
- csvEscape(api.methodName),
- csvEscape(api.methodDesc),
- )
- }
- }
-
- private fun resolvePackageName(fullClassName: String): String {
- val start = fullClassName.lastIndexOf('/')
- return fullClassName.substring(0, start).toHumanReadableClassName()
- }
-
- private fun resolveOuterClassName(fullClassName: String): String {
- val start = fullClassName.lastIndexOf('/')
- val end = fullClassName.indexOf('$')
- if (end == -1) {
- return fullClassName.substring(start + 1)
- } else {
- return fullClassName.substring(start + 1, end)
- }
- }
-
- private fun resolveClassName(fullClassName: String): String {
- val pos = fullClassName.lastIndexOf('/')
- if (pos == -1) {
- return fullClassName
- } else {
- return fullClassName.substring(pos + 1)
- }
- }
}
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt
index 83e122f..b8d1800 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt
@@ -76,17 +76,45 @@
return null
}
-private val removeLastElement = """[./][^./]*$""".toRegex()
+val periodOrSlash = charArrayOf('.', '/')
-fun getPackageNameFromClassName(className: String): String {
- return className.replace(removeLastElement, "")
+fun getPackageNameFromFullClassName(fullClassName: String): String {
+ val pos = fullClassName.lastIndexOfAny(periodOrSlash)
+ if (pos == -1) {
+ return ""
+ } else {
+ return fullClassName.substring(0, pos)
+ }
}
-fun resolveClassName(className: String, packageName: String): String {
+fun getClassNameFromFullClassName(fullClassName: String): String {
+ val pos = fullClassName.lastIndexOfAny(periodOrSlash)
+ if (pos == -1) {
+ return fullClassName
+ } else {
+ return fullClassName.substring(pos + 1)
+ }
+}
+
+fun getOuterClassNameFromFullClassName(fullClassName: String): String {
+ val start = fullClassName.lastIndexOfAny(periodOrSlash)
+ val end = fullClassName.indexOf('$')
+ if (end == -1) {
+ return fullClassName.substring(start + 1)
+ } else {
+ return fullClassName.substring(start + 1, end)
+ }
+}
+
+/**
+ * If [className] is a fully qualified name, just return it.
+ * Otherwise, prepend [defaultPackageName].
+ */
+fun resolveClassNameWithDefaultPackage(className: String, defaultPackageName: String): String {
if (className.contains('.') || className.contains('/')) {
return className
}
- return "$packageName.$className"
+ return "$defaultPackageName.$className"
}
fun String.toJvmClassName(): String {
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/dumper/ApiDumper.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/dumper/ApiDumper.kt
new file mode 100644
index 0000000..aaefee4
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/dumper/ApiDumper.kt
@@ -0,0 +1,202 @@
+/*
+ * 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.hoststubgen.dumper
+
+import com.android.hoststubgen.asm.CLASS_INITIALIZER_NAME
+import com.android.hoststubgen.asm.CTOR_NAME
+import com.android.hoststubgen.asm.ClassNodes
+import com.android.hoststubgen.asm.getClassNameFromFullClassName
+import com.android.hoststubgen.asm.getPackageNameFromFullClassName
+import com.android.hoststubgen.asm.toHumanReadableClassName
+import com.android.hoststubgen.csvEscape
+import com.android.hoststubgen.filters.FilterPolicy
+import com.android.hoststubgen.filters.FilterPolicyWithReason
+import com.android.hoststubgen.filters.OutputFilter
+import com.android.hoststubgen.log
+import org.objectweb.asm.Type
+import org.objectweb.asm.tree.ClassNode
+import java.io.PrintWriter
+
+/**
+ * Dump all the API methods in [classes], with inherited methods, with their policies.
+ */
+class ApiDumper(
+ val pw: PrintWriter,
+ val classes: ClassNodes,
+ val frameworkClasses: ClassNodes?,
+ val filter: OutputFilter,
+) {
+ private data class MethodKey(
+ val name: String,
+ val descriptor: String,
+ )
+
+ val javaStandardApiPolicy = FilterPolicy.Stub.withReason("Java standard API")
+
+ private val shownMethods = mutableSetOf<MethodKey>()
+
+ /**
+ * Do the dump.
+ */
+ fun dump() {
+ pw.printf("PackageName,ClassName,FromSubclass,DeclareClass,MethodName,MethodDesc" +
+ ",Supported,Policy,Reason\n")
+
+ classes.forEach { classNode ->
+ shownMethods.clear()
+ dump(classNode, classNode)
+ }
+ }
+
+ private fun dumpMethod(
+ classPackage: String,
+ className: String,
+ isSuperClass: Boolean,
+ methodClassName: String,
+ methodName: String,
+ methodDesc: String,
+ policy: FilterPolicyWithReason,
+ ) {
+ pw.printf(
+ "%s,%s,%d,%s,%s,%s,%d,%s,%s\n",
+ csvEscape(classPackage),
+ csvEscape(className),
+ if (isSuperClass) { 1 } else { 0 },
+ csvEscape(methodClassName),
+ csvEscape(methodName),
+ csvEscape(methodDesc),
+ if (policy.policy.isSupported) { 1 } else { 0 },
+ policy.policy,
+ csvEscape(policy.reason),
+ )
+ }
+
+ private fun isDuplicate(methodName: String, methodDesc: String): Boolean {
+ val methodKey = MethodKey(methodName, methodDesc)
+
+ if (shownMethods.contains(methodKey)) {
+ return true
+ }
+ shownMethods.add(methodKey)
+ return false
+ }
+
+ private fun dump(
+ dumpClass: ClassNode,
+ methodClass: ClassNode,
+ ) {
+ val pkg = getPackageNameFromFullClassName(dumpClass.name).toHumanReadableClassName()
+ val cls = getClassNameFromFullClassName(dumpClass.name).toHumanReadableClassName()
+
+ val isSuperClass = dumpClass != methodClass
+
+ methodClass.methods?.sortedWith(compareBy({ it.name }, { it.desc }))?.forEach { method ->
+
+ // Don't print ctor's from super classes.
+ if (isSuperClass) {
+ if (CTOR_NAME == method.name || CLASS_INITIALIZER_NAME == method.name) {
+ return@forEach
+ }
+ }
+ // If we already printed the method from a subclass, don't print it.
+ if (isDuplicate(method.name, method.desc)) {
+ return@forEach
+ }
+
+ val policy = filter.getPolicyForMethod(methodClass.name, method.name, method.desc)
+
+ // Let's skip "Remove" APIs. Ideally we want to print it, just to make the CSV
+ // complete, we still need to hide methods substituted (== @RavenwoodReplace) methods
+ // and for now we don't have an easy way to detect it.
+ if (policy.policy == FilterPolicy.Remove) {
+ return@forEach
+ }
+
+ val renameTo = filter.getRenameTo(methodClass.name, method.name, method.desc)
+
+ dumpMethod(pkg, cls, isSuperClass, methodClass.name.toHumanReadableClassName(),
+ renameTo ?: method.name, method.desc, policy)
+ }
+
+ // Dump super class methods.
+ dumpSuper(dumpClass, methodClass.superName)
+
+ // Dump interface methods (which may have default methods).
+ methodClass.interfaces?.sorted()?.forEach { interfaceName ->
+ dumpSuper(dumpClass, interfaceName)
+ }
+ }
+
+ /**
+ * Dump a given super class / interface.
+ */
+ private fun dumpSuper(
+ dumpClass: ClassNode,
+ methodClassName: String,
+ ) {
+ classes.findClass(methodClassName)?.let { methodClass ->
+ dump(dumpClass, methodClass)
+ return
+ }
+ frameworkClasses?.findClass(methodClassName)?.let { methodClass ->
+ dump(dumpClass, methodClass)
+ return
+ }
+ if (methodClassName.startsWith("java/") ||
+ methodClassName.startsWith("javax/")
+ ) {
+ dumpStandardClass(dumpClass, methodClassName)
+ return
+ }
+ log.w("Super class or interface $methodClassName (used by ${dumpClass.name}) not found.")
+ }
+
+ /**
+ * Dump methods from Java standard classes.
+ */
+ private fun dumpStandardClass(
+ dumpClass: ClassNode,
+ methodClassName: String,
+ ) {
+ val pkg = getPackageNameFromFullClassName(dumpClass.name).toHumanReadableClassName()
+ val cls = getClassNameFromFullClassName(dumpClass.name).toHumanReadableClassName()
+
+ val methodClassName = methodClassName.toHumanReadableClassName()
+
+ try {
+ val clazz = Class.forName(methodClassName)
+
+ // Method.getMethods() returns only public methods, but with inherited ones.
+ // Method.getDeclaredMethods() returns private methods too, but no inherited methods.
+ //
+ // Since we're only interested in public ones, just use getMethods().
+ clazz.methods.forEach { method ->
+ val methodName = method.name
+ val methodDesc = Type.getMethodDescriptor(method)
+
+ // If we already printed the method from a subclass, don't print it.
+ if (isDuplicate(methodName, methodDesc)) {
+ return@forEach
+ }
+
+ dumpMethod(pkg, cls, true, methodClassName,
+ methodName, methodDesc, javaStandardApiPolicy)
+ }
+ } catch (e: ClassNotFoundException) {
+ log.w("JVM type $methodClassName (used by ${dumpClass.name}) not found.")
+ }
+ }
+}
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/BaseAdapter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/BaseAdapter.kt
index 45e140c..6643492 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/BaseAdapter.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/BaseAdapter.kt
@@ -20,8 +20,8 @@
import com.android.hoststubgen.LogLevel
import com.android.hoststubgen.asm.ClassNodes
import com.android.hoststubgen.asm.UnifiedVisitor
-import com.android.hoststubgen.asm.getPackageNameFromClassName
-import com.android.hoststubgen.asm.resolveClassName
+import com.android.hoststubgen.asm.getPackageNameFromFullClassName
+import com.android.hoststubgen.asm.resolveClassNameWithDefaultPackage
import com.android.hoststubgen.asm.toJvmClassName
import com.android.hoststubgen.filters.FilterPolicy
import com.android.hoststubgen.filters.FilterPolicyWithReason
@@ -89,7 +89,7 @@
) {
super.visit(version, access, name, signature, superName, interfaces)
currentClassName = name
- currentPackageName = getPackageNameFromClassName(name)
+ currentPackageName = getPackageNameFromFullClassName(name)
classPolicy = filter.getPolicyForClass(currentClassName)
log.d("[%s] visit: %s (package: %s)", this.javaClass.simpleName, name, currentPackageName)
@@ -98,7 +98,8 @@
log.indent()
filter.getNativeSubstitutionClass(currentClassName)?.let { className ->
- val fullClassName = resolveClassName(className, currentPackageName).toJvmClassName()
+ val fullClassName = resolveClassNameWithDefaultPackage(className, currentPackageName)
+ .toJvmClassName()
log.d(" NativeSubstitutionClass: $fullClassName")
if (classes.findClass(fullClassName) == null) {
log.w("Native substitution class $fullClassName not found. Class must be " +