Merge "Rename STLState.snapToScene() to snapTo()" into main
diff --git a/cmds/am/am.sh b/cmds/am/am.sh
index 76ec214..f099be3 100755
--- a/cmds/am/am.sh
+++ b/cmds/am/am.sh
@@ -1,11 +1,10 @@
#!/system/bin/sh
-# set to top-app process group
-settaskprofile $$ SCHED_SP_TOP_APP >/dev/null 2>&1 || true
-
if [ "$1" != "instrument" ] ; then
cmd activity "$@"
else
+ # set to top-app process group for instrument
+ settaskprofile $$ SCHED_SP_TOP_APP >/dev/null 2>&1 || true
base=/system
export CLASSPATH=$base/framework/am.jar
exec app_process $base/bin com.android.commands.am.Am "$@"
diff --git a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
index 6310d32..696bc82 100644
--- a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
+++ b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
@@ -18,6 +18,7 @@
import android.annotation.IntDef;
import android.annotation.UserIdInt;
+import android.app.ActivityManager;
import android.app.backup.BackupManager;
import android.app.backup.BackupManagerMonitor;
import android.app.backup.BackupProgress;
@@ -73,6 +74,8 @@
"Error: Could not access the backup transport. Is the system running?";
private static final String PM_NOT_RUNNING_ERR =
"Error: Could not access the Package Manager. Is the system running?";
+ private static final String INVALID_USER_ID_ERR_TEMPLATE =
+ "Error: Invalid user id (%d).\n";
private String[] mArgs;
private int mNextArg;
@@ -104,6 +107,11 @@
mArgs = args;
mNextArg = 0;
int userId = parseUserId();
+ if (userId < 0) {
+ System.err.printf(INVALID_USER_ID_ERR_TEMPLATE, userId);
+ return;
+ }
+
String op = nextArg();
Slog.v(TAG, "Running " + op + " for user:" + userId);
@@ -955,12 +963,15 @@
private int parseUserId() {
String arg = nextArg();
- if ("--user".equals(arg)) {
- return UserHandle.parseUserArg(nextArg());
- } else {
+ if (!"--user".equals(arg)) {
mNextArg--;
return UserHandle.USER_SYSTEM;
}
+ int userId = UserHandle.parseUserArg(nextArg());
+ if (userId == UserHandle.USER_CURRENT) {
+ userId = ActivityManager.getCurrentUser();
+ }
+ return userId;
}
private static void showUsage() {
diff --git a/cmds/uinput/tests/Android.bp b/cmds/uinput/tests/Android.bp
index e728bd2..516de33 100644
--- a/cmds/uinput/tests/Android.bp
+++ b/cmds/uinput/tests/Android.bp
@@ -18,3 +18,17 @@
"device-tests",
],
}
+
+android_ravenwood_test {
+ name: "UinputTestsRavenwood",
+ srcs: [
+ "src/**/*.java",
+ ],
+ static_libs: [
+ "androidx.test.runner",
+ "frameworks-base-testutils",
+ "platform-test-annotations",
+ "truth",
+ "uinput",
+ ],
+}
diff --git a/core/api/current.txt b/core/api/current.txt
index 050cad4..d4ed533 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -2419,6 +2419,7 @@
field public static final int accessibilityActionScrollRight = 16908347; // 0x102003b
field public static final int accessibilityActionScrollToPosition = 16908343; // 0x1020037
field public static final int accessibilityActionScrollUp = 16908344; // 0x1020038
+ field @FlaggedApi("android.view.accessibility.a11y_selection_api") public static final int accessibilityActionSetExtendedSelection;
field public static final int accessibilityActionSetProgress = 16908349; // 0x102003d
field public static final int accessibilityActionShowOnScreen = 16908342; // 0x1020036
field public static final int accessibilityActionShowTextSuggestions = 16908376; // 0x1020058
@@ -56266,6 +56267,7 @@
method public android.view.accessibility.AccessibilityNodeInfo getParent();
method @Nullable public android.view.accessibility.AccessibilityNodeInfo getParent(int);
method public android.view.accessibility.AccessibilityNodeInfo.RangeInfo getRangeInfo();
+ method @FlaggedApi("android.view.accessibility.a11y_selection_api") @Nullable public android.view.accessibility.AccessibilityNodeInfo.Selection getSelection();
method @Nullable public CharSequence getStateDescription();
method @FlaggedApi("android.view.accessibility.supplemental_description") @Nullable public CharSequence getSupplementalDescription();
method public CharSequence getText();
@@ -56374,6 +56376,7 @@
method public void setScreenReaderFocusable(boolean);
method public void setScrollable(boolean);
method public void setSelected(boolean);
+ method @FlaggedApi("android.view.accessibility.a11y_selection_api") public void setSelection(@Nullable android.view.accessibility.AccessibilityNodeInfo.Selection);
method public void setShowingHintText(boolean);
method public void setSource(android.view.View);
method public void setSource(android.view.View, int);
@@ -56406,6 +56409,7 @@
field public static final String ACTION_ARGUMENT_ROW_INT = "android.view.accessibility.action.ARGUMENT_ROW_INT";
field @FlaggedApi("android.view.accessibility.granular_scrolling") public static final String ACTION_ARGUMENT_SCROLL_AMOUNT_FLOAT = "android.view.accessibility.action.ARGUMENT_SCROLL_AMOUNT_FLOAT";
field public static final String ACTION_ARGUMENT_SELECTION_END_INT = "ACTION_ARGUMENT_SELECTION_END_INT";
+ field @FlaggedApi("android.view.accessibility.a11y_selection_api") public static final String ACTION_ARGUMENT_SELECTION_PARCELABLE = "android.view.accessibility.action.ARGUMENT_SELECTION_PARCELABLE";
field public static final String ACTION_ARGUMENT_SELECTION_START_INT = "ACTION_ARGUMENT_SELECTION_START_INT";
field public static final String ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE = "ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE";
field public static final int ACTION_CLEAR_ACCESSIBILITY_FOCUS = 128; // 0x80
@@ -56503,6 +56507,7 @@
field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_SCROLL_TO_POSITION;
field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_SCROLL_UP;
field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_SELECT;
+ field @FlaggedApi("android.view.accessibility.a11y_selection_api") @NonNull public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_SET_EXTENDED_SELECTION;
field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_SET_PROGRESS;
field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_SET_SELECTION;
field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_SET_TEXT;
@@ -56588,6 +56593,26 @@
field public static final int RANGE_TYPE_PERCENT = 2; // 0x2
}
+ @FlaggedApi("android.view.accessibility.a11y_selection_api") public static final class AccessibilityNodeInfo.Selection implements android.os.Parcelable {
+ ctor public AccessibilityNodeInfo.Selection(@NonNull android.view.accessibility.AccessibilityNodeInfo.SelectionPosition, @NonNull android.view.accessibility.AccessibilityNodeInfo.SelectionPosition);
+ method public int describeContents();
+ method @NonNull public android.view.accessibility.AccessibilityNodeInfo.SelectionPosition getEnd();
+ method @NonNull public android.view.accessibility.AccessibilityNodeInfo.SelectionPosition getStart();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.view.accessibility.AccessibilityNodeInfo.Selection> CREATOR;
+ }
+
+ @FlaggedApi("android.view.accessibility.a11y_selection_api") public static final class AccessibilityNodeInfo.SelectionPosition implements android.os.Parcelable {
+ ctor public AccessibilityNodeInfo.SelectionPosition(@NonNull android.view.accessibility.AccessibilityNodeInfo, int);
+ ctor public AccessibilityNodeInfo.SelectionPosition(@NonNull android.view.View, int);
+ ctor public AccessibilityNodeInfo.SelectionPosition(@NonNull android.view.View, int, int);
+ method public int describeContents();
+ method @Nullable public android.view.accessibility.AccessibilityNodeInfo getNode();
+ method public int getOffset();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.view.accessibility.AccessibilityNodeInfo.SelectionPosition> CREATOR;
+ }
+
public static final class AccessibilityNodeInfo.TouchDelegateInfo implements android.os.Parcelable {
ctor public AccessibilityNodeInfo.TouchDelegateInfo(@NonNull java.util.Map<android.graphics.Region,android.view.View>);
method public int describeContents();
diff --git a/core/api/lint-baseline.txt b/core/api/lint-baseline.txt
index e71dffa..577113b 100644
--- a/core/api/lint-baseline.txt
+++ b/core/api/lint-baseline.txt
@@ -1,3 +1,4 @@
+
// Baseline format: 1.0
BroadcastBehavior: android.app.AlarmManager#ACTION_NEXT_ALARM_CLOCK_CHANGED:
Field 'ACTION_NEXT_ALARM_CLOCK_CHANGED' is missing @BroadcastBehavior
@@ -243,8 +244,6 @@
Field 'ACTION_SUBSCRIPTION_SPECIFIC_CARRIER_IDENTITY_CHANGED' is missing @BroadcastBehavior
BroadcastBehavior: android.telephony.euicc.EuiccManager#ACTION_NOTIFY_CARRIER_SETUP_INCOMPLETE:
Field 'ACTION_NOTIFY_CARRIER_SETUP_INCOMPLETE' is missing @BroadcastBehavior
-
-
DeprecationMismatch: android.accounts.AccountManager#newChooseAccountIntent(android.accounts.Account, java.util.ArrayList<android.accounts.Account>, String[], boolean, String, String, String[], android.os.Bundle):
Method android.accounts.AccountManager.newChooseAccountIntent(android.accounts.Account, java.util.ArrayList<android.accounts.Account>, String[], boolean, String, String, String[], android.os.Bundle): @Deprecated annotation (present) and @deprecated doc tag (not present) do not match
DeprecationMismatch: android.app.Activity#enterPictureInPictureMode():
@@ -381,8 +380,6 @@
Method android.webkit.WebViewDatabase.hasFormData(): @Deprecated annotation (present) and @deprecated doc tag (not present) do not match
DeprecationMismatch: javax.microedition.khronos.egl.EGL10#eglCreatePixmapSurface(javax.microedition.khronos.egl.EGLDisplay, javax.microedition.khronos.egl.EGLConfig, Object, int[]):
Method javax.microedition.khronos.egl.EGL10.eglCreatePixmapSurface(javax.microedition.khronos.egl.EGLDisplay, javax.microedition.khronos.egl.EGLConfig, Object, int[]): @Deprecated annotation (present) and @deprecated doc tag (not present) do not match
-
-
FlaggedApiLiteral: android.Manifest.permission#BIND_APP_FUNCTION_SERVICE:
@FlaggedApi contains a string literal, but should reference the field generated by aconfig (android.app.appfunctions.flags.Flags.FLAG_ENABLE_APP_FUNCTION_MANAGER).
FlaggedApiLiteral: android.Manifest.permission#BIND_TV_AD_SERVICE:
@@ -405,26 +402,22 @@
@FlaggedApi contains a string literal, but should reference the field generated by aconfig (android.content.pm.Flags.FLAG_SDK_LIB_INDEPENDENCE).
FlaggedApiLiteral: android.R.attr#supplementalDescription:
@FlaggedApi contains a string literal, but should reference the field generated by aconfig (android.view.accessibility.Flags.FLAG_SUPPLEMENTAL_DESCRIPTION).
+FlaggedApiLiteral: android.R.id#accessibilityActionSetExtendedSelection:
+ @FlaggedApi contains a string literal, but should reference the field generated by aconfig (android.view.accessibility.Flags.FLAG_A11Y_SELECTION_API).
FlaggedApiLiteral: android.accessibilityservice.AccessibilityService#OVERLAY_RESULT_INTERNAL_ERROR:
@FlaggedApi contains a string literal, but should reference the field generated by aconfig (android.view.accessibility.Flags.FLAG_A11Y_OVERLAY_CALLBACKS).
FlaggedApiLiteral: android.accessibilityservice.AccessibilityService#OVERLAY_RESULT_INVALID:
@FlaggedApi contains a string literal, but should reference the field generated by aconfig (android.view.accessibility.Flags.FLAG_A11Y_OVERLAY_CALLBACKS).
FlaggedApiLiteral: android.accessibilityservice.AccessibilityService#OVERLAY_RESULT_SUCCESS:
@FlaggedApi contains a string literal, but should reference the field generated by aconfig (android.view.accessibility.Flags.FLAG_A11Y_OVERLAY_CALLBACKS).
-
-
InvalidNullabilityOverride: android.app.Notification.TvExtender#extend(android.app.Notification.Builder) parameter #0:
Invalid nullability on parameter `builder` in method `extend`. Parameters of overrides cannot be NonNull if the super parameter is unannotated.
InvalidNullabilityOverride: android.media.midi.MidiUmpDeviceService#onBind(android.content.Intent) parameter #0:
Invalid nullability on parameter `intent` in method `onBind`. Parameters of overrides cannot be NonNull if the super parameter is unannotated.
-
-
KotlinOperator: android.graphics.Matrix44#get(int, int):
Method can be invoked with an indexing operator from Kotlin: `get` (this is usually desirable; just make sure it makes sense for this type of object)
KotlinOperator: android.graphics.Matrix44#set(int, int, float):
Method can be invoked with an indexing operator from Kotlin: `set` (this is usually desirable; just make sure it makes sense for this type of object)
-
-
MissingGetterMatchingBuilder: android.os.RemoteCallbackList.Builder#setInterfaceDiedCallback(android.os.RemoteCallbackList.Builder.InterfaceDiedCallback<E>):
android.os.RemoteCallbackList does not declare a `getInterfaceDiedCallback()` method matching method android.os.RemoteCallbackList.Builder.setInterfaceDiedCallback(android.os.RemoteCallbackList.Builder.InterfaceDiedCallback<E>)
RequiresPermission: android.accounts.AccountManager#getAccountsByTypeAndFeatures(String, String[], android.accounts.AccountManagerCallback<android.accounts.Account[]>, android.os.Handler):
@@ -1117,14 +1110,10 @@
Method 'setBlockNetworkLoads' documentation mentions permissions without declaring @RequiresPermission
RequiresPermission: android.webkit.WebSettings#setGeolocationEnabled(boolean):
Method 'setGeolocationEnabled' documentation mentions permissions without declaring @RequiresPermission
-
-
Todo: android.hardware.camera2.params.StreamConfigurationMap:
Documentation mentions 'TODO'
Todo: android.provider.ContactsContract.RawContacts#newEntityIterator(android.database.Cursor):
Documentation mentions 'TODO'
-
-
UnflaggedApi: android.R.color#on_surface_disabled_material:
New API must be flagged with @FlaggedApi: field android.R.color.on_surface_disabled_material
UnflaggedApi: android.R.color#outline_disabled_material:
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index e8ff546..9e9e3c2 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1806,17 +1806,17 @@
}
public class InputSettings {
- method @FlaggedApi("com.android.hardware.input.keyboard_a11y_bounce_keys_flag") public static int getAccessibilityBounceKeysThreshold(@NonNull android.content.Context);
- method @FlaggedApi("com.android.hardware.input.keyboard_a11y_slow_keys_flag") public static int getAccessibilitySlowKeysThreshold(@NonNull android.content.Context);
+ method public static int getAccessibilityBounceKeysThreshold(@NonNull android.content.Context);
+ method public static int getAccessibilitySlowKeysThreshold(@NonNull android.content.Context);
method @FlaggedApi("com.android.input.flags.keyboard_repeat_keys") public static int getRepeatKeysDelay(@NonNull android.content.Context);
method @FlaggedApi("com.android.input.flags.keyboard_repeat_keys") public static int getRepeatKeysTimeout(@NonNull android.content.Context);
method @FlaggedApi("com.android.hardware.input.keyboard_a11y_mouse_keys") public static boolean isAccessibilityMouseKeysEnabled(@NonNull android.content.Context);
- method @FlaggedApi("com.android.hardware.input.keyboard_a11y_sticky_keys_flag") public static boolean isAccessibilityStickyKeysEnabled(@NonNull android.content.Context);
+ method public static boolean isAccessibilityStickyKeysEnabled(@NonNull android.content.Context);
method @FlaggedApi("com.android.input.flags.keyboard_repeat_keys") public static boolean isRepeatKeysEnabled(@NonNull android.content.Context);
- method @FlaggedApi("com.android.hardware.input.keyboard_a11y_bounce_keys_flag") @RequiresPermission(android.Manifest.permission.WRITE_SETTINGS) public static void setAccessibilityBounceKeysThreshold(@NonNull android.content.Context, int);
+ method @RequiresPermission(android.Manifest.permission.WRITE_SETTINGS) public static void setAccessibilityBounceKeysThreshold(@NonNull android.content.Context, int);
method @FlaggedApi("com.android.hardware.input.keyboard_a11y_mouse_keys") @RequiresPermission(android.Manifest.permission.WRITE_SETTINGS) public static void setAccessibilityMouseKeysEnabled(@NonNull android.content.Context, boolean);
- method @FlaggedApi("com.android.hardware.input.keyboard_a11y_slow_keys_flag") @RequiresPermission(android.Manifest.permission.WRITE_SETTINGS) public static void setAccessibilitySlowKeysThreshold(@NonNull android.content.Context, int);
- method @FlaggedApi("com.android.hardware.input.keyboard_a11y_sticky_keys_flag") @RequiresPermission(android.Manifest.permission.WRITE_SETTINGS) public static void setAccessibilityStickyKeysEnabled(@NonNull android.content.Context, boolean);
+ method @RequiresPermission(android.Manifest.permission.WRITE_SETTINGS) public static void setAccessibilitySlowKeysThreshold(@NonNull android.content.Context, int);
+ method @RequiresPermission(android.Manifest.permission.WRITE_SETTINGS) public static void setAccessibilityStickyKeysEnabled(@NonNull android.content.Context, boolean);
method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public static void setMaximumObscuringOpacityForTouch(@NonNull android.content.Context, @FloatRange(from=0, to=1) float);
method @FlaggedApi("com.android.input.flags.keyboard_repeat_keys") @RequiresPermission(android.Manifest.permission.WRITE_SETTINGS) public static void setRepeatKeysDelay(@NonNull android.content.Context, int);
method @FlaggedApi("com.android.input.flags.keyboard_repeat_keys") @RequiresPermission(android.Manifest.permission.WRITE_SETTINGS) public static void setRepeatKeysEnabled(@NonNull android.content.Context, boolean);
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index dce15b8..0ca4a32 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -11204,10 +11204,15 @@
* </pre>
*
*
- *
+ * <p>
* NOTE: The progress bar layout will be mirrored for RTL layout.
+ * </p>
+ *
+ * <p>
* NOTE: The extras set by {@link Notification.Builder#setProgress} will be overridden by
- * the values set on this style object when the notification is built.
+ * the values set on this style object when the notification is built. Therefore, that method
+ * is not used with this style.
+ * </p>
*
*/
@FlaggedApi(Flags.FLAG_API_RICH_ONGOING)
@@ -11370,7 +11375,7 @@
if (mProgressPoints == null) {
mProgressPoints = new ArrayList<>();
}
- if (point.getPosition() >= 0) {
+ if (point.getPosition() > 0) {
mProgressPoints.add(point);
if (mProgressPoints.size() > MAX_PROGRESS_POINT_LIMIT) {
@@ -11379,7 +11384,7 @@
}
} else {
- Log.w(TAG, "Dropped the point. The position is a negative integer.");
+ Log.w(TAG, "Dropped the point. The position is a negative or zero integer.");
}
return this;
@@ -11893,7 +11898,9 @@
final List<Point> points = new ArrayList<>();
for (Point point : mProgressPoints) {
final int position = point.getPosition();
- if (position < 0 || position > totalLength) continue;
+ // The points at start/end aren't supposed to show in the progress bar.
+ // Therefore those are also dropped here.
+ if (position <= 0 || position >= totalLength) continue;
points.add(sanitizePoint(point, backgroundColor, defaultProgressColor));
if (points.size() == MAX_PROGRESS_POINT_LIMIT) {
break;
diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java
index 566e78a..2b0e941 100644
--- a/core/java/android/companion/CompanionDeviceManager.java
+++ b/core/java/android/companion/CompanionDeviceManager.java
@@ -277,6 +277,23 @@
*/
public static final int MESSAGE_ONEWAY_TO_WEARABLE = 0x43847987; // +TOW
+
+ /** @hide */
+ @IntDef(flag = true, prefix = { "TRANSPORT_FLAG_" }, value = {
+ TRANSPORT_FLAG_EXTEND_PATCH_DIFF,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface TransportFlags {}
+
+ /**
+ * A security flag that allows transports to be attached to devices that may be more vulnerable
+ * due to infrequent updates. Can only be used for associations with
+ * {@link AssociationRequest#DEVICE_PROFILE_WEARABLE_SENSING} device profile.
+ *
+ * @hide
+ */
+ public static final int TRANSPORT_FLAG_EXTEND_PATCH_DIFF = 1;
+
/**
* Callback for applications to receive updates about and the outcome of
* {@link AssociationRequest} issued via {@code associate()} call.
@@ -1452,7 +1469,52 @@
}
try {
- final Transport transport = new Transport(associationId, in, out);
+ final Transport transport = new Transport(associationId, in, out, 0);
+ mTransports.put(associationId, transport);
+ transport.start();
+ } catch (IOException e) {
+ throw new RuntimeException("Failed to attach transport", e);
+ }
+ }
+ }
+
+ /**
+ * Attach a bidirectional communication stream to be used as a transport channel for
+ * transporting system data between associated devices. Flags can be provided to further
+ * customize the behavior of the transport.
+ *
+ * @param associationId id of the associated device.
+ * @param in Already connected stream of data incoming from remote
+ * associated device.
+ * @param out Already connected stream of data outgoing to remote associated
+ * device.
+ * @param flags Flags to customize transport behavior.
+ * @throws DeviceNotAssociatedException Thrown if the associationId was not previously
+ * associated with this app.
+ *
+ * @see #buildPermissionTransferUserConsentIntent(int)
+ * @see #startSystemDataTransfer(int, Executor, OutcomeReceiver)
+ * @see #detachSystemDataTransport(int)
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.DELIVER_COMPANION_MESSAGES)
+ public void attachSystemDataTransport(int associationId,
+ @NonNull InputStream in,
+ @NonNull OutputStream out,
+ @TransportFlags int flags) throws DeviceNotAssociatedException {
+ if (mService == null) {
+ Log.w(TAG, "CompanionDeviceManager service is not available.");
+ return;
+ }
+
+ synchronized (mTransports) {
+ if (mTransports.contains(associationId)) {
+ detachSystemDataTransport(associationId);
+ }
+
+ try {
+ final Transport transport = new Transport(associationId, in, out, flags);
mTransports.put(associationId, transport);
transport.start();
} catch (IOException e) {
@@ -1931,16 +1993,22 @@
private final int mAssociationId;
private final InputStream mRemoteIn;
private final OutputStream mRemoteOut;
+ private final int mFlags;
private InputStream mLocalIn;
private OutputStream mLocalOut;
private volatile boolean mStopped;
- public Transport(int associationId, InputStream remoteIn, OutputStream remoteOut) {
+ Transport(int associationId, InputStream remoteIn, OutputStream remoteOut) {
+ this(associationId, remoteIn, remoteOut, 0);
+ }
+
+ Transport(int associationId, InputStream remoteIn, OutputStream remoteOut, int flags) {
mAssociationId = associationId;
mRemoteIn = remoteIn;
mRemoteOut = remoteOut;
+ mFlags = flags;
}
public void start() throws IOException {
@@ -1957,7 +2025,7 @@
try {
mService.attachSystemDataTransport(mContext.getOpPackageName(),
- mContext.getUserId(), mAssociationId, remoteFd);
+ mContext.getUserId(), mAssociationId, remoteFd, mFlags);
} catch (RemoteException e) {
throw new IOException("Failed to configure transport", e);
}
diff --git a/core/java/android/companion/ICompanionDeviceManager.aidl b/core/java/android/companion/ICompanionDeviceManager.aidl
index a2b7dd9..787e8b6 100644
--- a/core/java/android/companion/ICompanionDeviceManager.aidl
+++ b/core/java/android/companion/ICompanionDeviceManager.aidl
@@ -113,7 +113,7 @@
in ISystemDataTransferCallback callback);
@EnforcePermission("DELIVER_COMPANION_MESSAGES")
- void attachSystemDataTransport(String packageName, int userId, int associationId, in ParcelFileDescriptor fd);
+ void attachSystemDataTransport(String packageName, int userId, int associationId, in ParcelFileDescriptor fd, int flags);
@EnforcePermission("DELIVER_COMPANION_MESSAGES")
void detachSystemDataTransport(String packageName, int userId, int associationId);
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index 37f3f17..e645060 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -1643,6 +1643,19 @@
public static final long OVERRIDE_ENABLE_INSETS_DECOUPLED_CONFIGURATION = 327313645L;
/**
+ * When the override is enabled, the activity receives configuration coupled with caption bar
+ * insets. Normally, caption bar insets are decoupled from configuration.
+ *
+ * <p>Override applies only if the activity targets SDK level 34 or earlier version.
+ *
+ * @hide
+ */
+ @ChangeId
+ @Overridable
+ @Disabled
+ public static final long OVERRIDE_EXCLUDE_CAPTION_INSETS_FROM_APP_BOUNDS = 388014743L;
+
+ /**
* Optional set of a certificates identifying apps that are allowed to embed this activity. From
* the "knownActivityEmbeddingCerts" attribute.
*/
diff --git a/core/java/android/content/res/flags.aconfig b/core/java/android/content/res/flags.aconfig
index 027eb9d..88fbdad 100644
--- a/core/java/android/content/res/flags.aconfig
+++ b/core/java/android/content/res/flags.aconfig
@@ -137,4 +137,15 @@
namespace: "resource_manager"
description: "flag always meant to be false, for testing resource flagging within cts tests"
bug: "377974898"
-}
\ No newline at end of file
+}
+
+flag {
+ name: "use_new_aconfig_storage"
+ is_exported: true
+ namespace: "resource_manager"
+ description: "Retrieve flag values from new Aconfig flag storage in AconfigFlags.java"
+ bug: "352348353"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index fded882..d891916 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -641,6 +641,9 @@
* is triggered whenever the properties of a {@link android.view.Display}, such as size,
* state, density are modified.
*
+ * This event is not triggered for refresh rate changes as they can change very often.
+ * To monitor refresh rate changes, subscribe to {@link EVENT_TYPE_DISPLAY_REFRESH_RATE}.
+ *
* @see #registerDisplayListener(DisplayListener, Handler, long)
*
*/
@@ -839,6 +842,9 @@
* Registers a display listener to receive notifications about when
* displays are added, removed or changed.
*
+ * We encourage to use {@link #registerDisplayListener(Executor, long, DisplayListener)}
+ * instead to subscribe for explicit events of interest
+ *
* @param listener The listener to register.
* @param handler The handler on which the listener should be invoked, or null
* if the listener should be invoked on the calling thread's looper.
@@ -847,7 +853,9 @@
*/
public void registerDisplayListener(DisplayListener listener, Handler handler) {
registerDisplayListener(listener, handler, EVENT_TYPE_DISPLAY_ADDED
- | EVENT_TYPE_DISPLAY_CHANGED | EVENT_TYPE_DISPLAY_REMOVED);
+ | EVENT_TYPE_DISPLAY_CHANGED
+ | EVENT_TYPE_DISPLAY_REFRESH_RATE
+ | EVENT_TYPE_DISPLAY_REMOVED);
}
/**
diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
index b5715ed..339dbf2 100644
--- a/core/java/android/hardware/display/DisplayManagerGlobal.java
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -1766,29 +1766,23 @@
}
if ((eventFlags & DisplayManager.EVENT_TYPE_DISPLAY_CHANGED) != 0) {
- // For backward compatibility, a client subscribing to
- // DisplayManager.EVENT_FLAG_DISPLAY_CHANGED will be enrolled to both Basic and
- // RR changes
- baseEventMask |= INTERNAL_EVENT_FLAG_DISPLAY_BASIC_CHANGED
- | INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE;
+ baseEventMask |= INTERNAL_EVENT_FLAG_DISPLAY_BASIC_CHANGED;
}
- if ((eventFlags
- & DisplayManager.EVENT_TYPE_DISPLAY_REMOVED) != 0) {
+ if ((eventFlags & DisplayManager.EVENT_TYPE_DISPLAY_REMOVED) != 0) {
baseEventMask |= INTERNAL_EVENT_FLAG_DISPLAY_REMOVED;
}
- if (Flags.displayListenerPerformanceImprovements()) {
- if ((eventFlags & DisplayManager.EVENT_TYPE_DISPLAY_REFRESH_RATE) != 0) {
- baseEventMask |= INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE;
- }
+ if ((eventFlags & DisplayManager.EVENT_TYPE_DISPLAY_REFRESH_RATE) != 0) {
+ baseEventMask |= INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE;
+ }
+ if (Flags.displayListenerPerformanceImprovements()) {
if ((eventFlags & DisplayManager.EVENT_TYPE_DISPLAY_STATE) != 0) {
baseEventMask |= INTERNAL_EVENT_FLAG_DISPLAY_STATE;
}
}
-
return baseEventMask;
}
}
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index 0ead823..49db54d 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -19,7 +19,6 @@
import static com.android.input.flags.Flags.FLAG_INPUT_DEVICE_VIEW_BEHAVIOR_API;
import static com.android.input.flags.Flags.FLAG_DEVICE_ASSOCIATIONS;
import static com.android.hardware.input.Flags.enableCustomizableInputGestures;
-import static com.android.hardware.input.Flags.keyboardLayoutPreviewFlag;
import static com.android.hardware.input.Flags.keyboardGlyphMap;
import android.Manifest;
@@ -966,9 +965,6 @@
@Nullable
public Drawable getKeyboardLayoutPreview(@Nullable KeyboardLayout keyboardLayout, int width,
int height) {
- if (!keyboardLayoutPreviewFlag()) {
- return null;
- }
PhysicalKeyLayout keyLayout = new PhysicalKeyLayout(
mGlobal.getKeyCharacterMap(keyboardLayout), keyboardLayout);
return new KeyboardLayoutPreviewDrawable(mContext, keyLayout, width, height);
@@ -1403,9 +1399,6 @@
@RequiresPermission(Manifest.permission.MONITOR_STICKY_MODIFIER_STATE)
public void registerStickyModifierStateListener(@NonNull Executor executor,
@NonNull StickyModifierStateListener listener) throws IllegalArgumentException {
- if (!InputSettings.isAccessibilityStickyKeysFeatureEnabled()) {
- return;
- }
mGlobal.registerStickyModifierStateListener(executor, listener);
}
@@ -1419,9 +1412,6 @@
@RequiresPermission(Manifest.permission.MONITOR_STICKY_MODIFIER_STATE)
public void unregisterStickyModifierStateListener(
@NonNull StickyModifierStateListener listener) {
- if (!InputSettings.isAccessibilityStickyKeysFeatureEnabled()) {
- return;
- }
mGlobal.unregisterStickyModifierStateListener(listener);
}
diff --git a/core/java/android/hardware/input/InputSettings.java b/core/java/android/hardware/input/InputSettings.java
index af40188..3d4b885 100644
--- a/core/java/android/hardware/input/InputSettings.java
+++ b/core/java/android/hardware/input/InputSettings.java
@@ -16,15 +16,9 @@
package android.hardware.input;
-import static com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_BOUNCE_KEYS_FLAG;
import static com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_MOUSE_KEYS;
-import static com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_SLOW_KEYS_FLAG;
-import static com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_STICKY_KEYS_FLAG;
import static com.android.hardware.input.Flags.enableCustomizableInputGestures;
-import static com.android.hardware.input.Flags.keyboardA11yBounceKeysFlag;
import static com.android.hardware.input.Flags.keyboardA11yMouseKeys;
-import static com.android.hardware.input.Flags.keyboardA11ySlowKeysFlag;
-import static com.android.hardware.input.Flags.keyboardA11yStickyKeysFlag;
import static com.android.hardware.input.Flags.mouseScrollingAcceleration;
import static com.android.hardware.input.Flags.mouseReverseVerticalScrolling;
import static com.android.hardware.input.Flags.mouseSwapPrimaryButton;
@@ -871,21 +865,6 @@
}
/**
- * Whether Accessibility bounce keys feature is enabled.
- *
- * <p>
- * Bounce keys’ is an accessibility feature to aid users who have physical disabilities,
- * that allows the user to configure the device to ignore rapid, repeated keypresses of the
- * same key.
- * </p>
- *
- * @hide
- */
- public static boolean isAccessibilityBounceKeysFeatureEnabled() {
- return keyboardA11yBounceKeysFlag();
- }
-
- /**
* Whether Accessibility bounce keys is enabled.
*
* <p>
@@ -912,11 +891,7 @@
* @hide
*/
@TestApi
- @FlaggedApi(FLAG_KEYBOARD_A11Y_BOUNCE_KEYS_FLAG)
public static int getAccessibilityBounceKeysThreshold(@NonNull Context context) {
- if (!isAccessibilityBounceKeysFeatureEnabled()) {
- return 0;
- }
return Settings.Secure.getIntForUser(context.getContentResolver(),
Settings.Secure.ACCESSIBILITY_BOUNCE_KEYS, 0, UserHandle.USER_CURRENT);
}
@@ -936,13 +911,9 @@
* @hide
*/
@TestApi
- @FlaggedApi(FLAG_KEYBOARD_A11Y_BOUNCE_KEYS_FLAG)
@RequiresPermission(Manifest.permission.WRITE_SETTINGS)
public static void setAccessibilityBounceKeysThreshold(@NonNull Context context,
int thresholdTimeMillis) {
- if (!isAccessibilityBounceKeysFeatureEnabled()) {
- return;
- }
if (thresholdTimeMillis < 0
|| thresholdTimeMillis > MAX_ACCESSIBILITY_BOUNCE_KEYS_THRESHOLD_MILLIS) {
throw new IllegalArgumentException(
@@ -955,21 +926,6 @@
}
/**
- * Whether Accessibility slow keys feature flags is enabled.
- *
- * <p>
- * 'Slow keys' is an accessibility feature to aid users who have physical disabilities, that
- * allows the user to specify the duration for which one must press-and-hold a key before the
- * system accepts the keypress.
- * </p>
- *
- * @hide
- */
- public static boolean isAccessibilitySlowKeysFeatureFlagEnabled() {
- return keyboardA11ySlowKeysFlag();
- }
-
- /**
* Whether Accessibility slow keys is enabled.
*
* <p>
@@ -996,11 +952,7 @@
* @hide
*/
@TestApi
- @FlaggedApi(FLAG_KEYBOARD_A11Y_SLOW_KEYS_FLAG)
public static int getAccessibilitySlowKeysThreshold(@NonNull Context context) {
- if (!isAccessibilitySlowKeysFeatureFlagEnabled()) {
- return 0;
- }
return Settings.Secure.getIntForUser(context.getContentResolver(),
Settings.Secure.ACCESSIBILITY_SLOW_KEYS, 0, UserHandle.USER_CURRENT);
}
@@ -1020,13 +972,9 @@
* @hide
*/
@TestApi
- @FlaggedApi(FLAG_KEYBOARD_A11Y_SLOW_KEYS_FLAG)
@RequiresPermission(Manifest.permission.WRITE_SETTINGS)
public static void setAccessibilitySlowKeysThreshold(@NonNull Context context,
int thresholdTimeMillis) {
- if (!isAccessibilitySlowKeysFeatureFlagEnabled()) {
- return;
- }
if (thresholdTimeMillis < 0
|| thresholdTimeMillis > MAX_ACCESSIBILITY_SLOW_KEYS_THRESHOLD_MILLIS) {
throw new IllegalArgumentException(
@@ -1039,23 +987,6 @@
}
/**
- * Whether Accessibility sticky keys feature is enabled.
- *
- * <p>
- * 'Sticky keys' is an accessibility feature that assists users who have physical
- * disabilities or help users reduce repetitive strain injury. It serializes keystrokes
- * instead of pressing multiple keys at a time, allowing the user to press and release a
- * modifier key, such as Shift, Ctrl, Alt, or any other modifier key, and have it remain
- * active until any other key is pressed.
- * </p>
- *
- * @hide
- */
- public static boolean isAccessibilityStickyKeysFeatureEnabled() {
- return keyboardA11yStickyKeysFlag();
- }
-
- /**
* Whether Accessibility sticky keys is enabled.
*
* <p>
@@ -1069,11 +1000,7 @@
* @hide
*/
@TestApi
- @FlaggedApi(FLAG_KEYBOARD_A11Y_STICKY_KEYS_FLAG)
public static boolean isAccessibilityStickyKeysEnabled(@NonNull Context context) {
- if (!isAccessibilityStickyKeysFeatureEnabled()) {
- return false;
- }
return Settings.Secure.getIntForUser(context.getContentResolver(),
Settings.Secure.ACCESSIBILITY_STICKY_KEYS, 0, UserHandle.USER_CURRENT) != 0;
}
@@ -1092,13 +1019,9 @@
* @hide
*/
@TestApi
- @FlaggedApi(FLAG_KEYBOARD_A11Y_STICKY_KEYS_FLAG)
@RequiresPermission(Manifest.permission.WRITE_SETTINGS)
public static void setAccessibilityStickyKeysEnabled(@NonNull Context context,
boolean enabled) {
- if (!isAccessibilityStickyKeysFeatureEnabled()) {
- return;
- }
Settings.Secure.putIntForUser(context.getContentResolver(),
Settings.Secure.ACCESSIBILITY_STICKY_KEYS, enabled ? 1 : 0,
UserHandle.USER_CURRENT);
diff --git a/core/java/android/hardware/input/input_framework.aconfig b/core/java/android/hardware/input/input_framework.aconfig
index 8d58296..6c2ce36 100644
--- a/core/java/android/hardware/input/input_framework.aconfig
+++ b/core/java/android/hardware/input/input_framework.aconfig
@@ -8,35 +8,6 @@
flag {
namespace: "input_native"
- name: "keyboard_layout_preview_flag"
- description: "Controls whether a preview will be shown in Settings when selecting a physical keyboard layout"
- bug: "293579375"
-}
-
-
-flag {
- namespace: "input_native"
- name: "keyboard_a11y_sticky_keys_flag"
- description: "Controls if the sticky keys accessibility feature for physical keyboard is available to the user"
- bug: "294546335"
-}
-
-flag {
- namespace: "input_native"
- name: "keyboard_a11y_bounce_keys_flag"
- description: "Controls if the bounce keys accessibility feature for physical keyboard is available to the user"
- bug: "294546335"
-}
-
-flag {
- namespace: "input_native"
- name: "keyboard_a11y_slow_keys_flag"
- description: "Controls if the slow keys accessibility feature for physical keyboard is available to the user"
- bug: "294546335"
-}
-
-flag {
- namespace: "input_native"
name: "keyboard_glyph_map"
description: "Allows system to provide keyboard specific key drawables and shortcuts via config files"
bug: "345440920"
diff --git a/media/java/android/media/Image.java b/core/java/android/media/Image.java
similarity index 100%
rename from media/java/android/media/Image.java
rename to core/java/android/media/Image.java
diff --git a/media/java/android/media/ImageReader.java b/core/java/android/media/ImageReader.java
similarity index 100%
rename from media/java/android/media/ImageReader.java
rename to core/java/android/media/ImageReader.java
diff --git a/media/java/android/media/ImageUtils.java b/core/java/android/media/ImageUtils.java
similarity index 100%
rename from media/java/android/media/ImageUtils.java
rename to core/java/android/media/ImageUtils.java
diff --git a/media/java/android/media/ImageWriter.java b/core/java/android/media/ImageWriter.java
similarity index 100%
rename from media/java/android/media/ImageWriter.java
rename to core/java/android/media/ImageWriter.java
diff --git a/media/java/android/media/PublicFormatUtils.java b/core/java/android/media/PublicFormatUtils.java
similarity index 100%
rename from media/java/android/media/PublicFormatUtils.java
rename to core/java/android/media/PublicFormatUtils.java
diff --git a/core/java/android/os/TestLooperManager.java b/core/java/android/os/TestLooperManager.java
index 2d9d025..1a54f4d 100644
--- a/core/java/android/os/TestLooperManager.java
+++ b/core/java/android/os/TestLooperManager.java
@@ -159,7 +159,7 @@
*/
public void execute(Message message) {
checkReleased();
- if (Looper.myLooper() == mLooper) {
+ if (mLooper.isCurrentThread()) {
// This is being called from the thread it should be executed on, we can just dispatch.
message.target.dispatchMessage(message);
} else {
diff --git a/core/java/android/provider/DocumentsProvider.java b/core/java/android/provider/DocumentsProvider.java
index 5b527c7..1b65a88 100644
--- a/core/java/android/provider/DocumentsProvider.java
+++ b/core/java/android/provider/DocumentsProvider.java
@@ -1124,12 +1124,13 @@
}
final Uri documentUri = extraUri;
- final String authority = documentUri.getAuthority();
+ final String authorityWithoutUserId = getAuthorityWithoutUserId(documentUri.getAuthority());
final String documentId = DocumentsContract.getDocumentId(documentUri);
- if (!mAuthority.equals(authority)) {
+ if (!mAuthority.equals(authorityWithoutUserId)) {
throw new SecurityException(
- "Requested authority " + authority + " doesn't match provider " + mAuthority);
+ "Requested authority " + authorityWithoutUserId + " doesn't match provider "
+ + mAuthority);
}
if (METHOD_IS_CHILD_DOCUMENT.equals(method)) {
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 3cd7a00..f1a9514 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -13006,6 +13006,24 @@
public static final String STYLUS_POINTER_ICON_ENABLED = "stylus_pointer_icon_enabled";
/**
+ * Toggle for whether to redact OTP notification while connected to wifi. Defaults to
+ * false/0.
+ * @hide
+ */
+ @Readable
+ public static final String REDACT_OTP_NOTIFICATION_WHILE_CONNECTED_TO_WIFI =
+ "redact_otp_on_wifi";
+
+ /**
+ * Toggle for whether to immediately redact OTP notifications, or require the device to be
+ * locked for 10 minutes. Defaults to false/0
+ * @hide
+ */
+ @Readable
+ public static final String REDACT_OTP_NOTIFICATION_IMMEDIATELY =
+ "remove_otp_redaction_delay";
+
+ /**
* These entries are considered common between the personal and the managed profile,
* since the managed profile doesn't get to change them.
*/
diff --git a/core/java/android/view/FrameMetrics.java b/core/java/android/view/FrameMetrics.java
index 58b2a67..a4fc342 100644
--- a/core/java/android/view/FrameMetrics.java
+++ b/core/java/android/view/FrameMetrics.java
@@ -259,19 +259,20 @@
int FRAME_DEADLINE = 9;
int FRAME_START_TIME = 10;
int FRAME_INTERVAL = 11;
- int SYNC_QUEUED = 12;
- int SYNC_START = 13;
- int ISSUE_DRAW_COMMANDS_START = 14;
- int SWAP_BUFFERS = 15;
- int FRAME_COMPLETED = 16;
- int DEQUEUE_BUFFER_DURATION = 17;
- int QUEUE_BUFFER_DURATION = 18;
- int GPU_COMPLETED = 19;
- int SWAP_BUFFERS_COMPLETED = 20;
- int DISPLAY_PRESENT_TIME = 21;
- int COMMAND_SUBMISSION_COMPLETED = 22;
+ int WORKLOAD_TARGET = 12;
+ int SYNC_QUEUED = 13;
+ int SYNC_START = 14;
+ int ISSUE_DRAW_COMMANDS_START = 15;
+ int SWAP_BUFFERS = 16;
+ int FRAME_COMPLETED = 17;
+ int DEQUEUE_BUFFER_DURATION = 18;
+ int QUEUE_BUFFER_DURATION = 19;
+ int GPU_COMPLETED = 20;
+ int SWAP_BUFFERS_COMPLETED = 21;
+ int DISPLAY_PRESENT_TIME = 22;
+ int COMMAND_SUBMISSION_COMPLETED = 23;
- int FRAME_STATS_COUNT = 23; // must always be last and in sync with
+ int FRAME_STATS_COUNT = 24; // must always be last and in sync with
// FrameInfoIndex::NumIndexes in libs/hwui/FrameInfo.h
}
diff --git a/core/java/android/view/InputEventConsistencyVerifier.java b/core/java/android/view/InputEventConsistencyVerifier.java
index 195896d..0e78bfd 100644
--- a/core/java/android/view/InputEventConsistencyVerifier.java
+++ b/core/java/android/view/InputEventConsistencyVerifier.java
@@ -180,7 +180,7 @@
final MotionEvent motionEvent = (MotionEvent)event;
if (motionEvent.isTouchEvent()) {
onTouchEvent(motionEvent, nestingLevel);
- } else if ((motionEvent.getSource() & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
+ } else if (motionEvent.isFromSource(InputDevice.SOURCE_TRACKBALL)) {
onTrackballEvent(motionEvent, nestingLevel);
} else {
onGenericMotionEvent(motionEvent, nestingLevel);
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 0d6f827..80b4f2c 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -272,9 +272,9 @@
import android.window.OnBackInvokedDispatcher;
import android.window.ScreenCapture;
import android.window.SurfaceSyncGroup;
-import android.window.WindowContext;
import android.window.WindowOnBackInvokedDispatcher;
import android.window.WindowTokenClient;
+import android.window.WindowTokenClientController;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
@@ -6614,12 +6614,15 @@
} else {
if (enableWindowContextResourcesUpdateOnConfigChange()) {
// There is no activity callback - update resources for window token, if needed.
- final WindowTokenClient windowTokenClient = getWindowTokenClient();
- if (windowTokenClient != null) {
- windowTokenClient.onConfigurationChanged(
+ final IBinder windowContextToken = mContext.getWindowContextToken();
+ if (windowContextToken instanceof WindowTokenClient) {
+ WindowTokenClientController.getInstance().onWindowConfigurationChanged(
+ windowContextToken,
mLastReportedMergedConfiguration.getMergedConfiguration(),
- newDisplayId == INVALID_DISPLAY ? mDisplay.getDisplayId()
- : newDisplayId);
+ newDisplayId == INVALID_DISPLAY
+ ? mDisplay.getDisplayId()
+ : newDisplayId
+ );
}
}
updateConfiguration(newDisplayId);
@@ -6627,11 +6630,6 @@
mForceNextConfigUpdate = false;
}
- private WindowTokenClient getWindowTokenClient() {
- if (!(mContext instanceof WindowContext)) return null;
- return (WindowTokenClient) mContext.getWindowContextToken();
- }
-
/**
* Update display and views if last applied merged configuration changed.
* @param newDisplayId Id of new display if moved, {@link Display#INVALID_DISPLAY} otherwise.
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index edfa1d5..db699d7 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -1531,15 +1531,6 @@
*/
@TestApi
static boolean hasWindowExtensionsEnabled() {
- if (!Flags.enableWmExtensionsForAllFlag() && ACTIVITY_EMBEDDING_GUARD_WITH_ANDROID_15) {
- // Since enableWmExtensionsForAllFlag, HAS_WINDOW_EXTENSIONS_ON_DEVICE is now true
- // on all devices by default as a build file property.
- // Until finishing flag ramp up, only return true when
- // ACTIVITY_EMBEDDING_GUARD_WITH_ANDROID_15 is false, which is set per device by
- // OEMs.
- return false;
- }
-
if (!HAS_WINDOW_EXTENSIONS_ON_DEVICE) {
return false;
}
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index 8a10979..578b7b6 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -541,6 +541,22 @@
"ACTION_ARGUMENT_HTML_ELEMENT_STRING";
/**
+ * Argument for specifying the extended selection.
+ *
+ * <p><strong>Type:</strong> {@link AccessibilityNodeInfo.Selection}<br>
+ * <strong>Actions:</strong>
+ *
+ * <ul>
+ * <li>{@link AccessibilityAction#ACTION_SET_EXTENDED_SELECTION}
+ * </ul>
+ *
+ * @see AccessibilityAction#ACTION_SET_EXTENDED_SELECTION
+ */
+ @FlaggedApi(Flags.FLAG_A11Y_SELECTION_API)
+ public static final String ACTION_ARGUMENT_SELECTION_PARCELABLE =
+ "android.view.accessibility.action.ARGUMENT_SELECTION_PARCELABLE";
+
+ /**
* Argument for whether when moving at granularity to extend the selection
* or to move it otherwise.
* <p>
@@ -1146,6 +1162,8 @@
private int mConnectionId = UNDEFINED_CONNECTION_ID;
+ private Selection mSelection;
+
private RangeInfo mRangeInfo;
private CollectionInfo mCollectionInfo;
private CollectionItemInfo mCollectionItemInfo;
@@ -2660,6 +2678,56 @@
}
/**
+ * Sets the extended selection, which is a representation of selection that spans multiple nodes
+ * that exist within the subtree of the node defining selection.
+ *
+ * <p><b>Note:</b> The start and end {@link SelectionPosition} of the provided {@link Selection}
+ * should be constructed with {@code this} node or a descendant of it.
+ *
+ * <p><b>Note:</b> {@link AccessibilityNodeInfo#setFocusable} and {@link
+ * AccessibilityNodeInfo#setFocused} should both be called with {@code true} before setting the
+ * selection in order to make {@code this} node a candidate to contain a selection.
+ *
+ * <p><b>Note:</b> Cannot be called from an AccessibilityService. This class is made immutable
+ * before being delivered to an AccessibilityService.
+ *
+ * @param selection The extended selection within the node's subtree, or {@code null} if no
+ * selection exists.
+ * @see AccessibilityNodeInfo.AccessibilityAction#ACTION_SET_EXTENDED_SELECTION
+ * @throws IllegalStateException If called from an AccessibilityService
+ */
+ @FlaggedApi(Flags.FLAG_A11Y_SELECTION_API)
+ public void setSelection(@Nullable Selection selection) {
+ enforceNotSealed();
+ mSelection = selection;
+ }
+
+ /**
+ * Gets the extended selection, which is a representation of selection that spans multiple nodes
+ * that exist within the subtree of the node defining selection.
+ *
+ * <p><b>Note:</b> The start and end {@link SelectionPosition} of the provided {@link Selection}
+ * should be constructed with {@code this} node or a descendant of it.
+ *
+ * <p><b>Note:</b> In order for a node to be a candidate to contain a selection, {@link
+ * AccessibilityNodeInfo#isFocusable()} ()} and {@link AccessibilityNodeInfo#isFocused()} should
+ * both be return with {@code true}.
+ *
+ * @return The extended selection within the node's subtree, or {@code null} if no selection
+ * exists.
+ */
+ @FlaggedApi(Flags.FLAG_A11Y_SELECTION_API)
+ public @Nullable Selection getSelection() {
+ if (mSelection != null) {
+ mSelection.getStart().setWindowId(mWindowId);
+ mSelection.getStart().setConnectionId(mConnectionId);
+ mSelection.getEnd().setWindowId(mWindowId);
+ mSelection.getEnd().setConnectionId(mConnectionId);
+ }
+ return mSelection;
+ }
+
+ /**
* Gets whether this node is visible to the user.
* <p>
* Between {@link Build.VERSION_CODES#JELLY_BEAN API 16} and
@@ -4168,6 +4236,15 @@
* there is no text selection and no cursor.
*/
public int getTextSelectionStart() {
+ if (Flags.a11ySelectionApi()) {
+ Selection current = getSelection();
+ if ((current != null)
+ && current.getStart().usesNode(this)
+ && current.getEnd().usesNode(this)) {
+ return current.getStart().getOffset();
+ }
+ return UNDEFINED_SELECTION_INDEX;
+ }
return mTextSelectionStart;
}
@@ -4183,6 +4260,15 @@
* there is no text selection and no cursor.
*/
public int getTextSelectionEnd() {
+ if (Flags.a11ySelectionApi()) {
+ Selection current = getSelection();
+ if ((current != null)
+ && current.getStart().usesNode(this)
+ && current.getEnd().usesNode(this)) {
+ return current.getEnd().getOffset();
+ }
+ return UNDEFINED_SELECTION_INDEX;
+ }
return mTextSelectionEnd;
}
@@ -4201,6 +4287,13 @@
*/
public void setTextSelection(int start, int end) {
enforceNotSealed();
+ if (Flags.a11ySelectionApi()) {
+ Selection selection =
+ new Selection(
+ new SelectionPosition(this, start), new SelectionPosition(this, end));
+ setSelection(selection);
+ return;
+ }
mTextSelectionStart = start;
mTextSelectionEnd = end;
}
@@ -4875,6 +4968,10 @@
nonDefaultFields |= bitAt(fieldIndex);
}
fieldIndex++;
+ if (!Objects.equals(mSelection, DEFAULT.mSelection)) {
+ nonDefaultFields |= bitAt(fieldIndex);
+ }
+ fieldIndex++;
if (mChecked != DEFAULT.mChecked) {
nonDefaultFields |= bitAt(fieldIndex);
}
@@ -5055,6 +5152,9 @@
parcel.writeLong(mLeashedParentNodeId);
}
if (isBitSet(nonDefaultFields, fieldIndex++)) {
+ mSelection.writeToParcel(parcel, flags);
+ }
+ if (isBitSet(nonDefaultFields, fieldIndex++)) {
parcel.writeInt(mChecked);
}
if (isBitSet(nonDefaultFields, fieldIndex++)) {
@@ -5172,6 +5272,17 @@
ExtraRenderingInfo ti = other.mExtraRenderingInfo;
mExtraRenderingInfo = (ti == null) ? null
: new ExtraRenderingInfo(ti);
+
+ if (Flags.a11ySelectionApi()) {
+ if (other.getSelection() != null) {
+ SelectionPosition sps = other.getSelection().getStart();
+ SelectionPosition spe = other.getSelection().getEnd();
+ mSelection =
+ new Selection(
+ new SelectionPosition(sps.mSourceNodeId, sps.getOffset()),
+ new SelectionPosition(spe.mSourceNodeId, spe.getOffset()));
+ }
+ }
}
/**
@@ -5344,6 +5455,9 @@
mLeashedParentNodeId = parcel.readLong();
}
if (isBitSet(nonDefaultFields, fieldIndex++)) {
+ mSelection = Selection.CREATOR.createFromParcel(parcel);
+ }
+ if (isBitSet(nonDefaultFields, fieldIndex++)) {
mChecked = parcel.readInt();
}
if (isBitSet(nonDefaultFields, fieldIndex++)) {
@@ -5495,6 +5609,9 @@
if (action == R.id.accessibilityActionScrollInDirection) {
return "ACTION_SCROLL_IN_DIRECTION";
}
+ if (action == R.id.accessibilityActionSetExtendedSelection) {
+ return "ACTION_SET_EXTENDED_SELECTION";
+ }
return "ACTION_UNKNOWN";
}
}
@@ -5696,6 +5813,271 @@
}
/**
+ * A class which defines either the start or end of a selection that can span across multiple
+ * AccessibilityNodeInfo objects.
+ *
+ * @see AccessibilityNodeInfo.Selection
+ */
+ @FlaggedApi(Flags.FLAG_A11Y_SELECTION_API)
+ public static final class SelectionPosition implements Parcelable {
+
+ private final int mOffset;
+ private final long mSourceNodeId;
+ private int mConnectionId;
+ private int mWindowId;
+
+ /**
+ * Instantiates a new SelectionPosition.
+ *
+ * @param node The {@link AccessibilityNodeInfo} for the node of this selection.
+ * @param offset The offset for a {@link SelectionPosition} within {@code view}'s text
+ * content, which should be a value between 0 and the length of {@code view}'s text.
+ */
+ public SelectionPosition(@NonNull AccessibilityNodeInfo node, int offset) {
+ this(node.mSourceNodeId, offset);
+ }
+
+ /**
+ * Instantiates a new SelectionPosition.
+ *
+ * @param view The {@link View} containing the virtual descendant associated with the
+ * selection position.
+ * @param offset The offset for a selection position within {@code view}'s text content,
+ * which should be a value between 0 and the length of {@code view}'s text.
+ */
+ public SelectionPosition(@NonNull View view, int offset) {
+ this(
+ makeNodeId(
+ view.getAccessibilityViewId(), AccessibilityNodeProvider.HOST_VIEW_ID),
+ offset);
+ }
+
+ /**
+ * Instantiates a new {@link SelectionPosition}.
+ *
+ * @param view The view whose virtual descendant is associated with the selection position.
+ * @param virtualDescendantId The ID of the virtual descendant within {@code view}'s virtual
+ * subtree that contains the selection position.
+ * @param offset The offset for a selection position within the virtual descendant's text
+ * content, which should be a value between 0 and the length of the descendant's text.
+ * @see AccessibilityNodeProvider
+ */
+ public SelectionPosition(@NonNull View view, int virtualDescendantId, int offset) {
+ this(makeNodeId(view.getAccessibilityViewId(), virtualDescendantId), offset);
+ }
+
+ private SelectionPosition(long sourceNodeId, int offset) {
+ mOffset = offset;
+ mSourceNodeId = sourceNodeId;
+ }
+
+ private SelectionPosition(Parcel in) {
+ mOffset = in.readInt();
+ mSourceNodeId = in.readLong();
+ }
+
+ private void setWindowId(int windowId) {
+ mWindowId = windowId;
+ }
+
+ private void setConnectionId(int connectionId) {
+ mConnectionId = connectionId;
+ }
+
+ /**
+ * Gets the node for {@code this} {@link SelectionPosition}
+ * <br>
+ * <strong>Note:</strong> This api can only be called from {@link AccessibilityService}.
+ *
+ * @return The node associated with {@code this} {@link SelectionPosition}
+ */
+ public @Nullable AccessibilityNodeInfo getNode() {
+ return getNodeForAccessibilityId(mConnectionId, mWindowId, mSourceNodeId);
+ }
+
+ /**
+ * Gets the offset for {@code this} {@link SelectionPosition}.
+ *
+ * @return A value from 0 to the length of {@link #getNode()}'s content representing the
+ * offset of the {@link SelectionPosition}
+ */
+ public int getOffset() {
+ return mOffset;
+ }
+
+ private boolean usesNode(@NonNull AccessibilityNodeInfo node) {
+ return this.mSourceNodeId == node.mSourceNodeId
+ && this.mConnectionId == node.mConnectionId
+ && this.mWindowId == node.mWindowId;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == null) {
+ return false;
+ }
+
+ if (other == this) {
+ return true;
+ }
+
+ if (getClass() != other.getClass()) {
+ return false;
+ }
+
+ SelectionPosition rhs = (SelectionPosition) other;
+ if (getOffset() != rhs.getOffset()) {
+ return false;
+ }
+
+ return mSourceNodeId == rhs.mSourceNodeId;
+ }
+
+ @Override
+ public int hashCode() {
+ final long prime = 877;
+ long result = 1;
+
+ if (mOffset != 0) {
+ result *= mOffset;
+ }
+
+ if (mSourceNodeId != UNDEFINED_NODE_ID) {
+ result *= mSourceNodeId;
+ }
+
+ return Long.hashCode(result * prime);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mOffset);
+ dest.writeLong(mSourceNodeId);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * @see android.os.Parcelable.Creator
+ */
+ @NonNull
+ public static final Creator<SelectionPosition> CREATOR =
+ new Creator<SelectionPosition>() {
+ @Override
+ public SelectionPosition createFromParcel(Parcel in) {
+ return new SelectionPosition(in);
+ }
+
+ @Override
+ public SelectionPosition[] newArray(int size) {
+ return new SelectionPosition[size];
+ }
+ };
+ }
+
+ /**
+ * Represents a selection of content that may extend across more than one {@link
+ * AccessibilityNodeInfo} instance.
+ *
+ * @see AccessibilityNodeInfo.SelectionPosition
+ */
+ @FlaggedApi(Flags.FLAG_A11Y_SELECTION_API)
+ public static final class Selection implements Parcelable {
+
+ private final SelectionPosition mStart;
+ private final SelectionPosition mEnd;
+
+ /**
+ * Instantiates a new Selection.
+ *
+ * @param start The start of the extended selection.
+ * @param end The end of the extended selection.
+ */
+ public Selection(@NonNull SelectionPosition start, @NonNull SelectionPosition end) {
+ this.mStart = start;
+ this.mEnd = end;
+ }
+
+ private Selection(Parcel in) {
+ mStart = SelectionPosition.CREATOR.createFromParcel(in);
+ mEnd = SelectionPosition.CREATOR.createFromParcel(in);
+ }
+
+ /**
+ * @return The start of the extended selection.
+ */
+ public @NonNull SelectionPosition getStart() {
+ return mStart;
+ }
+
+ /**
+ * @return The end of the extended selection.
+ */
+ public @NonNull SelectionPosition getEnd() {
+ return mEnd;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == null) {
+ return false;
+ }
+
+ if (obj == this) {
+ return true;
+ }
+
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+
+ Selection rhs = (Selection) obj;
+ return getStart().equals(rhs.getStart()) && getEnd().equals(rhs.getEnd());
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 17;
+ return prime * getStart().hashCode() * getEnd().hashCode();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ mStart.writeToParcel(dest, flags);
+ mEnd.writeToParcel(dest, flags);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * @see android.os.Parcelable.Creator
+ */
+ @NonNull
+ public static final Creator<Selection> CREATOR =
+ new Creator<Selection>() {
+ @Override
+ public Selection createFromParcel(Parcel in) {
+ return new Selection(in);
+ }
+
+ @Override
+ public Selection[] newArray(int size) {
+ return new Selection[size];
+ }
+ };
+ }
+
+ /**
* A class defining an action that can be performed on an {@link AccessibilityNodeInfo}.
* Each action has a unique id that is mandatory and optional data.
* <p>
@@ -6419,6 +6801,29 @@
@NonNull public static final AccessibilityAction ACTION_SHOW_TEXT_SUGGESTIONS =
new AccessibilityAction(R.id.accessibilityActionShowTextSuggestions);
+ /**
+ * Action to set the extended selection. Performing this action with no arguments clears the
+ * selection.
+ *
+ * <p><strong>Arguments:</strong> {@link
+ * AccessibilityNodeInfo#ACTION_ARGUMENT_SELECTION_PARCELABLE
+ * AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_PARCELABLE}<br>
+ * <strong>Example:</strong> <code><pre><p>
+ * Bundle arguments = new Bundle();
+ * Selection selection = new Selection(null, null);
+ * arguments.setParcelable(
+ * AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_PARCELABLE, selection);
+ * info.performAction(
+ * AccessibilityAction.ACTION_SET_EXTENDED_SELECTION.getId(), arguments);
+ * </pre></code>
+ *
+ * @see AccessibilityNodeInfo#ACTION_ARGUMENT_SELECTION_PARCELABLE
+ */
+ @FlaggedApi(Flags.FLAG_A11Y_SELECTION_API)
+ @NonNull
+ public static final AccessibilityAction ACTION_SET_EXTENDED_SELECTION =
+ new AccessibilityAction(R.id.accessibilityActionSetExtendedSelection);
+
private final int mActionId;
private final CharSequence mLabel;
diff --git a/core/java/android/view/contentcapture/MainContentCaptureSession.java b/core/java/android/view/contentcapture/MainContentCaptureSession.java
index 2fb78c0..b66020b 100644
--- a/core/java/android/view/contentcapture/MainContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/MainContentCaptureSession.java
@@ -57,6 +57,7 @@
import android.view.ViewStructure;
import android.view.autofill.AutofillId;
import android.view.contentcapture.ViewNode.ViewStructureImpl;
+import android.view.contentcapture.flags.Flags;
import android.view.contentprotection.ContentProtectionEventProcessor;
import android.view.inputmethod.BaseInputConnection;
@@ -1008,6 +1009,9 @@
}
}
internalNotifyViewTreeEvent(sessionId, /* started= */ false);
+ if (Flags.flushAfterEachFrame()) {
+ internalNotifySessionFlushEvent(sessionId);
+ }
}
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
diff --git a/core/java/android/view/contentcapture/flags/content_capture_flags.aconfig b/core/java/android/view/contentcapture/flags/content_capture_flags.aconfig
index e7bc004..8c98fa4 100644
--- a/core/java/android/view/contentcapture/flags/content_capture_flags.aconfig
+++ b/core/java/android/view/contentcapture/flags/content_capture_flags.aconfig
@@ -15,3 +15,14 @@
bug: "380381249"
is_exported: true
}
+
+flag {
+ name: "flush_after_each_frame"
+ namespace: "pixel_state_server"
+ description: "Feature flag to send a flush event after each frame"
+ bug: "380381249"
+ is_fixed_read_only: true
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 0fb8042..56f0415 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -3778,8 +3778,32 @@
ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
if (Flags.refactorInsetsController()) {
- mCurRootView.getInsetsController().hide(WindowInsets.Type.ime(),
- false /* fromIme */, statsToken);
+ synchronized (mH) {
+ Handler vh = rootView.getHandler();
+ if (vh == null) {
+ // If the view doesn't have a handler, something has changed out from
+ // under us.
+ ImeTracker.forLogging().onFailed(statsToken,
+ ImeTracker.PHASE_CLIENT_VIEW_HANDLER_AVAILABLE);
+ return;
+ }
+ ImeTracker.forLogging().onProgress(statsToken,
+ ImeTracker.PHASE_CLIENT_VIEW_HANDLER_AVAILABLE);
+
+ if (vh.getLooper() != Looper.myLooper()) {
+ // The view is running on a different thread than our own, so
+ // we need to reschedule our work for over there.
+ if (DEBUG) {
+ Log.v(TAG, "Close current input: reschedule hide to view thread");
+ }
+ final var viewRootImpl = mCurRootView;
+ vh.post(() -> viewRootImpl.getInsetsController().hide(
+ WindowInsets.Type.ime(), false /* fromIme */, statsToken));
+ } else {
+ mCurRootView.getInsetsController().hide(WindowInsets.Type.ime(),
+ false /* fromIme */, statsToken);
+ }
+ }
} else {
IInputMethodManagerGlobalInvoker.hideSoftInput(
mClient,
diff --git a/core/java/android/window/ConfigurationDispatcher.java b/core/java/android/window/ConfigurationDispatcher.java
new file mode 100644
index 0000000..b8f0da1
--- /dev/null
+++ b/core/java/android/window/ConfigurationDispatcher.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.window;
+
+import android.annotation.NonNull;
+import android.content.ComponentCallbacks;
+import android.content.res.Configuration;
+
+/**
+ * Indicates a {@link android.content.Context} could propagate the
+ * {@link android.content.res.Configuration} from the server side and users may listen to the
+ * updates through {@link android.content.Context#registerComponentCallbacks(ComponentCallbacks)}.
+ *
+ * @hide
+ */
+public interface ConfigurationDispatcher {
+
+ /**
+ * Called when there's configuration update from the server side.
+ */
+ void dispatchConfigurationChanged(@NonNull Configuration configuration);
+
+ /**
+ * Indicates that if this dispatcher should report the change even if it's not
+ * {@link Configuration#diffPublicOnly}.
+ */
+ default boolean shouldReportPrivateChanges() {
+ return false;
+ }
+}
diff --git a/core/java/android/window/DesktopModeFlags.java b/core/java/android/window/DesktopModeFlags.java
index 7852460..1ce5df7 100644
--- a/core/java/android/window/DesktopModeFlags.java
+++ b/core/java/android/window/DesktopModeFlags.java
@@ -55,6 +55,7 @@
Flags::enableDesktopAppLaunchAlttabTransitionsBugfix, true),
ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX(Flags::enableDesktopAppLaunchTransitionsBugfix,
true),
+ ENABLE_DESKTOP_CLOSE_SHORTCUT_BUGFIX(Flags::enableDesktopCloseShortcutBugfix, false),
ENABLE_DESKTOP_COMPAT_UI_VISIBILITY_STATUS(Flags::enableCompatUiVisibilityStatus, true),
ENABLE_DESKTOP_RECENTS_TRANSITIONS_CORNERS_BUGFIX(
Flags::enableDesktopRecentsTransitionsCornersBugfix, false),
diff --git a/core/java/android/window/TaskFragmentCreationParams.java b/core/java/android/window/TaskFragmentCreationParams.java
index 89327fe..bc5ad50 100644
--- a/core/java/android/window/TaskFragmentCreationParams.java
+++ b/core/java/android/window/TaskFragmentCreationParams.java
@@ -24,6 +24,7 @@
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.TestApi;
+import android.content.pm.ActivityInfo;
import android.content.pm.ActivityInfo.ScreenOrientation;
import android.graphics.Rect;
import android.os.IBinder;
@@ -112,12 +113,21 @@
*/
private final @ScreenOrientation int mOverrideOrientation;
+ /**
+ * {@link android.content.pm.ActivityInfo.Config} mask that specifies which
+ * configuration changes should trigger TaskFragment info change callbacks.
+ *
+ * @see android.content.pm.ActivityInfo.Config
+ */
+ private final @ActivityInfo.Config int mConfigurationChangeMask;
+
private TaskFragmentCreationParams(
@NonNull TaskFragmentOrganizerToken organizer, @NonNull IBinder fragmentToken,
@NonNull IBinder ownerToken, @NonNull Rect initialRelativeBounds,
@WindowingMode int windowingMode, @Nullable IBinder pairedPrimaryFragmentToken,
@Nullable IBinder pairedActivityToken, boolean allowTransitionWhenEmpty,
- @ScreenOrientation int overrideOrientation) {
+ @ScreenOrientation int overrideOrientation,
+ @ActivityInfo.Config int configurationChangeMask) {
if (pairedPrimaryFragmentToken != null && pairedActivityToken != null) {
throw new IllegalArgumentException("pairedPrimaryFragmentToken and"
+ " pairedActivityToken should not be set at the same time.");
@@ -131,6 +141,7 @@
mPairedActivityToken = pairedActivityToken;
mAllowTransitionWhenEmpty = allowTransitionWhenEmpty;
mOverrideOrientation = overrideOrientation;
+ mConfigurationChangeMask = configurationChangeMask;
}
@NonNull
@@ -186,6 +197,11 @@
return mOverrideOrientation;
}
+ /** @hide */
+ public @ActivityInfo.Config int getConfigurationChangeMask() {
+ return mConfigurationChangeMask;
+ }
+
private TaskFragmentCreationParams(Parcel in) {
mOrganizer = TaskFragmentOrganizerToken.CREATOR.createFromParcel(in);
mFragmentToken = in.readStrongBinder();
@@ -196,6 +212,7 @@
mPairedActivityToken = in.readStrongBinder();
mAllowTransitionWhenEmpty = in.readBoolean();
mOverrideOrientation = in.readInt();
+ mConfigurationChangeMask = in.readInt();
}
/** @hide */
@@ -210,6 +227,7 @@
dest.writeStrongBinder(mPairedActivityToken);
dest.writeBoolean(mAllowTransitionWhenEmpty);
dest.writeInt(mOverrideOrientation);
+ dest.writeInt(mConfigurationChangeMask);
}
@NonNull
@@ -238,6 +256,7 @@
+ " pairedActivityToken=" + mPairedActivityToken
+ " allowTransitionWhenEmpty=" + mAllowTransitionWhenEmpty
+ " overrideOrientation=" + mOverrideOrientation
+ + " configurationChangeMask=" + mConfigurationChangeMask
+ "}";
}
@@ -275,6 +294,8 @@
private @ScreenOrientation int mOverrideOrientation = SCREEN_ORIENTATION_UNSPECIFIED;
+ private @ActivityInfo.Config int mConfigurationChangeMask = 0;
+
public Builder(@NonNull TaskFragmentOrganizerToken organizer,
@NonNull IBinder fragmentToken, @NonNull IBinder ownerToken) {
mOrganizer = organizer;
@@ -369,12 +390,30 @@
return this;
}
+ /**
+ * Sets {@link android.content.pm.ActivityInfo.Config} mask that specifies which
+ * configuration changes should trigger TaskFragment info change callbacks.
+ *
+ * Only system organizers are allowed to configure this value. This value is ignored for
+ * non-system organizers.
+ *
+ * @see android.content.pm.ActivityInfo.Config
+ * @hide
+ */
+ @NonNull
+ public Builder setConfigurationChangeMask(
+ @ActivityInfo.Config int configurationChangeMask) {
+ mConfigurationChangeMask = configurationChangeMask;
+ return this;
+ }
+
/** Constructs the options to create TaskFragment with. */
@NonNull
public TaskFragmentCreationParams build() {
return new TaskFragmentCreationParams(mOrganizer, mFragmentToken, mOwnerToken,
mInitialRelativeBounds, mWindowingMode, mPairedPrimaryFragmentToken,
- mPairedActivityToken, mAllowTransitionWhenEmpty, mOverrideOrientation);
+ mPairedActivityToken, mAllowTransitionWhenEmpty, mOverrideOrientation,
+ mConfigurationChangeMask);
}
}
}
diff --git a/core/java/android/window/WindowContext.java b/core/java/android/window/WindowContext.java
index 84a8b8f..778ccaf 100644
--- a/core/java/android/window/WindowContext.java
+++ b/core/java/android/window/WindowContext.java
@@ -17,8 +17,6 @@
import static android.view.WindowManagerImpl.createWindowContextWindowManager;
-import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
-
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UiContext;
@@ -46,7 +44,8 @@
* @hide
*/
@UiContext
-public class WindowContext extends ContextWrapper implements WindowProvider {
+public class WindowContext extends ContextWrapper implements WindowProvider,
+ ConfigurationDispatcher {
private final WindowManager mWindowManager;
@WindowManager.LayoutParams.WindowType
private final int mType;
@@ -155,7 +154,7 @@
}
/** Dispatch {@link Configuration} to each {@link ComponentCallbacks}. */
- @VisibleForTesting(visibility = PACKAGE)
+ @Override
public void dispatchConfigurationChanged(@NonNull Configuration newConfig) {
mCallbacksController.dispatchConfigurationChanged(newConfig);
}
@@ -170,4 +169,10 @@
public Bundle getWindowContextOptions() {
return mOptions;
}
+
+ @Override
+ public boolean shouldReportPrivateChanges() {
+ // Always dispatch config changes to WindowContext.
+ return true;
+ }
}
diff --git a/core/java/android/window/WindowContextController.java b/core/java/android/window/WindowContextController.java
index 1e2f454..d31e43f 100644
--- a/core/java/android/window/WindowContextController.java
+++ b/core/java/android/window/WindowContextController.java
@@ -86,7 +86,6 @@
* @param token The token used to attach to a window manager node. It is usually from
* {@link Context#getWindowContextToken()}.
*/
- @VisibleForTesting
public WindowContextController(@NonNull WindowTokenClient token) {
mToken = token;
}
diff --git a/core/java/android/window/WindowProviderService.java b/core/java/android/window/WindowProviderService.java
index c81c9ec..8468867 100644
--- a/core/java/android/window/WindowProviderService.java
+++ b/core/java/android/window/WindowProviderService.java
@@ -49,9 +49,11 @@
*
* @hide
*/
+@SuppressWarnings("HiddenSuperclass")
@TestApi
@UiContext
-public abstract class WindowProviderService extends Service implements WindowProvider {
+public abstract class WindowProviderService extends Service implements WindowProvider,
+ ConfigurationDispatcher {
private static final String TAG = WindowProviderService.class.getSimpleName();
@@ -240,4 +242,14 @@
mController.detachIfNeeded();
mCallbacksController.clearCallbacks();
}
+
+ /**
+ * {@inheritDoc}
+ *
+ * @hide
+ */
+ @Override
+ public void dispatchConfigurationChanged(@NonNull Configuration configuration) {
+ onConfigurationChanged(configuration);
+ }
}
diff --git a/core/java/android/window/WindowTokenClient.java b/core/java/android/window/WindowTokenClient.java
index f7bee61..9b296c8 100644
--- a/core/java/android/window/WindowTokenClient.java
+++ b/core/java/android/window/WindowTokenClient.java
@@ -107,6 +107,7 @@
* @param newDisplayId the updated {@link android.view.Display} ID
*/
@MainThread
+ @VisibleForTesting(visibility = PACKAGE)
public void onConfigurationChanged(Configuration newConfig, int newDisplayId) {
onConfigurationChanged(newConfig, newDisplayId, true /* shouldReportConfigChange */);
}
@@ -120,8 +121,6 @@
newDisplayId, true /* shouldReportConfigChange */).recycleOnUse());
}
- // TODO(b/192048581): rewrite this method based on WindowContext and WindowProviderService
- // are inherited from WindowProvider.
/**
* Called when {@link Configuration} updates from the server side receive.
*
@@ -168,7 +167,7 @@
CompatibilityInfo.applyOverrideIfNeeded(newConfig);
final boolean displayChanged;
final boolean shouldUpdateResources;
- final int diff;
+ final int publicDiff;
final Configuration currentConfig;
synchronized (mConfiguration) {
@@ -176,7 +175,7 @@
shouldUpdateResources = shouldUpdateResources(this, mConfiguration,
newConfig, newConfig /* overrideConfig */, displayChanged,
null /* configChanged */);
- diff = mConfiguration.diffPublicOnly(newConfig);
+ publicDiff = mConfiguration.diffPublicOnly(newConfig);
currentConfig = mShouldDumpConfigForIme ? new Configuration(mConfiguration) : null;
if (shouldUpdateResources) {
mConfiguration.setTo(newConfig);
@@ -199,17 +198,15 @@
// TODO(ag/9789103): update resource manager logic to track non-activity tokens
mResourcesManager.updateResourcesForActivity(this, newConfig, newDisplayId);
- if (shouldReportConfigChange && context instanceof WindowContext) {
- final WindowContext windowContext = (WindowContext) context;
- windowContext.dispatchConfigurationChanged(newConfig);
+ if (shouldReportConfigChange && context instanceof ConfigurationDispatcher dispatcher) {
+ // Updating resources implies some fields of configuration are updated despite they
+ // are public or not.
+ if (dispatcher.shouldReportPrivateChanges() || publicDiff != 0) {
+ dispatcher.dispatchConfigurationChanged(newConfig);
+ }
}
- if (shouldReportConfigChange && diff != 0
- && context instanceof WindowProviderService) {
- final WindowProviderService windowProviderService = (WindowProviderService) context;
- windowProviderService.onConfigurationChanged(newConfig);
- }
- freeTextLayoutCachesIfNeeded(diff);
+ freeTextLayoutCachesIfNeeded(publicDiff);
if (mShouldDumpConfigForIme) {
if (!shouldReportConfigChange) {
Log.d(TAG, "Only apply configuration update to Resources because "
@@ -218,7 +215,7 @@
+ ", config=" + context.getResources().getConfiguration()
+ ", display ID=" + context.getDisplayId() + "\n"
+ Debug.getCallers(5));
- } else if (diff == 0) {
+ } else if (publicDiff == 0) {
Log.d(TAG, "Configuration not dispatch to IME because configuration has no "
+ " public difference with updated config. "
+ " Current config=" + context.getResources().getConfiguration()
diff --git a/core/java/android/window/WindowTokenClientController.java b/core/java/android/window/WindowTokenClientController.java
index fcd7dfb..72278d9 100644
--- a/core/java/android/window/WindowTokenClientController.java
+++ b/core/java/android/window/WindowTokenClientController.java
@@ -25,7 +25,9 @@
import android.app.servertransaction.WindowContextInfoChangeItem;
import android.app.servertransaction.WindowContextWindowRemovalItem;
import android.content.Context;
+import android.content.res.Configuration;
import android.os.Bundle;
+import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.ArraySet;
@@ -50,6 +52,7 @@
private final Object mLock = new Object();
private final IApplicationThread mAppThread = ActivityThread.currentActivityThread()
.getApplicationThread();
+ private final Handler mHandler = ActivityThread.currentActivityThread().getHandler();
/** Attached {@link WindowTokenClient}. */
@GuardedBy("mLock")
@@ -257,6 +260,20 @@
}
}
+ /** Propagates the configuration change to the client token. */
+ public void onWindowConfigurationChanged(@NonNull IBinder clientToken,
+ @NonNull Configuration config, int displayId) {
+ final WindowTokenClient windowTokenClient = getWindowTokenClientIfAttached(clientToken);
+ if (windowTokenClient != null) {
+ // Let's make sure it's called on the main thread!
+ if (mHandler.getLooper().isCurrentThread()) {
+ windowTokenClient.onConfigurationChanged(config, displayId);
+ } else {
+ windowTokenClient.postOnConfigurationChanged(config, displayId);
+ }
+ }
+ }
+
@Nullable
private WindowTokenClient getWindowTokenClientIfAttached(@NonNull IBinder clientToken) {
if (!(clientToken instanceof WindowTokenClient windowTokenClient)) {
diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig
index 509e084..b805ac5 100644
--- a/core/java/android/window/flags/lse_desktop_experience.aconfig
+++ b/core/java/android/window/flags/lse_desktop_experience.aconfig
@@ -695,4 +695,14 @@
metadata {
purpose: PURPOSE_BUGFIX
}
-}
\ No newline at end of file
+}
+
+flag {
+ name: "enable_desktop_close_shortcut_bugfix"
+ namespace: "lse_desktop_experience"
+ description: "Fix the window-close keyboard shortcut in Desktop Mode."
+ bug: "394599430"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/core/java/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig
index ac6625b..54d0eef 100644
--- a/core/java/android/window/flags/windowing_sdk.aconfig
+++ b/core/java/android/window/flags/windowing_sdk.aconfig
@@ -55,14 +55,6 @@
flag {
namespace: "windowing_sdk"
- name: "enable_wm_extensions_for_all_flag"
- description: "Whether to enable WM Extensions for all devices"
- bug: "306666082"
- is_fixed_read_only: true
-}
-
-flag {
- namespace: "windowing_sdk"
name: "activity_embedding_animation_customization_flag"
description: "Whether the animation customization feature for AE is enabled"
bug: "293658614"
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index e60879e..38dc198 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -429,6 +429,27 @@
null /*dependentPackages*/, null /*dependencies*/, false /*isNative*/));
}
+ if (Flags.enableMediaAndLocationPreload()) {
+ // As these libraries are technically optional and not necessarily inherited from
+ // base_system.mk, only cache them if they exist.
+ final String mediaJarPath = "/system/framework/com.android.media.remotedisplay.jar";
+ if (new File(mediaJarPath).exists()) {
+ libs.add(new SharedLibraryInfo(
+ mediaJarPath, null /*packageName*/,
+ null /*codePaths*/, null /*name*/, 0 /*version*/,
+ SharedLibraryInfo.TYPE_BUILTIN, null /*declaringPackage*/,
+ null /*dependentPackages*/, null /*dependencies*/, false /*isNative*/));
+ }
+ final String locationJarPath = "/system/framework/com.android.location.provider.jar";
+ if (new File(locationJarPath).exists()) {
+ libs.add(new SharedLibraryInfo(
+ locationJarPath, null /*packageName*/,
+ null /*codePaths*/, null /*name*/, 0 /*version*/,
+ SharedLibraryInfo.TYPE_BUILTIN, null /*declaringPackage*/,
+ null /*dependentPackages*/, null /*dependencies*/, false /*isNative*/));
+ }
+ }
+
// WindowManager Extensions is an optional shared library that is required for WindowManager
// Jetpack to fully function. Since it is a widely used library, preload it to improve apps
// startup performance.
diff --git a/core/java/com/android/internal/os/flags.aconfig b/core/java/com/android/internal/os/flags.aconfig
index 25a9fbc..32cde50 100644
--- a/core/java/com/android/internal/os/flags.aconfig
+++ b/core/java/com/android/internal/os/flags.aconfig
@@ -53,6 +53,13 @@
}
flag {
+ name: "enable_media_and_location_preload"
+ namespace: "system_performance"
+ description: "Enables zygote preload of non-BCP media and location libraries."
+ bug: "241474956"
+}
+
+flag {
name: "use_transaction_codes_for_unknown_methods"
namespace: "stability"
description: "Use transaction codes when the method names is unknown"
diff --git a/core/java/com/android/internal/pm/pkg/component/AconfigFlags.java b/core/java/com/android/internal/pm/pkg/component/AconfigFlags.java
index 445dac7..21d000d 100644
--- a/core/java/com/android/internal/pm/pkg/component/AconfigFlags.java
+++ b/core/java/com/android/internal/pm/pkg/component/AconfigFlags.java
@@ -16,6 +16,7 @@
package com.android.internal.pm.pkg.component;
+import static android.provider.flags.Flags.newStoragePublicApi;
import static com.android.internal.pm.pkg.parsing.ParsingUtils.ANDROID_RES_NAMESPACE;
import android.aconfig.DeviceProtos;
@@ -27,6 +28,7 @@
import android.content.res.Flags;
import android.os.Environment;
import android.os.Process;
+import android.os.flagging.AconfigPackage;
import android.util.ArrayMap;
import android.util.Slog;
import android.util.Xml;
@@ -43,6 +45,7 @@
import java.io.IOException;
import java.util.Arrays;
import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
/**
* A class that manages a cache of all device feature flags and their default + override values.
@@ -58,7 +61,8 @@
private static final String OVERRIDE_PREFIX = "device_config_overrides/";
private static final String STAGED_PREFIX = "staged/";
- private final ArrayMap<String, Boolean> mFlagValues = new ArrayMap<>();
+ private final Map<String, Boolean> mFlagValues = new ArrayMap<>();
+ private final Map<String, AconfigPackage> mAconfigPackages = new ConcurrentHashMap<>();
public AconfigFlags() {
if (!Flags.manifestFlagging()) {
@@ -67,21 +71,31 @@
}
return;
}
- final var defaultFlagProtoFiles =
- (Process.myUid() == Process.SYSTEM_UID) ? DeviceProtos.parsedFlagsProtoPaths()
- : Arrays.asList(DeviceProtos.PATHS);
- for (String fileName : defaultFlagProtoFiles) {
- try (var inputStream = new FileInputStream(fileName)) {
- loadAconfigDefaultValues(inputStream.readAllBytes());
- } catch (IOException e) {
- Slog.e(LOG_TAG, "Failed to read Aconfig values from " + fileName, e);
+
+ if (useNewStorage()) {
+ Slog.i(LOG_TAG, "Using new flag storage");
+ } else {
+ Slog.i(LOG_TAG, "Using OLD proto flag storage");
+ final var defaultFlagProtoFiles =
+ (Process.myUid() == Process.SYSTEM_UID) ? DeviceProtos.parsedFlagsProtoPaths()
+ : Arrays.asList(DeviceProtos.PATHS);
+ for (String fileName : defaultFlagProtoFiles) {
+ try (var inputStream = new FileInputStream(fileName)) {
+ loadAconfigDefaultValues(inputStream.readAllBytes());
+ } catch (IOException e) {
+ Slog.w(LOG_TAG, "Failed to read Aconfig values from " + fileName, e);
+ }
+ }
+ if (Process.myUid() == Process.SYSTEM_UID) {
+ // Server overrides are only accessible to the system, no need to even try loading
+ // them in user processes.
+ loadServerOverrides();
}
}
- if (Process.myUid() == Process.SYSTEM_UID) {
- // Server overrides are only accessible to the system, no need to even try loading them
- // in user processes.
- loadServerOverrides();
- }
+ }
+
+ private static boolean useNewStorage() {
+ return newStoragePublicApi() && Flags.useNewAconfigStorage();
}
private void loadServerOverrides() {
@@ -200,7 +214,40 @@
*/
@Nullable
public Boolean getFlagValue(@NonNull String flagPackageAndName) {
- Boolean value = mFlagValues.get(flagPackageAndName);
+ if (useNewStorage()) {
+ return getFlagValueFromNewStorage(flagPackageAndName);
+ } else {
+ Boolean value = mFlagValues.get(flagPackageAndName);
+ if (DEBUG) {
+ Slog.v(LOG_TAG, "Aconfig flag value for " + flagPackageAndName + " = " + value);
+ }
+ return value;
+ }
+ }
+
+ private Boolean getFlagValueFromNewStorage(String flagPackageAndName) {
+ int index = flagPackageAndName.lastIndexOf('.');
+ if (index < 0) {
+ Slog.e(LOG_TAG, "Unable to parse package name from " + flagPackageAndName);
+ return null;
+ }
+ String flagPackage = flagPackageAndName.substring(0, index);
+ String flagName = flagPackageAndName.substring(index + 1);
+ Boolean value = null;
+ AconfigPackage aconfigPackage = mAconfigPackages.computeIfAbsent(flagPackage, p -> {
+ try {
+ return AconfigPackage.load(p);
+ } catch (Exception e) {
+ Slog.e(LOG_TAG, "Failed to load aconfig package " + p, e);
+ return null;
+ }
+ });
+ if (aconfigPackage != null) {
+ // Default value is false for when the flag is not found.
+ // Note: Unlike with the old storage, with AconfigPackage, we don't have a way to
+ // know if the flag is not found or if it's found but the value is false.
+ value = aconfigPackage.getBooleanFlagValue(flagName, false);
+ }
if (DEBUG) {
Slog.v(LOG_TAG, "Aconfig flag value for " + flagPackageAndName + " = " + value);
}
diff --git a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
index 05a33fe..d8cf258 100644
--- a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
@@ -160,19 +160,21 @@
Objects.requireNonNull(mConfigurationService,
"A null ProtoLog Configuration Service was provided!");
- try {
- var args = createConfigurationServiceRegisterClientArgs();
+ mBackgroundLoggingService.execute(() -> {
+ try {
+ var args = createConfigurationServiceRegisterClientArgs();
- final var groupArgs = mLogGroups.values().stream()
- .map(group -> new RegisterClientArgs
- .GroupConfig(group.name(), group.isLogToLogcat()))
- .toArray(RegisterClientArgs.GroupConfig[]::new);
- args.setGroups(groupArgs);
+ final var groupArgs = mLogGroups.values().stream()
+ .map(group -> new RegisterClientArgs
+ .GroupConfig(group.name(), group.isLogToLogcat()))
+ .toArray(RegisterClientArgs.GroupConfig[]::new);
+ args.setGroups(groupArgs);
- mConfigurationService.registerClient(this, args);
- } catch (RemoteException e) {
- throw new RuntimeException("Failed to register ProtoLog client");
- }
+ mConfigurationService.registerClient(this, args);
+ } catch (RemoteException e) {
+ throw new RuntimeException("Failed to register ProtoLog client");
+ }
+ });
}
/**
diff --git a/core/java/com/android/internal/protolog/WmProtoLogGroups.java b/core/java/com/android/internal/protolog/WmProtoLogGroups.java
index 4bd5d24..5edc2fb 100644
--- a/core/java/com/android/internal/protolog/WmProtoLogGroups.java
+++ b/core/java/com/android/internal/protolog/WmProtoLogGroups.java
@@ -100,6 +100,8 @@
WM_DEBUG_TPL(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false, Consts.TAG_WM),
WM_DEBUG_EMBEDDED_WINDOWS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
Consts.TAG_WM),
+ WM_DEBUG_PRESENTATION(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
+ Consts.TAG_WM),
TEST_GROUP(true, true, false, "WindowManagerProtoLogTest");
private final boolean mEnabled;
diff --git a/core/java/com/android/internal/widget/ConversationLayout.java b/core/java/com/android/internal/widget/ConversationLayout.java
index 9a5849a..641ecc9 100644
--- a/core/java/com/android/internal/widget/ConversationLayout.java
+++ b/core/java/com/android/internal/widget/ConversationLayout.java
@@ -399,7 +399,9 @@
@RemotableViewMethod(asyncImpl = "setIsCollapsedAsync")
public void setIsCollapsed(boolean isCollapsed) {
mIsCollapsed = isCollapsed;
- mMessagingLinearLayout.setMaxDisplayedLines(isCollapsed ? 1 : Integer.MAX_VALUE);
+ mMessagingLinearLayout.setMaxDisplayedLines(isCollapsed
+ ? TextUtils.isEmpty(mSummarizedContent) ? 1 : 2
+ : Integer.MAX_VALUE);
updateExpandButton();
updateContentEndPaddings();
}
@@ -448,7 +450,7 @@
List<MessagingMessage> newMessagingMessages;
mSummarizedContent = extras.getCharSequence(Notification.EXTRA_SUMMARIZED_CONTENT);
- if (mSummarizedContent != null && mIsCollapsed) {
+ if (!TextUtils.isEmpty(mSummarizedContent) && mIsCollapsed) {
Notification.MessagingStyle.Message summary =
new Notification.MessagingStyle.Message(mSummarizedContent, 0, "");
newMessagingMessages = createMessages(List.of(summary), false, usePrecomputedText);
@@ -1162,7 +1164,7 @@
nameOverride = mNameReplacement;
}
newGroup.setShowingAvatar(!mIsOneToOne && !mIsCollapsed);
- newGroup.setSingleLine(mIsCollapsed);
+ newGroup.setSingleLine(mIsCollapsed && TextUtils.isEmpty(mSummarizedContent));
newGroup.setSender(sender, nameOverride);
newGroup.setSending(groupIndex == (groups.size() - 1) && showSpinner);
mGroups.add(newGroup);
@@ -1462,7 +1464,6 @@
maxHeight = Math.max(maxHeight,
child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
}
-
maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
if (maxHeight != getMeasuredHeight()) {
setMeasuredDimension(getMeasuredWidth(), maxHeight);
diff --git a/core/java/com/android/internal/widget/MessagingLayout.java b/core/java/com/android/internal/widget/MessagingLayout.java
index 90ab660..e9d920c 100644
--- a/core/java/com/android/internal/widget/MessagingLayout.java
+++ b/core/java/com/android/internal/widget/MessagingLayout.java
@@ -198,7 +198,8 @@
/* isHistoric= */true, usePrecomputedText);
List<MessagingMessage> newMessagingMessages;
mSummarizedContent = extras.getCharSequence(Notification.EXTRA_SUMMARIZED_CONTENT);
- if (mSummarizedContent != null && mIsCollapsed) {
+ if (!TextUtils.isEmpty(mSummarizedContent) && mIsCollapsed) {
+ mMessagingLinearLayout.setMaxDisplayedLines(2);
Notification.MessagingStyle.Message summary =
new Notification.MessagingStyle.Message(mSummarizedContent, 0, "");
newMessagingMessages = createMessages(List.of(summary), false, usePrecomputedText);
@@ -488,7 +489,7 @@
if (sender != mUser && mNameReplacement != null) {
nameOverride = mNameReplacement;
}
- newGroup.setSingleLine(mIsCollapsed);
+ newGroup.setSingleLine(mIsCollapsed && TextUtils.isEmpty(mSummarizedContent));
newGroup.setShowingAvatar(!mIsCollapsed);
newGroup.setSender(sender, nameOverride);
newGroup.setSending(groupIndex == (groups.size() - 1) && showSpinner);
diff --git a/core/java/com/android/internal/widget/NotificationProgressBar.java b/core/java/com/android/internal/widget/NotificationProgressBar.java
index 5e82772..905d4dd 100644
--- a/core/java/com/android/internal/widget/NotificationProgressBar.java
+++ b/core/java/com/android/internal/widget/NotificationProgressBar.java
@@ -83,7 +83,7 @@
/** @see R.styleable#NotificationProgressBar_trackerHeight */
private final int mTrackerHeight;
- private int mTrackerWidth;
+ private int mTrackerDrawWidth = 0;
private int mTrackerPos;
private final Matrix mMatrix = new Matrix();
private Matrix mTrackerDrawMatrix = null;
@@ -157,7 +157,7 @@
} else {
// TODO: b/372908709 - maybe don't rerun the entire calculation every time the
// progress model is updated? For example, if the segments and parts aren't changed,
- // there is no need to call `processAndConvertToViewParts` again.
+ // there is no need to call `processModelAndConvertToViewParts` again.
final int progress = mProgressModel.getProgress();
final int progressMax = mProgressModel.getProgressMax();
@@ -286,8 +286,11 @@
private void configureTrackerBounds() {
// Reset the tracker draw matrix to null
mTrackerDrawMatrix = null;
+ mTrackerDrawWidth = 0;
- if (mTracker == null || mTrackerHeight <= 0) {
+ if (mTracker == null) return;
+ if (mTrackerHeight <= 0) {
+ mTrackerDrawWidth = mTracker.getIntrinsicWidth();
return;
}
@@ -306,14 +309,14 @@
if (dWidth > maxDWidth) {
scale = (float) mTrackerHeight / (float) dHeight;
dx = (maxDWidth * scale - dWidth * scale) * 0.5f;
- mTrackerWidth = (int) (maxDWidth * scale);
+ mTrackerDrawWidth = (int) (maxDWidth * scale);
} else if (dHeight > maxDHeight) {
scale = (float) mTrackerHeight * 0.5f / (float) dWidth;
dy = (maxDHeight * scale - dHeight * scale) * 0.5f;
- mTrackerWidth = mTrackerHeight / 2;
+ mTrackerDrawWidth = mTrackerHeight / 2;
} else {
scale = (float) mTrackerHeight / (float) dHeight;
- mTrackerWidth = (int) (dWidth * scale);
+ mTrackerDrawWidth = (int) (dWidth * scale);
}
mTrackerDrawMatrix.setScale(scale, scale);
@@ -449,7 +452,8 @@
segSegGap,
segPointGap,
pointRadius,
- mHasTrackerIcon
+ mHasTrackerIcon,
+ mTrackerDrawWidth
);
final float segmentMinWidth = mNotificationProgressDrawable.getSegmentMinWidth();
@@ -465,7 +469,6 @@
segmentMinWidth,
pointRadius,
progressFraction,
- width,
isStyledByProgress,
progressGap
);
@@ -493,8 +496,8 @@
pointRadius,
mHasTrackerIcon,
segmentMinWidth,
- isStyledByProgress
- );
+ isStyledByProgress,
+ mTrackerDrawWidth);
} catch (NotEnoughWidthToFitAllPartsException ex) {
Log.w(TAG, "Failed to stretch and rescale segments with single segment fallback",
ex);
@@ -522,8 +525,8 @@
pointRadius,
mHasTrackerIcon,
segmentMinWidth,
- isStyledByProgress
- );
+ isStyledByProgress,
+ mTrackerDrawWidth);
} catch (NotEnoughWidthToFitAllPartsException ex) {
Log.w(TAG,
"Failed to stretch and rescale segments with single segments and no points",
@@ -537,16 +540,20 @@
mParts,
mProgressDrawableParts,
progressFraction,
- width,
isStyledByProgress,
progressGap);
}
+ // Extend the first and last segments to fill the entire width.
+ p.first.getFirst().setStart(0);
+ p.first.getLast().setEnd(width);
+
if (DEBUG) {
Log.d(TAG, "Updating NotificationProgressDrawable parts");
}
mNotificationProgressDrawable.setParts(p.first);
- mAdjustedProgressFraction = p.second / width;
+ mAdjustedProgressFraction =
+ (p.second - mTrackerDrawWidth / 2F) / (width - mTrackerDrawWidth);
}
private void updateTrackerAndBarPos(int w, int h) {
@@ -607,7 +614,7 @@
int available = w - mPaddingLeft - mPaddingRight;
final int trackerWidth = tracker.getIntrinsicWidth();
final int trackerHeight = tracker.getIntrinsicHeight();
- available -= ((mTrackerHeight <= 0) ? trackerWidth : mTrackerWidth);
+ available -= mTrackerDrawWidth;
final int trackerPos = (int) (progressFraction * available + 0.5f);
@@ -672,7 +679,7 @@
canvas.translate(mPaddingLeft + mTrackerPos, mPaddingTop);
if (mTrackerHeight > 0) {
- canvas.clipRect(0, 0, mTrackerWidth, mTrackerHeight);
+ canvas.clipRect(0, 0, mTrackerDrawWidth, mTrackerHeight);
}
if (mTrackerDrawMatrix != null) {
@@ -751,6 +758,7 @@
throw new IllegalArgumentException("Invalid progress : " + progress);
}
+
for (ProgressStyle.Point point : points) {
final int pos = point.getPosition();
if (pos < 0 || pos > progressMax) {
@@ -758,6 +766,19 @@
}
}
+ // There should be no points at start or end. If there are, drop them with a warning.
+ points.removeIf(point -> {
+ final int pos = point.getPosition();
+ if (pos == 0) {
+ Log.w(TAG, "Dropping point at start");
+ return true;
+ } else if (pos == progressMax) {
+ Log.w(TAG, "Dropping point at end");
+ return true;
+ }
+ return false;
+ });
+
final Map<Integer, ProgressStyle.Segment> startToSegmentMap = generateStartToSegmentMap(
segments);
final Map<Integer, ProgressStyle.Point> positionToPointMap = generatePositionToPointMap(
@@ -891,12 +912,14 @@
float segSegGap,
float segPointGap,
float pointRadius,
- boolean hasTrackerIcon
- ) {
+ boolean hasTrackerIcon,
+ int trackerDrawWidth) {
List<DrawablePart> drawableParts = new ArrayList<>();
- // generally, we will start drawing at (x, y) and end at (x+w, y)
- float x = (float) 0;
+ float available = totalWidth - trackerDrawWidth;
+ // Generally, we will start the first segment at (x+trackerDrawWidth/2, y) and end the last
+ // segment at (x+w-trackerDrawWidth/2, y)
+ float x = trackerDrawWidth / 2F;
final int nParts = parts.size();
for (int iPart = 0; iPart < nParts; iPart++) {
@@ -904,15 +927,14 @@
final Part prevPart = iPart == 0 ? null : parts.get(iPart - 1);
final Part nextPart = iPart + 1 == nParts ? null : parts.get(iPart + 1);
if (part instanceof Segment segment) {
- final float segWidth = segment.mFraction * totalWidth;
+ final float segWidth = segment.mFraction * available;
// Advance the start position to account for a point immediately prior.
- final float startOffset = getSegStartOffset(prevPart, pointRadius, segPointGap,
- iPart == 1);
+ final float startOffset = getSegStartOffset(prevPart, pointRadius, segPointGap);
final float start = x + startOffset;
// Retract the end position to account for the padding and a point immediately
// after.
final float endOffset = getSegEndOffset(segment, nextPart, pointRadius, segPointGap,
- segSegGap, iPart == nParts - 2, hasTrackerIcon);
+ segSegGap, hasTrackerIcon);
final float end = x + segWidth - endOffset;
drawableParts.add(new DrawableSegment(start, end, segment.mColor, segment.mFaded));
@@ -927,16 +949,6 @@
final float pointWidth = 2 * pointRadius;
float start = x - pointRadius;
float end = x + pointRadius;
- // Only shift the points right at the start/end.
- // For the points close to the start/end, the segment minimum width requirement
- // would take care of shifting them to be within the bounds.
- if (iPart == 0) {
- start = 0;
- end = pointWidth;
- } else if (iPart == nParts - 1) {
- start = totalWidth - pointWidth;
- end = totalWidth;
- }
drawableParts.add(new DrawablePoint(start, end, point.mColor));
}
@@ -945,16 +957,13 @@
return drawableParts;
}
- private static float getSegStartOffset(Part prevPart, float pointRadius, float segPointGap,
- boolean isSecondPart) {
+ private static float getSegStartOffset(Part prevPart, float pointRadius, float segPointGap) {
if (!(prevPart instanceof Point)) return 0F;
- final float pointOffset = isSecondPart ? pointRadius : 0;
- return pointOffset + pointRadius + segPointGap;
+ return pointRadius + segPointGap;
}
private static float getSegEndOffset(Segment seg, Part nextPart, float pointRadius,
- float segPointGap, float segSegGap, boolean isSecondToLastPart,
- boolean hasTrackerIcon) {
+ float segPointGap, float segSegGap, boolean hasTrackerIcon) {
if (nextPart == null) return 0F;
if (nextPart instanceof Segment nextSeg) {
if (!seg.mFaded && nextSeg.mFaded) {
@@ -964,8 +973,7 @@
return segSegGap;
}
- final float pointOffset = isSecondToLastPart ? pointRadius : 0;
- return segPointGap + pointRadius + pointOffset;
+ return segPointGap + pointRadius;
}
/**
@@ -980,7 +988,6 @@
float segmentMinWidth,
float pointRadius,
float progressFraction,
- float totalWidth,
boolean isStyledByProgress,
float progressGap
) throws NotEnoughWidthToFitAllPartsException {
@@ -1003,7 +1010,6 @@
parts,
drawableParts,
progressFraction,
- totalWidth,
isStyledByProgress,
progressGap);
}
@@ -1056,7 +1062,6 @@
parts,
drawableParts,
progressFraction,
- totalWidth,
isStyledByProgress,
progressGap);
}
@@ -1071,11 +1076,12 @@
List<Part> parts,
List<DrawablePart> drawableParts,
float progressFraction,
- float totalWidth,
boolean isStyledByProgress,
float progressGap
) {
- if (progressFraction == 1) return new Pair<>(drawableParts, totalWidth);
+ if (progressFraction == 1) {
+ return new Pair<>(drawableParts, drawableParts.getLast().getEnd());
+ }
int iPartFirstSegmentToStyle = -1;
int iPartSegmentToSplit = -1;
@@ -1162,14 +1168,15 @@
float pointRadius,
boolean hasTrackerIcon,
float segmentMinWidth,
- boolean isStyledByProgress
+ boolean isStyledByProgress,
+ int trackerDrawWidth
) throws NotEnoughWidthToFitAllPartsException {
List<Part> parts = processModelAndConvertToViewParts(segments, points, progress,
progressMax);
List<DrawablePart> drawableParts = processPartsAndConvertToDrawableParts(parts, totalWidth,
- segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+ segSegGap, segPointGap, pointRadius, hasTrackerIcon, trackerDrawWidth);
return maybeStretchAndRescaleSegments(parts, drawableParts, segmentMinWidth, pointRadius,
- getProgressFraction(progressMax, progress), totalWidth, isStyledByProgress,
+ getProgressFraction(progressMax, progress), isStyledByProgress,
hasTrackerIcon ? 0F : segSegGap);
}
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 447822f..06702e2 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -134,6 +134,10 @@
"android_app_ActivityThread.cpp",
"android_app_NativeActivity.cpp",
"android_app_admin_SecurityLog.cpp",
+ "android_media_ImageReader.cpp",
+ "android_media_ImageWriter.cpp",
+ "android_media_PublicFormatUtils.cpp",
+ "android_media_Utils.cpp",
"android_opengl_EGL14.cpp",
"android_opengl_EGL15.cpp",
"android_opengl_EGLExt.cpp",
@@ -531,3 +535,35 @@
"vintf",
],
}
+
+cc_library_shared {
+ name: "libmedia_jni_utils",
+ srcs: [
+ ":libgui_frame_event_aidl",
+ "android_media_Utils.cpp",
+ ],
+
+ header_libs: [
+ "libgui_headers",
+ ],
+
+ shared_libs: [
+ "liblog",
+ "libui",
+ "libutils",
+ ],
+
+ include_dirs: [
+ "system/media/camera/include",
+ ],
+
+ export_include_dirs: ["."],
+
+ cflags: [
+ "-Wall",
+ "-Werror",
+ "-Wno-error=deprecated-declarations",
+ "-Wunused",
+ "-Wunreachable-code",
+ ],
+}
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 5c0b720..b2b8263 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -102,7 +102,10 @@
extern int register_android_media_AudioProductStrategies(JNIEnv *env);
extern int register_android_media_AudioVolumeGroups(JNIEnv *env);
extern int register_android_media_AudioVolumeGroupChangeHandler(JNIEnv *env);
+extern int register_android_media_ImageReader(JNIEnv *env);
+extern int register_android_media_ImageWriter(JNIEnv *env);
extern int register_android_media_MicrophoneInfo(JNIEnv *env);
+extern int register_android_media_PublicFormatUtils(JNIEnv *env);
extern int register_android_media_ToneGenerator(JNIEnv *env);
extern int register_android_media_audio_common_AidlConversion(JNIEnv* env);
extern int register_android_media_midi(JNIEnv *env);
@@ -1658,8 +1661,11 @@
REG_JNI(register_android_media_AudioProductStrategies),
REG_JNI(register_android_media_AudioVolumeGroups),
REG_JNI(register_android_media_AudioVolumeGroupChangeHandler),
+ REG_JNI(register_android_media_ImageReader),
+ REG_JNI(register_android_media_ImageWriter),
REG_JNI(register_android_media_MediaMetrics),
REG_JNI(register_android_media_MicrophoneInfo),
+ REG_JNI(register_android_media_PublicFormatUtils),
REG_JNI(register_android_media_RemoteDisplay),
REG_JNI(register_android_media_ToneGenerator),
REG_JNI(register_android_media_audio_common_AidlConversion),
diff --git a/media/jni/android_media_ImageReader.cpp b/core/jni/android_media_ImageReader.cpp
similarity index 99%
rename from media/jni/android_media_ImageReader.cpp
rename to core/jni/android_media_ImageReader.cpp
index effa92c..20b9c57 100644
--- a/media/jni/android_media_ImageReader.cpp
+++ b/core/jni/android_media_ImageReader.cpp
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+#undef ANDROID_UTILS_REF_BASE_DISABLE_IMPLICIT_CONSTRUCTION // TODO:remove this and fix code
//#define LOG_NDEBUG 0
#define LOG_TAG "ImageReader_JNI"
diff --git a/media/jni/android_media_ImageWriter.cpp b/core/jni/android_media_ImageWriter.cpp
similarity index 99%
rename from media/jni/android_media_ImageWriter.cpp
rename to core/jni/android_media_ImageWriter.cpp
index 93deb51..1357dd8 100644
--- a/media/jni/android_media_ImageWriter.cpp
+++ b/core/jni/android_media_ImageWriter.cpp
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+#undef ANDROID_UTILS_REF_BASE_DISABLE_IMPLICIT_CONSTRUCTION // TODO:remove this and fix code
//#define LOG_NDEBUG 0
#define LOG_TAG "ImageWriter_JNI"
diff --git a/media/jni/android_media_PublicFormatUtils.cpp b/core/jni/android_media_PublicFormatUtils.cpp
similarity index 100%
rename from media/jni/android_media_PublicFormatUtils.cpp
rename to core/jni/android_media_PublicFormatUtils.cpp
diff --git a/media/jni/android_media_Utils.cpp b/core/jni/android_media_Utils.cpp
similarity index 100%
rename from media/jni/android_media_Utils.cpp
rename to core/jni/android_media_Utils.cpp
diff --git a/media/jni/android_media_Utils.h b/core/jni/android_media_Utils.h
similarity index 100%
rename from media/jni/android_media_Utils.h
rename to core/jni/android_media_Utils.h
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index c9f4cdc..5104988 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -9385,6 +9385,10 @@
android:permission="android.permission.BIND_JOB_SERVICE">
</service>
+ <service android:name="com.android.server.security.UpdateCertificateRevocationStatusJobService"
+ android:permission="android.permission.BIND_JOB_SERVICE">
+ </service>
+
<service android:name="com.android.server.pm.PackageManagerShellCommandDataLoader"
android:exported="false">
<intent-filter>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index d7ffcc5..17acf9a 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -7352,6 +7352,21 @@
default on this device-->
<string-array translatable="false" name="config_notificationDefaultUnsupportedAdjustments" />
+ <!-- Preference name of bugreport-->
+ <string name="prefs_bugreport" translatable="false">bugreports</string>
+
+ <!-- key value of warning state stored in bugreport preference-->
+ <string name="key_warning_state" translatable="false">warning-state</string>
+
+ <!-- Bugreport warning dialog state unknown-->
+ <integer name="bugreport_state_unknown">0</integer>
+
+ <!-- Bugreport warning dialog state shows the warning dialog-->
+ <integer name="bugreport_state_show">1</integer>
+
+ <!-- Bugreport warning dialog state skips the warning dialog-->
+ <integer name="bugreport_state_hide">2</integer>
+
<!-- By default ActivityOptions#makeScaleUpAnimation is only used between activities. This
config enables OEMs to support its usage across tasks.-->
<bool name="config_enableCrossTaskScaleUpAnimation">false</bool>
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 595160e..e9d87e4 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -431,15 +431,18 @@
<!-- The minimum height of the notification content (even when there's only one line of text) -->
<dimen name="notification_2025_content_min_height">40dp</dimen>
- <!-- Height of a headerless notification with one or two lines -->
- <!-- 16 * 2 (margins) + 40 (min content height) = 72 (notification) -->
+ <!-- Max height of a collapsed (headerless) notification with a summarization -->
+ <dimen name="notification_collapsed_height_with_summarization">156dp</dimen>
+
+ <!-- Max height of a collapsed (headerless) notification with one or two lines -->
+ <!-- 16 * 2 (margins) + 48 (min content height) = 72 (notification) -->
<dimen name="notification_2025_min_height">72dp</dimen>
<!-- Height of a headerless notification with one line -->
<!-- 16 * 2 (margins) + 24 (1 line) = 56 (notification) -->
<dimen name="notification_headerless_min_height">56dp</dimen>
- <!-- Height of a small two-line notification -->
+ <!-- Max height of a collapsed two-line notification -->
<!-- 20 * 2 (margins) + 24 * 2 (2 lines) = 88 (notification) -->
<dimen name="notification_min_height">88dp</dimen>
diff --git a/core/res/res/values/ids.xml b/core/res/res/values/ids.xml
index 3b39a65..5b5ef09 100644
--- a/core/res/res/values/ids.xml
+++ b/core/res/res/values/ids.xml
@@ -157,6 +157,9 @@
<!-- Accessibility action identifier for {@link android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction#ACTION_CONTEXT_CLICK}. -->
<item type="id" name="accessibilityActionContextClick" />
+ <!-- Accessibility action identifier for {@link android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction#ACTION_SET_EXTENDED_SELECTION}. -->
+ <item type="id" name="accessibilityActionSetExtendedSelection" />
+
<item type="id" name="remote_input_tag" />
<item type="id" name="pending_intent_tag" />
<item type="id" name="remote_checked_change_listener_tag" />
diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml
index 2d411d0..e3137e2 100644
--- a/core/res/res/values/public-staging.xml
+++ b/core/res/res/values/public-staging.xml
@@ -129,6 +129,8 @@
<staging-public-group type="id" first-id="0x01b20000">
<!-- @FlaggedApi(android.appwidget.flags.Flags.FLAG_ENGAGEMENT_METRICS) -->
<public name="remoteViewsMetricsId"/>
+ <!-- @FlaggedApi("android.view.accessibility.a11y_selection_api") -->
+ <public name="accessibilityActionSetExtendedSelection"/>
</staging-public-group>
<staging-public-group type="style" first-id="0x01b10000">
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 6701e63..da6fb3b 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -5899,6 +5899,7 @@
<java-symbol type="array" name="config_notificationDefaultUnsupportedAdjustments" />
<java-symbol type="drawable" name="ic_notification_summarization" />
+ <java-symbol type="dimen" name="notification_collapsed_height_with_summarization" />
<!-- Advanced Protection Service USB feature -->
<java-symbol type="string" name="usb_apm_usb_plugged_in_when_locked_notification_title" />
@@ -5906,6 +5907,12 @@
<java-symbol type="string" name="usb_apm_usb_suspicious_activity_notification_title" />
<java-symbol type="string" name="usb_apm_usb_suspicious_activity_notification_text" />
+ <java-symbol type="string" name="prefs_bugreport" />
+ <java-symbol type="string" name="key_warning_state" />
+ <java-symbol type="integer" name="bugreport_state_unknown" />
+ <java-symbol type="integer" name="bugreport_state_show" />
+ <java-symbol type="integer" name="bugreport_state_hide" />
+
<!-- Enable OEMs to support scale up anim across tasks.-->
<java-symbol type="bool" name="config_enableCrossTaskScaleUpAnimation" />
diff --git a/core/tests/coretests/src/android/app/NotificationTest.java b/core/tests/coretests/src/android/app/NotificationTest.java
index ca6ad6f..f89e441 100644
--- a/core/tests/coretests/src/android/app/NotificationTest.java
+++ b/core/tests/coretests/src/android/app/NotificationTest.java
@@ -2532,6 +2532,46 @@
@Test
@EnableFlags(Flags.FLAG_API_RICH_ONGOING)
+ public void progressStyle_addProgressPoint_dropsZeroPoints() {
+ // GIVEN
+ final Notification.ProgressStyle progressStyle = new Notification.ProgressStyle();
+ // Points should not be a negative integer.
+ progressStyle
+ .addProgressPoint(new Notification.ProgressStyle.Point(0));
+
+ // THEN
+ assertThat(progressStyle.getProgressPoints()).isEmpty();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_API_RICH_ONGOING)
+ public void progressStyle_setProgressPoint_dropsZeroPoints() {
+ // GIVEN
+ final Notification.ProgressStyle progressStyle = new Notification.ProgressStyle();
+ // Points should not be a negative integer.
+ progressStyle
+ .setProgressPoints(List.of(new Notification.ProgressStyle.Point(0)));
+
+ // THEN
+ assertThat(progressStyle.getProgressPoints()).isEmpty();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_API_RICH_ONGOING)
+ public void progressStyle_createProgressModel_ignoresPointsAtMax() {
+ // GIVEN
+ final Notification.ProgressStyle progressStyle = new Notification.ProgressStyle();
+ progressStyle.addProgressSegment(new Notification.ProgressStyle.Segment(100));
+ // Points should not larger than progress maximum.
+ progressStyle
+ .addProgressPoint(new Notification.ProgressStyle.Point(100));
+
+ // THEN
+ assertThat(progressStyle.createProgressModel(Color.BLUE, Color.RED).getPoints()).isEmpty();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_API_RICH_ONGOING)
public void progressStyle_createProgressModel_ignoresPointsExceedingMax() {
// GIVEN
final Notification.ProgressStyle progressStyle = new Notification.ProgressStyle();
@@ -2573,14 +2613,14 @@
// THEN
assertThat(progressStyle.createProgressModel(defaultProgressColor, backgroundColor)
.getPoints()).isEqualTo(
- List.of(new Notification.ProgressStyle.Point(0)
- .setColor(expectedProgressColor),
- new Notification.ProgressStyle.Point(20)
+ List.of(new Notification.ProgressStyle.Point(20)
.setColor(expectedProgressColor),
new Notification.ProgressStyle.Point(50)
.setColor(expectedProgressColor),
new Notification.ProgressStyle.Point(70)
- .setColor(expectedProgressColor)
+ .setColor(expectedProgressColor),
+ new Notification.ProgressStyle.Point(80)
+ .setColor(expectedProgressColor)
)
);
}
diff --git a/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java b/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java
index 8fa5103..dc2f0a6 100644
--- a/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java
+++ b/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java
@@ -307,8 +307,10 @@
assertEquals(DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_ADDED,
mDisplayManagerGlobal
.mapFiltersToInternalEventFlag(DisplayManager.EVENT_TYPE_DISPLAY_ADDED, 0));
- assertEquals(DISPLAY_CHANGE_EVENTS, mDisplayManagerGlobal
- .mapFiltersToInternalEventFlag(DisplayManager.EVENT_TYPE_DISPLAY_CHANGED, 0));
+ assertEquals(DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_BASIC_CHANGED,
+ mDisplayManagerGlobal
+ .mapFiltersToInternalEventFlag(DisplayManager.EVENT_TYPE_DISPLAY_CHANGED,
+ 0));
assertEquals(DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REMOVED,
mDisplayManagerGlobal.mapFiltersToInternalEventFlag(
DisplayManager.EVENT_TYPE_DISPLAY_REMOVED, 0));
diff --git a/core/tests/coretests/src/android/hardware/input/InputFlagsTest.java b/core/tests/coretests/src/android/hardware/input/InputFlagsTest.java
deleted file mode 100644
index b4f1dee..0000000
--- a/core/tests/coretests/src/android/hardware/input/InputFlagsTest.java
+++ /dev/null
@@ -1,51 +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 android.hardware.input;
-
-import static com.android.hardware.input.Flags.keyboardA11yStickyKeysFlag;
-import static com.android.hardware.input.Flags.keyboardLayoutPreviewFlag;
-
-import android.platform.test.annotations.Presubmit;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/**
- * Tests for {@link com.android.hardware.input.Flags}
- *
- * Build/Install/Run:
- * atest FrameworksCoreTests:InputFlagsTest
- */
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-@Presubmit
-public class InputFlagsTest {
-
- /**
- * Test that the flags work
- */
- @Test
- public void testFlags() {
- // No crash when accessing the flag.
- keyboardLayoutPreviewFlag();
- keyboardA11yStickyKeysFlag();
- }
-}
-
diff --git a/core/tests/coretests/src/android/hardware/input/OWNERS b/core/tests/coretests/src/android/hardware/input/OWNERS
deleted file mode 100644
index 3f8a602..0000000
--- a/core/tests/coretests/src/android/hardware/input/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-include /core/java/android/hardware/input/OWNERS
-
diff --git a/core/tests/coretests/src/android/view/WindowManagerTests.java b/core/tests/coretests/src/android/view/WindowManagerTests.java
index 211d768..b32aa46 100644
--- a/core/tests/coretests/src/android/view/WindowManagerTests.java
+++ b/core/tests/coretests/src/android/view/WindowManagerTests.java
@@ -16,9 +16,6 @@
package android.view;
-import static com.android.window.flags.Flags.FLAG_ENABLE_WM_EXTENSIONS_FOR_ALL_FLAG;
-
-import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeTrue;
@@ -47,19 +44,8 @@
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@Test
- public void testHasWindowExtensionsEnabled_flagDisabled() {
- mSetFlagsRule.disableFlags(FLAG_ENABLE_WM_EXTENSIONS_FOR_ALL_FLAG);
-
- // Before FLAG_ENABLE_WM_EXTENSIONS_FOR_ALL_FLAG, Extensions are always bundled with AE.
- assertEquals(isActivityEmbeddingEnableForAll(),
- WindowManager.hasWindowExtensionsEnabled());
- }
-
- @Test
- public void testHasWindowExtensionsEnabled_flagEnabled() {
- mSetFlagsRule.enableFlags(FLAG_ENABLE_WM_EXTENSIONS_FOR_ALL_FLAG);
-
- // Extensions should be enabled on all devices.
+ public void testHasWindowExtensionsEnabled() {
+ // Extensions should be enabled on all phones/tablets.
assertTrue(WindowManager.hasWindowExtensionsEnabled());
}
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java
index 3b0eab4..cc5c6af 100644
--- a/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java
@@ -46,7 +46,7 @@
// The number of fields tested in the corresponding CTS AccessibilityNodeInfoTest:
// See fullyPopulateAccessibilityNodeInfo, assertEqualsAccessibilityNodeInfo,
// and assertAccessibilityNodeInfoCleared in that class.
- private static final int NUM_MARSHALLED_PROPERTIES = 47;
+ private static final int NUM_MARSHALLED_PROPERTIES = 48;
/**
* The number of properties that are purposely not marshalled
diff --git a/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java b/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java
index b42bcee..5f89f9c 100644
--- a/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java
+++ b/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java
@@ -36,11 +36,15 @@
import android.graphics.Insets;
import android.os.Handler;
import android.os.RemoteException;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.util.SparseArray;
import android.view.View;
import android.view.autofill.AutofillId;
+import android.view.contentcapture.flags.Flags;
import android.view.contentprotection.ContentProtectionEventProcessor;
import androidx.test.core.app.ApplicationProvider;
@@ -90,6 +94,8 @@
@Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+ @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
@Mock private IContentCaptureManager mMockSystemServerInterface;
@Mock private ContentProtectionEventProcessor mMockContentProtectionEventProcessor;
@@ -407,6 +413,7 @@
}
@Test
+ @DisableFlags(Flags.FLAG_FLUSH_AFTER_EACH_FRAME)
@SuppressWarnings("GuardedBy")
public void notifyContentCaptureEvents_started_ContentCaptureEnabled_ProtectionEnabled()
throws RemoteException {
@@ -434,6 +441,34 @@
}
@Test
+ @EnableFlags(Flags.FLAG_FLUSH_AFTER_EACH_FRAME)
+ @SuppressWarnings("GuardedBy")
+ public void notifyContentCaptureEvents_started_ContentCaptureEnabled_ProtectionEnabled_Flush()
+ throws RemoteException {
+ ContentCaptureOptions options =
+ createOptions(
+ /* enableContentCaptureReceiver= */ true,
+ /* enableContentProtectionReceiver= */ true);
+ MainContentCaptureSession session = createSession(options);
+ session.mDirectServiceInterface = mMockContentCaptureDirectManager;
+
+ session.onSessionStarted(0x2, null);
+ // Override the processor for interaction verification.
+ session.mContentProtectionEventProcessor = mMockContentProtectionEventProcessor;
+ notifyContentCaptureEvents(session);
+ mTestableLooper.processAllMessages();
+
+ // Force flush will happen twice.
+ verify(mMockContentCaptureDirectManager, times(1))
+ .sendEvents(any(), eq(FLUSH_REASON_VIEW_TREE_APPEARING), any());
+ verify(mMockContentCaptureDirectManager, times(1))
+ .sendEvents(any(), eq(FLUSH_REASON_VIEW_TREE_APPEARED), any());
+ // 5 view events + 2 view tree events + 1 flush event
+ verify(mMockContentProtectionEventProcessor, times(8)).processEvent(any());
+ assertThat(session.mEvents).isEmpty();
+ }
+
+ @Test
public void notifyViewAppearedBelowMaximumBufferSize() throws RemoteException {
ContentCaptureOptions options =
createOptions(
diff --git a/core/tests/coretests/src/android/window/ConfigurationDispatcherTest.kt b/core/tests/coretests/src/android/window/ConfigurationDispatcherTest.kt
new file mode 100644
index 0000000..e8e8a2e
--- /dev/null
+++ b/core/tests/coretests/src/android/window/ConfigurationDispatcherTest.kt
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.window
+
+import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW
+import android.content.ContextWrapper
+import android.content.res.Configuration
+import android.content.res.Configuration.ORIENTATION_PORTRAIT
+import android.platform.test.annotations.Presubmit
+import android.view.Display.DEFAULT_DISPLAY
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+/**
+ * Test to verify [ConfigurationDispatcher]
+ *
+ * Build/Install/Run:
+ * atest FrameworksCoreTests:ConfigurationDispatcherTest
+ */
+@SmallTest
+@Presubmit
+@RunWith(Parameterized::class)
+class ConfigurationDispatcherTest(private val shouldReportPrivateChanges: Boolean) {
+
+ /**
+ * Verifies [ConfigurationDispatcher.shouldReportPrivateChanges].
+ */
+ @Test
+ fun testConfigurationDispatcher() {
+ val receiver = TestConfigurationReceiver(shouldReportPrivateChanges)
+ val config = Configuration().apply {
+ orientation = ORIENTATION_PORTRAIT
+ }
+
+ // Verify public config field change
+ receiver.windowToken.onConfigurationChangedInner(receiver, config, DEFAULT_DISPLAY, true)
+
+ assertThat(receiver.receivedConfig).isEqualTo(config)
+
+ // Clear the config value
+ receiver.receivedConfig.unset()
+
+ // Verify private config field change
+ config.windowConfiguration.windowingMode = WINDOWING_MODE_MULTI_WINDOW
+
+ receiver.windowToken.onConfigurationChangedInner(receiver, config, DEFAULT_DISPLAY, true)
+
+ assertThat(receiver.receivedConfig).isEqualTo(
+ if (shouldReportPrivateChanges) {
+ config
+ } else {
+ Configuration.EMPTY
+ }
+ )
+ }
+
+ /**
+ * Test [android.content.Context] to implement [ConfigurationDispatcher] for testing.
+ *
+ * @param shouldReportPrivateChanges used to override
+ * [ConfigurationDispatcher.shouldReportPrivateChanges] for testing,
+ */
+ private class TestConfigurationReceiver(
+ private val shouldReportPrivateChanges: Boolean
+ ) : ContextWrapper(null), ConfigurationDispatcher {
+ val windowToken = WindowTokenClient()
+ val receivedConfig = Configuration()
+
+ init {
+ windowToken.attachContext(this)
+ }
+
+ override fun dispatchConfigurationChanged(configuration: Configuration) {
+ receivedConfig.setTo(configuration)
+ }
+
+ override fun shouldReportPrivateChanges(): Boolean {
+ return shouldReportPrivateChanges
+ }
+
+ override fun getDisplayId(): Int {
+ return DEFAULT_DISPLAY
+ }
+ }
+
+ companion object {
+ @Parameterized.Parameters(name = "shouldReportPrivateChange={0}")
+ @JvmStatic
+ fun data(): Collection<Any> {
+ return listOf(true, false)
+ }
+ }
+}
\ No newline at end of file
diff --git a/core/tests/coretests/src/android/window/WindowTokenClientControllerTest.java b/core/tests/coretests/src/android/window/WindowTokenClientControllerTest.java
index 84ff40f..116dc12 100644
--- a/core/tests/coretests/src/android/window/WindowTokenClientControllerTest.java
+++ b/core/tests/coretests/src/android/window/WindowTokenClientControllerTest.java
@@ -266,4 +266,25 @@
verify(mWindowTokenClient).onWindowTokenRemoved();
}
+
+ @Test
+ public void testOnWindowConfigurationChanged_propagatedToCorrectToken() throws RemoteException {
+ doReturn(mWindowContextInfo).when(mWindowManagerService)
+ .attachWindowContextToDisplayContent(any(), any(), anyInt());
+
+ mController.onWindowConfigurationChanged(mWindowTokenClient, mConfiguration,
+ DEFAULT_DISPLAY + 1);
+
+ // Not propagated before attaching
+ verify(mWindowTokenClient, never()).onConfigurationChanged(mConfiguration,
+ DEFAULT_DISPLAY + 1);
+
+ assertTrue(mController.attachToDisplayContent(mWindowTokenClient, DEFAULT_DISPLAY));
+
+ mController.onWindowConfigurationChanged(mWindowTokenClient, mConfiguration,
+ DEFAULT_DISPLAY + 1);
+
+ // Now that's attached, propagating it.
+ verify(mWindowTokenClient).postOnConfigurationChanged(mConfiguration, DEFAULT_DISPLAY + 1);
+ }
}
diff --git a/core/tests/coretests/src/com/android/internal/util/RateLimitingCacheTest.java b/core/tests/coretests/src/com/android/internal/util/RateLimitingCacheTest.java
index 52ff79d..2edab62 100644
--- a/core/tests/coretests/src/com/android/internal/util/RateLimitingCacheTest.java
+++ b/core/tests/coretests/src/com/android/internal/util/RateLimitingCacheTest.java
@@ -22,6 +22,8 @@
import android.os.SystemClock;
+import android.os.SystemClock;
+
import androidx.test.runner.AndroidJUnit4;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -54,13 +56,13 @@
*/
@Test
public void testTtl_Zero() {
- TestRateLimitingCache<Integer> s = new TestRateLimitingCache<>(0);
+ RateLimitingCache<Integer> s = new RateLimitingCache<>(0);
int first = s.get(mFetcher);
assertEquals(first, 0);
int second = s.get(mFetcher);
assertEquals(second, 1);
- s.advanceTime(20);
+ SystemClock.sleep(20);
int third = s.get(mFetcher);
assertEquals(third, 2);
}
@@ -71,14 +73,14 @@
*/
@Test
public void testTtl_100() {
- TestRateLimitingCache<Integer> s = new TestRateLimitingCache<>(100);
+ RateLimitingCache<Integer> s = new RateLimitingCache<>(100);
int first = s.get(mFetcher);
assertEquals(first, 0);
int second = s.get(mFetcher);
// Too early to change
assertEquals(second, 0);
- s.advanceTime(150);
+ SystemClock.sleep(150);
int third = s.get(mFetcher);
// Changed by now
assertEquals(third, 1);
@@ -93,11 +95,11 @@
*/
@Test
public void testTtl_Negative() {
- TestRateLimitingCache<Integer> s = new TestRateLimitingCache<>(-1);
+ RateLimitingCache<Integer> s = new RateLimitingCache<>(-1);
int first = s.get(mFetcher);
assertEquals(first, 0);
- s.advanceTime(200);
+ SystemClock.sleep(200);
// Should return the original value every time
int second = s.get(mFetcher);
assertEquals(second, 0);
@@ -109,7 +111,7 @@
*/
@Test
public void testTtl_Spam() {
- TestRateLimitingCache<Integer> s = new TestRateLimitingCache<>(100);
+ RateLimitingCache<Integer> s = new RateLimitingCache<>(100);
assertCount(s, 1000, 7, 15);
}
@@ -119,13 +121,28 @@
*/
@Test
public void testRate_10hz() {
- TestRateLimitingCache<Integer> s = new TestRateLimitingCache<>(1000, 10);
+ RateLimitingCache<Integer> s = new RateLimitingCache<>(1000, 10);
// At 10 per second, 2 seconds should not exceed about 30, assuming overlap into left and
// right windows that allow 10 each
assertCount(s, 2000, 20, 33);
}
/**
+ * Test that using a different timebase works correctly.
+ */
+ @Test
+ public void testTimebase() {
+ RateLimitingCache<Integer> s = new RateLimitingCache<>(1000, 10) {
+ @Override
+ protected long getTime() {
+ return SystemClock.elapsedRealtime() / 2;
+ }
+ };
+ // Timebase is moving at half the speed, so only allows for 1 second worth in 2 seconds.
+ assertCount(s, 2000, 10, 22);
+ }
+
+ /**
* Exercises concurrent access to the cache.
*/
@Test
@@ -270,36 +287,15 @@
* @param minCount the lower end of the expected number of fetches, with a margin for error
* @param maxCount the higher end of the expected number of fetches, with a margin for error
*/
- private void assertCount(TestRateLimitingCache<Integer> cache, long period,
+ private void assertCount(RateLimitingCache<Integer> cache, long period,
int minCount, int maxCount) {
- long startTime = cache.getTime();
- while (cache.getTime() < startTime + period) {
+ long startTime = SystemClock.elapsedRealtime();
+ while (SystemClock.elapsedRealtime() < startTime + period) {
int value = cache.get(mFetcher);
- cache.advanceTime(5);
+ SystemClock.sleep(5);
}
int latest = cache.get(mFetcher);
assertTrue("Latest should be between " + minCount + " and " + maxCount
+ " but is " + latest, latest <= maxCount && latest >= minCount);
}
-
- private static class TestRateLimitingCache<Value> extends RateLimitingCache<Value> {
- private long mTime;
-
- public TestRateLimitingCache(long periodMillis) {
- super(periodMillis);
- }
-
- public TestRateLimitingCache(long periodMillis, int count) {
- super(periodMillis, count);
- }
-
- public void advanceTime(long time) {
- mTime += time;
- }
-
- @Override
- public long getTime() {
- return mTime;
- }
- }
}
diff --git a/core/tests/coretests/src/com/android/internal/widget/NotificationProgressBarTest.java b/core/tests/coretests/src/com/android/internal/widget/NotificationProgressBarTest.java
index 9baa31f..282886a 100644
--- a/core/tests/coretests/src/com/android/internal/widget/NotificationProgressBarTest.java
+++ b/core/tests/coretests/src/com/android/internal/widget/NotificationProgressBarTest.java
@@ -121,18 +121,20 @@
assertThat(parts).isEqualTo(expectedParts);
- float drawableWidth = 300;
+ float drawableWidth = 320;
float segSegGap = 4;
float segPointGap = 4;
float pointRadius = 6;
boolean hasTrackerIcon = true;
+ int trackerDrawWidth = 20;
List<DrawablePart> drawableParts =
NotificationProgressBar.processPartsAndConvertToDrawableParts(
- parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+ parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon,
+ trackerDrawWidth);
List<DrawablePart> expectedDrawableParts = new ArrayList<>(
- List.of(new DrawableSegment(0, 300, Color.RED)));
+ List.of(new DrawableSegment(10, 310, Color.RED)));
assertThat(drawableParts).isEqualTo(expectedDrawableParts);
@@ -141,14 +143,14 @@
Pair<List<DrawablePart>, Float> p = NotificationProgressBar.maybeStretchAndRescaleSegments(
parts, drawableParts, segmentMinWidth, pointRadius, (float) progress / progressMax,
- 300, isStyledByProgress, hasTrackerIcon ? 0 : segSegGap);
+ isStyledByProgress, hasTrackerIcon ? 0 : segSegGap);
// Colors with 50% opacity
int fadedRed = 0x80FF0000;
expectedDrawableParts = new ArrayList<>(
- List.of(new DrawableSegment(0, 300, fadedRed, true)));
+ List.of(new DrawableSegment(10, 310, fadedRed, true)));
- assertThat(p.second).isEqualTo(0);
+ assertThat(p.second).isEqualTo(10);
assertThat(p.first).isEqualTo(expectedDrawableParts);
}
@@ -168,18 +170,20 @@
assertThat(parts).isEqualTo(expectedParts);
- float drawableWidth = 300;
+ float drawableWidth = 320;
float segSegGap = 4;
float segPointGap = 4;
float pointRadius = 6;
boolean hasTrackerIcon = true;
+ int trackerDrawWidth = 20;
List<DrawablePart> drawableParts =
NotificationProgressBar.processPartsAndConvertToDrawableParts(
- parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+ parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon,
+ trackerDrawWidth);
List<DrawablePart> expectedDrawableParts = new ArrayList<>(
- List.of(new DrawableSegment(0, 300, Color.RED)));
+ List.of(new DrawableSegment(10, 310, Color.RED)));
assertThat(drawableParts).isEqualTo(expectedDrawableParts);
@@ -188,9 +192,9 @@
Pair<List<DrawablePart>, Float> p = NotificationProgressBar.maybeStretchAndRescaleSegments(
parts, drawableParts, segmentMinWidth, pointRadius, (float) progress / progressMax,
- 300, isStyledByProgress, hasTrackerIcon ? 0 : segSegGap);
+ isStyledByProgress, hasTrackerIcon ? 0 : segSegGap);
- assertThat(p.second).isEqualTo(300);
+ assertThat(p.second).isEqualTo(310);
assertThat(p.first).isEqualTo(expectedDrawableParts);
}
@@ -219,6 +223,42 @@
progressMax);
}
+ @Test
+ public void processAndConvertToParts_pointPositionIsZero() {
+ List<ProgressStyle.Segment> segments = new ArrayList<>();
+ segments.add(new ProgressStyle.Segment(100).setColor(Color.RED));
+ List<ProgressStyle.Point> points = new ArrayList<>();
+ points.add(new ProgressStyle.Point(0).setColor(Color.RED));
+ int progress = 50;
+ int progressMax = 100;
+
+ List<Part> parts = NotificationProgressBar.processModelAndConvertToViewParts(segments,
+ points, progress, progressMax);
+
+ // Point at the start is dropped.
+ List<Part> expectedParts = new ArrayList<>(List.of(new Segment(1f, Color.RED)));
+
+ assertThat(parts).isEqualTo(expectedParts);
+ }
+
+ @Test
+ public void processAndConvertToParts_pointPositionAtMax() {
+ List<ProgressStyle.Segment> segments = new ArrayList<>();
+ segments.add(new ProgressStyle.Segment(100).setColor(Color.RED));
+ List<ProgressStyle.Point> points = new ArrayList<>();
+ points.add(new ProgressStyle.Point(100).setColor(Color.RED));
+ int progress = 50;
+ int progressMax = 100;
+
+ List<Part> parts = NotificationProgressBar.processModelAndConvertToViewParts(segments,
+ points, progress, progressMax);
+
+ // Point at the end is dropped.
+ List<Part> expectedParts = new ArrayList<>(List.of(new Segment(1f, Color.RED)));
+
+ assertThat(parts).isEqualTo(expectedParts);
+ }
+
@Test(expected = IllegalArgumentException.class)
public void processAndConvertToParts_pointPositionAboveMax() {
List<ProgressStyle.Segment> segments = new ArrayList<>();
@@ -249,18 +289,20 @@
assertThat(parts).isEqualTo(expectedParts);
- float drawableWidth = 300;
+ float drawableWidth = 320;
float segSegGap = 4;
float segPointGap = 4;
float pointRadius = 6;
boolean hasTrackerIcon = true;
+ int trackerDrawWidth = 20;
List<DrawablePart> drawableParts =
NotificationProgressBar.processPartsAndConvertToDrawableParts(
- parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+ parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon,
+ trackerDrawWidth);
List<DrawablePart> expectedDrawableParts = new ArrayList<>(
- List.of(new DrawableSegment(0, 300, Color.BLUE)));
+ List.of(new DrawableSegment(10, 310, Color.BLUE)));
assertThat(drawableParts).isEqualTo(expectedDrawableParts);
@@ -269,15 +311,15 @@
Pair<List<DrawablePart>, Float> p = NotificationProgressBar.maybeStretchAndRescaleSegments(
parts, drawableParts, segmentMinWidth, pointRadius, (float) progress / progressMax,
- 300, isStyledByProgress, hasTrackerIcon ? 0 : segSegGap);
+ isStyledByProgress, hasTrackerIcon ? 0 : segSegGap);
// Colors with 50% opacity
int fadedBlue = 0x800000FF;
expectedDrawableParts = new ArrayList<>(
- List.of(new DrawableSegment(0, 180, Color.BLUE),
- new DrawableSegment(180, 300, fadedBlue, true)));
+ List.of(new DrawableSegment(10, 190, Color.BLUE),
+ new DrawableSegment(190, 310, fadedBlue, true)));
- assertThat(p.second).isEqualTo(180);
+ assertThat(p.second).isEqualTo(190);
assertThat(p.first).isEqualTo(expectedDrawableParts);
}
@@ -299,19 +341,21 @@
assertThat(parts).isEqualTo(expectedParts);
- float drawableWidth = 300;
+ float drawableWidth = 320;
float segSegGap = 4;
float segPointGap = 4;
float pointRadius = 6;
boolean hasTrackerIcon = true;
+ int trackerDrawWidth = 20;
List<DrawablePart> drawableParts =
NotificationProgressBar.processPartsAndConvertToDrawableParts(
- parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+ parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon,
+ trackerDrawWidth);
List<DrawablePart> expectedDrawableParts = new ArrayList<>(
- List.of(new DrawableSegment(0, 146, Color.RED),
- new DrawableSegment(150, 300, Color.GREEN)));
+ List.of(new DrawableSegment(10, 156, Color.RED),
+ new DrawableSegment(160, 310, Color.GREEN)));
assertThat(drawableParts).isEqualTo(expectedDrawableParts);
@@ -319,15 +363,15 @@
boolean isStyledByProgress = true;
Pair<List<DrawablePart>, Float> p = NotificationProgressBar.maybeStretchAndRescaleSegments(
parts, drawableParts, segmentMinWidth, pointRadius, (float) progress / progressMax,
- 300, isStyledByProgress, hasTrackerIcon ? 0 : segSegGap);
+ isStyledByProgress, hasTrackerIcon ? 0 : segSegGap);
// Colors with 50% opacity
int fadedGreen = 0x8000FF00;
- expectedDrawableParts = new ArrayList<>(List.of(new DrawableSegment(0, 146, Color.RED),
- new DrawableSegment(150, 180, Color.GREEN),
- new DrawableSegment(180, 300, fadedGreen, true)));
+ expectedDrawableParts = new ArrayList<>(List.of(new DrawableSegment(10, 156, Color.RED),
+ new DrawableSegment(160, 190, Color.GREEN),
+ new DrawableSegment(190, 310, fadedGreen, true)));
- assertThat(p.second).isEqualTo(180);
+ assertThat(p.second).isEqualTo(190);
assertThat(p.first).isEqualTo(expectedDrawableParts);
}
@@ -353,10 +397,12 @@
float segPointGap = 4;
float pointRadius = 6;
boolean hasTrackerIcon = false;
+ int trackerDrawWidth = 0;
List<DrawablePart> drawableParts =
NotificationProgressBar.processPartsAndConvertToDrawableParts(
- parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+ parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon,
+ trackerDrawWidth);
List<DrawablePart> expectedDrawableParts = new ArrayList<>(
List.of(new DrawableSegment(0, 146, Color.RED),
@@ -368,7 +414,7 @@
boolean isStyledByProgress = true;
Pair<List<DrawablePart>, Float> p = NotificationProgressBar.maybeStretchAndRescaleSegments(
parts, drawableParts, segmentMinWidth, pointRadius, (float) progress / progressMax,
- 300, isStyledByProgress, hasTrackerIcon ? 0 : segSegGap);
+ isStyledByProgress, hasTrackerIcon ? 0 : segSegGap);
// Colors with 50% opacity
int fadedGreen = 0x8000FF00;
@@ -409,26 +455,28 @@
assertThat(parts).isEqualTo(expectedParts);
- float drawableWidth = 300;
+ float drawableWidth = 320;
float segSegGap = 4;
float segPointGap = 4;
float pointRadius = 6;
boolean hasTrackerIcon = true;
+ int trackerDrawWidth = 20;
List<DrawablePart> drawableParts =
NotificationProgressBar.processPartsAndConvertToDrawableParts(
- parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+ parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon,
+ trackerDrawWidth);
List<DrawablePart> expectedDrawableParts = new ArrayList<>(
- List.of(new DrawableSegment(0, 35, Color.BLUE),
- new DrawablePoint(39, 51, Color.RED),
- new DrawableSegment(55, 65, Color.BLUE),
- new DrawablePoint(69, 81, Color.BLUE),
- new DrawableSegment(85, 170, Color.BLUE),
- new DrawablePoint(174, 186, Color.BLUE),
- new DrawableSegment(190, 215, Color.BLUE),
- new DrawablePoint(219, 231, Color.YELLOW),
- new DrawableSegment(235, 300, Color.BLUE)));
+ List.of(new DrawableSegment(10, 45, Color.BLUE),
+ new DrawablePoint(49, 61, Color.RED),
+ new DrawableSegment(65, 75, Color.BLUE),
+ new DrawablePoint(79, 91, Color.BLUE),
+ new DrawableSegment(95, 180, Color.BLUE),
+ new DrawablePoint(184, 196, Color.BLUE),
+ new DrawableSegment(200, 225, Color.BLUE),
+ new DrawablePoint(229, 241, Color.YELLOW),
+ new DrawableSegment(245, 310, Color.BLUE)));
assertThat(drawableParts).isEqualTo(expectedDrawableParts);
@@ -437,23 +485,23 @@
Pair<List<DrawablePart>, Float> p = NotificationProgressBar.maybeStretchAndRescaleSegments(
parts, drawableParts, segmentMinWidth, pointRadius, (float) progress / progressMax,
- 300, isStyledByProgress, hasTrackerIcon ? 0 : segSegGap);
+ isStyledByProgress, hasTrackerIcon ? 0 : segSegGap);
// Colors with 50% opacity
int fadedBlue = 0x800000FF;
int fadedYellow = 0x80FFFF00;
expectedDrawableParts = new ArrayList<>(
- List.of(new DrawableSegment(0, 34.219177F, Color.BLUE),
- new DrawablePoint(38.219177F, 50.219177F, Color.RED),
- new DrawableSegment(54.219177F, 70.21918F, Color.BLUE),
- new DrawablePoint(74.21918F, 86.21918F, Color.BLUE),
- new DrawableSegment(90.21918F, 172.38356F, Color.BLUE),
- new DrawablePoint(176.38356F, 188.38356F, Color.BLUE),
- new DrawableSegment(192.38356F, 217.0137F, fadedBlue, true),
- new DrawablePoint(221.0137F, 233.0137F, fadedYellow),
- new DrawableSegment(237.0137F, 300F, fadedBlue, true)));
+ List.of(new DrawableSegment(10, 44.219177F, Color.BLUE),
+ new DrawablePoint(48.219177F, 60.219177F, Color.RED),
+ new DrawableSegment(64.219177F, 80.21918F, Color.BLUE),
+ new DrawablePoint(84.21918F, 96.21918F, Color.BLUE),
+ new DrawableSegment(100.21918F, 182.38356F, Color.BLUE),
+ new DrawablePoint(186.38356F, 198.38356F, Color.BLUE),
+ new DrawableSegment(202.38356F, 227.0137F, fadedBlue, true),
+ new DrawablePoint(231.0137F, 243.0137F, fadedYellow),
+ new DrawableSegment(247.0137F, 310F, fadedBlue, true)));
- assertThat(p.second).isEqualTo(182.38356F);
+ assertThat(p.second).isEqualTo(192.38356F);
assertThat(p.first).isEqualTo(expectedDrawableParts);
}
@@ -488,25 +536,29 @@
assertThat(parts).isEqualTo(expectedParts);
- float drawableWidth = 300;
+ float drawableWidth = 320;
float segSegGap = 4;
float segPointGap = 4;
float pointRadius = 6;
boolean hasTrackerIcon = true;
+ int trackerDrawWidth = 20;
+
List<DrawablePart> drawableParts =
NotificationProgressBar.processPartsAndConvertToDrawableParts(
- parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+ parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon,
+ trackerDrawWidth);
List<DrawablePart> expectedDrawableParts = new ArrayList<>(
- List.of(new DrawableSegment(0, 35, Color.RED), new DrawablePoint(39, 51, Color.RED),
- new DrawableSegment(55, 65, Color.RED),
- new DrawablePoint(69, 81, Color.BLUE),
- new DrawableSegment(85, 146, Color.RED),
- new DrawableSegment(150, 170, Color.GREEN),
- new DrawablePoint(174, 186, Color.BLUE),
- new DrawableSegment(190, 215, Color.GREEN),
- new DrawablePoint(219, 231, Color.YELLOW),
- new DrawableSegment(235, 300, Color.GREEN)));
+ List.of(new DrawableSegment(10, 45, Color.RED),
+ new DrawablePoint(49, 61, Color.RED),
+ new DrawableSegment(65, 75, Color.RED),
+ new DrawablePoint(79, 91, Color.BLUE),
+ new DrawableSegment(95, 156, Color.RED),
+ new DrawableSegment(160, 180, Color.GREEN),
+ new DrawablePoint(184, 196, Color.BLUE),
+ new DrawableSegment(200, 225, Color.GREEN),
+ new DrawablePoint(229, 241, Color.YELLOW),
+ new DrawableSegment(245, 310, Color.GREEN)));
assertThat(drawableParts).isEqualTo(expectedDrawableParts);
@@ -515,99 +567,24 @@
Pair<List<DrawablePart>, Float> p = NotificationProgressBar.maybeStretchAndRescaleSegments(
parts, drawableParts, segmentMinWidth, pointRadius, (float) progress / progressMax,
- 300, isStyledByProgress, hasTrackerIcon ? 0 : segSegGap);
+ isStyledByProgress, hasTrackerIcon ? 0 : segSegGap);
// Colors with 50% opacity
int fadedGreen = 0x8000FF00;
int fadedYellow = 0x80FFFF00;
expectedDrawableParts = new ArrayList<>(
- List.of(new DrawableSegment(0, 34.095238F, Color.RED),
- new DrawablePoint(38.095238F, 50.095238F, Color.RED),
- new DrawableSegment(54.095238F, 70.09524F, Color.RED),
- new DrawablePoint(74.09524F, 86.09524F, Color.BLUE),
- new DrawableSegment(90.09524F, 148.9524F, Color.RED),
- new DrawableSegment(152.95238F, 172.7619F, Color.GREEN),
- new DrawablePoint(176.7619F, 188.7619F, Color.BLUE),
- new DrawableSegment(192.7619F, 217.33333F, fadedGreen, true),
- new DrawablePoint(221.33333F, 233.33333F, fadedYellow),
- new DrawableSegment(237.33333F, 299.99997F, fadedGreen, true)));
+ List.of(new DrawableSegment(10, 44.095238F, Color.RED),
+ new DrawablePoint(48.095238F, 60.095238F, Color.RED),
+ new DrawableSegment(64.095238F, 80.09524F, Color.RED),
+ new DrawablePoint(84.09524F, 96.09524F, Color.BLUE),
+ new DrawableSegment(100.09524F, 158.9524F, Color.RED),
+ new DrawableSegment(162.95238F, 182.7619F, Color.GREEN),
+ new DrawablePoint(186.7619F, 198.7619F, Color.BLUE),
+ new DrawableSegment(202.7619F, 227.33333F, fadedGreen, true),
+ new DrawablePoint(231.33333F, 243.33333F, fadedYellow),
+ new DrawableSegment(247.33333F, 309.99997F, fadedGreen, true)));
- assertThat(p.second).isEqualTo(182.7619F);
- assertThat(p.first).isEqualTo(expectedDrawableParts);
- }
-
- @Test
- public void processAndConvertToParts_multipleSegmentsWithPointsAtStartAndEnd()
- throws NotEnoughWidthToFitAllPartsException {
- List<ProgressStyle.Segment> segments = new ArrayList<>();
- segments.add(new ProgressStyle.Segment(50).setColor(Color.RED));
- segments.add(new ProgressStyle.Segment(50).setColor(Color.GREEN));
- List<ProgressStyle.Point> points = new ArrayList<>();
- points.add(new ProgressStyle.Point(0).setColor(Color.RED));
- points.add(new ProgressStyle.Point(25).setColor(Color.BLUE));
- points.add(new ProgressStyle.Point(60).setColor(Color.BLUE));
- points.add(new ProgressStyle.Point(100).setColor(Color.YELLOW));
- int progress = 60;
- int progressMax = 100;
-
- List<Part> parts = NotificationProgressBar.processModelAndConvertToViewParts(segments,
- points, progress, progressMax);
-
- List<Part> expectedParts = new ArrayList<>(
- List.of(new Point(Color.RED),
- new Segment(0.25f, Color.RED),
- new Point(Color.BLUE),
- new Segment(0.25f, Color.RED),
- new Segment(0.10f, Color.GREEN),
- new Point(Color.BLUE),
- new Segment(0.4f, Color.GREEN),
- new Point(Color.YELLOW)));
-
- assertThat(parts).isEqualTo(expectedParts);
-
- float drawableWidth = 300;
- float segSegGap = 4;
- float segPointGap = 4;
- float pointRadius = 6;
- boolean hasTrackerIcon = true;
-
- List<DrawablePart> drawableParts =
- NotificationProgressBar.processPartsAndConvertToDrawableParts(
- parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
-
- List<DrawablePart> expectedDrawableParts = new ArrayList<>(
- List.of(new DrawablePoint(0, 12, Color.RED),
- new DrawableSegment(16, 65, Color.RED),
- new DrawablePoint(69, 81, Color.BLUE),
- new DrawableSegment(85, 146, Color.RED),
- new DrawableSegment(150, 170, Color.GREEN),
- new DrawablePoint(174, 186, Color.BLUE),
- new DrawableSegment(190, 284, Color.GREEN),
- new DrawablePoint(288, 300, Color.YELLOW)));
-
- assertThat(drawableParts).isEqualTo(expectedDrawableParts);
-
- float segmentMinWidth = 16;
- boolean isStyledByProgress = true;
-
- Pair<List<DrawablePart>, Float> p = NotificationProgressBar.maybeStretchAndRescaleSegments(
- parts, drawableParts, segmentMinWidth, pointRadius, (float) progress / progressMax,
- 300, isStyledByProgress, hasTrackerIcon ? 0 : segSegGap);
-
- // Colors with 50% opacity
- int fadedGreen = 0x8000FF00;
- int fadedYellow = 0x80FFFF00;
- expectedDrawableParts = new ArrayList<>(
- List.of(new DrawablePoint(0, 12, Color.RED),
- new DrawableSegment(16, 65, Color.RED),
- new DrawablePoint(69, 81, Color.BLUE),
- new DrawableSegment(85, 146, Color.RED),
- new DrawableSegment(150, 170, Color.GREEN),
- new DrawablePoint(174, 186, Color.BLUE),
- new DrawableSegment(190, 284, fadedGreen, true),
- new DrawablePoint(288, 300, fadedYellow)));
-
- assertThat(p.second).isEqualTo(180);
+ assertThat(p.second).isEqualTo(192.7619F);
assertThat(p.first).isEqualTo(expectedDrawableParts);
}
@@ -644,27 +621,29 @@
assertThat(parts).isEqualTo(expectedParts);
- float drawableWidth = 300;
+ float drawableWidth = 320;
float segSegGap = 4;
float segPointGap = 4;
float pointRadius = 6;
boolean hasTrackerIcon = true;
+ int trackerDrawWidth = 20;
List<DrawablePart> drawableParts =
NotificationProgressBar.processPartsAndConvertToDrawableParts(
- parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+ parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon,
+ trackerDrawWidth);
List<DrawablePart> expectedDrawableParts = new ArrayList<>(
- List.of(new DrawableSegment(0, -7, Color.RED),
- new DrawablePoint(-3, 9, Color.RED),
- new DrawableSegment(13, 65, Color.RED),
- new DrawablePoint(69, 81, Color.BLUE),
- new DrawableSegment(85, 146, Color.RED),
- new DrawableSegment(150, 170, Color.GREEN),
- new DrawablePoint(174, 186, Color.BLUE),
- new DrawableSegment(190, 287, Color.GREEN),
- new DrawablePoint(291, 303, Color.YELLOW),
- new DrawableSegment(307, 300, Color.GREEN)));
+ List.of(new DrawableSegment(10, 3, Color.RED),
+ new DrawablePoint(7, 19, Color.RED),
+ new DrawableSegment(23, 75, Color.RED),
+ new DrawablePoint(79, 91, Color.BLUE),
+ new DrawableSegment(95, 156, Color.RED),
+ new DrawableSegment(160, 180, Color.GREEN),
+ new DrawablePoint(184, 196, Color.BLUE),
+ new DrawableSegment(200, 297, Color.GREEN),
+ new DrawablePoint(301, 313, Color.YELLOW),
+ new DrawableSegment(317, 310, Color.GREEN)));
assertThat(drawableParts).isEqualTo(expectedDrawableParts);
@@ -673,24 +652,24 @@
Pair<List<DrawablePart>, Float> p = NotificationProgressBar.maybeStretchAndRescaleSegments(
parts, drawableParts, segmentMinWidth, pointRadius, (float) progress / progressMax,
- 300, isStyledByProgress, hasTrackerIcon ? 0 : segSegGap);
+ isStyledByProgress, hasTrackerIcon ? 0 : segSegGap);
// Colors with 50% opacity
int fadedGreen = 0x8000FF00;
int fadedYellow = 0x80FFFF00;
expectedDrawableParts = new ArrayList<>(
- List.of(new DrawableSegment(0, 16, Color.RED),
- new DrawablePoint(20, 32, Color.RED),
- new DrawableSegment(36, 78.02409F, Color.RED),
- new DrawablePoint(82.02409F, 94.02409F, Color.BLUE),
- new DrawableSegment(98.02409F, 146.55421F, Color.RED),
- new DrawableSegment(150.55421F, 169.44579F, Color.GREEN),
- new DrawablePoint(173.44579F, 185.44579F, Color.BLUE),
- new DrawableSegment(189.44579F, 264, fadedGreen, true),
- new DrawablePoint(268, 280, fadedYellow),
- new DrawableSegment(284, 300, fadedGreen, true)));
+ List.of(new DrawableSegment(10, 26, Color.RED),
+ new DrawablePoint(30, 42, Color.RED),
+ new DrawableSegment(46, 88.02409F, Color.RED),
+ new DrawablePoint(92.02409F, 104.02409F, Color.BLUE),
+ new DrawableSegment(108.02409F, 156.55421F, Color.RED),
+ new DrawableSegment(160.55421F, 179.44579F, Color.GREEN),
+ new DrawablePoint(183.44579F, 195.44579F, Color.BLUE),
+ new DrawableSegment(199.44579F, 274, fadedGreen, true),
+ new DrawablePoint(278, 290, fadedYellow),
+ new DrawableSegment(294, 310, fadedGreen, true)));
- assertThat(p.second).isEqualTo(179.44579F);
+ assertThat(p.second).isEqualTo(189.44579F);
assertThat(p.first).isEqualTo(expectedDrawableParts);
}
@@ -711,31 +690,38 @@
points, progress, progressMax);
List<Part> expectedParts = new ArrayList<>(
- List.of(new Segment(0.15f, Color.RED), new Point(Color.RED),
- new Segment(0.10f, Color.RED), new Point(Color.BLUE),
- new Segment(0.25f, Color.RED), new Segment(0.25f, Color.GREEN),
- new Point(Color.YELLOW), new Segment(0.25f, Color.GREEN)));
+ List.of(new Segment(0.15f, Color.RED),
+ new Point(Color.RED),
+ new Segment(0.10f, Color.RED),
+ new Point(Color.BLUE),
+ new Segment(0.25f, Color.RED),
+ new Segment(0.25f, Color.GREEN),
+ new Point(Color.YELLOW),
+ new Segment(0.25f, Color.GREEN)));
assertThat(parts).isEqualTo(expectedParts);
- float drawableWidth = 300;
+ float drawableWidth = 320;
float segSegGap = 4;
float segPointGap = 4;
float pointRadius = 6;
boolean hasTrackerIcon = true;
+ int trackerDrawWidth = 20;
List<DrawablePart> drawableParts =
NotificationProgressBar.processPartsAndConvertToDrawableParts(
- parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+ parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon,
+ trackerDrawWidth);
List<DrawablePart> expectedDrawableParts = new ArrayList<>(
- List.of(new DrawableSegment(0, 35, Color.RED), new DrawablePoint(39, 51, Color.RED),
- new DrawableSegment(55, 65, Color.RED),
- new DrawablePoint(69, 81, Color.BLUE),
- new DrawableSegment(85, 146, Color.RED),
- new DrawableSegment(150, 215, Color.GREEN),
- new DrawablePoint(219, 231, Color.YELLOW),
- new DrawableSegment(235, 300, Color.GREEN)));
+ List.of(new DrawableSegment(10, 45, Color.RED),
+ new DrawablePoint(49, 61, Color.RED),
+ new DrawableSegment(65, 75, Color.RED),
+ new DrawablePoint(79, 91, Color.BLUE),
+ new DrawableSegment(95, 156, Color.RED),
+ new DrawableSegment(160, 225, Color.GREEN),
+ new DrawablePoint(229, 241, Color.YELLOW),
+ new DrawableSegment(245, 310, Color.GREEN)));
assertThat(drawableParts).isEqualTo(expectedDrawableParts);
@@ -744,34 +730,34 @@
Pair<List<DrawablePart>, Float> p = NotificationProgressBar.maybeStretchAndRescaleSegments(
parts, drawableParts, segmentMinWidth, pointRadius, (float) progress / progressMax,
- 300, isStyledByProgress, hasTrackerIcon ? 0 : segSegGap);
+ isStyledByProgress, hasTrackerIcon ? 0 : segSegGap);
expectedDrawableParts = new ArrayList<>(
- List.of(new DrawableSegment(0, 34.296295F, Color.RED),
- new DrawablePoint(38.296295F, 50.296295F, Color.RED),
- new DrawableSegment(54.296295F, 70.296295F, Color.RED),
- new DrawablePoint(74.296295F, 86.296295F, Color.BLUE),
- new DrawableSegment(90.296295F, 149.62962F, Color.RED),
- new DrawableSegment(153.62962F, 216.8148F, Color.GREEN),
- new DrawablePoint(220.81482F, 232.81482F, Color.YELLOW),
- new DrawableSegment(236.81482F, 300, Color.GREEN)));
+ List.of(new DrawableSegment(10, 44.296295F, Color.RED),
+ new DrawablePoint(48.296295F, 60.296295F, Color.RED),
+ new DrawableSegment(64.296295F, 80.296295F, Color.RED),
+ new DrawablePoint(84.296295F, 96.296295F, Color.BLUE),
+ new DrawableSegment(100.296295F, 159.62962F, Color.RED),
+ new DrawableSegment(163.62962F, 226.8148F, Color.GREEN),
+ new DrawablePoint(230.81482F, 242.81482F, Color.YELLOW),
+ new DrawableSegment(246.81482F, 310, Color.GREEN)));
- assertThat(p.second).isEqualTo(182.9037F);
+ assertThat(p.second).isEqualTo(192.9037F);
assertThat(p.first).isEqualTo(expectedDrawableParts);
}
- // The only difference from the `zeroWidthDrawableSegment` test below is the longer
+ // The only difference from the `segmentWidthAtMin` test below is the longer
// segmentMinWidth (= 16dp).
@Test
- public void maybeStretchAndRescaleSegments_negativeWidthDrawableSegment()
+ public void maybeStretchAndRescaleSegments_segmentWidthBelowMin()
throws NotEnoughWidthToFitAllPartsException {
List<ProgressStyle.Segment> segments = new ArrayList<>();
- segments.add(new ProgressStyle.Segment(100).setColor(Color.BLUE));
segments.add(new ProgressStyle.Segment(200).setColor(Color.BLUE));
+ segments.add(new ProgressStyle.Segment(100).setColor(Color.BLUE));
segments.add(new ProgressStyle.Segment(300).setColor(Color.BLUE));
segments.add(new ProgressStyle.Segment(400).setColor(Color.BLUE));
List<ProgressStyle.Point> points = new ArrayList<>();
- points.add(new ProgressStyle.Point(0).setColor(Color.BLUE));
+ points.add(new ProgressStyle.Point(200).setColor(Color.BLUE));
int progress = 1000;
int progressMax = 1000;
@@ -779,28 +765,32 @@
points, progress, progressMax);
List<Part> expectedParts = new ArrayList<>(
- List.of(new Point(Color.BLUE), new Segment(0.1f, Color.BLUE),
- new Segment(0.2f, Color.BLUE), new Segment(0.3f, Color.BLUE),
+ List.of(new Segment(0.2f, Color.BLUE),
+ new Point(Color.BLUE),
+ new Segment(0.1f, Color.BLUE),
+ new Segment(0.3f, Color.BLUE),
new Segment(0.4f, Color.BLUE)));
assertThat(parts).isEqualTo(expectedParts);
- float drawableWidth = 200;
+ float drawableWidth = 220;
float segSegGap = 4;
float segPointGap = 4;
float pointRadius = 6;
boolean hasTrackerIcon = true;
+ int trackerDrawWidth = 20;
List<DrawablePart> drawableParts =
NotificationProgressBar.processPartsAndConvertToDrawableParts(
- parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+ parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon,
+ trackerDrawWidth);
List<DrawablePart> expectedDrawableParts = new ArrayList<>(
- List.of(new DrawablePoint(0, 12, Color.BLUE),
- new DrawableSegment(16, 16, Color.BLUE),
- new DrawableSegment(20, 56, Color.BLUE),
- new DrawableSegment(60, 116, Color.BLUE),
- new DrawableSegment(120, 200, Color.BLUE)));
+ List.of(new DrawableSegment(10, 40, Color.BLUE),
+ new DrawablePoint(44, 56, Color.BLUE),
+ new DrawableSegment(60, 66, Color.BLUE),
+ new DrawableSegment(70, 126, Color.BLUE),
+ new DrawableSegment(130, 210, Color.BLUE)));
assertThat(drawableParts).isEqualTo(expectedDrawableParts);
@@ -809,30 +799,31 @@
Pair<List<DrawablePart>, Float> p = NotificationProgressBar.maybeStretchAndRescaleSegments(
parts, drawableParts, segmentMinWidth, pointRadius, (float) progress / progressMax,
- 200, isStyledByProgress, hasTrackerIcon ? 0 : segSegGap);
+ isStyledByProgress, hasTrackerIcon ? 0 : segSegGap);
- expectedDrawableParts = new ArrayList<>(List.of(new DrawablePoint(0, 12, Color.BLUE),
- new DrawableSegment(16, 32, Color.BLUE),
- new DrawableSegment(36, 69.41936F, Color.BLUE),
- new DrawableSegment(73.41936F, 124.25807F, Color.BLUE),
- new DrawableSegment(128.25807F, 200, Color.BLUE)));
+ expectedDrawableParts = new ArrayList<>(
+ List.of(new DrawableSegment(10, 38.81356F, Color.BLUE),
+ new DrawablePoint(42.81356F, 54.81356F, Color.BLUE),
+ new DrawableSegment(58.81356F, 74.81356F, Color.BLUE),
+ new DrawableSegment(78.81356F, 131.42374F, Color.BLUE),
+ new DrawableSegment(135.42374F, 210, Color.BLUE)));
- assertThat(p.second).isEqualTo(200);
+ assertThat(p.second).isEqualTo(210);
assertThat(p.first).isEqualTo(expectedDrawableParts);
}
- // The only difference from the `negativeWidthDrawableSegment` test above is the shorter
+ // The only difference from the `segmentWidthBelowMin` test above is the shorter
// segmentMinWidth (= 10dp).
@Test
- public void maybeStretchAndRescaleSegments_zeroWidthDrawableSegment()
+ public void maybeStretchAndRescaleSegments_segmentWidthAtMin()
throws NotEnoughWidthToFitAllPartsException {
List<ProgressStyle.Segment> segments = new ArrayList<>();
- segments.add(new ProgressStyle.Segment(100).setColor(Color.BLUE));
segments.add(new ProgressStyle.Segment(200).setColor(Color.BLUE));
+ segments.add(new ProgressStyle.Segment(100).setColor(Color.BLUE));
segments.add(new ProgressStyle.Segment(300).setColor(Color.BLUE));
segments.add(new ProgressStyle.Segment(400).setColor(Color.BLUE));
List<ProgressStyle.Point> points = new ArrayList<>();
- points.add(new ProgressStyle.Point(0).setColor(Color.BLUE));
+ points.add(new ProgressStyle.Point(200).setColor(Color.BLUE));
int progress = 1000;
int progressMax = 1000;
@@ -840,28 +831,32 @@
points, progress, progressMax);
List<Part> expectedParts = new ArrayList<>(
- List.of(new Point(Color.BLUE), new Segment(0.1f, Color.BLUE),
- new Segment(0.2f, Color.BLUE), new Segment(0.3f, Color.BLUE),
+ List.of(new Segment(0.2f, Color.BLUE),
+ new Point(Color.BLUE),
+ new Segment(0.1f, Color.BLUE),
+ new Segment(0.3f, Color.BLUE),
new Segment(0.4f, Color.BLUE)));
assertThat(parts).isEqualTo(expectedParts);
- float drawableWidth = 200;
+ float drawableWidth = 220;
float segSegGap = 4;
float segPointGap = 4;
float pointRadius = 6;
boolean hasTrackerIcon = true;
+ int trackerDrawWidth = 20;
List<DrawablePart> drawableParts =
NotificationProgressBar.processPartsAndConvertToDrawableParts(
- parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+ parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon,
+ trackerDrawWidth);
List<DrawablePart> expectedDrawableParts = new ArrayList<>(
- List.of(new DrawablePoint(0, 12, Color.BLUE),
- new DrawableSegment(16, 16, Color.BLUE),
- new DrawableSegment(20, 56, Color.BLUE),
- new DrawableSegment(60, 116, Color.BLUE),
- new DrawableSegment(120, 200, Color.BLUE)));
+ List.of(new DrawableSegment(10, 40, Color.BLUE),
+ new DrawablePoint(44, 56, Color.BLUE),
+ new DrawableSegment(60, 66, Color.BLUE),
+ new DrawableSegment(70, 126, Color.BLUE),
+ new DrawableSegment(130, 210, Color.BLUE)));
assertThat(drawableParts).isEqualTo(expectedDrawableParts);
@@ -870,15 +865,16 @@
Pair<List<DrawablePart>, Float> p = NotificationProgressBar.maybeStretchAndRescaleSegments(
parts, drawableParts, segmentMinWidth, pointRadius, (float) progress / progressMax,
- 200, isStyledByProgress, hasTrackerIcon ? 0 : segSegGap);
+ isStyledByProgress, hasTrackerIcon ? 0 : segSegGap);
- expectedDrawableParts = new ArrayList<>(List.of(new DrawablePoint(0, 12, Color.BLUE),
- new DrawableSegment(16, 26, Color.BLUE),
- new DrawableSegment(30, 64.169014F, Color.BLUE),
- new DrawableSegment(68.169014F, 120.92958F, Color.BLUE),
- new DrawableSegment(124.92958F, 200, Color.BLUE)));
+ expectedDrawableParts = new ArrayList<>(
+ List.of(new DrawableSegment(10, 39.411766F, Color.BLUE),
+ new DrawablePoint(43.411766F, 55.411766F, Color.BLUE),
+ new DrawableSegment(59.411766F, 69.411766F, Color.BLUE),
+ new DrawableSegment(73.411766F, 128.05884F, Color.BLUE),
+ new DrawableSegment(132.05882F, 210, Color.BLUE)));
- assertThat(p.second).isEqualTo(200);
+ assertThat(p.second).isEqualTo(210);
assertThat(p.first).isEqualTo(expectedDrawableParts);
}
@@ -886,12 +882,12 @@
public void maybeStretchAndRescaleSegments_noStretchingNecessary()
throws NotEnoughWidthToFitAllPartsException {
List<ProgressStyle.Segment> segments = new ArrayList<>();
- segments.add(new ProgressStyle.Segment(200).setColor(Color.BLUE));
segments.add(new ProgressStyle.Segment(100).setColor(Color.BLUE));
+ segments.add(new ProgressStyle.Segment(200).setColor(Color.BLUE));
segments.add(new ProgressStyle.Segment(300).setColor(Color.BLUE));
segments.add(new ProgressStyle.Segment(400).setColor(Color.BLUE));
List<ProgressStyle.Point> points = new ArrayList<>();
- points.add(new ProgressStyle.Point(0).setColor(Color.BLUE));
+ points.add(new ProgressStyle.Point(100).setColor(Color.BLUE));
int progress = 1000;
int progressMax = 1000;
@@ -899,28 +895,32 @@
points, progress, progressMax);
List<Part> expectedParts = new ArrayList<>(
- List.of(new Point(Color.BLUE), new Segment(0.2f, Color.BLUE),
- new Segment(0.1f, Color.BLUE), new Segment(0.3f, Color.BLUE),
+ List.of(new Segment(0.1f, Color.BLUE),
+ new Point(Color.BLUE),
+ new Segment(0.2f, Color.BLUE),
+ new Segment(0.3f, Color.BLUE),
new Segment(0.4f, Color.BLUE)));
assertThat(parts).isEqualTo(expectedParts);
- float drawableWidth = 200;
+ float drawableWidth = 220;
float segSegGap = 4;
float segPointGap = 4;
float pointRadius = 6;
boolean hasTrackerIcon = true;
+ int trackerDrawWidth = 20;
List<DrawablePart> drawableParts =
NotificationProgressBar.processPartsAndConvertToDrawableParts(
- parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+ parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon,
+ trackerDrawWidth);
List<DrawablePart> expectedDrawableParts = new ArrayList<>(
- List.of(new DrawablePoint(0, 12, Color.BLUE),
- new DrawableSegment(16, 36, Color.BLUE),
- new DrawableSegment(40, 56, Color.BLUE),
- new DrawableSegment(60, 116, Color.BLUE),
- new DrawableSegment(120, 200, Color.BLUE)));
+ List.of(new DrawableSegment(10, 20, Color.BLUE),
+ new DrawablePoint(24, 36, Color.BLUE),
+ new DrawableSegment(40, 66, Color.BLUE),
+ new DrawableSegment(70, 126, Color.BLUE),
+ new DrawableSegment(130, 210, Color.BLUE)));
assertThat(drawableParts).isEqualTo(expectedDrawableParts);
@@ -929,9 +929,9 @@
Pair<List<DrawablePart>, Float> p = NotificationProgressBar.maybeStretchAndRescaleSegments(
parts, drawableParts, segmentMinWidth, pointRadius, (float) progress / progressMax,
- 200, isStyledByProgress, hasTrackerIcon ? 0 : segSegGap);
+ isStyledByProgress, hasTrackerIcon ? 0 : segSegGap);
- assertThat(p.second).isEqualTo(200);
+ assertThat(p.second).isEqualTo(210);
assertThat(p.first).isEqualTo(expectedDrawableParts);
}
@@ -951,10 +951,10 @@
segments.add(new ProgressStyle.Segment(10).setColor(Color.GREEN));
segments.add(new ProgressStyle.Segment(10).setColor(Color.RED));
List<ProgressStyle.Point> points = new ArrayList<>();
- points.add(new ProgressStyle.Point(0).setColor(orange));
points.add(new ProgressStyle.Point(1).setColor(Color.BLUE));
+ points.add(new ProgressStyle.Point(10).setColor(orange));
points.add(new ProgressStyle.Point(55).setColor(Color.BLUE));
- points.add(new ProgressStyle.Point(100).setColor(orange));
+ points.add(new ProgressStyle.Point(90).setColor(orange));
int progress = 50;
int progressMax = 100;
@@ -962,10 +962,10 @@
points, progress, progressMax);
List<Part> expectedParts = new ArrayList<>(
- List.of(new Point(orange),
- new Segment(0.01f, orange),
+ List.of(new Segment(0.01f, orange),
new Point(Color.BLUE),
new Segment(0.09f, orange),
+ new Point(orange),
new Segment(0.1f, Color.YELLOW),
new Segment(0.1f, Color.BLUE),
new Segment(0.1f, Color.GREEN),
@@ -976,21 +976,23 @@
new Segment(0.1f, Color.YELLOW),
new Segment(0.1f, Color.BLUE),
new Segment(0.1f, Color.GREEN),
- new Segment(0.1f, Color.RED),
- new Point(orange)));
+ new Point(orange),
+ new Segment(0.1f, Color.RED)));
assertThat(parts).isEqualTo(expectedParts);
// For the list of ProgressStyle.Part used in this test, 300 is the minimum width.
- float drawableWidth = 299;
+ float drawableWidth = 319;
float segSegGap = 4;
float segPointGap = 4;
float pointRadius = 6;
boolean hasTrackerIcon = true;
+ int trackerDrawWidth = 20;
List<DrawablePart> drawableParts =
NotificationProgressBar.processPartsAndConvertToDrawableParts(
- parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+ parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon,
+ trackerDrawWidth);
// Skips the validation of the intermediate list of DrawableParts.
@@ -999,7 +1001,7 @@
NotificationProgressBar.maybeStretchAndRescaleSegments(
parts, drawableParts, segmentMinWidth, pointRadius, (float) progress / progressMax,
- 300, isStyledByProgress, hasTrackerIcon ? 0 : segSegGap);
+ isStyledByProgress, hasTrackerIcon ? 0 : segSegGap);
}
@Test
@@ -1015,11 +1017,12 @@
int progress = 60;
int progressMax = 100;
- float drawableWidth = 300;
+ float drawableWidth = 320;
float segSegGap = 4;
float segPointGap = 4;
float pointRadius = 6;
boolean hasTrackerIcon = true;
+ int trackerDrawWidth = 20;
float segmentMinWidth = 16;
boolean isStyledByProgress = true;
@@ -1036,24 +1039,24 @@
pointRadius,
hasTrackerIcon,
segmentMinWidth,
- isStyledByProgress
- );
+ isStyledByProgress,
+ trackerDrawWidth);
// Colors with 50% opacity
int fadedBlue = 0x800000FF;
int fadedYellow = 0x80FFFF00;
List<DrawablePart> expectedDrawableParts = new ArrayList<>(
- List.of(new DrawableSegment(0, 34.219177F, Color.BLUE),
- new DrawablePoint(38.219177F, 50.219177F, Color.RED),
- new DrawableSegment(54.219177F, 70.21918F, Color.BLUE),
- new DrawablePoint(74.21918F, 86.21918F, Color.BLUE),
- new DrawableSegment(90.21918F, 172.38356F, Color.BLUE),
- new DrawablePoint(176.38356F, 188.38356F, Color.BLUE),
- new DrawableSegment(192.38356F, 217.0137F, fadedBlue, true),
- new DrawablePoint(221.0137F, 233.0137F, fadedYellow),
- new DrawableSegment(237.0137F, 300F, fadedBlue, true)));
+ List.of(new DrawableSegment(10, 44.219177F, Color.BLUE),
+ new DrawablePoint(48.219177F, 60.219177F, Color.RED),
+ new DrawableSegment(64.219177F, 80.21918F, Color.BLUE),
+ new DrawablePoint(84.21918F, 96.21918F, Color.BLUE),
+ new DrawableSegment(100.21918F, 182.38356F, Color.BLUE),
+ new DrawablePoint(186.38356F, 198.38356F, Color.BLUE),
+ new DrawableSegment(202.38356F, 227.0137F, fadedBlue, true),
+ new DrawablePoint(231.0137F, 243.0137F, fadedYellow),
+ new DrawableSegment(247.0137F, 310F, fadedBlue, true)));
- assertThat(p.second).isEqualTo(182.38356F);
+ assertThat(p.second).isEqualTo(192.38356F);
assertThat(p.first).isEqualTo(expectedDrawableParts);
}
@@ -1065,11 +1068,12 @@
int progress = 60;
int progressMax = 100;
- float drawableWidth = 100;
+ float drawableWidth = 120;
float segSegGap = 4;
float segPointGap = 4;
float pointRadius = 6;
boolean hasTrackerIcon = true;
+ int trackerDrawWidth = 20;
float segmentMinWidth = 16;
boolean isStyledByProgress = true;
@@ -1086,16 +1090,16 @@
pointRadius,
hasTrackerIcon,
segmentMinWidth,
- isStyledByProgress
- );
+ isStyledByProgress,
+ trackerDrawWidth);
- // Colors with 50%f opacity
+ // Colors with 50% opacity
int fadedBlue = 0x800000FF;
List<DrawablePart> expectedDrawableParts = new ArrayList<>(
- List.of(new DrawableSegment(0, 60.000004F, Color.BLUE),
- new DrawableSegment(60.000004F, 100, fadedBlue, true)));
+ List.of(new DrawableSegment(10, 70F, Color.BLUE),
+ new DrawableSegment(70F, 110, fadedBlue, true)));
- assertThat(p.second).isWithin(1e-5f).of(60);
+ assertThat(p.second).isEqualTo(70);
assertThat(p.first).isEqualTo(expectedDrawableParts);
}
}
diff --git a/core/tests/coretests/src/com/android/internal/widget/NotificationProgressModelTest.java b/core/tests/coretests/src/com/android/internal/widget/NotificationProgressModelTest.java
index e1f5b1c..140d268 100644
--- a/core/tests/coretests/src/com/android/internal/widget/NotificationProgressModelTest.java
+++ b/core/tests/coretests/src/com/android/internal/widget/NotificationProgressModelTest.java
@@ -90,7 +90,7 @@
new Notification.ProgressStyle.Segment(50).setColor(Color.YELLOW),
new Notification.ProgressStyle.Segment(50).setColor(Color.LTGRAY));
final List<Notification.ProgressStyle.Point> points = List.of(
- new Notification.ProgressStyle.Point(0).setColor(Color.RED),
+ new Notification.ProgressStyle.Point(1).setColor(Color.RED),
new Notification.ProgressStyle.Point(20).setColor(Color.BLUE));
final NotificationProgressModel savedModel = new NotificationProgressModel(segments,
points,
@@ -121,7 +121,7 @@
new Notification.ProgressStyle.Segment(50).setColor(Color.YELLOW),
new Notification.ProgressStyle.Segment(50).setColor(Color.YELLOW));
final List<Notification.ProgressStyle.Point> points = List.of(
- new Notification.ProgressStyle.Point(0).setColor(Color.RED),
+ new Notification.ProgressStyle.Point(1).setColor(Color.RED),
new Notification.ProgressStyle.Point(20).setColor(Color.BLUE));
final NotificationProgressModel savedModel = new NotificationProgressModel(segments,
points,
diff --git a/graphics/java/android/graphics/FrameInfo.java b/graphics/java/android/graphics/FrameInfo.java
index 8f12828..7d236d2 100644
--- a/graphics/java/android/graphics/FrameInfo.java
+++ b/graphics/java/android/graphics/FrameInfo.java
@@ -95,7 +95,8 @@
// Must be the last one
// This value must be in sync with `UI_THREAD_FRAME_INFO_SIZE` in FrameInfo.h
- private static final int FRAME_INFO_SIZE = FRAME_INTERVAL + 1;
+ // In calculating size, + 1 for Flags, and + 1 for WorkloadTarget from FrameInfo.h
+ private static final int FRAME_INFO_SIZE = FRAME_INTERVAL + 2;
/** checkstyle */
public void setVsync(long intendedVsync, long usedVsync, long frameTimelineVsyncId,
diff --git a/libs/WindowManager/Shell/aconfig/multitasking.aconfig b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
index 13d0169..a08f88a 100644
--- a/libs/WindowManager/Shell/aconfig/multitasking.aconfig
+++ b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
@@ -177,3 +177,10 @@
description: "Factor task-view state tracking out of taskviewtransitions"
bug: "384976265"
}
+
+flag {
+ name: "enable_bubble_bar_on_phones"
+ namespace: "multitasking"
+ description: "Try out bubble bar on phones"
+ bug: "394869612"
+}
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleControllerBubbleBarTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleControllerBubbleBarTest.kt
index bce6c59..a32ec22 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleControllerBubbleBarTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleControllerBubbleBarTest.kt
@@ -61,7 +61,6 @@
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.Mockito
import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever
import java.util.Optional
@@ -133,7 +132,7 @@
mainExecutor,
bgExecutor,
)
- bubbleController.asBubbles().setSysuiProxy(Mockito.mock(SysuiProxy::class.java))
+ bubbleController.asBubbles().setSysuiProxy(mock<SysuiProxy>())
shellInit.init()
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt
index 88bfeb2..e865111 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt
@@ -50,10 +50,10 @@
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.Mockito
import org.mockito.kotlin.any
import org.mockito.kotlin.mock
import org.mockito.kotlin.never
+import org.mockito.kotlin.spy
import org.mockito.kotlin.verify
import java.util.concurrent.Semaphore
import java.util.concurrent.TimeUnit
@@ -635,7 +635,7 @@
@Test
fun removeFromWindow_stopMonitoringSwipeUpGesture() {
- bubbleStackView = Mockito.spy(bubbleStackView)
+ bubbleStackView = spy(bubbleStackView)
InstrumentationRegistry.getInstrumentation().runOnMainSync {
// No way to add to window in the test environment right now so just pretend
bubbleStackView.onDetachedFromWindow()
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/UiEventSubjectTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/UiEventSubjectTest.kt
index af238d0..3499ee3 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/UiEventSubjectTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/UiEventSubjectTest.kt
@@ -29,7 +29,8 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.Mockito.mock
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever
/** Test for [UiEventSubject] */
@@ -130,10 +131,10 @@
}
private fun createBubble(appUid: Int, packageName: String, instanceId: InstanceId): Bubble {
- return mock(Bubble::class.java).apply {
- whenever(getAppUid()).thenReturn(appUid)
- whenever(getPackageName()).thenReturn(packageName)
- whenever(getInstanceId()).thenReturn(instanceId)
+ return mock<Bubble>() {
+ on { getAppUid() } doReturn appUid
+ on { getPackageName() } doReturn packageName
+ on { getInstanceId() } doReturn instanceId
}
}
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt
index c022a29..7b583137 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt
@@ -73,7 +73,6 @@
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.Mockito.mock
import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever
@@ -127,7 +126,7 @@
mainExecutor,
bgExecutor,
)
- bubbleController.asBubbles().setSysuiProxy(mock(SysuiProxy::class.java))
+ bubbleController.asBubbles().setSysuiProxy(mock<SysuiProxy>())
// Flush so that proxy gets set
mainExecutor.flushAll()
diff --git a/libs/WindowManager/Shell/res/drawable/decor_handle_dark.xml b/libs/WindowManager/Shell/res/drawable/decor_handle_dark.xml
index ce24275..05c1e09 100644
--- a/libs/WindowManager/Shell/res/drawable/decor_handle_dark.xml
+++ b/libs/WindowManager/Shell/res/drawable/decor_handle_dark.xml
@@ -13,20 +13,10 @@
~ 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="128dp"
- android:height="4dp"
- android:viewportWidth="128"
- android:viewportHeight="4"
- >
- <group>
- <clip-path
- android:pathData="M2 0H126C127.105 0 128 0.895431 128 2C128 3.10457 127.105 4 126 4H2C0.895431 4 0 3.10457 0 2C0 0.895431 0.895431 0 2 0Z"
- />
- <path
- android:pathData="M0 0V4H128V0"
- android:fillColor="@android:color/black"
- />
- </group>
-</vector>
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <solid android:color="@android:color/black"/>
+ <corners android:radius="2dp"/>
+ <size android:height="4dp"/>
+</shape>
diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_app_handle.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_app_handle.xml
index 1d1cdfa..9451fd4 100644
--- a/libs/WindowManager/Shell/res/layout/desktop_mode_app_handle.xml
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_app_handle.xml
@@ -20,7 +20,7 @@
android:id="@+id/desktop_mode_caption"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:gravity="center_horizontal">
+ android:gravity="center">
<com.android.wm.shell.windowdecor.HandleImageButton
android:id="@+id/caption_handle"
diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml
index 50c0873..477d207 100644
--- a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml
@@ -41,7 +41,7 @@
android:id="@+id/application_icon"
android:layout_width="@dimen/desktop_mode_caption_icon_radius"
android:layout_height="@dimen/desktop_mode_caption_icon_radius"
- android:layout_marginStart="12dp"
+ android:layout_marginStart="10dp"
android:layout_marginEnd="12dp"
android:contentDescription="@string/app_icon_text"
android:importantForAccessibility="no"/>
@@ -53,10 +53,9 @@
<com.android.wm.shell.windowdecor.HandleMenuImageButton
android:id="@+id/collapse_menu_button"
- android:layout_width="32dp"
- android:layout_height="32dp"
- android:padding="4dp"
- android:layout_marginEnd="14dp"
+ android:layout_width="16dp"
+ android:layout_height="16dp"
+ android:layout_marginEnd="16dp"
android:layout_marginStart="14dp"
android:contentDescription="@string/collapse_menu_text"
android:src="@drawable/ic_baseline_expand_more_24"
@@ -78,40 +77,55 @@
<ImageButton
android:id="@+id/fullscreen_button"
- android:layout_marginEnd="4dp"
+ android:paddingStart="16dp"
+ android:paddingEnd="12dp"
android:contentDescription="@string/fullscreen_text"
android:src="@drawable/desktop_mode_ic_handle_menu_fullscreen"
android:tint="@androidprv:color/materialColorOnSurface"
- android:layout_weight="1"
style="@style/DesktopModeHandleMenuWindowingButton"/>
+ <Space
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:layout_weight="1"/>
+
<ImageButton
android:id="@+id/split_screen_button"
- android:layout_marginStart="4dp"
- android:layout_marginEnd="4dp"
+ android:paddingStart="14dp"
+ android:paddingEnd="14dp"
android:contentDescription="@string/split_screen_text"
android:src="@drawable/desktop_mode_ic_handle_menu_splitscreen"
android:tint="@androidprv:color/materialColorOnSurface"
- android:layout_weight="1"
style="@style/DesktopModeHandleMenuWindowingButton"/>
+ <Space
+ android:id="@+id/floating_button_space"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:layout_weight="1"/>
+
<ImageButton
android:id="@+id/floating_button"
- android:layout_marginStart="4dp"
- android:layout_marginEnd="4dp"
+ android:paddingStart="14dp"
+ android:paddingEnd="14dp"
android:contentDescription="@string/float_button_text"
android:src="@drawable/desktop_mode_ic_handle_menu_floating"
android:tint="@androidprv:color/materialColorOnSurface"
- android:layout_weight="1"
style="@style/DesktopModeHandleMenuWindowingButton"/>
+ <Space
+ android:id="@+id/desktop_button_space"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:layout_weight="1"/>
+
<ImageButton
android:id="@+id/desktop_button"
- android:layout_marginStart="4dp"
+ android:paddingStart="12dp"
+ android:paddingEnd="16dp"
android:contentDescription="@string/desktop_text"
android:src="@drawable/desktop_mode_ic_handle_menu_desktop"
android:tint="@androidprv:color/materialColorOnSurface"
- android:layout_weight="1"
style="@style/DesktopModeHandleMenuWindowingButton"/>
</LinearLayout>
@@ -126,77 +140,33 @@
android:elevation="@dimen/desktop_mode_handle_menu_pill_elevation"
android:background="@drawable/desktop_mode_decor_handle_menu_background">
- <LinearLayout
+ <com.android.wm.shell.windowdecor.HandleMenuActionButton
android:id="@+id/screenshot_button"
android:contentDescription="@string/screenshot_text"
- style="@style/DesktopModeHandleMenuActionButtonLayout">
+ android:text="@string/screenshot_text"
+ android:src="@drawable/desktop_mode_ic_handle_menu_screenshot"
+ style="@style/DesktopModeHandleMenuActionButton"/>
- <ImageView
- android:id="@+id/image"
- android:src="@drawable/desktop_mode_ic_handle_menu_screenshot"
- android:importantForAccessibility="no"
- style="@style/DesktopModeHandleMenuActionButtonImage"/>
-
- <com.android.wm.shell.windowdecor.MarqueedTextView
- android:id="@+id/label"
- android:text="@string/screenshot_text"
- style="@style/DesktopModeHandleMenuActionButtonTextView"/>
-
- </LinearLayout>
-
- <LinearLayout
+ <com.android.wm.shell.windowdecor.HandleMenuActionButton
android:id="@+id/new_window_button"
android:contentDescription="@string/new_window_text"
- style="@style/DesktopModeHandleMenuActionButtonLayout">
+ android:text="@string/new_window_text"
+ android:src="@drawable/desktop_mode_ic_handle_menu_new_window"
+ style="@style/DesktopModeHandleMenuActionButton"/>
- <ImageView
- android:id="@+id/image"
- android:src="@drawable/desktop_mode_ic_handle_menu_new_window"
- android:importantForAccessibility="no"
- style="@style/DesktopModeHandleMenuActionButtonImage"/>
-
- <com.android.wm.shell.windowdecor.MarqueedTextView
- android:id="@+id/label"
- android:text="@string/new_window_text"
- style="@style/DesktopModeHandleMenuActionButtonTextView"/>
-
- </LinearLayout>
-
- <LinearLayout
+ <com.android.wm.shell.windowdecor.HandleMenuActionButton
android:id="@+id/manage_windows_button"
android:contentDescription="@string/manage_windows_text"
- style="@style/DesktopModeHandleMenuActionButtonLayout">
+ android:text="@string/manage_windows_text"
+ android:src="@drawable/desktop_mode_ic_handle_menu_manage_windows"
+ style="@style/DesktopModeHandleMenuActionButton"/>
- <ImageView
- android:id="@+id/image"
- android:src="@drawable/desktop_mode_ic_handle_menu_manage_windows"
- android:importantForAccessibility="no"
- style="@style/DesktopModeHandleMenuActionButtonImage"/>
-
- <com.android.wm.shell.windowdecor.MarqueedTextView
- android:id="@+id/label"
- android:text="@string/manage_windows_text"
- style="@style/DesktopModeHandleMenuActionButtonTextView"/>
-
- </LinearLayout>
-
- <LinearLayout
+ <com.android.wm.shell.windowdecor.HandleMenuActionButton
android:id="@+id/change_aspect_ratio_button"
android:contentDescription="@string/change_aspect_ratio_text"
- style="@style/DesktopModeHandleMenuActionButtonLayout">
-
- <ImageView
- android:id="@+id/image"
- android:src="@drawable/desktop_mode_ic_handle_menu_change_aspect_ratio"
- android:importantForAccessibility="no"
- style="@style/DesktopModeHandleMenuActionButtonImage"/>
-
- <com.android.wm.shell.windowdecor.MarqueedTextView
- android:id="@+id/label"
- android:text="@string/change_aspect_ratio_text"
- style="@style/DesktopModeHandleMenuActionButtonTextView"/>
-
- </LinearLayout>
+ android:text="@string/change_aspect_ratio_text"
+ android:src="@drawable/desktop_mode_ic_handle_menu_change_aspect_ratio"
+ style="@style/DesktopModeHandleMenuActionButton"/>
</LinearLayout>
<LinearLayout
@@ -209,29 +179,14 @@
android:elevation="@dimen/desktop_mode_handle_menu_pill_elevation"
android:background="@drawable/desktop_mode_decor_handle_menu_background">
- <LinearLayout
+ <com.android.wm.shell.windowdecor.HandleMenuActionButton
android:id="@+id/open_in_app_or_browser_button"
- android:layout_width="0dp"
- android:layout_height="match_parent"
- android:layout_weight="1"
- android:layout_marginEnd="8dp"
- android:gravity="start|center_vertical"
- android:paddingStart="16dp"
android:contentDescription="@string/open_in_browser_text"
- android:background="?android:selectableItemBackground">
-
- <ImageView
- android:id="@+id/image"
- android:src="@drawable/desktop_mode_ic_handle_menu_open_in_browser"
- android:importantForAccessibility="no"
- style="@style/DesktopModeHandleMenuActionButtonImage"/>
-
- <com.android.wm.shell.windowdecor.MarqueedTextView
- android:id="@+id/label"
- android:text="@string/open_in_browser_text"
- style="@style/DesktopModeHandleMenuActionButtonTextView"/>
-
- </LinearLayout>
+ android:text="@string/open_in_browser_text"
+ android:src="@drawable/desktop_mode_ic_handle_menu_open_in_browser"
+ style="@style/DesktopModeHandleMenuActionButton"
+ android:layout_width="0dp"
+ android:layout_weight="1"/>
<ImageButton
android:id="@+id/open_by_default_button"
diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu_action_button.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu_action_button.xml
new file mode 100644
index 0000000..379f4e9
--- /dev/null
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu_action_button.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2025 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:id="@+id/action_button"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="start|center_vertical"
+ android:paddingHorizontal="16dp"
+ android:clickable="true"
+ android:focusable="true"
+ android:orientation="horizontal"
+ android:background="?android:attr/selectableItemBackground">
+
+ <ImageView
+ android:id="@+id/image"
+ android:contentDescription="@+id/label"
+ style="@style/DesktopModeHandleMenuActionButtonImage"/>
+
+ <com.android.wm.shell.windowdecor.MarqueedTextView
+ android:id="@+id/label"
+ style="@style/DesktopModeHandleMenuActionButtonTextView"/>
+</LinearLayout>
diff --git a/libs/WindowManager/Shell/res/values/attrs.xml b/libs/WindowManager/Shell/res/values/attrs.xml
index fbb5caa..4ba0468 100644
--- a/libs/WindowManager/Shell/res/values/attrs.xml
+++ b/libs/WindowManager/Shell/res/values/attrs.xml
@@ -23,4 +23,11 @@
<declare-styleable name="MessageState">
<attr name="state_task_focused" format="boolean"/>
</declare-styleable>
+
+ <declare-styleable name="HandleMenuActionButton">
+ <attr name="android:text" format="string" />
+ <attr name="android:textColor" format="color" />
+ <attr name="android:src" format="reference" />
+ <attr name="android:drawableTint" format="color" />
+ </declare-styleable>
</resources>
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index e23d572..96e008e 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -570,7 +570,7 @@
<dimen name="desktop_mode_handle_menu_corner_radius">26dp</dimen>
<!-- The radius of the caption menu icon. -->
- <dimen name="desktop_mode_caption_icon_radius">24dp</dimen>
+ <dimen name="desktop_mode_caption_icon_radius">32dp</dimen>
<!-- The radius of the caption menu shadow. -->
<dimen name="desktop_mode_handle_menu_shadow_radius">2dp</dimen>
diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml
index a2231dd..1b7daa8 100644
--- a/libs/WindowManager/Shell/res/values/strings.xml
+++ b/libs/WindowManager/Shell/res/values/strings.xml
@@ -290,7 +290,7 @@
<!-- Accessibility text for the handle fullscreen button [CHAR LIMIT=NONE] -->
<string name="fullscreen_text">Fullscreen</string>
<!-- Accessibility text for the handle desktop button [CHAR LIMIT=NONE] -->
- <string name="desktop_text">Desktop Mode</string>
+ <string name="desktop_text">Desktop View</string>
<!-- Accessibility text for the handle split screen button [CHAR LIMIT=NONE] -->
<string name="split_screen_text">Split Screen</string>
<!-- Accessibility text for the handle more options button [CHAR LIMIT=NONE] -->
@@ -316,7 +316,7 @@
<!-- Accessibility text for the handle menu close menu button [CHAR LIMIT=NONE] -->
<string name="collapse_menu_text">Close Menu</string>
<!-- Accessibility text for the App Header's App Chip [CHAR LIMIT=NONE] -->
- <string name="desktop_mode_app_header_chip_text">Open Menu</string>
+ <string name="desktop_mode_app_header_chip_text"><xliff:g id="app_name" example="Chrome">%1$s</xliff:g> (Desktop View)</string>
<!-- Maximize menu maximize button string. -->
<string name="desktop_mode_maximize_menu_maximize_text">Maximize Screen</string>
<!-- Maximize menu snap buttons string. -->
@@ -342,10 +342,10 @@
<!-- Accessibility text for the Maximize Menu's snap maximize/restore [CHAR LIMIT=NONE] -->
<string name="desktop_mode_a11y_action_maximize_restore">Maximize or restore window size</string>
- <!-- Accessibility action replacement for caption handle menu split screen button [CHAR LIMIT=NONE] -->
- <string name="app_handle_menu_talkback_split_screen_mode_button_text">Enter split screen mode</string>
- <!-- Accessibility action replacement for caption handle menu enter desktop mode button [CHAR LIMIT=NONE] -->
- <string name="app_handle_menu_talkback_desktop_mode_button_text">Enter desktop windowing mode</string>
+ <!-- Accessibility action replacement for caption handle app chip buttons [CHAR LIMIT=NONE] -->
+ <string name="app_handle_chip_accessibility_announce">Open Menu</string>
+ <!-- Accessibility action replacement for caption handle menu buttons [CHAR LIMIT=NONE] -->
+ <string name="app_handle_menu_accessibility_announce">Enter <xliff:g id="windowing_mode" example="Desktop View">%1$s</xliff:g></string>
<!-- Accessibility action replacement for maximize menu enter snap left button [CHAR LIMIT=NONE] -->
<string name="maximize_menu_talkback_action_snap_left_text">Resize window to left</string>
<!-- Accessibility action replacement for maximize menu enter snap right button [CHAR LIMIT=NONE] -->
diff --git a/libs/WindowManager/Shell/res/values/styles.xml b/libs/WindowManager/Shell/res/values/styles.xml
index 035004b..637b47a 100644
--- a/libs/WindowManager/Shell/res/values/styles.xml
+++ b/libs/WindowManager/Shell/res/values/styles.xml
@@ -40,13 +40,11 @@
<item name="android:activityCloseExitAnimation">@anim/forced_resizable_exit</item>
</style>
- <style name="DesktopModeHandleMenuActionButtonLayout">
+ <style name="DesktopModeHandleMenuActionButton">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">52dp</item>
- <item name="android:layout_weight">1</item>
- <item name="android:gravity">start|center_vertical</item>
- <item name="android:paddingHorizontal">16dp</item>
- <item name="android:background">?android:selectableItemBackground</item>
+ <item name="android:textColor">@androidprv:color/materialColorOnSurface</item>
+ <item name="android:drawableTint">@androidprv:color/materialColorOnSurface</item>
</style>
<style name="DesktopModeHandleMenuActionButtonImage">
@@ -71,7 +69,6 @@
<style name="DesktopModeHandleMenuWindowingButton">
<item name="android:layout_width">48dp</item>
<item name="android:layout_height">48dp</item>
- <item name="android:padding">14dp</item>
<item name="android:scaleType">fitCenter</item>
<item name="android:background">?android:selectableItemBackgroundBorderless</item>
</style>
diff --git a/libs/WindowManager/Shell/shared/res/values/dimen.xml b/libs/WindowManager/Shell/shared/res/values/dimen.xml
index 0b1f76f..d280083 100644
--- a/libs/WindowManager/Shell/shared/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/shared/res/values/dimen.xml
@@ -17,4 +17,23 @@
<resources>
<dimen name="floating_dismiss_icon_size">32dp</dimen>
<dimen name="floating_dismiss_background_size">96dp</dimen>
+
+ <!-- Bubble drag zone dimensions -->
+ <dimen name="drag_zone_dismiss_fold">140dp</dimen>
+ <dimen name="drag_zone_dismiss_tablet">200dp</dimen>
+ <dimen name="drag_zone_bubble_fold">140dp</dimen>
+ <dimen name="drag_zone_bubble_tablet">200dp</dimen>
+ <dimen name="drag_zone_full_screen_width">512dp</dimen>
+ <dimen name="drag_zone_full_screen_height">44dp</dimen>
+ <dimen name="drag_zone_desktop_window_width">880dp</dimen>
+ <dimen name="drag_zone_desktop_window_height">300dp</dimen>
+ <dimen name="drag_zone_desktop_window_expanded_view_width">200dp</dimen>
+ <dimen name="drag_zone_desktop_window_expanded_view_height">350dp</dimen>
+ <dimen name="drag_zone_split_from_bubble_height">100dp</dimen>
+ <dimen name="drag_zone_split_from_bubble_width">60dp</dimen>
+ <dimen name="drag_zone_h_split_from_expanded_view_width">60dp</dimen>
+ <dimen name="drag_zone_v_split_from_expanded_view_width">200dp</dimen>
+ <dimen name="drag_zone_v_split_from_expanded_view_height_tablet">285dp</dimen>
+ <dimen name="drag_zone_v_split_from_expanded_view_height_fold_tall">150dp</dimen>
+ <dimen name="drag_zone_v_split_from_expanded_view_height_fold_short">100dp</dimen>
</resources>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DragZoneFactory.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DragZoneFactory.kt
index 35802c93..909e9d2 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DragZoneFactory.kt
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DragZoneFactory.kt
@@ -16,11 +16,15 @@
package com.android.wm.shell.shared.bubbles
+import android.content.Context
import android.graphics.Rect
+import androidx.annotation.DimenRes
+import com.android.wm.shell.shared.R
import com.android.wm.shell.shared.bubbles.DragZoneFactory.SplitScreenModeChecker.SplitScreenMode
/** A class for creating drag zones for dragging bubble objects or dragging into bubbles. */
class DragZoneFactory(
+ private val context: Context,
private val deviceConfig: DeviceConfig,
private val splitScreenModeChecker: SplitScreenModeChecker,
private val desktopWindowModeChecker: DesktopWindowModeChecker,
@@ -29,23 +33,65 @@
private val windowBounds: Rect
get() = deviceConfig.windowBounds
- // TODO b/393172431: move these to xml
- private val dismissDragZoneSize = if (deviceConfig.isSmallTablet) 140 else 200
- private val bubbleDragZoneTabletSize = 200
- private val bubbleDragZoneFoldableSize = 140
- private val fullScreenDragZoneWidth = 512
- private val fullScreenDragZoneHeight = 44
- private val desktopWindowDragZoneWidth = 880
- private val desktopWindowDragZoneHeight = 300
- private val desktopWindowFromExpandedViewDragZoneWidth = 200
- private val desktopWindowFromExpandedViewDragZoneHeight = 350
- private val splitFromBubbleDragZoneHeight = 100
- private val splitFromBubbleDragZoneWidth = 60
- private val hSplitFromExpandedViewDragZoneWidth = 60
- private val vSplitFromExpandedViewDragZoneWidth = 200
- private val vSplitFromExpandedViewDragZoneHeightTablet = 285
- private val vSplitFromExpandedViewDragZoneHeightFoldTall = 150
- private val vSplitFromExpandedViewDragZoneHeightFoldShort = 100
+ private var dismissDragZoneSize = 0
+ private var bubbleDragZoneTabletSize = 0
+ private var bubbleDragZoneFoldableSize = 0
+ private var fullScreenDragZoneWidth = 0
+ private var fullScreenDragZoneHeight = 0
+ private var desktopWindowDragZoneWidth = 0
+ private var desktopWindowDragZoneHeight = 0
+ private var desktopWindowFromExpandedViewDragZoneWidth = 0
+ private var desktopWindowFromExpandedViewDragZoneHeight = 0
+ private var splitFromBubbleDragZoneHeight = 0
+ private var splitFromBubbleDragZoneWidth = 0
+ private var hSplitFromExpandedViewDragZoneWidth = 0
+ private var vSplitFromExpandedViewDragZoneWidth = 0
+ private var vSplitFromExpandedViewDragZoneHeightTablet = 0
+ private var vSplitFromExpandedViewDragZoneHeightFoldTall = 0
+ private var vSplitFromExpandedViewDragZoneHeightFoldShort = 0
+
+ init {
+ onConfigurationUpdated()
+ }
+
+ /** Updates all dimensions after a configuration change. */
+ fun onConfigurationUpdated() {
+ dismissDragZoneSize =
+ if (deviceConfig.isSmallTablet) {
+ context.resolveDimension(R.dimen.drag_zone_dismiss_fold)
+ } else {
+ context.resolveDimension(R.dimen.drag_zone_dismiss_tablet)
+ }
+ bubbleDragZoneTabletSize = context.resolveDimension(R.dimen.drag_zone_bubble_tablet)
+ bubbleDragZoneFoldableSize = context.resolveDimension(R.dimen.drag_zone_bubble_fold)
+ fullScreenDragZoneWidth = context.resolveDimension(R.dimen.drag_zone_full_screen_width)
+ fullScreenDragZoneHeight = context.resolveDimension(R.dimen.drag_zone_full_screen_height)
+ desktopWindowDragZoneWidth =
+ context.resolveDimension(R.dimen.drag_zone_desktop_window_width)
+ desktopWindowDragZoneHeight =
+ context.resolveDimension(R.dimen.drag_zone_desktop_window_height)
+ desktopWindowFromExpandedViewDragZoneWidth =
+ context.resolveDimension(R.dimen.drag_zone_desktop_window_expanded_view_width)
+ desktopWindowFromExpandedViewDragZoneHeight =
+ context.resolveDimension(R.dimen.drag_zone_desktop_window_expanded_view_height)
+ splitFromBubbleDragZoneHeight =
+ context.resolveDimension(R.dimen.drag_zone_split_from_bubble_height)
+ splitFromBubbleDragZoneWidth =
+ context.resolveDimension(R.dimen.drag_zone_split_from_bubble_width)
+ hSplitFromExpandedViewDragZoneWidth =
+ context.resolveDimension(R.dimen.drag_zone_h_split_from_expanded_view_width)
+ vSplitFromExpandedViewDragZoneWidth =
+ context.resolveDimension(R.dimen.drag_zone_v_split_from_expanded_view_width)
+ vSplitFromExpandedViewDragZoneHeightTablet =
+ context.resolveDimension(R.dimen.drag_zone_v_split_from_expanded_view_height_tablet)
+ vSplitFromExpandedViewDragZoneHeightFoldTall =
+ context.resolveDimension(R.dimen.drag_zone_v_split_from_expanded_view_height_fold_tall)
+ vSplitFromExpandedViewDragZoneHeightFoldShort =
+ context.resolveDimension(R.dimen.drag_zone_v_split_from_expanded_view_height_fold_short)
+ }
+
+ private fun Context.resolveDimension(@DimenRes dimension: Int) =
+ resources.getDimensionPixelSize(dimension)
/**
* Creates the list of drag zones for the dragged object.
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicy.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicy.kt
index f234ff5c2c..126ab3d 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicy.kt
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicy.kt
@@ -21,6 +21,7 @@
import android.content.pm.ActivityInfo
import android.content.pm.ActivityInfo.INSETS_DECOUPLED_CONFIGURATION_ENFORCED
import android.content.pm.ActivityInfo.OVERRIDE_ENABLE_INSETS_DECOUPLED_CONFIGURATION
+import android.content.pm.ActivityInfo.OVERRIDE_EXCLUDE_CAPTION_INSETS_FROM_APP_BOUNDS
import android.window.DesktopModeFlags
import com.android.internal.R
import com.android.window.flags.Flags
@@ -49,7 +50,7 @@
numActivities: Int, isTopActivityNoDisplay: Boolean, isActivityStackTransparent: Boolean) =
DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODALS_POLICY.isTrue
&& ((isSystemUiTask(packageName)
- || isPartOfDefaultHomePackage(packageName)
+ || isPartOfDefaultHomePackageOrNoHomeAvailable(packageName)
|| isTransparentTask(isActivityStackTransparent, numActivities))
&& !isTopActivityNoDisplay)
@@ -59,13 +60,16 @@
* The treatment is enabled when all the of the following is true:
* * Any flags to forcibly consume caption insets are enabled.
* * Top activity have configuration coupled with insets.
- * * Task is not resizeable.
+ * * Task is not resizeable or [ActivityInfo.OVERRIDE_EXCLUDE_CAPTION_INSETS_FROM_APP_BOUNDS]
+ * is enabled.
*/
fun shouldExcludeCaptionFromAppBounds(taskInfo: TaskInfo): Boolean =
Flags.excludeCaptionFromAppBounds()
&& isAnyForceConsumptionFlagsEnabled()
&& taskInfo.topActivityInfo?.let {
- isInsetsCoupledWithConfiguration(it) && !taskInfo.isResizeable
+ isInsetsCoupledWithConfiguration(it) && (!taskInfo.isResizeable || it.isChangeEnabled(
+ OVERRIDE_EXCLUDE_CAPTION_INSETS_FROM_APP_BOUNDS
+ ))
} ?: false
/**
@@ -81,10 +85,11 @@
private fun isSystemUiTask(packageName: String?) = packageName == systemUiPackage
/**
- * Returns true if the tasks base activity is part of the default home package.
+ * Returns true if the tasks base activity is part of the default home package, or there is
+ * currently no default home package available.
*/
- private fun isPartOfDefaultHomePackage(packageName: String?) =
- packageName != null && packageName == defaultHomePackage
+ private fun isPartOfDefaultHomePackageOrNoHomeAvailable(packageName: String?) =
+ defaultHomePackage == null || (packageName != null && packageName == defaultHomePackage)
private fun isAnyForceConsumptionFlagsEnabled(): Boolean =
DesktopModeFlags.ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION_ALWAYS.isTrue
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index b2b99d6..5de49b7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -914,12 +914,15 @@
Context context,
Transitions transitions,
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
+ @DynamicOverride DesktopUserRepositories desktopUserRepositories,
InteractionJankMonitor interactionJankMonitor) {
return ENABLE_DESKTOP_WINDOWING_ENTER_TRANSITIONS_BUGFIX.isTrue()
? new SpringDragToDesktopTransitionHandler(
- context, transitions, rootTaskDisplayAreaOrganizer, interactionJankMonitor)
+ context, transitions, rootTaskDisplayAreaOrganizer, desktopUserRepositories,
+ interactionJankMonitor)
: new DefaultDragToDesktopTransitionHandler(
- context, transitions, rootTaskDisplayAreaOrganizer, interactionJankMonitor);
+ context, transitions, rootTaskDisplayAreaOrganizer, desktopUserRepositories,
+ interactionJankMonitor);
}
@WMSingleton
@@ -1303,7 +1306,8 @@
WindowDecorCaptionHandleRepository windowDecorCaptionHandleRepository,
DesktopWindowingEducationTooltipController desktopWindowingEducationTooltipController,
@ShellMainThread CoroutineScope applicationScope,
- @ShellBackgroundThread MainCoroutineDispatcher backgroundDispatcher) {
+ @ShellBackgroundThread MainCoroutineDispatcher backgroundDispatcher,
+ DesktopModeUiEventLogger desktopModeUiEventLogger) {
return new AppHandleEducationController(
context,
appHandleEducationFilter,
@@ -1311,7 +1315,8 @@
windowDecorCaptionHandleRepository,
desktopWindowingEducationTooltipController,
applicationScope,
- backgroundDispatcher);
+ backgroundDispatcher,
+ desktopModeUiEventLogger);
}
@WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUiEventLogger.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUiEventLogger.kt
index b96b9d2..b9cb32d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUiEventLogger.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUiEventLogger.kt
@@ -149,7 +149,25 @@
@UiEvent(doc = "Enter multi-instance by using the New Window button")
DESKTOP_WINDOW_MULTI_INSTANCE_NEW_WINDOW_CLICK(2069),
@UiEvent(doc = "Enter multi-instance by clicking an icon in the Manage Windows menu")
- DESKTOP_WINDOW_MULTI_INSTANCE_MANAGE_WINDOWS_ICON_CLICK(2070);
+ DESKTOP_WINDOW_MULTI_INSTANCE_MANAGE_WINDOWS_ICON_CLICK(2070),
+ @UiEvent(doc = "Education tooltip on the app handle is shown")
+ APP_HANDLE_EDUCATION_TOOLTIP_SHOWN(2097),
+ @UiEvent(doc = "Education tooltip on the app handle is clicked")
+ APP_HANDLE_EDUCATION_TOOLTIP_CLICKED(2098),
+ @UiEvent(doc = "Education tooltip on the app handle is dismissed by the user")
+ APP_HANDLE_EDUCATION_TOOLTIP_DISMISSED(2099),
+ @UiEvent(doc = "Enter desktop mode education tooltip on the app handle menu is shown")
+ ENTER_DESKTOP_MODE_EDUCATION_TOOLTIP_SHOWN(2100),
+ @UiEvent(doc = "Enter desktop mode education tooltip on the app handle menu is clicked")
+ ENTER_DESKTOP_MODE_EDUCATION_TOOLTIP_CLICKED(2101),
+ @UiEvent(doc = "Enter desktop mode education tooltip is dismissed by the user")
+ ENTER_DESKTOP_MODE_EDUCATION_TOOLTIP_DISMISSED(2102),
+ @UiEvent(doc = "Exit desktop mode education tooltip on the app header menu is shown")
+ EXIT_DESKTOP_MODE_EDUCATION_TOOLTIP_SHOWN(2103),
+ @UiEvent(doc = "Exit desktop mode education tooltip on the app header menu is clicked")
+ EXIT_DESKTOP_MODE_EDUCATION_TOOLTIP_CLICKED(2104),
+ @UiEvent(doc = "Exit desktop mode education tooltip is dismissed by the user")
+ EXIT_DESKTOP_MODE_EDUCATION_TOOLTIP_DISMISSED(2105);
override fun getId(): Int = mId
}
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 7491abd..531304d 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
@@ -1659,11 +1659,16 @@
private fun addWallpaperActivity(displayId: Int, wct: WindowContainerTransaction) {
logV("addWallpaperActivity")
if (ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER.isTrue()) {
+
+ // If the wallpaper activity for this display already exists, let's reorder it to top.
+ val wallpaperActivityToken = desktopWallpaperActivityTokenProvider.getToken(displayId)
+ if (wallpaperActivityToken != null) {
+ wct.reorder(wallpaperActivityToken, /* onTop= */ true)
+ return
+ }
+
val intent = Intent(context, DesktopWallpaperActivity::class.java)
- if (
- desktopWallpaperActivityTokenProvider.getToken(displayId) == null &&
- Flags.enablePerDisplayDesktopWallpaperActivity()
- ) {
+ if (Flags.enablePerDisplayDesktopWallpaperActivity()) {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK)
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopUserRepositories.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopUserRepositories.kt
index a5ba661..c10752d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopUserRepositories.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopUserRepositories.kt
@@ -90,6 +90,11 @@
return desktopRepoByUserId.getOrCreate(profileId)
}
+ fun getUserIdForProfile(profileId: Int): Int {
+ if (userIdToProfileIdsMap[userId]?.contains(profileId) == true) return userId
+ else return profileId
+ }
+
/** Dumps [DesktopRepository] for each user. */
fun dump(pw: PrintWriter, prefix: String) {
desktopRepoByUserId.forEach { key, value ->
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 2ac76f3..8194d3c 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
@@ -70,6 +70,7 @@
private val context: Context,
private val transitions: Transitions,
private val taskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer,
+ private val desktopUserRepositories: DesktopUserRepositories,
protected val interactionJankMonitor: InteractionJankMonitor,
protected val transactionSupplier: Supplier<SurfaceControl.Transaction>,
) : TransitionHandler {
@@ -127,15 +128,18 @@
pendingIntentCreatorBackgroundActivityStartMode =
ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
}
- val taskUser = UserHandle.of(taskInfo.userId)
+ // If we are launching home for a profile of a user, just use the [userId] of that user
+ // instead of the [profileId] to create the context.
+ val userToLaunchWith =
+ UserHandle.of(desktopUserRepositories.getUserIdForProfile(taskInfo.userId))
val pendingIntent =
PendingIntent.getActivityAsUser(
- context.createContextAsUser(taskUser, /* flags= */ 0),
+ context.createContextAsUser(userToLaunchWith, /* flags= */ 0),
/* requestCode= */ 0,
launchHomeIntent,
FLAG_MUTABLE or FLAG_ALLOW_UNSAFE_IMPLICIT_INTENT or FILL_IN_COMPONENT,
options.toBundle(),
- taskUser,
+ userToLaunchWith,
)
val wct = WindowContainerTransaction()
// The app that is being dragged into desktop mode might cause new transitions, make this
@@ -881,6 +885,7 @@
context: Context,
transitions: Transitions,
taskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer,
+ desktopUserRepositories: DesktopUserRepositories,
interactionJankMonitor: InteractionJankMonitor,
transactionSupplier: Supplier<SurfaceControl.Transaction> = Supplier {
SurfaceControl.Transaction()
@@ -890,6 +895,7 @@
context,
transitions,
taskDisplayAreaOrganizer,
+ desktopUserRepositories,
interactionJankMonitor,
transactionSupplier,
) {
@@ -917,6 +923,7 @@
context: Context,
transitions: Transitions,
taskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer,
+ desktopUserRepositories: DesktopUserRepositories,
interactionJankMonitor: InteractionJankMonitor,
transactionSupplier: Supplier<SurfaceControl.Transaction> = Supplier {
SurfaceControl.Transaction()
@@ -926,6 +933,7 @@
context,
transitions,
taskDisplayAreaOrganizer,
+ desktopUserRepositories,
interactionJankMonitor,
transactionSupplier,
) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppHandleEducationController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppHandleEducationController.kt
index 5d83556..f664514 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppHandleEducationController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppHandleEducationController.kt
@@ -26,6 +26,8 @@
import com.android.window.flags.Flags
import com.android.wm.shell.R
import com.android.wm.shell.desktopmode.CaptionState
+import com.android.wm.shell.desktopmode.DesktopModeUiEventLogger
+import com.android.wm.shell.desktopmode.DesktopModeUiEventLogger.DesktopUiEventEnum
import com.android.wm.shell.desktopmode.WindowDecorCaptionHandleRepository
import com.android.wm.shell.desktopmode.education.data.AppHandleEducationDatastoreRepository
import com.android.wm.shell.shared.annotations.ShellBackgroundThread
@@ -62,6 +64,7 @@
private val windowingEducationViewController: DesktopWindowingEducationTooltipController,
@ShellMainThread private val applicationCoroutineScope: CoroutineScope,
@ShellBackgroundThread private val backgroundDispatcher: MainCoroutineDispatcher,
+ private val desktopModeUiEventLogger: DesktopModeUiEventLogger,
) {
private lateinit var openHandleMenuCallback: (Int) -> Unit
private lateinit var toDesktopModeCallback: (Int, DesktopModeTransitionSource) -> Unit
@@ -171,6 +174,7 @@
private fun showEducation(captionState: CaptionState) {
val appHandleBounds = (captionState as CaptionState.AppHandle).globalAppHandleBounds
+ val taskInfo = captionState.runningTaskInfo
val tooltipGlobalCoordinates =
Point(appHandleBounds.left + appHandleBounds.width() / 2, appHandleBounds.bottom)
// Populate information important to inflate app handle education tooltip.
@@ -188,22 +192,34 @@
arrowDirection =
DesktopWindowingEducationTooltipController.TooltipArrowDirection.UP,
onEducationClickAction = {
- openHandleMenuCallback(captionState.runningTaskInfo.taskId)
+ openHandleMenuCallback(taskInfo.taskId)
+ desktopModeUiEventLogger.log(
+ taskInfo,
+ DesktopUiEventEnum.APP_HANDLE_EDUCATION_TOOLTIP_CLICKED,
+ )
},
onDismissAction = {
- // TODO: b/341320146 - Log previous tooltip was dismissed
+ desktopModeUiEventLogger.log(
+ taskInfo,
+ DesktopUiEventEnum.APP_HANDLE_EDUCATION_TOOLTIP_DISMISSED,
+ )
},
)
windowingEducationViewController.showEducationTooltip(
tooltipViewConfig = appHandleTooltipConfig,
- taskId = captionState.runningTaskInfo.taskId,
+ taskId = taskInfo.taskId,
+ )
+ desktopModeUiEventLogger.log(
+ taskInfo,
+ DesktopUiEventEnum.APP_HANDLE_EDUCATION_TOOLTIP_SHOWN,
)
}
/** Show tooltip that points to windowing image button in app handle menu */
private suspend fun showWindowingImageButtonTooltip(captionState: CaptionState.AppHandle) {
val appInfoPillHeight = getSize(R.dimen.desktop_mode_handle_menu_app_info_pill_height)
+ val taskInfo = captionState.runningTaskInfo
val windowingOptionPillHeight =
getSize(R.dimen.desktop_mode_handle_menu_windowing_pill_height)
val appHandleMenuWidth =
@@ -245,24 +261,36 @@
DesktopWindowingEducationTooltipController.TooltipArrowDirection.HORIZONTAL,
onEducationClickAction = {
toDesktopModeCallback(
- captionState.runningTaskInfo.taskId,
+ taskInfo.taskId,
DesktopModeTransitionSource.APP_HANDLE_MENU_BUTTON,
)
+ desktopModeUiEventLogger.log(
+ taskInfo,
+ DesktopUiEventEnum.ENTER_DESKTOP_MODE_EDUCATION_TOOLTIP_CLICKED,
+ )
},
onDismissAction = {
- // TODO: b/341320146 - Log previous tooltip was dismissed
+ desktopModeUiEventLogger.log(
+ taskInfo,
+ DesktopUiEventEnum.ENTER_DESKTOP_MODE_EDUCATION_TOOLTIP_DISMISSED,
+ )
},
)
windowingEducationViewController.showEducationTooltip(
- taskId = captionState.runningTaskInfo.taskId,
+ taskId = taskInfo.taskId,
tooltipViewConfig = windowingImageButtonTooltipConfig,
)
+ desktopModeUiEventLogger.log(
+ taskInfo,
+ DesktopUiEventEnum.ENTER_DESKTOP_MODE_EDUCATION_TOOLTIP_SHOWN,
+ )
}
/** Show tooltip that points to app chip button and educates user on how to exit desktop mode */
private suspend fun showExitWindowingTooltip(captionState: CaptionState.AppHeader) {
val globalAppChipBounds = captionState.globalAppChipBounds
+ val taskInfo = captionState.runningTaskInfo
val tooltipGlobalCoordinates =
Point(
if (isRtl()) {
@@ -287,16 +315,27 @@
arrowDirection =
DesktopWindowingEducationTooltipController.TooltipArrowDirection.HORIZONTAL,
onDismissAction = {
- // TODO: b/341320146 - Log previous tooltip was dismissed
+ desktopModeUiEventLogger.log(
+ taskInfo,
+ DesktopUiEventEnum.EXIT_DESKTOP_MODE_EDUCATION_TOOLTIP_DISMISSED,
+ )
},
onEducationClickAction = {
- openHandleMenuCallback(captionState.runningTaskInfo.taskId)
+ openHandleMenuCallback(taskInfo.taskId)
+ desktopModeUiEventLogger.log(
+ taskInfo,
+ DesktopUiEventEnum.EXIT_DESKTOP_MODE_EDUCATION_TOOLTIP_CLICKED,
+ )
},
)
windowingEducationViewController.showEducationTooltip(
- taskId = captionState.runningTaskInfo.taskId,
+ taskId = taskInfo.taskId,
tooltipViewConfig = exitWindowingTooltipConfig,
)
+ desktopModeUiEventLogger.log(
+ taskInfo,
+ DesktopUiEventEnum.EXIT_DESKTOP_MODE_EDUCATION_TOOLTIP_SHOWN,
+ )
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipAppIconOverlay.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipAppIconOverlay.java
index b4cf890..88ac865 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipAppIconOverlay.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipAppIconOverlay.java
@@ -26,6 +26,7 @@
import android.graphics.Matrix;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
+import android.hardware.HardwareBuffer;
import android.util.TypedValue;
import android.view.SurfaceControl;
@@ -39,7 +40,6 @@
private final Context mContext;
private final int mAppIconSizePx;
- private final Rect mAppBounds;
private final int mOverlayHalfSize;
private final Matrix mTmpTransform = new Matrix();
private final float[] mTmpFloat9 = new float[9];
@@ -56,10 +56,6 @@
final int overlaySize = getOverlaySize(appBounds, destinationBounds);
mOverlayHalfSize = overlaySize >> 1;
- // When the activity is in the secondary split, make sure the scaling center is not
- // offset.
- mAppBounds = new Rect(0, 0, appBounds.width(), appBounds.height());
-
mBitmap = Bitmap.createBitmap(overlaySize, overlaySize, Bitmap.Config.ARGB_8888);
prepareAppIconOverlay(appIcon);
mLeash = new SurfaceControl.Builder()
@@ -85,12 +81,17 @@
@Override
public void attach(SurfaceControl.Transaction tx, SurfaceControl parentLeash) {
+ final HardwareBuffer buffer = mBitmap.getHardwareBuffer();
tx.show(mLeash);
tx.setLayer(mLeash, Integer.MAX_VALUE);
- tx.setBuffer(mLeash, mBitmap.getHardwareBuffer());
+ tx.setBuffer(mLeash, buffer);
tx.setAlpha(mLeash, 0f);
tx.reparent(mLeash, parentLeash);
tx.apply();
+ // Cleanup the bitmap and buffer after setting up the leash
+ mBitmap.recycle();
+ mBitmap = null;
+ buffer.close();
}
@Override
@@ -108,16 +109,6 @@
.setAlpha(mLeash, fraction < 0.5f ? 0 : (fraction - 0.5f) * 2);
}
-
-
- @Override
- public void detach(SurfaceControl.Transaction tx) {
- super.detach(tx);
- if (mBitmap != null && !mBitmap.isRecycled()) {
- mBitmap.recycle();
- }
- }
-
private void prepareAppIconOverlay(Drawable appIcon) {
final Canvas canvas = new Canvas();
canvas.setBitmap(mBitmap);
@@ -139,6 +130,8 @@
mOverlayHalfSize + mAppIconSizePx / 2);
appIcon.setBounds(appIconBounds);
appIcon.draw(canvas);
+ Bitmap oldBitmap = mBitmap;
mBitmap = mBitmap.copy(Bitmap.Config.HARDWARE, false /* mutable */);
+ oldBitmap.recycle();
}
}
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 bb9b479..a57b4b9 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
@@ -72,7 +72,6 @@
import com.android.wm.shell.pip2.animation.PipEnterAnimator;
import com.android.wm.shell.pip2.animation.PipExpandAnimator;
import com.android.wm.shell.shared.TransitionUtil;
-import com.android.wm.shell.shared.pip.PipContentOverlay;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
@@ -422,7 +421,7 @@
final Rect destinationBounds = pipChange.getEndAbsBounds();
final SurfaceControl swipePipToHomeOverlay = mPipTransitionState.getSwipePipToHomeOverlay();
if (swipePipToHomeOverlay != null) {
- final int overlaySize = PipContentOverlay.PipAppIconOverlay.getOverlaySize(
+ final int overlaySize = PipAppIconOverlay.getOverlaySize(
mPipTransitionState.getSwipePipToHomeAppBounds(), destinationBounds);
// It is possible we reparent the PIP activity to a new PIP task (in multi-activity
// apps), so we should also reparent the overlay to the final PIP task.
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 15ac03c..a799b7f 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
@@ -2859,14 +2859,6 @@
prepareExitSplitScreen(dismissTop, out, EXIT_REASON_APP_FINISHED);
mSplitTransitions.setDismissTransition(transition, dismissTop,
EXIT_REASON_APP_FINISHED);
- } else if (isOpening && !mPausingTasks.isEmpty()) {
- // One of the splitting task is opening while animating the split pair in
- // recents, which means to dismiss the split pair to this task.
- int dismissTop = getStageType(stage) == STAGE_TYPE_MAIN
- ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE;
- prepareExitSplitScreen(dismissTop, out, EXIT_REASON_APP_FINISHED);
- mSplitTransitions.setDismissTransition(transition, dismissTop,
- EXIT_REASON_APP_FINISHED);
} else if (!isSplitScreenVisible() && isOpening) {
// If split is running in the background and the trigger task is appearing into
// split, prepare to enter split screen.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index a5a5fd7..01428e6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -149,8 +149,7 @@
private Drawable mEnterpriseThumbnailDrawable;
- static final InteractionJankMonitor sInteractionJankMonitor =
- InteractionJankMonitor.getInstance();
+ final InteractionJankMonitor mInteractionJankMonitor;
private BroadcastReceiver mEnterpriseResourceUpdatedReceiver = new BroadcastReceiver() {
@Override
@@ -169,7 +168,8 @@
@NonNull TransactionPool transactionPool,
@NonNull ShellExecutor mainExecutor, @NonNull Handler mainHandler,
@NonNull ShellExecutor animExecutor,
- @NonNull RootTaskDisplayAreaOrganizer rootTDAOrganizer) {
+ @NonNull RootTaskDisplayAreaOrganizer rootTDAOrganizer,
+ @NonNull InteractionJankMonitor interactionJankMonitor) {
mDisplayController = displayController;
mTransactionPool = transactionPool;
mContext = context;
@@ -181,6 +181,7 @@
mDevicePolicyManager = mContext.getSystemService(DevicePolicyManager.class);
shellInit.addInitCallback(this::onInit, this);
mRootTDAOrganizer = rootTDAOrganizer;
+ mInteractionJankMonitor = interactionJankMonitor;
}
private void onInit() {
@@ -331,14 +332,14 @@
final boolean isTaskTransition = isTaskTransition(info);
if (isTaskTransition) {
- sInteractionJankMonitor.begin(info.getRoot(0).getLeash(), mContext,
+ mInteractionJankMonitor.begin(info.getRoot(0).getLeash(), mContext,
mMainHandler, CUJ_DEFAULT_TASK_TO_TASK_ANIMATION);
}
final Runnable onAnimFinish = () -> {
if (!animations.isEmpty()) return;
if (isTaskTransition) {
- sInteractionJankMonitor.end(CUJ_DEFAULT_TASK_TO_TASK_ANIMATION);
+ mInteractionJankMonitor.end(CUJ_DEFAULT_TASK_TO_TASK_ANIMATION);
}
mAnimations.remove(transition);
finishCallback.onTransitionFinished(null /* wct */);
@@ -1031,6 +1032,6 @@
@Override
public void onTransitionConsumed(@NonNull IBinder transition, boolean aborted,
@Nullable SurfaceControl.Transaction finishTransaction) {
- sInteractionJankMonitor.cancel(CUJ_DEFAULT_TASK_TO_TASK_ANIMATION);
+ mInteractionJankMonitor.cancel(CUJ_DEFAULT_TASK_TO_TASK_ANIMATION);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index c90f6cf..deb8839 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -77,6 +77,7 @@
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.protolog.ProtoLog;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
@@ -335,7 +336,8 @@
mDisplayController = displayController;
mPlayerImpl = new TransitionPlayerImpl();
mDefaultTransitionHandler = new DefaultTransitionHandler(context, shellInit,
- displayController, pool, mainExecutor, mainHandler, animExecutor, rootTDAOrganizer);
+ displayController, pool, mainExecutor, mainHandler, animExecutor, rootTDAOrganizer,
+ InteractionJankMonitor.getInstance());
mRemoteTransitionHandler = new RemoteTransitionHandler(mMainExecutor);
mShellCommandHandler = shellCommandHandler;
mShellController = shellController;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
index c92e67f..ff50672 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
@@ -35,9 +35,10 @@
import android.view.View
import android.view.WindowInsets.Type.systemBars
import android.view.WindowManager
-import android.widget.Button
import android.widget.ImageButton
import android.widget.ImageView
+import android.widget.LinearLayout
+import android.widget.Space
import android.widget.TextView
import android.window.DesktopModeFlags
import android.window.SurfaceSyncGroup
@@ -482,16 +483,23 @@
private val splitscreenBtn = windowingPill.requireViewById<ImageButton>(
R.id.split_screen_button)
private val floatingBtn = windowingPill.requireViewById<ImageButton>(R.id.floating_button)
+ private val floatingBtnSpace = windowingPill.requireViewById<Space>(
+ R.id.floating_button_space)
+
private val desktopBtn = windowingPill.requireViewById<ImageButton>(R.id.desktop_button)
+ private val desktopBtnSpace = windowingPill.requireViewById<Space>(
+ R.id.desktop_button_space)
// More Actions Pill.
private val moreActionsPill = rootView.requireViewById<View>(R.id.more_actions_pill)
- private val screenshotBtn = moreActionsPill.requireViewById<View>(R.id.screenshot_button)
- private val newWindowBtn = moreActionsPill.requireViewById<View>(R.id.new_window_button)
+ private val screenshotBtn = moreActionsPill.requireViewById<HandleMenuActionButton>(
+ R.id.screenshot_button)
+ private val newWindowBtn = moreActionsPill.requireViewById<HandleMenuActionButton>(
+ R.id.new_window_button)
private val manageWindowBtn = moreActionsPill
- .requireViewById<View>(R.id.manage_windows_button)
+ .requireViewById<HandleMenuActionButton>(R.id.manage_windows_button)
private val changeAspectRatioBtn = moreActionsPill
- .requireViewById<View>(R.id.change_aspect_ratio_button)
+ .requireViewById<HandleMenuActionButton>(R.id.change_aspect_ratio_button)
// Open in Browser/App Pill.
private val openInAppOrBrowserPill = rootView.requireViewById<View>(
@@ -540,17 +548,35 @@
return@setOnTouchListener true
}
- with(context.resources) {
- // Update a11y read out to say "double tap to enter desktop windowing mode"
+ with(context) {
+ // Update a11y announcement out to say "double tap to enter Fullscreen"
ViewCompat.replaceAccessibilityAction(
- desktopBtn, ACTION_CLICK,
- getString(R.string.app_handle_menu_talkback_desktop_mode_button_text), null
+ fullscreenBtn, ACTION_CLICK,
+ getString(
+ R.string.app_handle_menu_accessibility_announce,
+ getString(R.string.fullscreen_text)
+ ),
+ null,
)
- // Update a11y read out to say "double tap to enter split screen mode"
+ // Update a11y announcement out to say "double tap to enter Desktop View"
+ ViewCompat.replaceAccessibilityAction(
+ desktopBtn, ACTION_CLICK,
+ getString(
+ R.string.app_handle_menu_accessibility_announce,
+ getString(R.string.desktop_text)
+ ),
+ null,
+ )
+
+ // Update a11y announcement to say "double tap to enter Split Screen"
ViewCompat.replaceAccessibilityAction(
splitscreenBtn, ACTION_CLICK,
- getString(R.string.app_handle_menu_talkback_split_screen_mode_button_text), null
+ getString(
+ R.string.app_handle_menu_accessibility_announce,
+ getString(R.string.split_screen_text)
+ ),
+ null,
)
}
}
@@ -666,6 +692,7 @@
if (!BubbleAnythingFlagHelper.enableBubbleToFullscreen()) {
floatingBtn.visibility = View.GONE
+ floatingBtnSpace.visibility = View.GONE
}
fullscreenBtn.isSelected = taskInfo.isFullscreen
@@ -694,8 +721,10 @@
).forEach {
val button = it.first
val shouldShow = it.second
- val label = button.requireViewById<MarqueedTextView>(R.id.label)
- val image = button.requireViewById<ImageView>(R.id.image)
+
+ val buttonRoot = button.requireViewById<LinearLayout>(R.id.action_button)
+ val label = buttonRoot.requireViewById<MarqueedTextView>(R.id.label)
+ val image = buttonRoot.requireViewById<ImageView>(R.id.image)
button.isGone = !shouldShow
label.apply {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuActionButton.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuActionButton.kt
new file mode 100644
index 0000000..4b2e473
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuActionButton.kt
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.windowdecor
+
+import android.annotation.ColorInt
+import android.annotation.IdRes
+import android.content.Context
+import android.content.res.ColorStateList
+import android.util.AttributeSet
+import android.view.LayoutInflater
+import android.widget.ImageView
+import android.widget.LinearLayout
+import android.widget.TextView
+import androidx.core.content.withStyledAttributes
+import androidx.core.view.isGone
+import com.android.wm.shell.R
+
+/**
+ * Button-like component used to display the "Additional options" elements of the Handle menu window
+ * decoration.
+ *
+ * The possible options for which this button is used for are "Screenshot", "New Window", "Manage
+ * Windows" and "Change Aspect Ratio".
+ */
+class HandleMenuActionButton @JvmOverloads constructor(
+ context: Context,
+ attrs: AttributeSet? = null,
+ defStyleAttr: Int = 0
+) : LinearLayout(context, attrs, defStyleAttr) {
+
+ private val rootElement: LinearLayout
+ private val iconView: ImageView
+ private val textView: MarqueedTextView
+
+ init {
+ val view = LayoutInflater.from(context).inflate(
+ R.layout.desktop_mode_window_decor_handle_menu_action_button, this, true)
+ rootElement = findViewById(R.id.action_button)
+ iconView = findViewById(R.id.image)
+ textView = findViewById(R.id.label)
+
+ context.withStyledAttributes(attrs, R.styleable.HandleMenuActionButton) {
+ textView.text = getString(R.styleable.HandleMenuActionButton_android_text)
+ textView.setTextColor(getColor(R.styleable.HandleMenuActionButton_android_textColor, 0))
+ iconView.setImageResource(getResourceId(
+ R.styleable.HandleMenuActionButton_android_src, 0))
+ iconView.imageTintList = getColorStateList(
+ R.styleable.HandleMenuActionButton_android_drawableTint)
+ }
+ }
+
+ /**
+ * Sets a listener to be invoked when this view is clicked.
+ *
+ * @param l the [OnClickListener] that receives click events.
+ */
+ override fun setOnClickListener(l: OnClickListener?) {
+ rootElement.setOnClickListener(l)
+ }
+
+ /**
+ * Sets the text color for the text inside the button.
+ *
+ * @param color the color to set for the text, as a color integer.
+ */
+ fun setTextColor(@ColorInt color: Int) {
+ textView.setTextColor(color)
+ }
+
+ /**
+ * Sets the icon for the button using a resource ID.
+ *
+ * @param resourceId the resource ID of the drawable to set as the icon.
+ */
+ fun setIconResource(@IdRes resourceId: Int) {
+ iconView.setImageResource(resourceId)
+ }
+
+ /**
+ * Sets the text to display inside the button.
+ *
+ * @param text the text to display.
+ */
+ fun setText(text: CharSequence?) {
+ textView.text = text
+ }
+
+ /**
+ * Sets the tint color for the icon.
+ *
+ * @param color the color to use for the tint, as a color integer.
+ */
+ fun setDrawableTint(@ColorInt color: Int) {
+ iconView.imageTintList = ColorStateList.valueOf(color)
+ }
+
+ /**
+ * Gets or sets the tint applied to the icon.
+ *
+ * @return The [ColorStateList] representing the tint, or null if no tint is applied.
+ */
+ var compoundDrawableTintList: ColorStateList?
+ get() = iconView.imageTintList
+ set(value) {
+ iconView.imageTintList = value
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt
index 4762bc2..90c865e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt
@@ -93,9 +93,6 @@
private val lightColors = dynamicLightColorScheme(context)
private val darkColors = dynamicDarkColorScheme(context)
- private val headerButtonOpenMenuA11yText = context.resources
- .getString(R.string.desktop_mode_app_header_chip_text)
-
/**
* The corner radius to apply to the app chip, maximize and close button's background drawable.
**/
@@ -231,35 +228,29 @@
}
}
- val a11yActionOpenHeaderMenu = AccessibilityAction(AccessibilityNodeInfo.ACTION_CLICK,
- headerButtonOpenMenuA11yText)
- openMenuButton.accessibilityDelegate = object : View.AccessibilityDelegate() {
- override fun onInitializeAccessibilityNodeInfo(
- host: View,
- info: AccessibilityNodeInfo
- ) {
- super.onInitializeAccessibilityNodeInfo(host, info)
- info.addAction(a11yActionOpenHeaderMenu)
- }
- }
+ // Update a11y announcement to say "double tap to open menu"
+ ViewCompat.replaceAccessibilityAction(
+ openMenuButton,
+ AccessibilityActionCompat.ACTION_CLICK,
+ context.getString(R.string.app_handle_chip_accessibility_announce),
+ null
+ )
- with(context.resources) {
- // Update a11y read out to say "double tap to maximize or restore window size"
- ViewCompat.replaceAccessibilityAction(
- maximizeWindowButton,
- AccessibilityActionCompat.ACTION_CLICK,
- getString(R.string.maximize_button_talkback_action_maximize_restore_text),
- null
- )
+ // Update a11y announcement to say "double tap to maximize or restore window size"
+ ViewCompat.replaceAccessibilityAction(
+ maximizeWindowButton,
+ AccessibilityActionCompat.ACTION_CLICK,
+ context.getString(R.string.maximize_button_talkback_action_maximize_restore_text),
+ null
+ )
- // Update a11y read out to say "double tap to minimize app window"
- ViewCompat.replaceAccessibilityAction(
- minimizeWindowButton,
- AccessibilityActionCompat.ACTION_CLICK,
- getString(R.string.minimize_button_talkback_action_maximize_restore_text),
- null
- )
- }
+ // Update a11y announcement out to say "double tap to minimize app window"
+ ViewCompat.replaceAccessibilityAction(
+ minimizeWindowButton,
+ AccessibilityActionCompat.ACTION_CLICK,
+ context.getString(R.string.minimize_button_talkback_action_maximize_restore_text),
+ null
+ )
}
override fun bindData(data: HeaderData) {
@@ -275,7 +266,8 @@
/** Sets the app's name in the header. */
fun setAppName(name: CharSequence) {
appNameTextView.text = name
- openMenuButton.contentDescription = name
+ openMenuButton.contentDescription =
+ context.getString(R.string.desktop_mode_app_header_chip_text, name)
}
/** Sets the app's icon in the header. */
diff --git a/libs/WindowManager/Shell/tests/flicker/bubble/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/flicker/bubble/AndroidTestTemplate.xml
index 02b2cec..ae73dae 100644
--- a/libs/WindowManager/Shell/tests/flicker/bubble/AndroidTestTemplate.xml
+++ b/libs/WindowManager/Shell/tests/flicker/bubble/AndroidTestTemplate.xml
@@ -53,10 +53,12 @@
<option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/>
<option name="run-command" value="settings put system show_touches 1"/>
<option name="run-command" value="settings put system pointer_location 1"/>
+ <option name="run-command" value="settings put secure glanceable_hub_enabled 0"/>
<option name="teardown-command"
value="settings delete secure show_ime_with_hard_keyboard"/>
<option name="teardown-command" value="settings delete system show_touches"/>
<option name="teardown-command" value="settings delete system pointer_location"/>
+ <option name="teardown-command" value="settings delete secure glanceable_hub_enabled"/>
<option name="teardown-command"
value="cmd overlay enable com.android.internal.systemui.navbar.gestural"/>
</target_preparer>
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index 98c5bc96..be53272 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -51,6 +51,7 @@
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.FlagsParameterization
+import android.testing.TestableContext
import android.view.Display.DEFAULT_DISPLAY
import android.view.DragEvent
import android.view.Gravity
@@ -147,6 +148,7 @@
import com.android.wm.shell.transition.Transitions
import com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS
import com.android.wm.shell.transition.Transitions.TransitionHandler
+import com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModelTestsBase.Companion.HOME_LAUNCHER_PACKAGE_NAME
import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration
import com.android.wm.shell.windowdecor.tiling.DesktopTilingDecorViewModel
import com.google.common.truth.Truth.assertThat
@@ -171,7 +173,6 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers.isA
import org.mockito.ArgumentMatchers.isNull
import org.mockito.Mock
@@ -187,7 +188,6 @@
import org.mockito.kotlin.argThat
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.atLeastOnce
-import org.mockito.kotlin.capture
import org.mockito.kotlin.eq
import org.mockito.kotlin.whenever
import org.mockito.quality.Strictness
@@ -257,6 +257,7 @@
@Mock private lateinit var desksOrganizer: DesksOrganizer
@Mock private lateinit var userProfileContexts: UserProfileContexts
@Mock private lateinit var desksTransitionsObserver: DesksTransitionObserver
+ @Mock private lateinit var packageManager: PackageManager
private lateinit var controller: DesktopTasksController
private lateinit var shellInit: ShellInit
@@ -266,6 +267,7 @@
private lateinit var recentsTransitionStateListener: RecentsTransitionStateListener
private lateinit var testScope: CoroutineScope
private lateinit var desktopModeCompatPolicy: DesktopModeCompatPolicy
+ private lateinit var spyContext: TestableContext
private val shellExecutor = TestShellExecutor()
@@ -282,6 +284,7 @@
private val UNRESIZABLE_LANDSCAPE_BOUNDS = Rect(25, 449, 1575, 1611)
private val UNRESIZABLE_PORTRAIT_BOUNDS = Rect(830, 75, 1730, 1275)
private val wallpaperToken = MockToken().token()
+ private val homeComponentName = ComponentName(HOME_LAUNCHER_PACKAGE_NAME, /* class */ "")
@Before
fun setUp() {
@@ -295,6 +298,7 @@
doReturn(true).`when` { DesktopModeStatus.canEnterDesktopMode(any()) }
testScope = CoroutineScope(Dispatchers.Unconfined + SupervisorJob())
+ spyContext = spy(mContext)
shellInit = spy(ShellInit(testExecutor))
userRepositories =
DesktopUserRepositories(
@@ -316,7 +320,7 @@
mContext,
mockHandler,
)
- desktopModeCompatPolicy = spy(DesktopModeCompatPolicy(context))
+ desktopModeCompatPolicy = spy(DesktopModeCompatPolicy(spyContext))
whenever(shellTaskOrganizer.getRunningTasks(anyInt())).thenAnswer { runningTasks }
whenever(transitions.startTransition(anyInt(), any(), isNull())).thenAnswer { Binder() }
@@ -363,9 +367,9 @@
shellInit.init()
- val captor = ArgumentCaptor.forClass(RecentsTransitionStateListener::class.java)
+ val captor = argumentCaptor<RecentsTransitionStateListener>()
verify(recentsTransitionHandler).addTransitionStateListener(captor.capture())
- recentsTransitionStateListener = captor.value
+ recentsTransitionStateListener = captor.firstValue
controller.taskbarDesktopTaskListener = taskbarDesktopTaskListener
@@ -374,6 +378,9 @@
taskRepository = userRepositories.current
taskRepository.addDesk(displayId = DEFAULT_DISPLAY, deskId = DEFAULT_DISPLAY)
taskRepository.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = DEFAULT_DISPLAY)
+
+ spyContext.setMockPackageManager(packageManager)
+ whenever(packageManager.getHomeActivities(ArrayList())).thenReturn(homeComponentName)
}
private fun createController() =
@@ -441,7 +448,7 @@
fun doesAnyTaskRequireTaskbarRounding_toggleResizeOfFreeFormTask_returnTrue() {
val task1 = setUpFreeformTask()
- val argumentCaptor = ArgumentCaptor.forClass(Boolean::class.java)
+ val argumentCaptor = argumentCaptor<Boolean>()
controller.toggleDesktopTaskSize(
task1,
ToggleTaskSizeInteraction(
@@ -461,7 +468,7 @@
STABLE_BOUNDS.height(),
displayController,
)
- assertThat(argumentCaptor.value).isTrue()
+ assertThat(argumentCaptor.firstValue).isTrue()
}
@Test
@@ -476,7 +483,7 @@
val stableBounds = Rect().apply { displayLayout.getStableBounds(this) }
val task1 = setUpFreeformTask(bounds = stableBounds, active = true)
- val argumentCaptor = ArgumentCaptor.forClass(Boolean::class.java)
+ val argumentCaptor = argumentCaptor<Boolean>()
controller.toggleDesktopTaskSize(
task1,
ToggleTaskSizeInteraction(
@@ -497,7 +504,7 @@
eq(displayController),
anyOrNull(),
)
- assertThat(argumentCaptor.value).isFalse()
+ assertThat(argumentCaptor.firstValue).isFalse()
}
@Test
@@ -547,6 +554,7 @@
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
@DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
fun showDesktopApps_allAppsInvisible_bringsToFront_desktopWallpaperEnabled() {
+ whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
val task1 = setUpFreeformTask()
val task2 = setUpFreeformTask()
markTaskHidden(task1)
@@ -581,7 +589,7 @@
val wct =
getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
// Wallpaper is moved to front.
- wct.assertPendingIntentAt(index = 0, desktopWallpaperIntent)
+ wct.assertReorderAt(index = 0, wallpaperToken)
// Desk is activated.
verify(desksOrganizer).activateDesk(wct, deskId)
}
@@ -783,6 +791,7 @@
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
@DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
fun showDesktopApps_appsAlreadyVisible_bringsToFront_desktopWallpaperEnabled() {
+ whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
val task1 = setUpFreeformTask()
val task2 = setUpFreeformTask()
markTaskVisible(task1)
@@ -825,7 +834,8 @@
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
@DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
- fun showDesktopApps_someAppsInvisible_reordersAll_desktopWallpaperEnabled() {
+ fun showDesktopApps_someAppsInvisible_desktopWallpaperEnabled_reordersOnlyFreeformTasks() {
+ whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
val task1 = setUpFreeformTask()
val task2 = setUpFreeformTask()
markTaskHidden(task1)
@@ -842,6 +852,24 @@
wct.assertReorderAt(index = 2, task2)
}
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun showDesktopApps_someAppsInvisible_desktopWallpaperEnabled_reordersAll() {
+ val task1 = setUpFreeformTask()
+ val task2 = setUpFreeformTask()
+ markTaskHidden(task1)
+ markTaskVisible(task2)
+
+ controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
+
+ val wct =
+ getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
+ assertThat(wct.hierarchyOps).hasSize(3)
+ // Expect order to be from bottom: wallpaper intent, task1, task2
+ wct.assertReorderAt(index = 0, wallpaperToken)
+ wct.assertReorderAt(index = 1, task1)
+ wct.assertReorderAt(index = 2, task2)
+ }
+
@Test
@DisableFlags(
Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
@@ -860,9 +888,9 @@
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun showDesktopApps_noActiveTasks_addDesktopWallpaper_desktopWallpaperEnabled() {
- whenever(transitions.startTransition(eq(TRANSIT_TO_FRONT), any(), anyOrNull()))
- .thenReturn(Binder())
+ fun showDesktopApps_noActiveTasks_desktopWallpaperEnabled_addsDesktopWallpaper() {
+ whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
+
controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
val wct =
@@ -871,10 +899,18 @@
}
@Test
- @DisableFlags(
- Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
- Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
- )
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun showDesktopApps_noActiveTasks_desktopWallpaperEnabled_reordersDesktopWallpaper() {
+ controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
+
+ val wct =
+ getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
+ wct.assertReorderAt(index = 0, wallpaperToken)
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
fun showDesktopApps_twoDisplays_bringsToFrontOnlyOneDisplay_desktopWallpaperDisabled() {
taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
val homeTaskDefaultDisplay = setUpHomeTask(DEFAULT_DISPLAY)
@@ -899,6 +935,7 @@
fun showDesktopApps_twoDisplays_bringsToFrontOnlyOneDisplay_desktopWallpaperEnabled() {
whenever(transitions.startTransition(eq(TRANSIT_TO_FRONT), any(), anyOrNull()))
.thenReturn(Binder())
+ whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
val homeTaskDefaultDisplay = setUpHomeTask(DEFAULT_DISPLAY)
val taskDefaultDisplay = setUpFreeformTask(DEFAULT_DISPLAY)
@@ -991,6 +1028,7 @@
/** TODO: b/362720497 - add multi-desk version when minimization is implemented. */
@DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
fun showDesktopApps_desktopWallpaperEnabled_dontReorderMinimizedTask() {
+ whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
val homeTask = setUpHomeTask()
val freeformTask = setUpFreeformTask()
val minimizedTask = setUpFreeformTask()
@@ -1569,6 +1607,7 @@
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
fun moveTaskToDesktop_desktopWallpaperEnabled_nonRunningTask_launchesInFreeform() {
+ whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
val task = createTaskInfo(1)
whenever(shellTaskOrganizer.getRunningTaskInfo(anyInt())).thenReturn(null)
whenever(recentTasksController.findTaskInBackground(anyInt())).thenReturn(task)
@@ -1736,7 +1775,7 @@
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
fun moveBackgroundTaskToDesktop_remoteTransition_usesOneShotHandler() {
- val transitionHandlerArgCaptor = ArgumentCaptor.forClass(TransitionHandler::class.java)
+ val transitionHandlerArgCaptor = argumentCaptor<TransitionHandler>()
whenever(transitions.startTransition(anyInt(), any(), transitionHandlerArgCaptor.capture()))
.thenReturn(Binder())
@@ -1751,12 +1790,12 @@
verify(desktopModeEnterExitTransitionListener)
.onEnterDesktopModeTransitionStarted(FREEFORM_ANIMATION_DURATION)
- assertIs<OneShotRemoteHandler>(transitionHandlerArgCaptor.value)
+ assertIs<OneShotRemoteHandler>(transitionHandlerArgCaptor.firstValue)
}
@Test
fun moveRunningTaskToDesktop_remoteTransition_usesOneShotHandler() {
- val transitionHandlerArgCaptor = ArgumentCaptor.forClass(TransitionHandler::class.java)
+ val transitionHandlerArgCaptor = argumentCaptor<TransitionHandler>()
whenever(transitions.startTransition(anyInt(), any(), transitionHandlerArgCaptor.capture()))
.thenReturn(Binder())
@@ -1768,7 +1807,7 @@
verify(desktopModeEnterExitTransitionListener)
.onEnterDesktopModeTransitionStarted(FREEFORM_ANIMATION_DURATION)
- assertIs<OneShotRemoteHandler>(transitionHandlerArgCaptor.value)
+ assertIs<OneShotRemoteHandler>(transitionHandlerArgCaptor.firstValue)
}
@Test
@@ -1802,6 +1841,7 @@
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
@DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
fun moveRunningTaskToDesktop_otherFreeformTasksBroughtToFront_desktopWallpaperEnabled() {
+ whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
val freeformTask = setUpFreeformTask()
val fullscreenTask = setUpFullscreenTask()
markTaskHidden(freeformTask)
@@ -1828,6 +1868,7 @@
@EnableFlags(
Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER,
)
fun moveRunningTaskToDesktop_desktopWallpaperEnabled_multiDesksEnabled() {
val freeformTask = setUpFreeformTask()
@@ -1840,7 +1881,7 @@
)
val wct = getLatestEnterDesktopWct()
- wct.assertPendingIntentAt(index = 0, desktopWallpaperIntent)
+ wct.assertReorderAt(index = 0, wallpaperToken)
verify(desksOrganizer).moveTaskToDesk(wct, deskId = 0, fullscreenTask)
verify(desksOrganizer).activateDesk(wct, deskId = 0)
verify(desktopModeEnterExitTransitionListener)
@@ -1967,6 +2008,7 @@
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
@DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
fun moveRunningTaskToDesktop_desktopWallpaperEnabled_bringsTasksOverLimit_dontShowBackTask() {
+ whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
val freeformTasks = (1..MAX_TASK_LIMIT).map { _ -> setUpFreeformTask() }
val newTask = setUpFullscreenTask()
val homeTask = setUpHomeTask()
@@ -2224,26 +2266,26 @@
fun moveTaskToFront_remoteTransition_usesOneshotHandler() {
setUpHomeTask()
val freeformTasks = List(MAX_TASK_LIMIT) { setUpFreeformTask() }
- val transitionHandlerArgCaptor = ArgumentCaptor.forClass(TransitionHandler::class.java)
+ val transitionHandlerArgCaptor = argumentCaptor<TransitionHandler>()
whenever(transitions.startTransition(anyInt(), any(), transitionHandlerArgCaptor.capture()))
.thenReturn(Binder())
controller.moveTaskToFront(freeformTasks[0], RemoteTransition(TestRemoteTransition()))
- assertIs<OneShotRemoteHandler>(transitionHandlerArgCaptor.value)
+ assertIs<OneShotRemoteHandler>(transitionHandlerArgCaptor.firstValue)
}
@Test
fun moveTaskToFront_bringsTasksOverLimit_remoteTransition_usesWindowLimitHandler() {
setUpHomeTask()
val freeformTasks = List(MAX_TASK_LIMIT + 1) { setUpFreeformTask() }
- val transitionHandlerArgCaptor = ArgumentCaptor.forClass(TransitionHandler::class.java)
+ val transitionHandlerArgCaptor = argumentCaptor<TransitionHandler>()
whenever(transitions.startTransition(anyInt(), any(), transitionHandlerArgCaptor.capture()))
.thenReturn(Binder())
controller.moveTaskToFront(freeformTasks[0], RemoteTransition(TestRemoteTransition()))
- assertThat(transitionHandlerArgCaptor.value)
+ assertThat(transitionHandlerArgCaptor.firstValue)
.isInstanceOf(DesktopWindowLimitRemoteHandler::class.java)
}
@@ -2718,9 +2760,9 @@
controller.minimizeTask(task, MinimizeReason.MINIMIZE_BUTTON)
- val captor = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+ val captor = argumentCaptor<WindowContainerTransaction>()
verify(freeformTaskTransitionStarter).startMinimizedModeTransition(captor.capture())
- captor.value.hierarchyOps.none { hop ->
+ captor.firstValue.hierarchyOps.none { hop ->
hop.type == HIERARCHY_OP_TYPE_REMOVE_TASK && hop.container == wallpaperToken.asBinder()
}
}
@@ -2759,9 +2801,9 @@
controller.minimizeTask(task, MinimizeReason.MINIMIZE_BUTTON)
- val captor = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+ val captor = argumentCaptor<WindowContainerTransaction>()
verify(freeformTaskTransitionStarter).startPipTransition(captor.capture())
- captor.value.hierarchyOps.none { hop ->
+ captor.firstValue.hierarchyOps.none { hop ->
hop.type == HIERARCHY_OP_TYPE_REMOVE_TASK && hop.container == wallpaperToken.asBinder()
}
}
@@ -2775,9 +2817,9 @@
controller.minimizeTask(task, MinimizeReason.MINIMIZE_BUTTON)
- val captor = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+ val captor = argumentCaptor<WindowContainerTransaction>()
verify(freeformTaskTransitionStarter).startMinimizedModeTransition(captor.capture())
- captor.value.hierarchyOps.none { hop -> hop.type == HIERARCHY_OP_TYPE_REMOVE_TASK }
+ captor.firstValue.hierarchyOps.none { hop -> hop.type == HIERARCHY_OP_TYPE_REMOVE_TASK }
}
@Test
@@ -2791,10 +2833,10 @@
// The only active task is being minimized.
controller.minimizeTask(task, MinimizeReason.MINIMIZE_BUTTON)
- val captor = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+ val captor = argumentCaptor<WindowContainerTransaction>()
verify(freeformTaskTransitionStarter).startMinimizedModeTransition(captor.capture())
// Adds remove wallpaper operation
- captor.value.assertReorderAt(index = 0, wallpaperToken, toTop = false)
+ captor.firstValue.assertReorderAt(index = 0, wallpaperToken, toTop = false)
}
@Test
@@ -2808,9 +2850,9 @@
// The only active task is already minimized.
controller.minimizeTask(task, MinimizeReason.MINIMIZE_BUTTON)
- val captor = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+ val captor = argumentCaptor<WindowContainerTransaction>()
verify(freeformTaskTransitionStarter).startMinimizedModeTransition(captor.capture())
- captor.value.hierarchyOps.none { hop ->
+ captor.firstValue.hierarchyOps.none { hop ->
hop.type == HIERARCHY_OP_TYPE_REMOVE_TASK && hop.container == wallpaperToken.asBinder()
}
}
@@ -2825,9 +2867,9 @@
controller.minimizeTask(task1, MinimizeReason.MINIMIZE_BUTTON)
- val captor = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+ val captor = argumentCaptor<WindowContainerTransaction>()
verify(freeformTaskTransitionStarter).startMinimizedModeTransition(captor.capture())
- captor.value.hierarchyOps.none { hop ->
+ captor.firstValue.hierarchyOps.none { hop ->
hop.type == HIERARCHY_OP_TYPE_REMOVE_TASK && hop.container == wallpaperToken.asBinder()
}
}
@@ -2845,10 +2887,10 @@
// task1 is the only visible task as task2 is minimized.
controller.minimizeTask(task1, MinimizeReason.MINIMIZE_BUTTON)
// Adds remove wallpaper operation
- val captor = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+ val captor = argumentCaptor<WindowContainerTransaction>()
verify(freeformTaskTransitionStarter).startMinimizedModeTransition(captor.capture())
// Adds remove wallpaper operation
- captor.value.assertReorderAt(index = 0, wallpaperToken, toTop = false)
+ captor.firstValue.assertReorderAt(index = 0, wallpaperToken, toTop = false)
}
@Test
@@ -2987,6 +3029,7 @@
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
fun handleRequest_fullscreenTask_noTasks_enforceDesktop_freeformDisplay_returnFreeformWCT() {
+ whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
whenever(DesktopModeStatus.enterDesktopByDefaultOnFreeformDisplay(context)).thenReturn(true)
val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM
@@ -3118,6 +3161,7 @@
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
fun handleRequest_freeformTask_desktopWallpaperEnabled_freeformNotVisible_reorderedToTop() {
+ whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
val freeformTask1 = setUpFreeformTask()
val freeformTask2 = createFreeformTask()
@@ -3152,7 +3196,9 @@
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
fun handleRequest_freeformTask_desktopWallpaperEnabled_noOtherTasks_reorderedToTop() {
+ whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
val task = createFreeformTask()
+
val result = controller.handleRequest(Binder(), createTransition(task))
assertNotNull(result, "Should handle request")
@@ -3180,6 +3226,7 @@
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
fun handleRequest_freeformTask_dskWallpaperEnabled_freeformOnOtherDisplayOnly_reorderedToTop() {
+ whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
val taskDefaultDisplay = createFreeformTask(displayId = DEFAULT_DISPLAY)
// Second display task
createFreeformTask(displayId = SECOND_DISPLAY)
@@ -4635,7 +4682,7 @@
controller.enterSplit(DEFAULT_DISPLAY, leftOrTop = false)
- val wctArgument = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+ val wctArgument = argumentCaptor<WindowContainerTransaction>()
verify(splitScreenController)
.requestEnterSplitSelect(
eq(task2),
@@ -4643,9 +4690,9 @@
eq(SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT),
eq(task2.configuration.windowConfiguration.bounds),
)
- assertThat(wctArgument.value.hierarchyOps).hasSize(1)
+ assertThat(wctArgument.firstValue.hierarchyOps).hasSize(1)
// Removes wallpaper activity when leaving desktop
- wctArgument.value.assertReorderAt(index = 0, wallpaperToken, toTop = false)
+ wctArgument.firstValue.assertReorderAt(index = 0, wallpaperToken, toTop = false)
}
@Test
@@ -4660,7 +4707,7 @@
controller.enterSplit(DEFAULT_DISPLAY, leftOrTop = false)
- val wctArgument = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+ val wctArgument = argumentCaptor<WindowContainerTransaction>()
verify(splitScreenController)
.requestEnterSplitSelect(
eq(task2),
@@ -4669,7 +4716,7 @@
eq(task2.configuration.windowConfiguration.bounds),
)
// Does not remove wallpaper activity, as desktop still has visible desktop tasks
- assertThat(wctArgument.value.hierarchyOps).isEmpty()
+ assertThat(wctArgument.firstValue.hierarchyOps).isEmpty()
}
@Test
@@ -4677,7 +4724,7 @@
fun newWindow_fromFullscreenOpensInSplit() {
setUpLandscapeDisplay()
val task = setUpFullscreenTask()
- val optionsCaptor = ArgumentCaptor.forClass(Bundle::class.java)
+ val optionsCaptor = argumentCaptor<Bundle>()
runOpenNewWindow(task)
verify(splitScreenController)
.startIntent(
@@ -4690,7 +4737,7 @@
eq(true),
eq(SPLIT_INDEX_UNDEFINED),
)
- assertThat(ActivityOptions.fromBundle(optionsCaptor.value).launchWindowingMode)
+ assertThat(ActivityOptions.fromBundle(optionsCaptor.firstValue).launchWindowingMode)
.isEqualTo(WINDOWING_MODE_MULTI_WINDOW)
}
@@ -4699,7 +4746,7 @@
fun newWindow_fromSplitOpensInSplit() {
setUpLandscapeDisplay()
val task = setUpSplitScreenTask()
- val optionsCaptor = ArgumentCaptor.forClass(Bundle::class.java)
+ val optionsCaptor = argumentCaptor<Bundle>()
runOpenNewWindow(task)
verify(splitScreenController)
.startIntent(
@@ -4712,7 +4759,7 @@
eq(true),
eq(SPLIT_INDEX_UNDEFINED),
)
- assertThat(ActivityOptions.fromBundle(optionsCaptor.value).launchWindowingMode)
+ assertThat(ActivityOptions.fromBundle(optionsCaptor.firstValue).launchWindowingMode)
.isEqualTo(WINDOWING_MODE_MULTI_WINDOW)
}
@@ -4807,11 +4854,11 @@
setUpLandscapeDisplay()
val task = setUpFullscreenTask()
val taskToRequest = setUpFreeformTask()
- val optionsCaptor = ArgumentCaptor.forClass(Bundle::class.java)
+ val optionsCaptor = argumentCaptor<Bundle>()
runOpenInstance(task, taskToRequest.taskId)
verify(splitScreenController)
.startTask(anyInt(), anyInt(), optionsCaptor.capture(), anyOrNull())
- assertThat(ActivityOptions.fromBundle(optionsCaptor.value).launchWindowingMode)
+ assertThat(ActivityOptions.fromBundle(optionsCaptor.firstValue).launchWindowingMode)
.isEqualTo(WINDOWING_MODE_MULTI_WINDOW)
}
@@ -4821,11 +4868,11 @@
setUpLandscapeDisplay()
val task = setUpSplitScreenTask()
val taskToRequest = setUpFreeformTask()
- val optionsCaptor = ArgumentCaptor.forClass(Bundle::class.java)
+ val optionsCaptor = argumentCaptor<Bundle>()
runOpenInstance(task, taskToRequest.taskId)
verify(splitScreenController)
.startTask(anyInt(), anyInt(), optionsCaptor.capture(), anyOrNull())
- assertThat(ActivityOptions.fromBundle(optionsCaptor.value).launchWindowingMode)
+ assertThat(ActivityOptions.fromBundle(optionsCaptor.firstValue).launchWindowingMode)
.isEqualTo(WINDOWING_MODE_MULTI_WINDOW)
}
@@ -5912,35 +5959,37 @@
mockDragEvent,
mockCallback as Consumer<Boolean>,
)
- val arg: ArgumentCaptor<WindowContainerTransaction> =
- ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+ val arg = argumentCaptor<WindowContainerTransaction>()
var expectedWindowingMode: Int
if (indicatorType == DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR) {
expectedWindowingMode = WINDOWING_MODE_FULLSCREEN
// Fullscreen launches currently use default transitions
- verify(transitions).startTransition(any(), capture(arg), anyOrNull())
+ verify(transitions).startTransition(any(), arg.capture(), anyOrNull())
} else {
expectedWindowingMode = WINDOWING_MODE_FREEFORM
if (tabTearingAnimationFlagEnabled) {
verify(desktopMixedTransitionHandler)
.startLaunchTransition(
eq(TRANSIT_OPEN),
- capture(arg),
+ arg.capture(),
anyOrNull(),
anyOrNull(),
anyOrNull(),
)
} else {
// All other launches use a special handler.
- verify(dragAndDropTransitionHandler).handleDropEvent(capture(arg))
+ verify(dragAndDropTransitionHandler).handleDropEvent(arg.capture())
}
}
assertThat(
- ActivityOptions.fromBundle(arg.value.hierarchyOps[0].launchOptions)
+ ActivityOptions.fromBundle(arg.firstValue.hierarchyOps[0].launchOptions)
.launchWindowingMode
)
.isEqualTo(expectedWindowingMode)
- assertThat(ActivityOptions.fromBundle(arg.value.hierarchyOps[0].launchOptions).launchBounds)
+ assertThat(
+ ActivityOptions.fromBundle(arg.firstValue.hierarchyOps[0].launchOptions)
+ .launchBounds
+ )
.isEqualTo(expectedBounds)
}
@@ -6122,52 +6171,49 @@
@WindowManager.TransitionType type: Int = TRANSIT_OPEN,
handlerClass: Class<out TransitionHandler>? = null,
): WindowContainerTransaction {
- val arg = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+ val arg = argumentCaptor<WindowContainerTransaction>()
if (handlerClass == null) {
verify(transitions).startTransition(eq(type), arg.capture(), isNull())
} else {
verify(transitions).startTransition(eq(type), arg.capture(), isA(handlerClass))
}
- return arg.value
+ return arg.lastValue
}
private fun getLatestToggleResizeDesktopTaskWct(
currentBounds: Rect? = null
): WindowContainerTransaction {
- val arg: ArgumentCaptor<WindowContainerTransaction> =
- ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+ val arg = argumentCaptor<WindowContainerTransaction>()
verify(toggleResizeDesktopTaskTransitionHandler, atLeastOnce())
- .startTransition(capture(arg), eq(currentBounds))
- return arg.value
+ .startTransition(arg.capture(), eq(currentBounds))
+ return arg.lastValue
}
private fun getLatestDesktopMixedTaskWct(
@WindowManager.TransitionType type: Int = TRANSIT_OPEN
): WindowContainerTransaction {
- val arg: ArgumentCaptor<WindowContainerTransaction> =
- ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+ val arg = argumentCaptor<WindowContainerTransaction>()
verify(desktopMixedTransitionHandler)
- .startLaunchTransition(eq(type), capture(arg), anyOrNull(), anyOrNull(), anyOrNull())
- return arg.value
+ .startLaunchTransition(eq(type), arg.capture(), anyOrNull(), anyOrNull(), anyOrNull())
+ return arg.lastValue
}
private fun getLatestEnterDesktopWct(): WindowContainerTransaction {
- val arg = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+ val arg = argumentCaptor<WindowContainerTransaction>()
verify(enterDesktopTransitionHandler).moveToDesktop(arg.capture(), any())
- return arg.value
+ return arg.lastValue
}
private fun getLatestDragToDesktopWct(): WindowContainerTransaction {
- val arg: ArgumentCaptor<WindowContainerTransaction> =
- ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
- verify(dragToDesktopTransitionHandler).finishDragToDesktopTransition(capture(arg))
- return arg.value
+ val arg = argumentCaptor<WindowContainerTransaction>()
+ verify(dragToDesktopTransitionHandler).finishDragToDesktopTransition(arg.capture())
+ return arg.lastValue
}
private fun getLatestExitDesktopWct(): WindowContainerTransaction {
- val arg = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+ val arg = argumentCaptor<WindowContainerTransaction>()
verify(exitDesktopTransitionHandler).startTransition(any(), arg.capture(), any(), any())
- return arg.value
+ return arg.lastValue
}
private fun findBoundsChange(wct: WindowContainerTransaction, task: RunningTaskInfo): Rect? =
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopUserRepositoriesTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopUserRepositoriesTest.kt
index 83e4872..030bb1ac 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopUserRepositoriesTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopUserRepositoriesTest.kt
@@ -123,8 +123,26 @@
assertThat(desktopRepository.userId).isEqualTo(PROFILE_ID_2)
}
+ @Test
+ @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_HSUM)
+ fun getUserForProfile_flagEnabled_returnsUserIdForProfile() {
+ userRepositories.onUserChanged(USER_ID_2, mock())
+ val profiles: MutableList<UserInfo> =
+ mutableListOf(
+ UserInfo(USER_ID_2, "User profile", 0),
+ UserInfo(PROFILE_ID_1, "Work profile", 0),
+ )
+ userRepositories.onUserProfilesChanged(profiles)
+
+ val userIdForProfile = userRepositories.getUserIdForProfile(PROFILE_ID_1)
+
+ assertThat(userIdForProfile).isEqualTo(USER_ID_2)
+ }
+
private companion object {
const val USER_ID_1 = 7
+ const val USER_ID_2 = 8
+ const val PROFILE_ID_1 = 4
const val PROFILE_ID_2 = 5
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
index 25246d9..1732875 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
@@ -70,6 +70,7 @@
@Mock private lateinit var mockInteractionJankMonitor: InteractionJankMonitor
@Mock private lateinit var draggedTaskLeash: SurfaceControl
@Mock private lateinit var homeTaskLeash: SurfaceControl
+ @Mock private lateinit var desktopUserRepositories: DesktopUserRepositories
private val transactionSupplier = Supplier { mock<SurfaceControl.Transaction>() }
@@ -84,6 +85,7 @@
context,
transitions,
taskDisplayAreaOrganizer,
+ desktopUserRepositories,
mockInteractionJankMonitor,
transactionSupplier,
)
@@ -93,6 +95,7 @@
context,
transitions,
taskDisplayAreaOrganizer,
+ desktopUserRepositories,
mockInteractionJankMonitor,
transactionSupplier,
)
@@ -484,17 +487,22 @@
val mergedFinishTransaction = mock<SurfaceControl.Transaction>()
val finishCallback = mock<Transitions.TransitionFinishCallback>()
val task = createTask()
- val startTransition = startDrag(
- springHandler, task, finishTransaction = playingFinishTransaction, homeChange = null)
+ val startTransition =
+ startDrag(
+ springHandler,
+ task,
+ finishTransaction = playingFinishTransaction,
+ homeChange = null,
+ )
springHandler.onTaskResizeAnimationListener = mock()
springHandler.mergeAnimation(
transition = mock<IBinder>(),
info =
- createTransitionInfo(
- type = TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP,
- draggedTask = task,
- ),
+ createTransitionInfo(
+ type = TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP,
+ draggedTask = task,
+ ),
startT = mergedStartTransaction,
finishT = mergedFinishTransaction,
mergeTarget = startTransition,
@@ -723,7 +731,8 @@
private fun createTransitionInfo(
type: Int,
draggedTask: RunningTaskInfo,
- homeChange: TransitionInfo.Change? = createHomeChange()) =
+ homeChange: TransitionInfo.Change? = createHomeChange(),
+ ) =
TransitionInfo(type, /* flags= */ 0).apply {
homeChange?.let { addChange(it) }
addChange( // Dragged Task.
@@ -741,11 +750,12 @@
)
}
- private fun createHomeChange() = TransitionInfo.Change(mock(), homeTaskLeash).apply {
- parent = null
- taskInfo = TestRunningTaskInfoBuilder().setActivityType(ACTIVITY_TYPE_HOME).build()
- flags = flags or FLAG_IS_WALLPAPER
- }
+ private fun createHomeChange() =
+ TransitionInfo.Change(mock(), homeTaskLeash).apply {
+ parent = null
+ taskInfo = TestRunningTaskInfoBuilder().setActivityType(ACTIVITY_TYPE_HOME).build()
+ flags = flags or FLAG_IS_WALLPAPER
+ }
private fun systemPropertiesKey(name: String) =
"${SpringDragToDesktopTransitionHandler.SYSTEM_PROPERTIES_GROUP}.$name"
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/compatui/SystemModalsTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/compatui/SystemModalsTransitionHandlerTest.kt
index dfb1b0c..9cb2a05 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/compatui/SystemModalsTransitionHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/compatui/SystemModalsTransitionHandlerTest.kt
@@ -16,9 +16,12 @@
package com.android.wm.shell.desktopmode.compatui
+import android.content.ComponentName
import android.content.Intent
+import android.content.pm.PackageManager
import android.os.Binder
import android.testing.AndroidTestingRunner
+import android.testing.TestableContext
import android.view.SurfaceControl
import android.view.WindowManager.TRANSIT_CHANGE
import android.view.WindowManager.TRANSIT_CLOSE
@@ -37,6 +40,7 @@
import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.transition.TransitionInfoBuilder
import com.android.wm.shell.transition.Transitions
+import com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModelTestsBase.Companion.HOME_LAUNCHER_PACKAGE_NAME
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
@@ -44,6 +48,7 @@
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.kotlin.any
import org.mockito.kotlin.mock
+import org.mockito.kotlin.spy
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
@@ -62,16 +67,23 @@
private val desktopRepository = mock<DesktopRepository>()
private val startT = mock<SurfaceControl.Transaction>()
private val finishT = mock<SurfaceControl.Transaction>()
+ private val packageManager = mock<PackageManager>()
+ private val componentName = mock<ComponentName>()
+ private lateinit var spyContext: TestableContext
private lateinit var transitionHandler: SystemModalsTransitionHandler
private lateinit var desktopModeCompatPolicy: DesktopModeCompatPolicy
@Before
fun setUp() {
+ spyContext = spy(mContext)
// Simulate having one Desktop task so that we see Desktop Mode as active
whenever(desktopUserRepositories.current).thenReturn(desktopRepository)
whenever(desktopRepository.getVisibleTaskCount(anyInt())).thenReturn(1)
- desktopModeCompatPolicy = DesktopModeCompatPolicy(context)
+ whenever(spyContext.packageManager).thenReturn(packageManager)
+ whenever(componentName.packageName).thenReturn(HOME_LAUNCHER_PACKAGE_NAME)
+ whenever(packageManager.getHomeActivities(ArrayList())).thenReturn(componentName)
+ desktopModeCompatPolicy = DesktopModeCompatPolicy(spyContext)
transitionHandler = createTransitionHandler()
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationControllerTest.kt
index 86e8142..08b9e04 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationControllerTest.kt
@@ -26,6 +26,8 @@
import com.android.window.flags.Flags
import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.desktopmode.CaptionState
+import com.android.wm.shell.desktopmode.DesktopModeUiEventLogger
+import com.android.wm.shell.desktopmode.DesktopModeUiEventLogger.DesktopUiEventEnum
import com.android.wm.shell.desktopmode.WindowDecorCaptionHandleRepository
import com.android.wm.shell.desktopmode.education.AppHandleEducationController.Companion.APP_HANDLE_EDUCATION_DELAY_MILLIS
import com.android.wm.shell.desktopmode.education.AppHandleEducationController.Companion.TOOLTIP_VISIBLE_DURATION_MILLIS
@@ -86,6 +88,7 @@
@Mock private lateinit var mockDataStoreRepository: AppHandleEducationDatastoreRepository
@Mock private lateinit var mockCaptionHandleRepository: WindowDecorCaptionHandleRepository
@Mock private lateinit var mockTooltipController: DesktopWindowingEducationTooltipController
+ @Mock private lateinit var mockDesktopModeUiEventLogger: DesktopModeUiEventLogger
@Before
fun setUp() {
@@ -105,6 +108,7 @@
mockTooltipController,
testScope.backgroundScope,
Dispatchers.Main,
+ mockDesktopModeUiEventLogger,
)
}
@@ -123,6 +127,8 @@
verify(mockTooltipController, times(1)).showEducationTooltip(any(), any())
verify(mockDataStoreRepository, times(1))
.updateAppHandleHintViewedTimestampMillis(eq(true))
+ verify(mockDesktopModeUiEventLogger, times(1))
+ .log(any(), eq(DesktopUiEventEnum.APP_HANDLE_EDUCATION_TOOLTIP_SHOWN))
}
@Test
@@ -155,6 +161,8 @@
verify(mockTooltipController, times(1)).showEducationTooltip(any(), any())
verify(mockDataStoreRepository, times(1))
.updateEnterDesktopModeHintViewedTimestampMillis(eq(true))
+ verify(mockDesktopModeUiEventLogger, times(1))
+ .log(any(), eq(DesktopUiEventEnum.ENTER_DESKTOP_MODE_EDUCATION_TOOLTIP_SHOWN))
}
@Test
@@ -170,6 +178,8 @@
verify(mockTooltipController, times(1)).showEducationTooltip(any(), any())
verify(mockDataStoreRepository, times(1))
.updateExitDesktopModeHintViewedTimestampMillis(eq(true))
+ verify(mockDesktopModeUiEventLogger, times(1))
+ .log(any(), eq(DesktopUiEventEnum.EXIT_DESKTOP_MODE_EDUCATION_TOOLTIP_SHOWN))
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/bubbles/DragZoneFactoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/bubbles/DragZoneFactoryTest.kt
index 7cd46af..fd22a84 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/bubbles/DragZoneFactoryTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/bubbles/DragZoneFactoryTest.kt
@@ -16,8 +16,10 @@
package com.android.wm.shell.shared.bubbles
+import android.content.Context
import android.graphics.Insets
import android.graphics.Rect
+import androidx.test.core.app.ApplicationProvider.getApplicationContext
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.wm.shell.shared.bubbles.DragZoneFactory.DesktopWindowModeChecker
@@ -34,6 +36,7 @@
/** Unit tests for [DragZoneFactory]. */
class DragZoneFactoryTest {
+ private val context = getApplicationContext<Context>()
private lateinit var dragZoneFactory: DragZoneFactory
private val tabletPortrait =
DeviceConfig(
@@ -57,7 +60,12 @@
@Test
fun dragZonesForBubbleBar_tablet() {
dragZoneFactory =
- DragZoneFactory(tabletPortrait, splitScreenModeChecker, desktopWindowModeChecker)
+ DragZoneFactory(
+ context,
+ tabletPortrait,
+ splitScreenModeChecker,
+ desktopWindowModeChecker
+ )
val dragZones =
dragZoneFactory.createSortedDragZones(DraggedObject.BubbleBar(BubbleBarLocation.LEFT))
val expectedZones: List<DragZoneVerifier> =
@@ -73,7 +81,12 @@
@Test
fun dragZonesForBubble_tablet_portrait() {
dragZoneFactory =
- DragZoneFactory(tabletPortrait, splitScreenModeChecker, desktopWindowModeChecker)
+ DragZoneFactory(
+ context,
+ tabletPortrait,
+ splitScreenModeChecker,
+ desktopWindowModeChecker
+ )
val dragZones =
dragZoneFactory.createSortedDragZones(DraggedObject.Bubble(BubbleBarLocation.LEFT))
val expectedZones: List<DragZoneVerifier> =
@@ -92,7 +105,13 @@
@Test
fun dragZonesForBubble_tablet_landscape() {
- dragZoneFactory = DragZoneFactory(tabletLandscape, splitScreenModeChecker, desktopWindowModeChecker)
+ dragZoneFactory =
+ DragZoneFactory(
+ context,
+ tabletLandscape,
+ splitScreenModeChecker,
+ desktopWindowModeChecker
+ )
val dragZones =
dragZoneFactory.createSortedDragZones(DraggedObject.Bubble(BubbleBarLocation.LEFT))
val expectedZones: List<DragZoneVerifier> =
@@ -111,7 +130,13 @@
@Test
fun dragZonesForBubble_foldable_portrait() {
- dragZoneFactory = DragZoneFactory(foldablePortrait, splitScreenModeChecker, desktopWindowModeChecker)
+ dragZoneFactory =
+ DragZoneFactory(
+ context,
+ foldablePortrait,
+ splitScreenModeChecker,
+ desktopWindowModeChecker
+ )
val dragZones =
dragZoneFactory.createSortedDragZones(DraggedObject.Bubble(BubbleBarLocation.LEFT))
val expectedZones: List<DragZoneVerifier> =
@@ -129,7 +154,13 @@
@Test
fun dragZonesForBubble_foldable_landscape() {
- dragZoneFactory = DragZoneFactory(foldableLandscape, splitScreenModeChecker, desktopWindowModeChecker)
+ dragZoneFactory =
+ DragZoneFactory(
+ context,
+ foldableLandscape,
+ splitScreenModeChecker,
+ desktopWindowModeChecker
+ )
val dragZones =
dragZoneFactory.createSortedDragZones(DraggedObject.Bubble(BubbleBarLocation.LEFT))
val expectedZones: List<DragZoneVerifier> =
@@ -148,7 +179,12 @@
@Test
fun dragZonesForExpandedView_tablet_portrait() {
dragZoneFactory =
- DragZoneFactory(tabletPortrait, splitScreenModeChecker, desktopWindowModeChecker)
+ DragZoneFactory(
+ context,
+ tabletPortrait,
+ splitScreenModeChecker,
+ desktopWindowModeChecker
+ )
val dragZones =
dragZoneFactory.createSortedDragZones(
DraggedObject.ExpandedView(BubbleBarLocation.LEFT)
@@ -169,9 +205,17 @@
@Test
fun dragZonesForExpandedView_tablet_landscape() {
- dragZoneFactory = DragZoneFactory(tabletLandscape, splitScreenModeChecker, desktopWindowModeChecker)
+ dragZoneFactory =
+ DragZoneFactory(
+ context,
+ tabletLandscape,
+ splitScreenModeChecker,
+ desktopWindowModeChecker
+ )
val dragZones =
- dragZoneFactory.createSortedDragZones(DraggedObject.ExpandedView(BubbleBarLocation.LEFT))
+ dragZoneFactory.createSortedDragZones(
+ DraggedObject.ExpandedView(BubbleBarLocation.LEFT)
+ )
val expectedZones: List<DragZoneVerifier> =
listOf(
verifyInstance<DragZone.Dismiss>(),
@@ -188,9 +232,17 @@
@Test
fun dragZonesForExpandedView_foldable_portrait() {
- dragZoneFactory = DragZoneFactory(foldablePortrait, splitScreenModeChecker, desktopWindowModeChecker)
+ dragZoneFactory =
+ DragZoneFactory(
+ context,
+ foldablePortrait,
+ splitScreenModeChecker,
+ desktopWindowModeChecker
+ )
val dragZones =
- dragZoneFactory.createSortedDragZones(DraggedObject.ExpandedView(BubbleBarLocation.LEFT))
+ dragZoneFactory.createSortedDragZones(
+ DraggedObject.ExpandedView(BubbleBarLocation.LEFT)
+ )
val expectedZones: List<DragZoneVerifier> =
listOf(
verifyInstance<DragZone.Dismiss>(),
@@ -206,9 +258,17 @@
@Test
fun dragZonesForExpandedView_foldable_landscape() {
- dragZoneFactory = DragZoneFactory(foldableLandscape, splitScreenModeChecker, desktopWindowModeChecker)
+ dragZoneFactory =
+ DragZoneFactory(
+ context,
+ foldableLandscape,
+ splitScreenModeChecker,
+ desktopWindowModeChecker
+ )
val dragZones =
- dragZoneFactory.createSortedDragZones(DraggedObject.ExpandedView(BubbleBarLocation.LEFT))
+ dragZoneFactory.createSortedDragZones(
+ DraggedObject.ExpandedView(BubbleBarLocation.LEFT)
+ )
val expectedZones: List<DragZoneVerifier> =
listOf(
verifyInstance<DragZone.Dismiss>(),
@@ -225,7 +285,13 @@
@Test
fun dragZonesForBubble_tablet_desktopModeDisabled() {
isDesktopWindowModeSupported = false
- dragZoneFactory = DragZoneFactory(foldableLandscape, splitScreenModeChecker, desktopWindowModeChecker)
+ dragZoneFactory =
+ DragZoneFactory(
+ context,
+ foldableLandscape,
+ splitScreenModeChecker,
+ desktopWindowModeChecker
+ )
val dragZones =
dragZoneFactory.createSortedDragZones(DraggedObject.Bubble(BubbleBarLocation.LEFT))
assertThat(dragZones.filterIsInstance<DragZone.DesktopWindow>()).isEmpty()
@@ -234,9 +300,17 @@
@Test
fun dragZonesForExpandedView_tablet_desktopModeDisabled() {
isDesktopWindowModeSupported = false
- dragZoneFactory = DragZoneFactory(foldableLandscape, splitScreenModeChecker, desktopWindowModeChecker)
+ dragZoneFactory =
+ DragZoneFactory(
+ context,
+ foldableLandscape,
+ splitScreenModeChecker,
+ desktopWindowModeChecker
+ )
val dragZones =
- dragZoneFactory.createSortedDragZones(DraggedObject.ExpandedView(BubbleBarLocation.LEFT))
+ dragZoneFactory.createSortedDragZones(
+ DraggedObject.ExpandedView(BubbleBarLocation.LEFT)
+ )
assertThat(dragZones.filterIsInstance<DragZone.DesktopWindow>()).isEmpty()
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicyTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicyTest.kt
index 55e9de5..f69bf34 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicyTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicyTest.kt
@@ -30,6 +30,7 @@
import com.android.window.flags.Flags
import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.desktopmode.DesktopTestHelpers.createFreeformTask
+import com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModelTestsBase.Companion.HOME_LAUNCHER_PACKAGE_NAME
import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges
import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges
import org.junit.Assert.assertFalse
@@ -52,10 +53,14 @@
class DesktopModeCompatPolicyTest : ShellTestCase() {
@get:Rule val compatRule = PlatformCompatChangeRule()
private lateinit var desktopModeCompatPolicy: DesktopModeCompatPolicy
+ private val packageManager: PackageManager = mock()
+ private val homeActivities = ComponentName(HOME_LAUNCHER_PACKAGE_NAME, /* class */ "")
@Before
fun setUp() {
desktopModeCompatPolicy = DesktopModeCompatPolicy(mContext)
+ whenever(packageManager.getHomeActivities(any())).thenReturn(homeActivities)
+ mContext.setMockPackageManager(packageManager)
}
@Test
@@ -128,10 +133,6 @@
@Test
fun testIsTopActivityExemptFromDesktopWindowing_defaultHomePackage() {
- val packageManager: PackageManager = mock()
- val homeActivities = ComponentName("defaultHomePackage", /* class */ "")
- whenever(packageManager.getHomeActivities(any())).thenReturn(homeActivities)
- mContext.setMockPackageManager(packageManager)
assertTrue(desktopModeCompatPolicy.isTopActivityExemptFromDesktopWindowing(
createFreeformTask(/* displayId */ 0)
.apply {
@@ -142,10 +143,6 @@
@Test
fun testIsTopActivityExemptFromDesktopWindowing_defaultHomePackage_notDisplayed() {
- val packageManager: PackageManager = mock()
- val homeActivities = ComponentName("defaultHomePackage", /* class */ "")
- whenever(packageManager.getHomeActivities(any())).thenReturn(homeActivities)
- mContext.setMockPackageManager(packageManager)
assertFalse(desktopModeCompatPolicy.isTopActivityExemptFromDesktopWindowing(
createFreeformTask(/* displayId */ 0)
.apply {
@@ -155,6 +152,21 @@
}
@Test
+ fun testIsTopActivityExemptFromDesktopWindowing_defaultHomePackage_notYetAvailable() {
+ val emptyHomeActivities: ComponentName = mock()
+ mContext.setMockPackageManager(packageManager)
+
+ whenever(emptyHomeActivities.packageName).thenReturn(null)
+ whenever(packageManager.getHomeActivities(any())).thenReturn(emptyHomeActivities)
+
+ assertTrue(desktopModeCompatPolicy.isTopActivityExemptFromDesktopWindowing(
+ createFreeformTask(/* displayId */ 0)
+ .apply {
+ isTopActivityNoDisplay = false
+ }))
+ }
+
+ @Test
@EnableFlags(Flags.FLAG_EXCLUDE_CAPTION_FROM_APP_BOUNDS)
@DisableCompatChanges(ActivityInfo.INSETS_DECOUPLED_CONFIGURATION_ENFORCED)
fun testShouldExcludeCaptionFromAppBounds_resizeable_false() {
@@ -181,6 +193,17 @@
)
}
+
+ @Test
+ @EnableFlags(Flags.FLAG_EXCLUDE_CAPTION_FROM_APP_BOUNDS)
+ @DisableCompatChanges(ActivityInfo.INSETS_DECOUPLED_CONFIGURATION_ENFORCED)
+ @EnableCompatChanges(ActivityInfo.OVERRIDE_EXCLUDE_CAPTION_INSETS_FROM_APP_BOUNDS)
+ fun testShouldExcludeCaptionFromAppBounds_resizeable_overridden_true() {
+ assertTrue(desktopModeCompatPolicy.shouldExcludeCaptionFromAppBounds(
+ setUpFreeformTask().apply { isResizeable = true })
+ )
+ }
+
fun setUpFreeformTask(): TaskInfo =
createFreeformTask().apply {
val componentName =
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/DefaultTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/DefaultTransitionHandlerTest.java
index 82392e0..18fdbef 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/DefaultTransitionHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/DefaultTransitionHandlerTest.java
@@ -48,6 +48,7 @@
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
+import com.android.internal.jank.InteractionJankMonitor;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestShellExecutor;
@@ -96,7 +97,7 @@
mTransitionHandler = new DefaultTransitionHandler(
mContext, mShellInit, mDisplayController,
mTransactionPool, mMainExecutor, mMainHandler, mAnimExecutor,
- mRootTaskDisplayAreaOrganizer);
+ mRootTaskDisplayAreaOrganizer, mock(InteractionJankMonitor.class));
mShellInit.init();
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
index 49812d3..da41a23 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
@@ -28,7 +28,6 @@
import android.content.Context
import android.content.Intent
import android.content.Intent.ACTION_MAIN
-import android.content.pm.PackageManager
import android.graphics.Rect
import android.graphics.Region
import android.hardware.display.DisplayManager
@@ -310,14 +309,10 @@
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
fun testDecorationIsNotCreatedForDefaultHomePackage() {
- val packageManager: PackageManager = org.mockito.kotlin.mock()
- val homeActivities = ComponentName("defaultHomePackage", /* class */ "")
val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN).apply {
- baseActivity = homeActivities
+ baseActivity = homeComponentName
isTopActivityNoDisplay = false
}
- mContext.setMockPackageManager(packageManager)
- whenever(packageManager.getHomeActivities(any())).thenReturn(homeActivities)
onTaskOpening(task)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt
index 9f106da..e40034b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt
@@ -20,7 +20,9 @@
import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW
import android.app.WindowConfiguration.WindowingMode
+import android.content.ComponentName
import android.content.pm.ActivityInfo
+import android.content.pm.PackageManager
import android.graphics.Rect
import android.hardware.input.InputManager
import android.os.Handler
@@ -146,8 +148,10 @@
protected val mockDesktopRepository: DesktopRepository = mock<DesktopRepository>()
protected val mockRecentsTransitionHandler = mock<RecentsTransitionHandler>()
protected val motionEvent = mock<MotionEvent>()
- val displayLayout = mock<DisplayLayout>()
- val display = mock<Display>()
+ private val displayLayout = mock<DisplayLayout>()
+ private val display = mock<Display>()
+ private val packageManager = mock<PackageManager>()
+ protected val homeComponentName = ComponentName(HOME_LAUNCHER_PACKAGE_NAME, /* class */ "")
protected lateinit var spyContext: TestableContext
private lateinit var desktopModeEventLogger: DesktopModeEventLogger
@@ -178,7 +182,7 @@
whenever(mockDisplayController.getDisplay(any())).thenReturn(display)
whenever(mockDesktopUserRepositories.getProfile(anyInt()))
.thenReturn(mockDesktopRepository)
- desktopModeCompatPolicy = DesktopModeCompatPolicy(context)
+ desktopModeCompatPolicy = DesktopModeCompatPolicy(spyContext)
desktopModeWindowDecorViewModel = DesktopModeWindowDecorViewModel(
spyContext,
testShellExecutor,
@@ -273,6 +277,8 @@
whenever(displayLayout.getStableBounds(any())).thenAnswer { i ->
(i.arguments.first() as Rect).set(STABLE_BOUNDS)
}
+ spyContext.setMockPackageManager(packageManager)
+ whenever(packageManager.getHomeActivities(ArrayList())).thenReturn(homeComponentName)
}
@After
@@ -354,5 +360,6 @@
val STABLE_INSETS = Rect(0, 100, 0, 0)
val INITIAL_BOUNDS = Rect(0, 0, 100, 100)
val STABLE_BOUNDS = Rect(0, 0, 1000, 1000)
+ val HOME_LAUNCHER_PACKAGE_NAME = "com.android.launcher"
}
}
diff --git a/libs/hwui/FrameInfo.cpp b/libs/hwui/FrameInfo.cpp
index a958a09..36feabd 100644
--- a/libs/hwui/FrameInfo.cpp
+++ b/libs/hwui/FrameInfo.cpp
@@ -32,8 +32,9 @@
"PerformTraversalsStart",
"DrawStart",
"FrameDeadline",
- "FrameInterval",
"FrameStartTime",
+ "FrameInterval",
+ "WorkloadTarget",
"SyncQueued",
"SyncStart",
"IssueDrawCommandsStart",
@@ -48,7 +49,7 @@
};
-static_assert(static_cast<int>(FrameInfoIndex::NumIndexes) == 23,
+static_assert(static_cast<int>(FrameInfoIndex::NumIndexes) == 24,
"Must update value in FrameMetrics.java#FRAME_STATS_COUNT (and here)");
void FrameInfo::importUiThreadInfo(int64_t* info) {
diff --git a/libs/hwui/FrameInfo.h b/libs/hwui/FrameInfo.h
index f7ad139..61c30b8 100644
--- a/libs/hwui/FrameInfo.h
+++ b/libs/hwui/FrameInfo.h
@@ -30,7 +30,8 @@
namespace android {
namespace uirenderer {
-static constexpr size_t UI_THREAD_FRAME_INFO_SIZE = 12;
+// This value must be in sync with `FRAME_INFO_SIZE` in FrameInfo.Java
+static constexpr size_t UI_THREAD_FRAME_INFO_SIZE = 13;
enum class FrameInfoIndex {
Flags = 0,
@@ -47,6 +48,11 @@
FrameInterval,
// End of UI frame info
+ // The target workload duration based on the original frame deadline and
+ // and intended vsync. Counted in UI_THREAD_FRAME_INFO_SIZE so its value
+ // can be set in setVsync().
+ WorkloadTarget,
+
SyncQueued,
SyncStart,
@@ -109,6 +115,7 @@
set(FrameInfoIndex::FrameStartTime) = vsyncTime;
set(FrameInfoIndex::FrameDeadline) = frameDeadline;
set(FrameInfoIndex::FrameInterval) = frameInterval;
+ set(FrameInfoIndex::WorkloadTarget) = frameDeadline - intendedVsync;
return *this;
}
diff --git a/libs/hwui/JankTracker.cpp b/libs/hwui/JankTracker.cpp
index 638a060..80eb6bc 100644
--- a/libs/hwui/JankTracker.cpp
+++ b/libs/hwui/JankTracker.cpp
@@ -201,7 +201,7 @@
// If we are in triple buffering, we have enough buffers in queue to sustain a single frame
// drop without jank, so adjust the frame interval to the deadline.
if (isTripleBuffered) {
- int64_t originalDeadlineDuration = deadline - frame[FrameInfoIndex::IntendedVsync];
+ int64_t originalDeadlineDuration = frame[FrameInfoIndex::WorkloadTarget];
deadline = mNextFrameStartUnstuffed + originalDeadlineDuration;
frame.set(FrameInfoIndex::FrameDeadline) = deadline;
}
diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp
index 7d01dfb..21430f7 100644
--- a/libs/hwui/Properties.cpp
+++ b/libs/hwui/Properties.cpp
@@ -57,6 +57,9 @@
constexpr bool early_preload_gl_context() {
return false;
}
+constexpr bool calc_workload_orig_deadline() {
+ return false;
+}
} // namespace hwui_flags
#endif
@@ -299,5 +302,10 @@
hwui_flags::early_preload_gl_context());
}
+bool Properties::calcWorkloadOrigDeadline() {
+ static bool sCalcWorkloadOrigDeadline = base::GetBoolProperty(
+ "debug.hwui.calc_workload_orig_deadline", hwui_flags::calc_workload_orig_deadline());
+ return sCalcWorkloadOrigDeadline;
+}
} // namespace uirenderer
} // namespace android
diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h
index 280a75a..a7a5644 100644
--- a/libs/hwui/Properties.h
+++ b/libs/hwui/Properties.h
@@ -384,6 +384,7 @@
static bool initializeGlAlways();
static bool resampleGainmapRegions();
static bool earlyPreloadGlContext();
+ static bool calcWorkloadOrigDeadline();
private:
static StretchEffectBehavior stretchEffectBehavior;
diff --git a/libs/hwui/aconfig/hwui_flags.aconfig b/libs/hwui/aconfig/hwui_flags.aconfig
index 2851dd8..62fd7d3 100644
--- a/libs/hwui/aconfig/hwui_flags.aconfig
+++ b/libs/hwui/aconfig/hwui_flags.aconfig
@@ -176,4 +176,15 @@
namespace: "core_graphics"
description: "Initialize GL context and GraphicBufferAllocater init on renderThread preload. This improves app startup time for apps using GL."
bug: "383612849"
+}
+
+flag {
+ name: "calc_workload_orig_deadline"
+ namespace: "window_surfaces"
+ description: "Use original frame deadline to calculate the workload target deadline for jank tracking"
+ bug: "389939827"
+ is_fixed_read_only: true
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
}
\ No newline at end of file
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index b36b8be..e3e393c 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -789,7 +789,13 @@
int64_t frameDeadline = mCurrentFrameInfo->get(FrameInfoIndex::FrameDeadline);
int64_t dequeueBufferDuration = mCurrentFrameInfo->get(FrameInfoIndex::DequeueBufferDuration);
- mHintSessionWrapper->updateTargetWorkDuration(frameDeadline - intendedVsync);
+ if (Properties::calcWorkloadOrigDeadline()) {
+ // Uses the unmodified frame deadline in calculating workload target duration
+ mHintSessionWrapper->updateTargetWorkDuration(
+ mCurrentFrameInfo->get(FrameInfoIndex::WorkloadTarget));
+ } else {
+ mHintSessionWrapper->updateTargetWorkDuration(frameDeadline - intendedVsync);
+ }
if (didDraw) {
int64_t frameStartTime = mCurrentFrameInfo->get(FrameInfoIndex::FrameStartTime);
diff --git a/libs/hwui/tests/unit/JankTrackerTests.cpp b/libs/hwui/tests/unit/JankTrackerTests.cpp
index b67e419..c289d67 100644
--- a/libs/hwui/tests/unit/JankTrackerTests.cpp
+++ b/libs/hwui/tests/unit/JankTrackerTests.cpp
@@ -45,6 +45,7 @@
info->set(FrameInfoIndex::FrameCompleted) = 115_ms;
info->set(FrameInfoIndex::FrameInterval) = 16_ms;
info->set(FrameInfoIndex::FrameDeadline) = 120_ms;
+ info->set(FrameInfoIndex::WorkloadTarget) = 20_ms;
jankTracker.finishFrame(*info, reporter, frameNumber, surfaceId);
info = jankTracker.startFrame();
@@ -55,6 +56,7 @@
info->set(FrameInfoIndex::FrameCompleted) = 131_ms;
info->set(FrameInfoIndex::FrameInterval) = 16_ms;
info->set(FrameInfoIndex::FrameDeadline) = 136_ms;
+ info->set(FrameInfoIndex::WorkloadTarget) = 20_ms;
jankTracker.finishFrame(*info, reporter, frameNumber, surfaceId);
ASSERT_EQ(2, container.get()->totalFrameCount());
@@ -79,6 +81,7 @@
info->set(FrameInfoIndex::FrameCompleted) = 121_ms;
info->set(FrameInfoIndex::FrameInterval) = 16_ms;
info->set(FrameInfoIndex::FrameDeadline) = 120_ms;
+ info->set(FrameInfoIndex::WorkloadTarget) = 20_ms;
jankTracker.finishFrame(*info, reporter, frameNumber, surfaceId);
ASSERT_EQ(1, container.get()->totalFrameCount());
@@ -102,6 +105,7 @@
info->set(FrameInfoIndex::FrameCompleted) = 118_ms;
info->set(FrameInfoIndex::FrameInterval) = 16_ms;
info->set(FrameInfoIndex::FrameDeadline) = 120_ms;
+ info->set(FrameInfoIndex::WorkloadTarget) = 20_ms;
jankTracker.finishFrame(*info, reporter, frameNumber, surfaceId);
ASSERT_EQ(1, container.get()->totalFrameCount());
@@ -127,6 +131,7 @@
info->set(FrameInfoIndex::FrameCompleted) = 121_ms;
info->set(FrameInfoIndex::FrameInterval) = 16_ms;
info->set(FrameInfoIndex::FrameDeadline) = 120_ms;
+ info->set(FrameInfoIndex::WorkloadTarget) = 20_ms;
jankTracker.finishFrame(*info, reporter, frameNumber, surfaceId);
ASSERT_EQ(1, container.get()->jankFrameCount());
@@ -140,6 +145,7 @@
info->set(FrameInfoIndex::FrameCompleted) = 137_ms;
info->set(FrameInfoIndex::FrameInterval) = 16_ms;
info->set(FrameInfoIndex::FrameDeadline) = 136_ms;
+ info->set(FrameInfoIndex::WorkloadTarget) = 20_ms;
jankTracker.finishFrame(*info, reporter, frameNumber, surfaceId);
ASSERT_EQ(2, container.get()->totalFrameCount());
@@ -164,6 +170,7 @@
info->set(FrameInfoIndex::FrameCompleted) = 121_ms;
info->set(FrameInfoIndex::FrameInterval) = 16_ms;
info->set(FrameInfoIndex::FrameDeadline) = 120_ms;
+ info->set(FrameInfoIndex::WorkloadTarget) = 20_ms;
jankTracker.finishFrame(*info, reporter, frameNumber, surfaceId);
ASSERT_EQ(1, container.get()->jankFrameCount());
@@ -177,6 +184,7 @@
info->set(FrameInfoIndex::FrameCompleted) = 137_ms;
info->set(FrameInfoIndex::FrameInterval) = 16_ms;
info->set(FrameInfoIndex::FrameDeadline) = 136_ms;
+ info->set(FrameInfoIndex::WorkloadTarget) = 20_ms;
jankTracker.finishFrame(*info, reporter, frameNumber, surfaceId);
ASSERT_EQ(1, container.get()->jankFrameCount());
@@ -190,6 +198,7 @@
info->set(FrameInfoIndex::FrameCompleted) = 169_ms;
info->set(FrameInfoIndex::FrameInterval) = 16_ms;
info->set(FrameInfoIndex::FrameDeadline) = 168_ms;
+ info->set(FrameInfoIndex::WorkloadTarget) = 20_ms;
jankTracker.finishFrame(*info, reporter, frameNumber, surfaceId);
ASSERT_EQ(3, container.get()->totalFrameCount());
@@ -214,6 +223,7 @@
info->set(FrameInfoIndex::FrameCompleted) = 117_ms;
info->set(FrameInfoIndex::FrameInterval) = 16_ms;
info->set(FrameInfoIndex::FrameDeadline) = 116_ms;
+ info->set(FrameInfoIndex::WorkloadTarget) = 16_ms;
jankTracker.finishFrame(*info, reporter, frameNumber, surfaceId);
ASSERT_EQ(1, container.get()->jankFrameCount());
@@ -228,6 +238,7 @@
info->set(FrameInfoIndex::FrameCompleted) = 133_ms;
info->set(FrameInfoIndex::FrameInterval) = 16_ms;
info->set(FrameInfoIndex::FrameDeadline) = 132_ms;
+ info->set(FrameInfoIndex::WorkloadTarget) = 16_ms;
jankTracker.finishFrame(*info, reporter, frameNumber, surfaceId);
ASSERT_EQ(1, container.get()->jankFrameCount());
@@ -242,6 +253,7 @@
info->set(FrameInfoIndex::FrameCompleted) = 165_ms;
info->set(FrameInfoIndex::FrameInterval) = 16_ms;
info->set(FrameInfoIndex::FrameDeadline) = 148_ms;
+ info->set(FrameInfoIndex::WorkloadTarget) = 16_ms;
jankTracker.finishFrame(*info, reporter, frameNumber, surfaceId);
ASSERT_EQ(2, container.get()->jankFrameCount());
@@ -256,6 +268,7 @@
info->set(FrameInfoIndex::FrameCompleted) = 181_ms;
info->set(FrameInfoIndex::FrameInterval) = 16_ms;
info->set(FrameInfoIndex::FrameDeadline) = 164_ms;
+ info->set(FrameInfoIndex::WorkloadTarget) = 16_ms;
jankTracker.finishFrame(*info, reporter, frameNumber, surfaceId);
ASSERT_EQ(2, container.get()->jankFrameCount());
diff --git a/media/java/android/media/audiofx/HapticGenerator.java b/media/java/android/media/audiofx/HapticGenerator.java
index d2523ef..7f94dde 100644
--- a/media/java/android/media/audiofx/HapticGenerator.java
+++ b/media/java/android/media/audiofx/HapticGenerator.java
@@ -36,6 +36,20 @@
* <p>See {@link android.media.MediaPlayer#getAudioSessionId()} for details on audio sessions.
* <p>See {@link android.media.audiofx.AudioEffect} class for more details on controlling audio
* effects.
+ *
+ * <pre>{@code
+ * AudioManager audioManager = context.getSystemService(AudioManager.class);
+ * player = MediaPlayer.create(
+ * context,
+ * audioUri,
+ * new AudioAttributes.Builder().setHapticChannelsMuted(false).build(),
+ * audioManager.generateAudioSessionId()
+ * );
+ * if (HapticGenerator.isAvailable()) {
+ * HapticGenerator.create(player.getAudioSessionId()).setEnabled(true);
+ * }
+ * player.start();
+ * }</pre>
*/
public class HapticGenerator extends AudioEffect implements AutoCloseable {
diff --git a/media/jni/Android.bp b/media/jni/Android.bp
index f09dc72..6b72173 100644
--- a/media/jni/Android.bp
+++ b/media/jni/Android.bp
@@ -25,8 +25,6 @@
min_sdk_version: "",
srcs: [
- "android_media_ImageWriter.cpp",
- "android_media_ImageReader.cpp",
"android_media_JetPlayer.cpp",
"android_media_MediaCrypto.cpp",
"android_media_MediaCodec.cpp",
@@ -42,7 +40,6 @@
"android_media_MediaProfiles.cpp",
"android_media_MediaRecorder.cpp",
"android_media_MediaSync.cpp",
- "android_media_PublicFormatUtils.cpp",
"android_media_ResampleInputStream.cpp",
"android_media_Streams.cpp",
"android_media_SyncParams.cpp",
@@ -64,7 +61,6 @@
"libbinder",
"libmedia",
"libmedia_codeclist",
- "libmedia_jni_utils",
"libmedia_omx",
"libmediametrics",
"libmediadrm",
@@ -133,38 +129,6 @@
}
cc_library_shared {
- name: "libmedia_jni_utils",
- srcs: [
- ":libgui_frame_event_aidl",
- "android_media_Utils.cpp",
- ],
-
- header_libs: [
- "libgui_headers",
- ],
-
- shared_libs: [
- "liblog",
- "libui",
- "libutils",
- ],
-
- include_dirs: [
- "system/media/camera/include",
- ],
-
- export_include_dirs: ["."],
-
- cflags: [
- "-Wall",
- "-Werror",
- "-Wno-error=deprecated-declarations",
- "-Wunused",
- "-Wunreachable-code",
- ],
-}
-
-cc_library_shared {
name: "libmedia_tv_tuner",
min_sdk_version: "",
srcs: [
diff --git a/media/jni/android_media_MediaPlayer.cpp b/media/jni/android_media_MediaPlayer.cpp
index a942300..647b553 100644
--- a/media/jni/android_media_MediaPlayer.cpp
+++ b/media/jni/android_media_MediaPlayer.cpp
@@ -1476,8 +1476,6 @@
return AndroidRuntime::registerNativeMethods(env,
"android/media/MediaPlayer", gMethods, NELEM(gMethods));
}
-extern int register_android_media_ImageReader(JNIEnv *env);
-extern int register_android_media_ImageWriter(JNIEnv *env);
extern int register_android_media_JetPlayer(JNIEnv *env);
extern int register_android_media_Crypto(JNIEnv *env);
extern int register_android_media_Drm(JNIEnv *env);
@@ -1490,7 +1488,6 @@
extern int register_android_media_MediaMuxer(JNIEnv *env);
extern int register_android_media_MediaRecorder(JNIEnv *env);
extern int register_android_media_MediaSync(JNIEnv *env);
-extern int register_android_media_PublicFormatUtils(JNIEnv *env);
extern int register_android_media_ResampleInputStream(JNIEnv *env);
extern int register_android_media_MediaProfiles(JNIEnv *env);
extern int register_android_mtp_MtpDatabase(JNIEnv *env);
@@ -1508,16 +1505,6 @@
}
assert(env != NULL);
- if (register_android_media_ImageWriter(env) != JNI_OK) {
- ALOGE("ERROR: ImageWriter native registration failed");
- goto bail;
- }
-
- if (register_android_media_ImageReader(env) < 0) {
- ALOGE("ERROR: ImageReader native registration failed");
- goto bail;
- }
-
if (register_android_media_JetPlayer(env) < 0) {
ALOGE("ERROR: JetPlayer native registration failed");
goto bail;
@@ -1538,11 +1525,6 @@
goto bail;
}
- if (register_android_media_PublicFormatUtils(env) < 0) {
- ALOGE("ERROR: PublicFormatUtils native registration failed\n");
- goto bail;
- }
-
if (register_android_media_ResampleInputStream(env) < 0) {
ALOGE("ERROR: ResampleInputStream native registration failed\n");
goto bail;
diff --git a/nfc-non-updatable/java/android/nfc/cardemulation/ApduServiceInfo.java b/nfc-non-updatable/java/android/nfc/cardemulation/ApduServiceInfo.java
index e83b9f1..d0de1fc 100644
--- a/nfc-non-updatable/java/android/nfc/cardemulation/ApduServiceInfo.java
+++ b/nfc-non-updatable/java/android/nfc/cardemulation/ApduServiceInfo.java
@@ -817,7 +817,7 @@
if (!mOnHost && !autoTransact) {
return;
}
- mAutoTransact.put(pollingLoopFilter, autoTransact);
+ mAutoTransact.put(pollingLoopFilter.toUpperCase(Locale.ROOT), autoTransact);
}
/**
@@ -845,7 +845,8 @@
if (!mOnHost && !autoTransact) {
return;
}
- mAutoTransactPatterns.put(Pattern.compile(pollingLoopPatternFilter), autoTransact);
+ mAutoTransactPatterns.put(Pattern.compile(
+ pollingLoopPatternFilter.toUpperCase(Locale.ROOT)), autoTransact);
}
/**
diff --git a/packages/InputDevices/res/raw/keyboard_layout_english_india.kcm b/packages/InputDevices/res/raw/keyboard_layout_english_india.kcm
new file mode 100644
index 0000000..0059d00
--- /dev/null
+++ b/packages/InputDevices/res/raw/keyboard_layout_english_india.kcm
@@ -0,0 +1,400 @@
+# Copyright 2025 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+#
+# English (India) keyboard layout.
+#
+
+type OVERLAY
+
+map key 86 PLUS
+
+### ROW 1
+
+key GRAVE {
+ label: '`'
+ base: '`'
+ shift: '~'
+ ralt: '\u0300'
+ ralt+shift: '\u0303'
+}
+
+key 1 {
+ label: '1'
+ base: '1'
+ shift: '!'
+}
+
+key 2 {
+ label: '2'
+ base: '2'
+ shift: '@'
+}
+
+key 3 {
+ label: '3'
+ base: '3'
+ shift: '#'
+}
+
+key 4 {
+ label: '4'
+ base: '4'
+ shift: '$'
+ ralt, ctrl+shift: '\u20b9'
+}
+
+key 5 {
+ label: '5'
+ base: '5'
+ shift: '%'
+}
+
+key 6 {
+ label: '6'
+ base: '6'
+ shift: '^'
+ ralt+shift: '\u0302'
+}
+
+key 7 {
+ label: '7'
+ base: '7'
+ shift: '&'
+}
+
+key 8 {
+ label: '8'
+ base: '8'
+ shift: '*'
+}
+
+key 9 {
+ label: '9'
+ base: '9'
+ shift: '('
+ ralt+shift: '\u0306'
+}
+
+key 0 {
+ label: '0'
+ base: '0'
+ shift: ')'
+}
+
+key MINUS {
+ label: '-'
+ base: '-'
+ shift: '_'
+ ralt+shift: '\u0331'
+}
+
+key EQUALS {
+ label: '='
+ base: '='
+ shift: '+'
+ ralt: '\u2013'
+ ralt+shift: '\u2014'
+}
+
+### ROW 2
+
+key Q {
+ label: 'Q'
+ base: 'q'
+ shift, capslock: 'Q'
+ capslock+shift: 'q'
+ ralt: '\u00e6'
+ ralt+shift, ralt+capslock: '\u00c6'
+ ralt+shift+capslock: '\u00e6'
+}
+
+key W {
+ label: 'W'
+ base: 'w'
+ shift, capslock: 'W'
+ capslock+shift: 'w'
+}
+
+key E {
+ label: 'E'
+ base: 'e'
+ shift, capslock: 'E'
+ capslock+shift: 'e'
+ ralt: '\u0113'
+ ralt+shift, ralt+capslock: '\u0112'
+ ralt+shift+capslock: '\u0113'
+}
+
+key R {
+ label: 'R'
+ base: 'r'
+ shift, capslock: 'R'
+ capslock+shift: 'r'
+}
+
+key T {
+ label: 'T'
+ base: 't'
+ shift, capslock: 'T'
+ capslock+shift: 't'
+ ralt: '\u1e6d'
+ ralt+shift, ralt+capslock: '\u1e6c'
+ ralt+shift+capslock: '\u1e6d'
+}
+
+key Y {
+ label: 'Y'
+ base: 'y'
+ shift, capslock: 'Y'
+ capslock+shift: 'y'
+ ralt: '\u00f1'
+ ralt+shift, ralt+capslock: '\u00d1'
+ ralt+shift+capslock: '\u00f1'
+}
+
+key U {
+ label: 'U'
+ base: 'u'
+ shift, capslock: 'U'
+ capslock+shift: 'u'
+ ralt: '\u016b'
+ ralt+shift, ralt+capslock: '\u016a'
+ ralt+shift+capslock: '\u016b'
+}
+
+key I {
+ label: 'I'
+ base: 'i'
+ shift, capslock: 'I'
+ capslock+shift: 'i'
+ ralt: '\u012b'
+ ralt+shift, ralt+capslock: '\u012a'
+ ralt+shift+capslock: '\u012b'
+}
+
+key O {
+ label: 'O'
+ base: 'o'
+ shift, capslock: 'O'
+ capslock+shift: 'o'
+ ralt: '\u014d'
+ ralt+shift, ralt+capslock: '\u014c'
+ ralt+shift+capslock: '\u014d'
+}
+
+key P {
+ label: 'P'
+ base: 'p'
+ shift, capslock: 'P'
+ capslock+shift: 'p'
+}
+
+key LEFT_BRACKET {
+ label: '['
+ base: '['
+ shift: '{'
+}
+
+key RIGHT_BRACKET {
+ label: ']'
+ base: ']'
+ shift: '}'
+}
+
+### ROW 3
+
+key A {
+ label: 'A'
+ base: 'a'
+ shift, capslock: 'A'
+ capslock+shift: 'a'
+ ralt: '\u0101'
+ ralt+shift, ralt+capslock: '\u0100'
+ ralt+shift+capslock: '\u0101'
+}
+
+key S {
+ label: 'S'
+ base: 's'
+ shift, capslock: 'S'
+ capslock+shift: 's'
+ ralt: '\u015b'
+ ralt+shift, ralt+capslock: '\u015a'
+ ralt+shift+capslock: '\u015b'
+}
+
+key D {
+ label: 'D'
+ base: 'd'
+ shift, capslock: 'D'
+ capslock+shift: 'd'
+ ralt: '\u1e0d'
+ ralt+shift, ralt+capslock: '\u1e0c'
+ ralt+shift+capslock: '\u1e0d'
+}
+
+key F {
+ label: 'F'
+ base: 'f'
+ shift, capslock: 'F'
+ capslock+shift: 'f'
+}
+
+key G {
+ label: 'G'
+ base: 'g'
+ shift, capslock: 'G'
+ capslock+shift: 'g'
+ ralt: '\u1e45'
+ ralt+shift, ralt+capslock: '\u1e44'
+ ralt+shift+capslock: '\u1e45'
+}
+
+key H {
+ label: 'H'
+ base: 'h'
+ shift, capslock: 'H'
+ capslock+shift: 'h'
+ ralt: '\u1e25'
+ ralt+shift, ralt+capslock: '\u1e24'
+ ralt+shift+capslock: '\u1e25'
+}
+
+key J {
+ label: 'J'
+ base: 'j'
+ shift, capslock: 'J'
+ capslock+shift: 'j'
+}
+
+key K {
+ label: 'K'
+ base: 'k'
+ shift, capslock: 'K'
+ capslock+shift: 'k'
+}
+
+key L {
+ label: 'L'
+ base: 'l'
+ shift, capslock: 'L'
+ capslock+shift: 'l'
+}
+
+key SEMICOLON {
+ label: ';'
+ base: ';'
+ shift: ':'
+}
+
+key APOSTROPHE {
+ label: '\''
+ base: '\''
+ shift: '\u0022'
+ ralt: '\u030d'
+ ralt+shift: '\u030e'
+}
+
+key BACKSLASH {
+ label: '\\'
+ base: '\\'
+ shift: '|'
+}
+
+### ROW 4
+
+key PLUS {
+ label: '\\'
+ base: '\\'
+ shift: '|'
+}
+
+key Z {
+ label: 'Z'
+ base: 'z'
+ shift, capslock: 'Z'
+ capslock+shift: 'z'
+}
+
+key X {
+ label: 'X'
+ base: 'x'
+ shift, capslock: 'X'
+ capslock+shift: 'x'
+ ralt: '\u1e63'
+ ralt+shift, ralt+capslock: '\u1e62'
+ ralt+shift+capslock: '\u1e63'
+}
+
+key C {
+ label: 'C'
+ base: 'c'
+ shift, capslock: 'C'
+ capslock+shift: 'c'
+}
+
+key V {
+ label: 'V'
+ base: 'v'
+ shift, capslock: 'V'
+ capslock+shift: 'v'
+}
+
+key B {
+ label: 'B'
+ base: 'b'
+ shift, capslock: 'B'
+ capslock+shift: 'b'
+}
+
+key N {
+ label: 'N'
+ base: 'n'
+ shift, capslock: 'N'
+ capslock+shift: 'n'
+ ralt: '\u1e47'
+ ralt+shift, ralt+capslock: '\u1e46'
+ ralt+shift+capslock: '\u1e47'
+}
+
+key M {
+ label: 'M'
+ base: 'm'
+ shift, capslock: 'M'
+ capslock+shift: 'm'
+ ralt: '\u1e41'
+ ralt+shift, ralt+capslock: '\u1e40'
+ ralt+shift+capslock: '\u1e41'
+}
+
+key COMMA {
+ label: ','
+ base: ','
+ shift: '<'
+ ralt+shift: '\u030C'
+}
+
+key PERIOD {
+ label: '.'
+ base: '.'
+ shift: '>'
+ ralt: '\u0323'
+}
+
+key SLASH {
+ label: '/'
+ base: '/'
+ shift: '?'
+}
diff --git a/packages/InputDevices/res/values/strings.xml b/packages/InputDevices/res/values/strings.xml
index bd7cdc4..8a397a5 100644
--- a/packages/InputDevices/res/values/strings.xml
+++ b/packages/InputDevices/res/values/strings.xml
@@ -167,4 +167,7 @@
<!-- Romanian keyboard layout label. [CHAR LIMIT=35] -->
<string name="keyboard_layout_romanian">Romanian</string>
+
+ <!-- English (India) keyboard layout label. [CHAR LIMIT=35] -->
+ <string name="keyboard_layout_english_india">English (India)</string>
</resources>
diff --git a/packages/InputDevices/res/xml/keyboard_layouts.xml b/packages/InputDevices/res/xml/keyboard_layouts.xml
index 9ce9a87..fa0ed13 100644
--- a/packages/InputDevices/res/xml/keyboard_layouts.xml
+++ b/packages/InputDevices/res/xml/keyboard_layouts.xml
@@ -367,4 +367,11 @@
android:keyboardLayout="@raw/keyboard_layout_romanian"
android:keyboardLocale="ro-Latn-RO"
android:keyboardLayoutType="qwerty" />
+
+ <keyboard-layout
+ android:name="keyboard_layout_english_india"
+ android:label="@string/keyboard_layout_english_india"
+ android:keyboardLayout="@raw/keyboard_layout_english_india"
+ android:keyboardLocale="en-Latn-IN"
+ android:keyboardLayoutType="qwerty" />
</keyboard-layouts>
diff --git a/packages/PrintSpooler/src/com/android/printspooler/widget/PreviewPageFrame.java b/packages/PrintSpooler/src/com/android/printspooler/widget/PreviewPageFrame.java
index 95bdb09..e735610 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/widget/PreviewPageFrame.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/widget/PreviewPageFrame.java
@@ -16,6 +16,8 @@
package com.android.printspooler.widget;
+import static android.view.accessibility.Flags.triStateChecked;
+
import android.content.Context;
import android.util.AttributeSet;
import android.view.accessibility.AccessibilityEvent;
@@ -38,6 +40,20 @@
}
@Override
+ public boolean performClick() {
+ final boolean result = super.performClick();
+ // This widget is incorrectly using the notion of "selection"
+ // to represent checked state. We can't send this event in
+ // setSelected() because setSelected() is called when this widget
+ // is not attached.
+ if (triStateChecked()) {
+ notifyViewAccessibilityStateChangedIfNeeded(
+ AccessibilityEvent.CONTENT_CHANGE_TYPE_CHECKED);
+ }
+ return result;
+ }
+
+ @Override
public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
super.onInitializeAccessibilityEvent(event);
event.setChecked(isSelected());
@@ -48,6 +64,11 @@
super.onInitializeAccessibilityNodeInfo(info);
info.setSelected(false);
info.setCheckable(true);
- info.setChecked(isSelected());
+ if (triStateChecked()) {
+ info.setChecked(isSelected() ? AccessibilityNodeInfo.CHECKED_STATE_TRUE :
+ AccessibilityNodeInfo.CHECKED_STATE_FALSE);
+ } else {
+ info.setChecked(isSelected());
+ }
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java
index bf86911..572444e 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java
@@ -30,11 +30,13 @@
import androidx.annotation.ChecksSdkIntAtLeast;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.settingslib.flags.Flags;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
@@ -385,7 +387,7 @@
preferredMainDevice.refresh();
hasChanged = true;
}
- syncAudioSharingSourceIfNeeded(preferredMainDevice);
+ syncAudioSharingStatusIfNeeded(preferredMainDevice);
}
if (hasChanged) {
log("addMemberDevicesIntoMainDevice: After changed, CachedBluetoothDevice list: "
@@ -399,13 +401,16 @@
return userManager != null && userManager.isManagedProfile();
}
- private void syncAudioSharingSourceIfNeeded(CachedBluetoothDevice mainDevice) {
+ private void syncAudioSharingStatusIfNeeded(CachedBluetoothDevice mainDevice) {
boolean isAudioSharingEnabled = BluetoothUtils.isAudioSharingUIAvailable(mContext);
- if (isAudioSharingEnabled) {
+ if (isAudioSharingEnabled && mainDevice != null) {
if (isWorkProfile()) {
- log("addMemberDevicesIntoMainDevice: skip sync source for work profile");
+ log("addMemberDevicesIntoMainDevice: skip sync audio sharing status, work profile");
return;
}
+ Set<CachedBluetoothDevice> deviceSet = new HashSet<>();
+ deviceSet.add(mainDevice);
+ deviceSet.addAll(mainDevice.getMemberDevice());
boolean hasBroadcastSource = BluetoothUtils.isBroadcasting(mBtManager)
&& BluetoothUtils.hasConnectedBroadcastSource(
mainDevice, mBtManager);
@@ -419,9 +424,6 @@
if (metadata != null && assistant != null) {
log("addMemberDevicesIntoMainDevice: sync audio sharing source after "
+ "combining the top level devices.");
- Set<CachedBluetoothDevice> deviceSet = new HashSet<>();
- deviceSet.add(mainDevice);
- deviceSet.addAll(mainDevice.getMemberDevice());
Set<BluetoothDevice> sinksToSync = deviceSet.stream()
.map(CachedBluetoothDevice::getDevice)
.filter(device ->
@@ -435,8 +437,24 @@
}
}
}
+ if (Flags.enableTemporaryBondDevicesUi()) {
+ log("addMemberDevicesIntoMainDevice: sync temp bond metadata for audio sharing "
+ + "sinks after combining the top level devices.");
+ Set<BluetoothDevice> sinksToSync = deviceSet.stream()
+ .map(CachedBluetoothDevice::getDevice).filter(Objects::nonNull).collect(
+ Collectors.toSet());
+ if (sinksToSync.stream().anyMatch(BluetoothUtils::isTemporaryBondDevice)) {
+ for (BluetoothDevice device : sinksToSync) {
+ if (!BluetoothUtils.isTemporaryBondDevice(device)) {
+ log("addMemberDevicesIntoMainDevice: sync temp bond metadata for "
+ + device.getAnonymizedAddress());
+ BluetoothUtils.setTemporaryBondMetadata(device);
+ }
+ }
+ }
+ }
} else {
- log("addMemberDevicesIntoMainDevice: skip sync source, flag disabled");
+ log("addMemberDevicesIntoMainDevice: skip sync audio sharing status, flag disabled");
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/MediaSessions.kt b/packages/SettingsLib/src/com/android/settingslib/volume/MediaSessions.kt
index 10156c4..bac564c 100644
--- a/packages/SettingsLib/src/com/android/settingslib/volume/MediaSessions.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/MediaSessions.kt
@@ -20,6 +20,7 @@
import android.content.pm.PackageManager
import android.media.MediaMetadata
import android.media.session.MediaController
+import android.media.session.MediaController.PlaybackInfo
import android.media.session.MediaSession
import android.media.session.MediaSessionManager
import android.media.session.PlaybackState
@@ -98,16 +99,22 @@
}
/** Set volume `level` to remote media `token` */
- fun setVolume(token: MediaSession.Token, level: Int) {
+ fun setVolume(sessionId: SessionId, volumeLevel: Int) {
+ when (sessionId) {
+ is SessionId.Media -> setMediaSessionVolume(sessionId.token, volumeLevel)
+ }
+ }
+
+ private fun setMediaSessionVolume(token: MediaSession.Token, volumeLevel: Int) {
val record = mRecords[token]
if (record == null) {
Log.w(TAG, "setVolume: No record found for token $token")
return
}
if (D.BUG) {
- Log.d(TAG, "Setting level to $level")
+ Log.d(TAG, "Setting level to $volumeLevel")
}
- record.controller.setVolumeTo(level, 0)
+ record.controller.setVolumeTo(volumeLevel, 0)
}
private fun onRemoteVolumeChangedH(sessionToken: MediaSession.Token, flags: Int) {
@@ -122,7 +129,7 @@
)
}
val token = controller.sessionToken
- mCallbacks.onRemoteVolumeChanged(token, flags)
+ mCallbacks.onRemoteVolumeChanged(SessionId.from(token), flags)
}
private fun onUpdateRemoteSessionListH(sessionToken: MediaSession.Token?) {
@@ -158,7 +165,7 @@
controller.registerCallback(record, mHandler)
}
val record = mRecords[token]
- val remote = isRemote(playbackInfo)
+ val remote = playbackInfo.isRemote()
if (remote) {
updateRemoteH(token, record!!.name, playbackInfo)
record.sentRemote = true
@@ -172,7 +179,7 @@
Log.d(TAG, "Removing " + record.name + " sentRemote=" + record.sentRemote)
}
if (record.sentRemote) {
- mCallbacks.onRemoteRemoved(token)
+ mCallbacks.onRemoteRemoved(SessionId.from(token))
record.sentRemote = false
}
}
@@ -213,8 +220,8 @@
private fun updateRemoteH(
token: MediaSession.Token,
name: String?,
- pi: MediaController.PlaybackInfo,
- ) = mCallbacks.onRemoteUpdate(token, name, pi)
+ playbackInfo: PlaybackInfo,
+ ) = mCallbacks.onRemoteUpdate(SessionId.from(token), name, VolumeInfo.from(playbackInfo))
private inner class MediaControllerRecord(val controller: MediaController) :
MediaController.Callback() {
@@ -225,7 +232,7 @@
return method + " " + controller.packageName + " "
}
- override fun onAudioInfoChanged(info: MediaController.PlaybackInfo) {
+ override fun onAudioInfoChanged(info: PlaybackInfo) {
if (D.BUG) {
Log.d(
TAG,
@@ -235,9 +242,9 @@
sentRemote),
)
}
- val remote = isRemote(info)
+ val remote = info.isRemote()
if (!remote && sentRemote) {
- mCallbacks.onRemoteRemoved(controller.sessionToken)
+ mCallbacks.onRemoteRemoved(SessionId.from(controller.sessionToken))
sentRemote = false
} else if (remote) {
updateRemoteH(controller.sessionToken, name, info)
@@ -301,20 +308,36 @@
}
}
+ /** Opaque id for ongoing sessions that support volume adjustment. */
+ sealed interface SessionId {
+
+ companion object {
+ fun from(token: MediaSession.Token) = Media(token)
+ }
+
+ data class Media(val token: MediaSession.Token) : SessionId
+ }
+
+ /** Holds session volume information. */
+ data class VolumeInfo(val currentVolume: Int, val maxVolume: Int) {
+
+ companion object {
+
+ fun from(playbackInfo: PlaybackInfo) =
+ VolumeInfo(playbackInfo.currentVolume, playbackInfo.maxVolume)
+ }
+ }
+
/** Callback for remote media sessions */
interface Callbacks {
/** Invoked when remote media session is updated */
- fun onRemoteUpdate(
- token: MediaSession.Token?,
- name: String?,
- pi: MediaController.PlaybackInfo?,
- )
+ fun onRemoteUpdate(token: SessionId?, name: String?, volumeInfo: VolumeInfo?)
/** Invoked when remote media session is removed */
- fun onRemoteRemoved(token: MediaSession.Token?)
+ fun onRemoteRemoved(token: SessionId?)
/** Invoked when remote volume is changed */
- fun onRemoteVolumeChanged(token: MediaSession.Token?, flags: Int)
+ fun onRemoteVolumeChanged(token: SessionId?, flags: Int)
}
companion object {
@@ -325,12 +348,11 @@
const val UPDATE_REMOTE_SESSION_LIST: Int = 3
private const val USE_SERVICE_LABEL = false
-
- private fun isRemote(pi: MediaController.PlaybackInfo?): Boolean =
- pi != null && pi.playbackType == MediaController.PlaybackInfo.PLAYBACK_TYPE_REMOTE
}
}
+private fun PlaybackInfo?.isRemote() = this?.playbackType == PlaybackInfo.PLAYBACK_TYPE_REMOTE
+
private fun MediaController.dump(n: Int, writer: PrintWriter) {
writer.println(" Controller $n: $packageName")
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/display/DisplayDensityUtilsTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/display/DisplayDensityUtilsTest.java
new file mode 100644
index 0000000..bba278a
--- /dev/null
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/display/DisplayDensityUtilsTest.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.display;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.DisplayManagerGlobal;
+import android.os.RemoteException;
+import android.view.Display;
+import android.view.DisplayAdjustments;
+import android.view.DisplayInfo;
+import android.view.IWindowManager;
+import android.view.WindowManagerGlobal;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.settingslib.R;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+public class DisplayDensityUtilsTest {
+
+ private static final float MAX_SCALE = 1.33f;
+ private static final float MIN_SCALE = 0.85f;
+ private static final float MIN_INTERVAL = 0.09f;
+ @Mock
+ private Context mContext;
+ @Mock
+ private Resources mResources;
+ @Mock
+ private DisplayManager mDisplayManager;
+ @Mock
+ private DisplayManagerGlobal mDisplayManagerGlobal;
+ @Mock
+ private IWindowManager mIWindowManager;
+ private IWindowManager mWindowManagerToRestore;
+ private DisplayDensityUtils mDisplayDensityUtils;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ doReturn(mDisplayManager).when(mContext).getSystemService((Class<Object>) any());
+ mWindowManagerToRestore = WindowManagerGlobal.getWindowManagerService();
+ WindowManagerGlobal.setWindowManagerServiceForSystemProcess(mIWindowManager);
+ when(mContext.getResources()).thenReturn(mResources);
+ when(mResources.getFraction(R.fraction.display_density_max_scale, 1, 1)).thenReturn(
+ MAX_SCALE);
+ when(mResources.getFraction(R.fraction.display_density_min_scale, 1, 1)).thenReturn(
+ MIN_SCALE);
+ when(mResources.getFraction(R.fraction.display_density_min_scale_interval, 1,
+ 1)).thenReturn(MIN_INTERVAL);
+ when(mResources.getString(anyInt())).thenReturn("summary");
+ }
+
+ @After
+ public void teardown() {
+ WindowManagerGlobal.setWindowManagerServiceForSystemProcess(mWindowManagerToRestore);
+ }
+
+ @Test
+ public void createDisplayDensityUtil_onlyDefaultDisplay() throws RemoteException {
+ var info = createDisplayInfoForDisplay(Display.DEFAULT_DISPLAY, Display.TYPE_INTERNAL, 2560,
+ 1600, 320);
+ var display = new Display(mDisplayManagerGlobal, info.displayId, info,
+ (DisplayAdjustments) null);
+ doReturn(new Display[]{display}).when(mDisplayManager).getDisplays(any());
+ doReturn(display).when(mDisplayManager).getDisplay(info.displayId);
+
+ mDisplayDensityUtils = new DisplayDensityUtils(mContext);
+
+ assertThat(mDisplayDensityUtils.getValues()).isEqualTo(new int[]{272, 320, 354, 390, 424});
+ }
+
+ @Test
+ public void createDisplayDensityUtil_multipleInternalDisplays() throws RemoteException {
+ // Default display
+ var defaultDisplayInfo = createDisplayInfoForDisplay(Display.DEFAULT_DISPLAY,
+ Display.TYPE_INTERNAL, 2000, 2000, 390);
+ var defaultDisplay = new Display(mDisplayManagerGlobal, defaultDisplayInfo.displayId,
+ defaultDisplayInfo,
+ (DisplayAdjustments) null);
+ doReturn(defaultDisplay).when(mDisplayManager).getDisplay(defaultDisplayInfo.displayId);
+
+ // Create another internal display
+ var internalDisplayInfo = createDisplayInfoForDisplay(1, Display.TYPE_INTERNAL,
+ 2000, 1000, 390);
+ var internalDisplay = new Display(mDisplayManagerGlobal, internalDisplayInfo.displayId,
+ internalDisplayInfo,
+ (DisplayAdjustments) null);
+ doReturn(internalDisplay).when(mDisplayManager).getDisplay(internalDisplayInfo.displayId);
+
+ doReturn(new Display[]{defaultDisplay, internalDisplay}).when(mDisplayManager).getDisplays(
+ anyString());
+
+ mDisplayDensityUtils = new DisplayDensityUtils(mContext);
+
+ assertThat(mDisplayDensityUtils.getValues()).isEqualTo(new int[]{330, 390, 426, 462, 500});
+ }
+
+ private DisplayInfo createDisplayInfoForDisplay(int displayId, int displayType,
+ int width, int height, int density) throws RemoteException {
+ var displayInfo = new DisplayInfo();
+ displayInfo.displayId = displayId;
+ displayInfo.type = displayType;
+ displayInfo.logicalWidth = width;
+ displayInfo.logicalHeight = height;
+ displayInfo.logicalDensityDpi = density;
+
+ doReturn(displayInfo).when(mDisplayManagerGlobal).getDisplayInfo(displayInfo.displayId);
+ doReturn(displayInfo.logicalDensityDpi).when(mIWindowManager).getInitialDisplayDensity(
+ displayInfo.displayId);
+ return displayInfo;
+ }
+}
+
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CsipDeviceManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CsipDeviceManagerTest.java
index fd14d1f..2eccaa6 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CsipDeviceManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CsipDeviceManagerTest.java
@@ -40,6 +40,8 @@
import android.os.Looper;
import android.os.Parcel;
import android.os.UserManager;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
import com.android.settingslib.flags.Flags;
@@ -74,6 +76,9 @@
private final static String DEVICE_ADDRESS_1 = "AA:BB:CC:DD:EE:11";
private final static String DEVICE_ADDRESS_2 = "AA:BB:CC:DD:EE:22";
private final static String DEVICE_ADDRESS_3 = "AA:BB:CC:DD:EE:33";
+ private static final int METADATA_FAST_PAIR_CUSTOMIZED_FIELDS = 25;
+ private static final String TEMP_BOND_METADATA =
+ "<TEMP_BOND_TYPE>le_audio_sharing</TEMP_BOND_TYPE>";
private final static int GROUP1 = 1;
private final BluetoothClass DEVICE_CLASS_1 =
createBtClass(BluetoothClass.Device.AUDIO_VIDEO_HEADPHONES);
@@ -337,6 +342,7 @@
}
@Test
+ @DisableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING)
public void addMemberDevicesIntoMainDevice_preferredDeviceIsMainAndTwoMain_returnTrue() {
// Condition: The preferredDevice is main and there is another main device in top list
// Expected Result: return true and there is the preferredDevice in top list
@@ -346,7 +352,6 @@
mCachedDevices.add(preferredDevice);
mCachedDevices.add(mCachedDevice2);
mCachedDevices.add(mCachedDevice3);
- mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
assertThat(mCsipDeviceManager.addMemberDevicesIntoMainDevice(GROUP1, preferredDevice))
.isTrue();
@@ -359,6 +364,7 @@
}
@Test
+ @EnableFlags({Flags.FLAG_ENABLE_LE_AUDIO_SHARING, Flags.FLAG_ENABLE_TEMPORARY_BOND_DEVICES_UI})
public void
addMemberDevicesIntoMainDevice_preferredDeviceIsMainAndTwoMain_workProfile_doNothing() {
// Condition: The preferredDevice is main and there is another main device in top list
@@ -369,7 +375,6 @@
mCachedDevices.add(preferredDevice);
mCachedDevices.add(mCachedDevice2);
mCachedDevices.add(mCachedDevice3);
- mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
when(mBroadcast.isEnabled(null)).thenReturn(true);
BluetoothLeBroadcastMetadata metadata = Mockito.mock(BluetoothLeBroadcastMetadata.class);
when(mBroadcast.getLatestBluetoothLeBroadcastMetadata()).thenReturn(metadata);
@@ -377,6 +382,8 @@
BluetoothLeBroadcastReceiveState.class);
when(state.getBisSyncState()).thenReturn(ImmutableList.of(1L));
when(mAssistant.getAllSources(mDevice2)).thenReturn(ImmutableList.of(state));
+ when(mDevice2.getMetadata(METADATA_FAST_PAIR_CUSTOMIZED_FIELDS))
+ .thenReturn(TEMP_BOND_METADATA.getBytes());
when(mContext.getSystemService(UserManager.class)).thenReturn(mUserManager);
when(mUserManager.isManagedProfile()).thenReturn(true);
@@ -387,10 +394,13 @@
assertThat(mCachedDevices.contains(mCachedDevice3)).isTrue();
assertThat(preferredDevice.getMemberDevice()).contains(mCachedDevice2);
verify(mAssistant, never()).addSource(mDevice1, metadata, /* isGroupOp= */ false);
+ verify(mDevice1, never()).setMetadata(METADATA_FAST_PAIR_CUSTOMIZED_FIELDS,
+ TEMP_BOND_METADATA.getBytes());
}
@Test
- public void addMemberDevicesIntoMainDevice_preferredDeviceIsMainAndTwoMain_syncSource() {
+ @EnableFlags({Flags.FLAG_ENABLE_LE_AUDIO_SHARING, Flags.FLAG_ENABLE_TEMPORARY_BOND_DEVICES_UI})
+ public void addMemberDevicesIntoMainDevice_preferredDeviceIsMainAndTwoMain_syncState() {
// Condition: The preferredDevice is main and there is another main device in top list
// Expected Result: return true and there is the preferredDevice in top list
CachedBluetoothDevice preferredDevice = mCachedDevice1;
@@ -399,7 +409,6 @@
mCachedDevices.add(preferredDevice);
mCachedDevices.add(mCachedDevice2);
mCachedDevices.add(mCachedDevice3);
- mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
when(mBroadcast.isEnabled(null)).thenReturn(true);
BluetoothLeBroadcastMetadata metadata = Mockito.mock(BluetoothLeBroadcastMetadata.class);
when(mBroadcast.getLatestBluetoothLeBroadcastMetadata()).thenReturn(metadata);
@@ -407,6 +416,8 @@
BluetoothLeBroadcastReceiveState.class);
when(state.getBisSyncState()).thenReturn(ImmutableList.of(1L));
when(mAssistant.getAllSources(mDevice2)).thenReturn(ImmutableList.of(state));
+ when(mDevice2.getMetadata(METADATA_FAST_PAIR_CUSTOMIZED_FIELDS))
+ .thenReturn(TEMP_BOND_METADATA.getBytes());
assertThat(mCsipDeviceManager.addMemberDevicesIntoMainDevice(GROUP1, preferredDevice))
.isTrue();
@@ -415,6 +426,8 @@
assertThat(mCachedDevices.contains(mCachedDevice3)).isTrue();
assertThat(preferredDevice.getMemberDevice()).contains(mCachedDevice2);
verify(mAssistant).addSource(mDevice1, metadata, /* isGroupOp= */ false);
+ verify(mDevice1).setMetadata(METADATA_FAST_PAIR_CUSTOMIZED_FIELDS,
+ TEMP_BOND_METADATA.getBytes());
}
@Test
@@ -436,13 +449,13 @@
}
@Test
+ @EnableFlags({Flags.FLAG_ENABLE_LE_AUDIO_SHARING, Flags.FLAG_ENABLE_TEMPORARY_BOND_DEVICES_UI})
public void addMemberDevicesIntoMainDevice_preferredDeviceIsMemberAndTwoMain_returnTrue() {
// Condition: The preferredDevice is member and there are two main device in top list
// Expected Result: return true and there is the preferredDevice in top list
CachedBluetoothDevice preferredDevice = mCachedDevice2;
BluetoothDevice expectedMainBluetoothDevice = preferredDevice.getDevice();
mCachedDevice3.setGroupId(GROUP1);
- mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
when(mBroadcast.isEnabled(null)).thenReturn(false);
assertThat(mCsipDeviceManager.addMemberDevicesIntoMainDevice(GROUP1, preferredDevice))
@@ -457,16 +470,20 @@
assertThat(mCachedDevice1.getDevice()).isEqualTo(expectedMainBluetoothDevice);
verify(mAssistant, never()).addSource(any(BluetoothDevice.class),
any(BluetoothLeBroadcastMetadata.class), anyBoolean());
+ verify(mDevice2, never()).setMetadata(METADATA_FAST_PAIR_CUSTOMIZED_FIELDS,
+ TEMP_BOND_METADATA.getBytes());
+ verify(mDevice3, never()).setMetadata(METADATA_FAST_PAIR_CUSTOMIZED_FIELDS,
+ TEMP_BOND_METADATA.getBytes());
}
@Test
- public void addMemberDevicesIntoMainDevice_preferredDeviceIsMemberAndTwoMain_syncSource() {
+ @EnableFlags({Flags.FLAG_ENABLE_LE_AUDIO_SHARING, Flags.FLAG_ENABLE_TEMPORARY_BOND_DEVICES_UI})
+ public void addMemberDevicesIntoMainDevice_preferredDeviceIsMemberAndTwoMain_syncState() {
// Condition: The preferredDevice is member and there are two main device in top list
// Expected Result: return true and there is the preferredDevice in top list
CachedBluetoothDevice preferredDevice = mCachedDevice2;
BluetoothDevice expectedMainBluetoothDevice = preferredDevice.getDevice();
mCachedDevice3.setGroupId(GROUP1);
- mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
when(mBroadcast.isEnabled(null)).thenReturn(true);
BluetoothLeBroadcastMetadata metadata = Mockito.mock(BluetoothLeBroadcastMetadata.class);
when(mBroadcast.getLatestBluetoothLeBroadcastMetadata()).thenReturn(metadata);
@@ -474,6 +491,8 @@
BluetoothLeBroadcastReceiveState.class);
when(state.getBisSyncState()).thenReturn(ImmutableList.of(1L));
when(mAssistant.getAllSources(mDevice1)).thenReturn(ImmutableList.of(state));
+ when(mDevice1.getMetadata(METADATA_FAST_PAIR_CUSTOMIZED_FIELDS))
+ .thenReturn(TEMP_BOND_METADATA.getBytes());
assertThat(mCsipDeviceManager.addMemberDevicesIntoMainDevice(GROUP1, preferredDevice))
.isTrue();
@@ -488,6 +507,10 @@
assertThat(mCachedDevice1.getDevice()).isEqualTo(expectedMainBluetoothDevice);
verify(mAssistant).addSource(mDevice2, metadata, /* isGroupOp= */ false);
verify(mAssistant).addSource(mDevice3, metadata, /* isGroupOp= */ false);
+ verify(mDevice2).setMetadata(METADATA_FAST_PAIR_CUSTOMIZED_FIELDS,
+ TEMP_BOND_METADATA.getBytes());
+ verify(mDevice3).setMetadata(METADATA_FAST_PAIR_CUSTOMIZED_FIELDS,
+ TEMP_BOND_METADATA.getBytes());
}
@Test
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 246aa71..85617ba 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -809,7 +809,9 @@
Settings.Secure.DND_CONFIGS_MIGRATED,
Settings.Secure.NAVIGATION_MODE_RESTORE,
Settings.Secure.V_TO_U_RESTORE_ALLOWLIST,
- Settings.Secure.V_TO_U_RESTORE_DENYLIST);
+ Settings.Secure.V_TO_U_RESTORE_DENYLIST,
+ Settings.Secure.REDACT_OTP_NOTIFICATION_WHILE_CONNECTED_TO_WIFI,
+ Settings.Secure.REDACT_OTP_NOTIFICATION_IMMEDIATELY);
@Test
public void systemSettingsBackedUpOrDenied() {
diff --git a/packages/Shell/src/com/android/shell/BugreportPrefs.java b/packages/Shell/src/com/android/shell/BugreportPrefs.java
index 93690d4..b0fd925 100644
--- a/packages/Shell/src/com/android/shell/BugreportPrefs.java
+++ b/packages/Shell/src/com/android/shell/BugreportPrefs.java
@@ -23,25 +23,24 @@
* Preferences related to bug reports.
*/
final class BugreportPrefs {
- static final String PREFS_BUGREPORT = "bugreports";
-
- private static final String KEY_WARNING_STATE = "warning-state";
-
- static final int STATE_UNKNOWN = 0;
- // Shows the warning dialog.
- static final int STATE_SHOW = 1;
- // Skips the warning dialog.
- static final int STATE_HIDE = 2;
static int getWarningState(Context context, int def) {
- final SharedPreferences prefs = context.getSharedPreferences(
- PREFS_BUGREPORT, Context.MODE_PRIVATE);
- return prefs.getInt(KEY_WARNING_STATE, def);
+ String prefsBugreport = context.getResources().getString(
+ com.android.internal.R.string.prefs_bugreport);
+ String keyWarningState = context.getResources().getString(
+ com.android.internal.R.string.key_warning_state);
+ final SharedPreferences prefs = context.getSharedPreferences(prefsBugreport,
+ Context.MODE_PRIVATE);
+ return prefs.getInt(keyWarningState, def);
}
static void setWarningState(Context context, int value) {
- final SharedPreferences prefs = context.getSharedPreferences(
- PREFS_BUGREPORT, Context.MODE_PRIVATE);
- prefs.edit().putInt(KEY_WARNING_STATE, value).apply();
+ String prefsBugreport = context.getResources().getString(
+ com.android.internal.R.string.prefs_bugreport);
+ String keyWarningState = context.getResources().getString(
+ com.android.internal.R.string.key_warning_state);
+ final SharedPreferences prefs = context.getSharedPreferences(prefsBugreport,
+ Context.MODE_PRIVATE);
+ prefs.edit().putInt(keyWarningState, value).apply();
}
}
diff --git a/packages/Shell/src/com/android/shell/BugreportProgressService.java b/packages/Shell/src/com/android/shell/BugreportProgressService.java
index 61f49db..fb0678f 100644
--- a/packages/Shell/src/com/android/shell/BugreportProgressService.java
+++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java
@@ -21,8 +21,6 @@
import static android.content.pm.PackageManager.FEATURE_TELEVISION;
import static android.os.Process.THREAD_PRIORITY_BACKGROUND;
-import static com.android.shell.BugreportPrefs.STATE_HIDE;
-import static com.android.shell.BugreportPrefs.STATE_UNKNOWN;
import static com.android.shell.BugreportPrefs.getWarningState;
import static com.android.shell.flags.Flags.handleBugreportsForWear;
@@ -1347,7 +1345,11 @@
}
private boolean hasUserDecidedNotToGetWarningMessage() {
- return getWarningState(mContext, STATE_UNKNOWN) == STATE_HIDE;
+ int bugreportStateUnknown = mContext.getResources().getInteger(
+ com.android.internal.R.integer.bugreport_state_unknown);
+ int bugreportStateHide = mContext.getResources().getInteger(
+ com.android.internal.R.integer.bugreport_state_hide);
+ return getWarningState(mContext, bugreportStateUnknown) == bugreportStateHide;
}
private void maybeShowWarningMessageAndCloseNotification(int id) {
diff --git a/packages/Shell/src/com/android/shell/BugreportWarningActivity.java b/packages/Shell/src/com/android/shell/BugreportWarningActivity.java
index a44e236..0e835f9 100644
--- a/packages/Shell/src/com/android/shell/BugreportWarningActivity.java
+++ b/packages/Shell/src/com/android/shell/BugreportWarningActivity.java
@@ -16,9 +16,6 @@
package com.android.shell;
-import static com.android.shell.BugreportPrefs.STATE_HIDE;
-import static com.android.shell.BugreportPrefs.STATE_SHOW;
-import static com.android.shell.BugreportPrefs.STATE_UNKNOWN;
import static com.android.shell.BugreportPrefs.getWarningState;
import static com.android.shell.BugreportPrefs.setWarningState;
import static com.android.shell.BugreportProgressService.sendShareIntent;
@@ -69,12 +66,19 @@
mConfirmRepeat = (CheckBox) ap.mView.findViewById(android.R.id.checkbox);
- final int state = getWarningState(this, STATE_UNKNOWN);
+ int bugreportStateUnknown = getResources().getInteger(
+ com.android.internal.R.integer.bugreport_state_unknown);
+ int bugreportStateHide = getResources().getInteger(
+ com.android.internal.R.integer.bugreport_state_hide);
+ int bugreportStateShow = getResources().getInteger(
+ com.android.internal.R.integer.bugreport_state_show);
+
+ final int state = getWarningState(this, bugreportStateUnknown);
final boolean checked;
if (Build.IS_USER) {
- checked = state == STATE_HIDE; // Only checks if specifically set to.
+ checked = state == bugreportStateHide; // Only checks if specifically set to.
} else {
- checked = state != STATE_SHOW; // Checks by default.
+ checked = state != bugreportStateShow; // Checks by default.
}
mConfirmRepeat.setChecked(checked);
@@ -83,9 +87,14 @@
@Override
public void onClick(DialogInterface dialog, int which) {
+ int bugreportStateHide = getResources().getInteger(
+ com.android.internal.R.integer.bugreport_state_hide);
+ int bugreportStateShow = getResources().getInteger(
+ com.android.internal.R.integer.bugreport_state_show);
if (which == AlertDialog.BUTTON_POSITIVE) {
// Remember confirm state, and launch target
- setWarningState(this, mConfirmRepeat.isChecked() ? STATE_HIDE : STATE_SHOW);
+ setWarningState(this, mConfirmRepeat.isChecked() ? bugreportStateHide
+ : bugreportStateShow);
if (mSendIntent != null) {
sendShareIntent(this, mSendIntent);
}
diff --git a/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java b/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java
index 7bda2ea..2d6abe6 100644
--- a/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java
+++ b/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java
@@ -19,10 +19,6 @@
import static android.test.MoreAsserts.assertContainsRegex;
import static com.android.shell.ActionSendMultipleConsumerActivity.UI_NAME;
-import static com.android.shell.BugreportPrefs.PREFS_BUGREPORT;
-import static com.android.shell.BugreportPrefs.STATE_HIDE;
-import static com.android.shell.BugreportPrefs.STATE_SHOW;
-import static com.android.shell.BugreportPrefs.STATE_UNKNOWN;
import static com.android.shell.BugreportPrefs.getWarningState;
import static com.android.shell.BugreportPrefs.setWarningState;
import static com.android.shell.BugreportProgressService.INTENT_BUGREPORT_REQUESTED;
@@ -201,8 +197,9 @@
return null;
}).when(mMockIDumpstate).startBugreport(anyInt(), any(), any(), any(), anyInt(), anyInt(),
any(), anyBoolean(), anyBoolean());
-
- setWarningState(mContext, STATE_HIDE);
+ int bugreportStateHide = mContext.getResources().getInteger(
+ com.android.internal.R.integer.bugreport_state_hide);
+ setWarningState(mContext, bugreportStateHide);
mUiBot.turnScreenOn();
}
@@ -469,22 +466,31 @@
@Test
public void testBugreportFinished_withWarningUnknownState() throws Exception {
- bugreportFinishedWithWarningTest(STATE_UNKNOWN);
+ int bugreportStateUnknown = mContext.getResources().getInteger(
+ com.android.internal.R.integer.bugreport_state_unknown);
+ bugreportFinishedWithWarningTest(bugreportStateUnknown);
}
@Test
public void testBugreportFinished_withWarningShowAgain() throws Exception {
- bugreportFinishedWithWarningTest(STATE_SHOW);
+ int bugreportStateShow = mContext.getResources().getInteger(
+ com.android.internal.R.integer.bugreport_state_show);
+ bugreportFinishedWithWarningTest(bugreportStateShow);
}
private void bugreportFinishedWithWarningTest(Integer propertyState) throws Exception {
+ int bugreportStateUnknown = mContext.getResources().getInteger(
+ com.android.internal.R.integer.bugreport_state_unknown);
+ int bugreportStateHide = mContext.getResources().getInteger(
+ com.android.internal.R.integer.bugreport_state_hide);
if (propertyState == null) {
// Clear properties
- mContext.getSharedPreferences(PREFS_BUGREPORT, Context.MODE_PRIVATE)
- .edit().clear().commit();
+ mContext.getSharedPreferences(
+ mContext.getResources().getString(com.android.internal.R.string.prefs_bugreport)
+ , Context.MODE_PRIVATE).edit().clear().commit();
// Confidence check...
- assertEquals("Did not reset properties", STATE_UNKNOWN,
- getWarningState(mContext, STATE_UNKNOWN));
+ assertEquals("Did not reset properties", bugreportStateUnknown,
+ getWarningState(mContext, bugreportStateUnknown));
} else {
setWarningState(mContext, propertyState);
}
@@ -501,7 +507,8 @@
// TODO: get ok and dontShowAgain from the dialog reference above
UiObject dontShowAgain =
mUiBot.getVisibleObject(mContext.getString(R.string.bugreport_confirm_dont_repeat));
- final boolean firstTime = propertyState == null || propertyState == STATE_UNKNOWN;
+ final boolean firstTime =
+ propertyState == null || propertyState == bugreportStateUnknown;
if (firstTime) {
if (Build.IS_USER) {
assertFalse("Checkbox should NOT be checked by default on user builds",
@@ -524,8 +531,8 @@
assertActionSendMultiple(extras);
// Make sure it's hidden now.
- int newState = getWarningState(mContext, STATE_UNKNOWN);
- assertEquals("Didn't change state", STATE_HIDE, newState);
+ int newState = getWarningState(mContext, bugreportStateUnknown);
+ assertEquals("Didn't change state", bugreportStateHide, newState);
}
@Test
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
index fad8ae7..2f38dc2 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
@@ -24,6 +24,7 @@
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.testTag
import com.android.compose.animation.scene.ContentScope
import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.UserAction
@@ -102,6 +103,8 @@
viewModel,
dialogFactory,
Modifier.element(Bouncer.Elements.Content)
+ // TODO(b/393516240): Use the same sysuiResTag() as views instead.
+ .testTag(Bouncer.Elements.Content.testTag)
.overscroll(verticalOverscrollEffect)
.sysuiResTag(Bouncer.TestTags.Root)
.fillMaxSize(),
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 2ca7055..0b17a3f 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
@@ -200,8 +200,9 @@
scene(
CommunalScenes.Blank,
userActions =
- if (viewModel.v2FlagEnabled()) emptyMap()
- else mapOf(Swipe.Start(fromSource = Edge.End) to CommunalScenes.Communal),
+ if (viewModel.swipeToHubEnabled())
+ mapOf(Swipe.Start(fromSource = Edge.End) to CommunalScenes.Communal)
+ else emptyMap(),
) {
// This scene shows nothing only allowing for transitions to the communal scene.
Box(modifier = Modifier.fillMaxSize())
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 9c57efc..835dd7a 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
@@ -76,7 +76,7 @@
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.selection.selectable
import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.foundation.text.AutoSize
+import androidx.compose.foundation.text.TextAutoSize
import androidx.compose.foundation.text.BasicText
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
@@ -1045,7 +1045,7 @@
color = colors.onPrimary,
textAlign = TextAlign.Center,
),
- autoSize = AutoSize.StepBased(maxFontSize = 36.sp, stepSize = 0.1.sp),
+ autoSize = TextAutoSize.StepBased(maxFontSize = 36.sp, stepSize = 0.1.sp),
modifier =
Modifier.focusable().semantics(mergeDescendants = true) {
contentDescription = titleForEmptyStateCTA
@@ -1705,15 +1705,38 @@
contentScope: ContentScope?,
modifier: Modifier = Modifier,
) {
- if (SceneContainerFlag.isEnabled && contentScope != null) {
- contentScope.MediaCarousel(
- modifier = modifier.fillMaxSize(),
- isVisible = true,
- mediaHost = viewModel.mediaHost,
- carouselController = viewModel.mediaCarouselController,
- )
- } else {
- UmoLegacy(viewModel, modifier)
+ val showNextActionLabel = stringResource(R.string.accessibility_action_label_umo_show_next)
+ val showPreviousActionLabel =
+ stringResource(R.string.accessibility_action_label_umo_show_previous)
+
+ Box(
+ modifier =
+ modifier.thenIf(!viewModel.isEditMode) {
+ Modifier.semantics {
+ customActions =
+ listOf(
+ CustomAccessibilityAction(showNextActionLabel) {
+ viewModel.onShowNextMedia()
+ true
+ },
+ CustomAccessibilityAction(showPreviousActionLabel) {
+ viewModel.onShowPreviousMedia()
+ true
+ },
+ )
+ }
+ }
+ ) {
+ if (SceneContainerFlag.isEnabled && contentScope != null) {
+ contentScope.MediaCarousel(
+ modifier = modifier.fillMaxSize(),
+ isVisible = true,
+ mediaHost = viewModel.mediaHost,
+ carouselController = viewModel.mediaCarouselController,
+ )
+ } else {
+ UmoLegacy(viewModel, modifier)
+ }
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt
index 16002bc..8ad96a5 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt
@@ -57,7 +57,8 @@
internal constructor(
communalContent: List<CommunalContentModel>,
private val onAddWidget: (componentName: ComponentName, user: UserHandle, rank: Int) -> Unit,
- private val onDeleteWidget: (id: Int, componentName: ComponentName, rank: Int) -> Unit,
+ private val onDeleteWidget:
+ (id: Int, key: String, componentName: ComponentName, rank: Int) -> Unit,
private val onReorderWidgets: (widgetIdToRankMap: Map<Int, Int>) -> Unit,
private val onResizeWidget:
(
@@ -81,7 +82,7 @@
if (list[indexToRemove].isWidgetContent()) {
val widget = list[indexToRemove] as CommunalContentModel.WidgetContent
list.apply { removeAt(indexToRemove) }
- onDeleteWidget(widget.appWidgetId, widget.componentName, widget.rank)
+ onDeleteWidget(widget.appWidgetId, widget.key, widget.componentName, widget.rank)
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ResponsiveLazyHorizontalGrid.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ResponsiveLazyHorizontalGrid.kt
index 62aa31b..73a2425 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ResponsiveLazyHorizontalGrid.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ResponsiveLazyHorizontalGrid.kt
@@ -50,7 +50,6 @@
import androidx.window.layout.WindowMetricsCalculator
import com.android.systemui.communal.util.WindowSizeUtils.COMPACT_HEIGHT
import com.android.systemui.communal.util.WindowSizeUtils.COMPACT_WIDTH
-import com.android.systemui.communal.util.WindowSizeUtils.MEDIUM_WIDTH
/**
* Renders a responsive [LazyHorizontalGrid] with dynamic columns and rows. Each cell will maintain
@@ -267,9 +266,8 @@
}
private fun calculateNumCellsWidth(width: Dp) =
- // See https://developer.android.com/develop/ui/views/layout/use-window-size-classes
when {
- width >= MEDIUM_WIDTH -> 3
+ width >= 900.dp -> 3
width >= COMPACT_WIDTH -> 2
else -> 1
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
index 5e61af6..aa07370 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
@@ -19,6 +19,7 @@
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.testTag
import com.android.compose.animation.scene.ContentScope
import com.android.compose.animation.scene.UserAction
import com.android.compose.animation.scene.UserActionResult
@@ -55,7 +56,11 @@
@Composable
override fun ContentScope.Content(modifier: Modifier) {
- LockscreenScene(lockscreenContent = lockscreenContent, modifier = modifier)
+ LockscreenScene(
+ lockscreenContent = lockscreenContent,
+ // TODO(b/393516240): Use the same sysuiResTag() as views instead.
+ modifier = modifier.testTag(key.rootElementKey.testTag),
+ )
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
index aa0d474..0c502e6 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
@@ -109,6 +109,13 @@
rememberMutableSceneTransitionLayoutState(
initialScene = initialSceneKey,
canChangeScene = { toScene -> viewModel.canChangeScene(toScene) },
+ canShowOverlay = { overlay -> viewModel.canShowOrReplaceOverlay(overlay) },
+ canReplaceOverlay = { beingReplaced, newlyShown ->
+ viewModel.canShowOrReplaceOverlay(
+ newlyShown = newlyShown,
+ beingReplaced = beingReplaced,
+ )
+ },
transitions = sceneTransitions,
onTransitionStart = { transition ->
sceneJankMonitor.onTransitionStart(
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 907b5bc..05958a2 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
@@ -169,7 +169,7 @@
Modifier.maybeElevateInContent(layoutImpl, content, key, currentTransitionStates)
}
.then(ElementModifier(layoutImpl, currentTransitionStates, content, key))
- .testTag(key.testTag)
+ .thenIf(layoutImpl.implicitTestTags) { Modifier.testTag(key.testTag) }
}
/**
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
index 72bb82b..d47210c 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
@@ -65,6 +65,8 @@
swipeSourceDetector: SwipeSourceDetector = DefaultEdgeDetector,
swipeDetector: SwipeDetector = DefaultSwipeDetector,
@FloatRange(from = 0.0, to = 0.5) transitionInterceptionThreshold: Float = 0.05f,
+ // TODO(b/240432457) Remove this once test utils can access the internal STLForTesting().
+ implicitTestTags: Boolean = false,
builder: SceneTransitionLayoutScope<ContentScope>.() -> Unit,
) {
SceneTransitionLayoutForTesting(
@@ -73,6 +75,7 @@
swipeSourceDetector,
swipeDetector,
transitionInterceptionThreshold,
+ implicitTestTags = implicitTestTags,
onLayoutImpl = null,
builder = builder,
)
@@ -725,10 +728,8 @@
}
/**
- * An internal version of [SceneTransitionLayout] to be used for tests.
- *
- * Important: You should use this only in tests and if you need to access the underlying
- * [SceneTransitionLayoutImpl]. In other cases, you should use [SceneTransitionLayout].
+ * An internal version of [SceneTransitionLayout] to be used for tests, that provides access to the
+ * internal [SceneTransitionLayoutImpl] and implicitly tags all scenes and elements.
*/
@Composable
internal fun SceneTransitionLayoutForTesting(
@@ -741,6 +742,7 @@
sharedElementMap: MutableMap<ElementKey, Element> = remember { mutableMapOf() },
ancestors: List<Ancestor> = remember { emptyList() },
lookaheadScope: LookaheadScope? = null,
+ implicitTestTags: Boolean = true,
builder: SceneTransitionLayoutScope<InternalContentScope>.() -> Unit,
) {
val density = LocalDensity.current
@@ -765,6 +767,7 @@
directionChangeSlop = directionChangeSlop,
defaultEffectFactory = defaultEffectFactory,
decayAnimationSpec = decayAnimationSpec,
+ implicitTestTags = implicitTestTags,
)
.also { onLayoutImpl?.invoke(it) }
}
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 53996d2..e3c4eb0 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
@@ -122,6 +122,9 @@
* This is used to enable transformations and shared elements across NestedSTLs.
*/
internal val ancestors: List<Ancestor> = emptyList(),
+
+ /** Whether elements and scene should be tagged using `Modifier.testTag`. */
+ internal val implicitTestTags: Boolean = false,
lookaheadScope: LookaheadScope? = null,
defaultEffectFactory: OverscrollFactory,
) {
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt
index 95d6440..64cfe38 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt
@@ -171,7 +171,7 @@
.thenIf(layoutImpl.state.isElevationPossible(content = key, element = null)) {
Modifier.container(containerState)
}
- .testTag(key.testTag)
+ .thenIf(layoutImpl.implicitTestTags) { Modifier.testTag(key.testTag) }
) {
CompositionLocalProvider(LocalOverscrollFactory provides lastFactory) {
scope.content()
@@ -290,6 +290,7 @@
sharedElementMap = layoutImpl.elements,
ancestors = ancestors,
lookaheadScope = layoutImpl.lookaheadScope,
+ implicitTestTags = layoutImpl.implicitTestTags,
)
}
}
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 698a808..8fce708 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
@@ -227,7 +227,7 @@
to = SceneB,
transitionLayout = { state ->
coroutineScope = rememberCoroutineScope()
- SceneTransitionLayout(state) {
+ SceneTransitionLayoutForTesting(state) {
scene(SceneA) {
Box(Modifier.size(layoutSize)) {
// Transformed element
@@ -633,7 +633,7 @@
val scope =
rule.setContentAndCreateMainScope {
- SceneTransitionLayout(state) {
+ SceneTransitionLayoutForTesting(state) {
scene(SceneA) { Box(Modifier.element(TestElements.Foo).size(20.dp)) }
scene(SceneB) {}
}
@@ -674,7 +674,7 @@
CompositionLocalProvider(
LocalOverscrollFactory provides rememberOffsetOverscrollEffectFactory()
) {
- SceneTransitionLayout(state, Modifier.size(layoutWidth, layoutHeight)) {
+ SceneTransitionLayoutForTesting(state, Modifier.size(layoutWidth, layoutHeight)) {
scene(key = SceneA, userActions = mapOf(Swipe.Down to SceneB)) {
Spacer(Modifier.fillMaxSize())
}
@@ -734,7 +734,7 @@
CompositionLocalProvider(
LocalOverscrollFactory provides rememberOffsetOverscrollEffectFactory()
) {
- SceneTransitionLayout(state, Modifier.size(layoutWidth, layoutHeight)) {
+ SceneTransitionLayoutForTesting(state, Modifier.size(layoutWidth, layoutHeight)) {
scene(key = SceneA, userActions = mapOf(Swipe.Down to SceneB)) {
Spacer(
Modifier.overscroll(verticalOverscrollEffect)
@@ -834,7 +834,7 @@
CompositionLocalProvider(
LocalOverscrollFactory provides rememberOffsetOverscrollEffectFactory()
) {
- SceneTransitionLayout(state, Modifier.size(layoutWidth, layoutHeight)) {
+ SceneTransitionLayoutForTesting(state, Modifier.size(layoutWidth, layoutHeight)) {
scene(key = SceneA, userActions = mapOf(Swipe.Down to SceneB)) {
Spacer(Modifier.fillMaxSize())
}
@@ -893,7 +893,7 @@
CompositionLocalProvider(
LocalOverscrollFactory provides rememberOffsetOverscrollEffectFactory()
) {
- SceneTransitionLayout(
+ SceneTransitionLayoutForTesting(
state = state,
modifier = Modifier.size(layoutWidth, layoutHeight),
) {
@@ -970,7 +970,7 @@
rule.setContent {
touchSlop = LocalViewConfiguration.current.touchSlop
- SceneTransitionLayout(
+ SceneTransitionLayoutForTesting(
state = state,
modifier = Modifier.size(layoutWidth, layoutHeight),
) {
@@ -1057,7 +1057,7 @@
rule.setContent {
coroutineScope = rememberCoroutineScope()
- SceneTransitionLayout(state) {
+ SceneTransitionLayoutForTesting(state) {
scene(SceneA) {
Box(Modifier.size(layoutSize)) {
Box(
@@ -1374,7 +1374,7 @@
val scope =
rule.setContentAndCreateMainScope {
- SceneTransitionLayout(state, Modifier.size(layoutSize)) {
+ SceneTransitionLayoutForTesting(state, Modifier.size(layoutSize)) {
scene(SceneA) {
Box(Modifier.fillMaxSize()) { Foo(Modifier.align(Alignment.TopStart)) }
}
@@ -1742,7 +1742,7 @@
val scope =
rule.setContentAndCreateMainScope {
- SceneTransitionLayout(state, Modifier.size(200.dp)) {
+ SceneTransitionLayoutForTesting(state, Modifier.size(200.dp)) {
scene(SceneA) { Foo(offset = 0.dp) }
scene(SceneB) { Foo(offset = 20.dp) }
scene(SceneC) { Foo(offset = 40.dp) }
@@ -1828,7 +1828,7 @@
val scope =
rule.setContentAndCreateMainScope {
- SceneTransitionLayout(state) {
+ SceneTransitionLayoutForTesting(state) {
scene(SceneB) { Foo(Modifier.offset(40.dp, 60.dp)) }
// Define A after B so that Foo is placed in A during A <=> B.
@@ -1887,7 +1887,7 @@
val scope =
rule.setContentAndCreateMainScope {
- SceneTransitionLayout(state) {
+ SceneTransitionLayoutForTesting(state) {
scene(SceneA) { Foo() }
scene(SceneB) { Foo(Modifier.offset(40.dp, 60.dp)) }
}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/OverlayTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/OverlayTest.kt
index 04c762f..98ecb64 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/OverlayTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/OverlayTest.kt
@@ -90,7 +90,7 @@
lateinit var coroutineScope: CoroutineScope
rule.setContent {
coroutineScope = rememberCoroutineScope()
- SceneTransitionLayout(state, Modifier.size(200.dp)) {
+ SceneTransitionLayoutForTesting(state, Modifier.size(200.dp)) {
scene(SceneA) { Box(Modifier.fillMaxSize()) { Foo() } }
overlay(OverlayA) { Foo() }
}
@@ -132,7 +132,7 @@
lateinit var coroutineScope: CoroutineScope
rule.setContent {
coroutineScope = rememberCoroutineScope()
- SceneTransitionLayout(state, Modifier.size(200.dp)) {
+ SceneTransitionLayoutForTesting(state, Modifier.size(200.dp)) {
scene(SceneA) { Box(Modifier.fillMaxSize()) { Foo() } }
overlay(OverlayA) { Foo() }
overlay(OverlayB) { Foo() }
@@ -230,7 +230,7 @@
lateinit var coroutineScope: CoroutineScope
rule.setContent {
coroutineScope = rememberCoroutineScope()
- SceneTransitionLayout(state, Modifier.size(200.dp)) {
+ SceneTransitionLayoutForTesting(state, Modifier.size(200.dp)) {
scene(SceneA) { Box(Modifier.fillMaxSize()) { MovableBar() } }
overlay(OverlayA) { MovableBar() }
overlay(OverlayB) { MovableBar() }
@@ -302,7 +302,7 @@
}
var alignment by mutableStateOf(Alignment.Center)
rule.setContent {
- SceneTransitionLayout(state, Modifier.size(200.dp)) {
+ SceneTransitionLayoutForTesting(state, Modifier.size(200.dp)) {
scene(SceneA) { Box(Modifier.fillMaxSize()) { Foo() } }
overlay(OverlayA, alignment = alignment) { Foo() }
}
@@ -761,7 +761,7 @@
val movableElementChildTag = "movableElementChildTag"
val scope =
rule.setContentAndCreateMainScope {
- SceneTransitionLayout(state) {
+ SceneTransitionLayoutForTesting(state) {
scene(SceneA) {
MovableElement(key, Modifier) {
content { Box(Modifier.testTag(movableElementChildTag).size(100.dp)) }
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/PredictiveBackHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/PredictiveBackHandlerTest.kt
index 2bf2358..366b11d 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/PredictiveBackHandlerTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/PredictiveBackHandlerTest.kt
@@ -250,7 +250,7 @@
}
rule.setContent {
- SceneTransitionLayout(state, Modifier.size(200.dp)) {
+ SceneTransitionLayoutForTesting(state, Modifier.size(200.dp)) {
scene(SceneA) { Box(Modifier.fillMaxSize()) }
overlay(OverlayA) { Box(Modifier.fillMaxSize()) }
overlay(OverlayB) { Box(Modifier.fillMaxSize()) }
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
index 3578be4..5cbc98f 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
@@ -97,7 +97,7 @@
MutableSceneTransitionLayoutStateForTests(SceneA, EmptyTestTransitions)
}
- SceneTransitionLayout(state = layoutState, modifier = Modifier.size(LayoutSize)) {
+ SceneTransitionLayoutForTesting(state = layoutState, modifier = Modifier.size(LayoutSize)) {
scene(SceneA, userActions = mapOf(Back to SceneB)) {
Box(Modifier.fillMaxSize()) {
SharedFoo(size = 50.dp, childOffset = 0.dp, Modifier.align(Alignment.TopEnd))
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
index 751b314..11abbbe 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
@@ -763,7 +763,7 @@
var touchSlop = 0f
rule.setContent {
touchSlop = LocalViewConfiguration.current.touchSlop
- SceneTransitionLayout(state, Modifier.size(layoutSize)) {
+ SceneTransitionLayoutForTesting(state, Modifier.size(layoutSize)) {
scene(SceneA, userActions = mapOf(Swipe.Start to SceneB, Swipe.End to SceneC)) {
Box(Modifier.fillMaxSize())
}
@@ -837,7 +837,7 @@
rule.setContent {
touchSlop = LocalViewConfiguration.current.touchSlop
CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) {
- SceneTransitionLayout(state, Modifier.size(layoutSize)) {
+ SceneTransitionLayoutForTesting(state, Modifier.size(layoutSize)) {
scene(SceneA, userActions = mapOf(Swipe.Start to SceneB, Swipe.End to SceneC)) {
Box(Modifier.fillMaxSize())
}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/NestedElementTransformationTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/NestedElementTransformationTest.kt
index bb511bc..8b56892 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/NestedElementTransformationTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/NestedElementTransformationTest.kt
@@ -40,7 +40,7 @@
import com.android.compose.animation.scene.MutableSceneTransitionLayoutStateForTests
import com.android.compose.animation.scene.Scale
import com.android.compose.animation.scene.SceneKey
-import com.android.compose.animation.scene.SceneTransitionLayout
+import com.android.compose.animation.scene.SceneTransitionLayoutForTesting
import com.android.compose.animation.scene.SceneTransitions
import com.android.compose.animation.scene.TestScenes
import com.android.compose.animation.scene.testNestedTransition
@@ -114,7 +114,7 @@
@Composable
(states: List<MutableSceneTransitionLayoutState>) -> Unit =
{ states ->
- SceneTransitionLayout(states[0]) {
+ SceneTransitionLayoutForTesting(states[0]) {
scene(TestScenes.SceneA, content = { TestElement(elementVariant0A) })
scene(
TestScenes.SceneB,
diff --git a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestContentScope.kt b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestContentScope.kt
index 6d47bab..e56d1be 100644
--- a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestContentScope.kt
+++ b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestContentScope.kt
@@ -30,5 +30,7 @@
content: @Composable ContentScope.() -> Unit,
) {
val state = rememberMutableSceneTransitionLayoutState(currentScene)
- SceneTransitionLayout(state, modifier) { scene(currentScene, content = content) }
+ SceneTransitionLayout(state, modifier, implicitTestTags = true) {
+ scene(currentScene, content = content)
+ }
}
diff --git a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt
index f94a7ed..a362a37 100644
--- a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt
+++ b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt
@@ -137,7 +137,7 @@
},
changeState = changeState,
transitionLayout = { state ->
- SceneTransitionLayout(state, layoutModifier) {
+ SceneTransitionLayout(state, layoutModifier, implicitTestTags = true) {
scene(fromScene, content = fromSceneContent)
scene(toScene, content = toSceneContent)
}
@@ -163,7 +163,7 @@
)
},
transitionLayout = { state ->
- SceneTransitionLayout(state) {
+ SceneTransitionLayout(state, implicitTestTags = true) {
scene(fromScene) { fromSceneContent() }
overlay(overlay) { overlayContent() }
}
@@ -191,7 +191,7 @@
)
},
transitionLayout = { state ->
- SceneTransitionLayout(state) {
+ SceneTransitionLayout(state, implicitTestTags = true) {
scene(toScene) { toSceneContent() }
overlay(overlay) { overlayContent() }
}
@@ -223,7 +223,7 @@
)
},
transitionLayout = { state ->
- SceneTransitionLayout(state) {
+ SceneTransitionLayout(state, implicitTestTags = true) {
scene(currentScene) { currentSceneContent() }
overlay(from, alignment = fromAlignment) { fromContent() }
overlay(to, alignment = toAlignment) { toContent() }
@@ -273,7 +273,7 @@
}
}
- SceneTransitionLayout(state, layoutModifier) {
+ SceneTransitionLayout(state, layoutModifier, implicitTestTags = true) {
scene(fromScene, content = fromSceneContent)
scene(toScene, content = toSceneContent)
}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
index 2e5b5b5..aad1276 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
@@ -113,8 +113,8 @@
companion object {
// 750ms @ 120hz -> 90 frames of animation
- // In practice, 45 looks good enough
- const val NUM_CLOCK_FONT_ANIMATION_STEPS = 45
+ // In practice, 30 looks good enough and limits our memory usage
+ const val NUM_CLOCK_FONT_ANIMATION_STEPS = 30
val FLEX_TYPEFACE by lazy {
// TODO(b/364680873): Move constant to config_clockFontFamily when shipping
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
index fe665e6..24b9e84 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
@@ -84,6 +84,7 @@
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.settings.GlobalSettings
import com.android.systemui.util.time.FakeSystemClock
+import com.android.systemui.window.domain.interactor.windowRootViewBlurInteractor
import com.google.common.truth.Truth
import junit.framework.Assert
import kotlinx.coroutines.flow.MutableStateFlow
@@ -280,9 +281,9 @@
kosmos.keyguardDismissTransitionInteractor,
{ primaryBouncerInteractor },
executor,
- ) {
- deviceEntryInteractor
- }
+ { deviceEntryInteractor },
+ { kosmos.windowRootViewBlurInteractor },
+ )
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
index 6f2082b..7051f81 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
@@ -24,53 +24,47 @@
import androidx.test.filters.SmallTest
import com.android.internal.logging.uiEventLoggerFake
import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
-import com.android.systemui.Flags.FLAG_COMMUNAL_SCENE_KTF_REFACTOR
+import com.android.systemui.Flags.FLAG_GLANCEABLE_HUB_V2
import com.android.systemui.Flags.FLAG_SCENE_CONTAINER
import com.android.systemui.SysuiTestCase
import com.android.systemui.communal.domain.interactor.communalInteractor
import com.android.systemui.communal.domain.interactor.communalSceneInteractor
import com.android.systemui.communal.domain.interactor.communalSettingsInteractor
import com.android.systemui.communal.domain.interactor.setCommunalAvailable
+import com.android.systemui.communal.domain.interactor.setCommunalV2ConfigEnabled
import com.android.systemui.communal.shared.log.CommunalUiEvent
import com.android.systemui.communal.shared.model.CommunalScenes
-import com.android.systemui.communal.shared.model.EditModeState
-import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.dock.dockManager
-import com.android.systemui.dock.fakeDockManager
import com.android.systemui.flags.Flags.COMMUNAL_SERVICE_ENABLED
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.domain.interactor.KeyguardInteractor
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.applicationCoroutineScope
+import com.android.systemui.kosmos.collectLastValue
+import com.android.systemui.kosmos.runTest
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.statusbar.notificationShadeWindowController
-import com.android.systemui.statusbar.phone.centralSurfaces
-import com.android.systemui.statusbar.phone.centralSurfacesOptional
import com.android.systemui.testKosmos
import com.android.systemui.util.settings.fakeSettings
import com.google.common.truth.Truth.assertThat
+import kotlin.time.Duration
import kotlin.time.Duration.Companion.milliseconds
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.advanceTimeBy
-import kotlinx.coroutines.test.runCurrent
-import kotlinx.coroutines.test.runTest
import org.junit.Before
-import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.kotlin.whenever
import platform.test.runner.parameterized.ParameterizedAndroidJunit4
import platform.test.runner.parameterized.Parameters
+@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(ParameterizedAndroidJunit4::class)
@EnableFlags(FLAG_COMMUNAL_HUB)
@@ -82,7 +76,8 @@
@JvmStatic
@Parameters(name = "{0}")
fun getParams(): List<FlagsParameterization> {
- return FlagsParameterization.allCombinationsOf().andSceneContainer()
+ return FlagsParameterization.allCombinationsOf(FLAG_GLANCEABLE_HUB_V2)
+ .andSceneContainer()
}
}
@@ -90,9 +85,22 @@
mSetFlagsRule.setFlagsParameterization(flags)
}
- private val kosmos = testKosmos()
+ private val kosmos = testKosmos().useUnconfinedTestDispatcher()
- private lateinit var underTest: CommunalSceneStartable
+ private val Kosmos.underTest by
+ Kosmos.Fixture {
+ CommunalSceneStartable(
+ communalInteractor = communalInteractor,
+ communalSettingsInteractor = communalSettingsInteractor,
+ communalSceneInteractor = communalSceneInteractor,
+ keyguardInteractor = keyguardInteractor,
+ systemSettings = fakeSettings,
+ notificationShadeWindowController = notificationShadeWindowController,
+ bgScope = applicationCoroutineScope,
+ mainDispatcher = testDispatcher,
+ uiEventLogger = uiEventLoggerFake,
+ )
+ }
@Before
fun setUp() {
@@ -102,646 +110,314 @@
SCREEN_TIMEOUT,
UserHandle.USER_CURRENT,
)
- kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, true)
+ fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, true)
- underTest =
- CommunalSceneStartable(
- dockManager = dockManager,
- communalInteractor = communalInteractor,
- communalSettingsInteractor = communalSettingsInteractor,
- communalSceneInteractor = communalSceneInteractor,
- keyguardTransitionInteractor = keyguardTransitionInteractor,
- keyguardInteractor = keyguardInteractor,
- systemSettings = fakeSettings,
- notificationShadeWindowController = notificationShadeWindowController,
- applicationScope = applicationCoroutineScope,
- bgScope = applicationCoroutineScope,
- mainDispatcher = testDispatcher,
- centralSurfacesOpt = centralSurfacesOptional,
- uiEventLogger = uiEventLoggerFake,
- )
- .apply { start() }
+ underTest.start()
// Make communal available so that communalInteractor.desiredScene accurately reflects
// scene changes instead of just returning Blank.
- with(kosmos.testScope) {
- launch { setCommunalAvailable(true) }
- testScheduler.runCurrent()
- }
+ runBlocking { setCommunalAvailable(true) }
+ setCommunalV2ConfigEnabled(true)
}
}
@Test
- @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR, FLAG_SCENE_CONTAINER)
- fun keyguardGoesAway_whenLaunchingEditMode_doNotForceBlankScene() =
- with(kosmos) {
- testScope.runTest {
- val scene by collectLastValue(communalSceneInteractor.currentScene)
-
- communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
- assertThat(scene).isEqualTo(CommunalScenes.Communal)
-
- communalSceneInteractor.setEditModeState(EditModeState.STARTING)
- fakeKeyguardTransitionRepository.sendTransitionSteps(
- from = KeyguardState.PRIMARY_BOUNCER,
- to = KeyguardState.GONE,
- testScope = this,
- )
-
- assertThat(scene).isEqualTo(CommunalScenes.Communal)
- }
- }
-
- @Test
- @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR, FLAG_SCENE_CONTAINER)
- fun keyguardGoesAway_whenLaunchingWidget_doNotForceBlankScene() =
- with(kosmos) {
- testScope.runTest {
- val scene by collectLastValue(communalSceneInteractor.currentScene)
-
- communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
- assertThat(scene).isEqualTo(CommunalScenes.Communal)
-
- communalSceneInteractor.setIsLaunchingWidget(true)
- fakeKeyguardTransitionRepository.sendTransitionSteps(
- from = KeyguardState.PRIMARY_BOUNCER,
- to = KeyguardState.GONE,
- testScope = this,
- )
-
- assertThat(scene).isEqualTo(CommunalScenes.Communal)
- }
- }
-
- @Test
- @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR, FLAG_SCENE_CONTAINER)
- fun keyguardGoesAway_whenNotLaunchingWidget_forceBlankScene() =
- with(kosmos) {
- testScope.runTest {
- val scene by collectLastValue(communalSceneInteractor.currentScene)
-
- communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
- assertThat(scene).isEqualTo(CommunalScenes.Communal)
-
- communalSceneInteractor.setIsLaunchingWidget(false)
- fakeKeyguardTransitionRepository.sendTransitionSteps(
- from = KeyguardState.PRIMARY_BOUNCER,
- to = KeyguardState.GONE,
- testScope = this,
- )
-
- assertThat(scene).isEqualTo(CommunalScenes.Blank)
- }
- }
-
- @Test
- @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR, FLAG_SCENE_CONTAINER)
- fun keyguardGoesAway_whenInEditMode_doesNotChangeScene() =
- with(kosmos) {
- testScope.runTest {
- val scene by collectLastValue(communalSceneInteractor.currentScene)
- communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
- assertThat(scene).isEqualTo(CommunalScenes.Communal)
-
- communalInteractor.setEditModeOpen(true)
- fakeKeyguardTransitionRepository.sendTransitionSteps(
- from = KeyguardState.ALTERNATE_BOUNCER,
- to = KeyguardState.GONE,
- testScope = this,
- )
- // Scene change will be handled in EditWidgetsActivity not here
- assertThat(scene).isEqualTo(CommunalScenes.Communal)
- }
- }
-
- @Ignore("Ignored until custom animations are implemented in b/322787129")
- @Test
- @DisableFlags(FLAG_SCENE_CONTAINER)
- fun deviceDocked_forceCommunalScene() =
- with(kosmos) {
- testScope.runTest {
- val scene by collectLastValue(communalSceneInteractor.currentScene)
- assertThat(scene).isEqualTo(CommunalScenes.Blank)
-
- updateDocked(true)
- fakeKeyguardTransitionRepository.sendTransitionSteps(
- from = KeyguardState.GONE,
- to = KeyguardState.LOCKSCREEN,
- testScope = this,
- )
- assertThat(scene).isEqualTo(CommunalScenes.Communal)
- }
- }
-
- @Test
- @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR, FLAG_SCENE_CONTAINER)
- fun occluded_forceBlankScene() =
- with(kosmos) {
- testScope.runTest {
- whenever(centralSurfaces.isLaunchingActivityOverLockscreen).thenReturn(false)
- val scene by collectLastValue(communalSceneInteractor.currentScene)
- communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
- assertThat(scene).isEqualTo(CommunalScenes.Communal)
-
- updateDocked(true)
- fakeKeyguardTransitionRepository.sendTransitionSteps(
- from = KeyguardState.GLANCEABLE_HUB,
- to = KeyguardState.OCCLUDED,
- testScope = this,
- )
- assertThat(scene).isEqualTo(CommunalScenes.Blank)
- }
- }
-
- @Test
- @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR, FLAG_SCENE_CONTAINER)
- fun occluded_doesNotForceBlankSceneIfLaunchingActivityOverLockscreen() =
- with(kosmos) {
- testScope.runTest {
- whenever(centralSurfaces.isLaunchingActivityOverLockscreen).thenReturn(true)
- val scene by collectLastValue(communalSceneInteractor.currentScene)
- communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
- assertThat(scene).isEqualTo(CommunalScenes.Communal)
-
- updateDocked(true)
- fakeKeyguardTransitionRepository.sendTransitionSteps(
- from = KeyguardState.GLANCEABLE_HUB,
- to = KeyguardState.OCCLUDED,
- testScope = this,
- )
- assertThat(scene).isEqualTo(CommunalScenes.Communal)
- }
- }
-
- @Test
- @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR, FLAG_SCENE_CONTAINER)
- fun deviceDocked_doesNotForceCommunalIfTransitioningFromCommunal() =
- with(kosmos) {
- testScope.runTest {
- val scene by collectLastValue(communalSceneInteractor.currentScene)
- assertThat(scene).isEqualTo(CommunalScenes.Blank)
-
- updateDocked(true)
- fakeKeyguardTransitionRepository.sendTransitionSteps(
- from = KeyguardState.GLANCEABLE_HUB,
- to = KeyguardState.LOCKSCREEN,
- testScope = this,
- )
- assertThat(scene).isEqualTo(CommunalScenes.Blank)
- }
- }
-
- @Test
- @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR, FLAG_SCENE_CONTAINER)
- fun deviceAsleep_forceBlankSceneAfterTimeout() =
- with(kosmos) {
- testScope.runTest {
- val scene by collectLastValue(communalSceneInteractor.currentScene)
- communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
- assertThat(scene).isEqualTo(CommunalScenes.Communal)
-
- fakeKeyguardTransitionRepository.sendTransitionSteps(
- from = KeyguardState.GLANCEABLE_HUB,
- to = KeyguardState.OFF,
- testScope = this,
- )
- assertThat(scene).isEqualTo(CommunalScenes.Communal)
-
- advanceTimeBy(CommunalSceneStartable.AWAKE_DEBOUNCE_DELAY)
-
- assertThat(scene).isEqualTo(CommunalScenes.Blank)
- }
- }
-
- @Test
- @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR, FLAG_SCENE_CONTAINER)
- fun deviceAsleep_wakesUpBeforeTimeout_noChangeInScene() =
- with(kosmos) {
- testScope.runTest {
- val scene by collectLastValue(communalSceneInteractor.currentScene)
- communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
- assertThat(scene).isEqualTo(CommunalScenes.Communal)
-
- fakeKeyguardTransitionRepository.sendTransitionSteps(
- from = KeyguardState.GLANCEABLE_HUB,
- to = KeyguardState.OFF,
- testScope = this,
- )
- assertThat(scene).isEqualTo(CommunalScenes.Communal)
- advanceTimeBy(CommunalSceneStartable.AWAKE_DEBOUNCE_DELAY / 2)
- assertThat(scene).isEqualTo(CommunalScenes.Communal)
-
- fakeKeyguardTransitionRepository.sendTransitionSteps(
- from = KeyguardState.OFF,
- to = KeyguardState.GLANCEABLE_HUB,
- testScope = this,
- )
-
- advanceTimeBy(CommunalSceneStartable.AWAKE_DEBOUNCE_DELAY)
- assertThat(scene).isEqualTo(CommunalScenes.Communal)
- }
- }
-
- @Ignore("Ignored until custom animations are implemented in b/322787129")
- @Test
- @DisableFlags(FLAG_SCENE_CONTAINER)
- fun dockingOnLockscreen_forcesCommunal() =
- with(kosmos) {
- testScope.runTest {
- communalSceneInteractor.changeScene(CommunalScenes.Blank, "test")
- val scene by collectLastValue(communalSceneInteractor.currentScene)
-
- // device is docked while on the lockscreen
- fakeKeyguardTransitionRepository.sendTransitionSteps(
- from = KeyguardState.GLANCEABLE_HUB,
- to = KeyguardState.LOCKSCREEN,
- testScope = this,
- )
- updateDocked(true)
-
- assertThat(scene).isEqualTo(CommunalScenes.Blank)
- advanceTimeBy(CommunalSceneStartable.DOCK_DEBOUNCE_DELAY)
- assertThat(scene).isEqualTo(CommunalScenes.Communal)
- }
- }
-
- @Ignore("Ignored until custom animations are implemented in b/322787129")
- @Test
- @DisableFlags(FLAG_SCENE_CONTAINER)
- fun dockingOnLockscreen_doesNotForceCommunalIfDreamStarts() =
- with(kosmos) {
- testScope.runTest {
- communalSceneInteractor.changeScene(CommunalScenes.Blank, "test")
- val scene by collectLastValue(communalSceneInteractor.currentScene)
-
- // device is docked while on the lockscreen
- fakeKeyguardTransitionRepository.sendTransitionSteps(
- from = KeyguardState.GLANCEABLE_HUB,
- to = KeyguardState.LOCKSCREEN,
- testScope = this,
- )
- updateDocked(true)
-
- assertThat(scene).isEqualTo(CommunalScenes.Blank)
- advanceTimeBy(CommunalSceneStartable.DOCK_DEBOUNCE_DELAY / 2)
- assertThat(scene).isEqualTo(CommunalScenes.Blank)
-
- // dream starts shortly after docking
- fakeKeyguardTransitionRepository.sendTransitionSteps(
- from = KeyguardState.LOCKSCREEN,
- to = KeyguardState.DREAMING,
- testScope = this,
- )
- advanceTimeBy(CommunalSceneStartable.DOCK_DEBOUNCE_DELAY)
- assertThat(scene).isEqualTo(CommunalScenes.Blank)
- }
- }
-
- @Test
- @DisableFlags(FLAG_SCENE_CONTAINER)
+ @DisableFlags(FLAG_SCENE_CONTAINER, FLAG_GLANCEABLE_HUB_V2)
fun hubTimeout_whenDreaming_goesToBlank() =
- with(kosmos) {
- testScope.runTest {
- // Device is dreaming and on communal.
- updateDreaming(true)
- communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
+ kosmos.runTest {
+ // Device is dreaming and on communal.
+ fakeKeyguardRepository.setDreaming(true)
+ communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
- val scene by collectLastValue(communalSceneInteractor.currentScene)
- assertThat(scene).isEqualTo(CommunalScenes.Communal)
+ val scene by collectLastValue(communalSceneInteractor.currentScene)
+ assertThat(scene).isEqualTo(CommunalScenes.Communal)
- // Scene times out back to blank after the screen timeout.
- advanceTimeBy(SCREEN_TIMEOUT.milliseconds)
- assertThat(scene).isEqualTo(CommunalScenes.Blank)
- }
+ // Scene times out back to blank after the screen timeout.
+ advanceTimeBy(SCREEN_TIMEOUT.milliseconds)
+ assertThat(scene).isEqualTo(CommunalScenes.Blank)
}
@Test
- @DisableFlags(FLAG_SCENE_CONTAINER)
+ @DisableFlags(FLAG_SCENE_CONTAINER, FLAG_GLANCEABLE_HUB_V2)
fun hubTimeout_notDreaming_staysOnCommunal() =
- with(kosmos) {
- testScope.runTest {
- // Device is not dreaming and on communal.
- updateDreaming(false)
- communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
+ kosmos.runTest {
+ // Device is not dreaming and on communal.
+ fakeKeyguardRepository.setDreaming(false)
+ communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
- // Scene stays as Communal
- advanceTimeBy(SCREEN_TIMEOUT.milliseconds)
- val scene by collectLastValue(communalSceneInteractor.currentScene)
- assertThat(scene).isEqualTo(CommunalScenes.Communal)
- }
+ // Scene stays as Communal
+ advanceTimeBy(SCREEN_TIMEOUT.milliseconds)
+ val scene by collectLastValue(communalSceneInteractor.currentScene)
+ assertThat(scene).isEqualTo(CommunalScenes.Communal)
}
@Test
- @DisableFlags(FLAG_SCENE_CONTAINER)
+ @DisableFlags(FLAG_SCENE_CONTAINER, FLAG_GLANCEABLE_HUB_V2)
fun hubTimeout_dreamStopped_staysOnCommunal() =
- with(kosmos) {
- testScope.runTest {
- // Device is dreaming and on communal.
- updateDreaming(true)
- communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
+ kosmos.runTest {
+ // Device is dreaming and on communal.
+ fakeKeyguardRepository.setDreaming(true)
+ communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
- val scene by collectLastValue(communalSceneInteractor.currentScene)
- assertThat(scene).isEqualTo(CommunalScenes.Communal)
+ val scene by collectLastValue(communalSceneInteractor.currentScene)
+ assertThat(scene).isEqualTo(CommunalScenes.Communal)
- // Wait a bit, but not long enough to timeout.
- advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds)
- assertThat(scene).isEqualTo(CommunalScenes.Communal)
+ // Wait a bit, but not long enough to timeout.
+ advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds)
+ assertThat(scene).isEqualTo(CommunalScenes.Communal)
- // Dream stops, timeout is cancelled and device stays on hub, because the regular
- // screen timeout will take effect at this point.
- updateDreaming(false)
- advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds)
- assertThat(scene).isEqualTo(CommunalScenes.Communal)
- }
+ // Dream stops, timeout is cancelled and device stays on hub, because the regular
+ // screen timeout will take effect at this point.
+ fakeKeyguardRepository.setDreaming(false)
+ advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds)
+ assertThat(scene).isEqualTo(CommunalScenes.Communal)
}
@Test
- @DisableFlags(FLAG_SCENE_CONTAINER)
+ @DisableFlags(FLAG_SCENE_CONTAINER, FLAG_GLANCEABLE_HUB_V2)
fun hubTimeout_dreamStartedHalfway_goesToCommunal() =
- with(kosmos) {
- testScope.runTest {
- // Device is on communal, but not dreaming.
- updateDreaming(false)
- communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
+ kosmos.runTest {
+ // Device is on communal, but not dreaming.
+ fakeKeyguardRepository.setDreaming(false)
+ communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
- val scene by collectLastValue(communalSceneInteractor.currentScene)
- assertThat(scene).isEqualTo(CommunalScenes.Communal)
+ val scene by collectLastValue(communalSceneInteractor.currentScene)
+ assertThat(scene).isEqualTo(CommunalScenes.Communal)
- // Wait a bit, but not long enough to timeout, then start dreaming.
- advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds)
- updateDreaming(true)
- assertThat(scene).isEqualTo(CommunalScenes.Communal)
+ // Wait a bit, but not long enough to timeout, then start dreaming.
+ advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds)
+ fakeKeyguardRepository.setDreaming(true)
+ assertThat(scene).isEqualTo(CommunalScenes.Communal)
- // Device times out after one screen timeout interval, dream doesn't reset timeout.
- advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds)
- assertThat(scene).isEqualTo(CommunalScenes.Blank)
- }
+ // Device times out after one screen timeout interval, dream doesn't reset timeout.
+ advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds)
+ assertThat(scene).isEqualTo(CommunalScenes.Blank)
}
@Test
- @DisableFlags(FLAG_SCENE_CONTAINER)
+ @DisableFlags(FLAG_SCENE_CONTAINER, FLAG_GLANCEABLE_HUB_V2)
fun hubTimeout_dreamAfterInitialTimeout_goesToBlank() =
- with(kosmos) {
- testScope.runTest {
- // Device is on communal.
- communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
+ kosmos.runTest {
+ // Device is on communal.
+ communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
- // Device stays on the hub after the timeout since we're not dreaming.
- advanceTimeBy(SCREEN_TIMEOUT.milliseconds * 2)
- val scene by collectLastValue(communalSceneInteractor.currentScene)
- assertThat(scene).isEqualTo(CommunalScenes.Communal)
+ // Device stays on the hub after the timeout since we're not dreaming.
+ testScope.advanceTimeBy(SCREEN_TIMEOUT.milliseconds * 2)
+ val scene by collectLastValue(communalSceneInteractor.currentScene)
+ assertThat(scene).isEqualTo(CommunalScenes.Communal)
- // Start dreaming.
- updateDreaming(true)
- advanceTimeBy(KeyguardInteractor.IS_ABLE_TO_DREAM_DELAY_MS)
+ // Start dreaming.
+ fakeKeyguardRepository.setDreaming(true)
+ advanceTimeBy(KeyguardInteractor.IS_ABLE_TO_DREAM_DELAY_MS.milliseconds)
- // Hub times out immediately.
- assertThat(scene).isEqualTo(CommunalScenes.Blank)
- }
+ // Hub times out immediately.
+ assertThat(scene).isEqualTo(CommunalScenes.Blank)
}
@Test
- @DisableFlags(FLAG_SCENE_CONTAINER)
+ @DisableFlags(FLAG_SCENE_CONTAINER, FLAG_GLANCEABLE_HUB_V2)
fun hubTimeout_userActivityTriggered_resetsTimeout() =
- with(kosmos) {
- testScope.runTest {
- // Device is dreaming and on communal.
- updateDreaming(true)
- communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
+ kosmos.runTest {
+ // Device is dreaming and on communal.
+ fakeKeyguardRepository.setDreaming(true)
+ communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
- val scene by collectLastValue(communalSceneInteractor.currentScene)
- assertThat(scene).isEqualTo(CommunalScenes.Communal)
+ val scene by collectLastValue(communalSceneInteractor.currentScene)
+ assertThat(scene).isEqualTo(CommunalScenes.Communal)
- // Wait a bit, but not long enough to timeout.
- advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds)
+ // Wait a bit, but not long enough to timeout.
+ advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds)
- // Send user interaction to reset timeout.
- communalInteractor.signalUserInteraction()
+ // Send user interaction to reset timeout.
+ communalInteractor.signalUserInteraction()
- // If user activity didn't reset timeout, we would have gone back to Blank by now.
- advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds)
- assertThat(scene).isEqualTo(CommunalScenes.Communal)
+ // If user activity didn't reset timeout, we would have gone back to Blank by now.
+ advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds)
+ assertThat(scene).isEqualTo(CommunalScenes.Communal)
- // Timeout happens one interval after the user interaction.
- advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds)
- assertThat(scene).isEqualTo(CommunalScenes.Blank)
- }
+ // Timeout happens one interval after the user interaction.
+ advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds)
+ assertThat(scene).isEqualTo(CommunalScenes.Blank)
}
@Test
- @DisableFlags(FLAG_SCENE_CONTAINER)
+ @DisableFlags(FLAG_SCENE_CONTAINER, FLAG_GLANCEABLE_HUB_V2)
fun hubTimeout_screenTimeoutChanged() =
- with(kosmos) {
- testScope.runTest {
- fakeSettings.putInt(Settings.System.SCREEN_OFF_TIMEOUT, SCREEN_TIMEOUT * 2)
+ kosmos.runTest {
+ fakeSettings.putInt(Settings.System.SCREEN_OFF_TIMEOUT, SCREEN_TIMEOUT * 2)
- // Device is dreaming and on communal.
- updateDreaming(true)
- communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
+ // Device is dreaming and on communal.
+ fakeKeyguardRepository.setDreaming(true)
+ communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
- val scene by collectLastValue(communalSceneInteractor.currentScene)
- assertThat(scene).isEqualTo(CommunalScenes.Communal)
+ val scene by collectLastValue(communalSceneInteractor.currentScene)
+ assertThat(scene).isEqualTo(CommunalScenes.Communal)
- // Scene times out back to blank after the screen timeout.
- advanceTimeBy(SCREEN_TIMEOUT.milliseconds)
- assertThat(scene).isEqualTo(CommunalScenes.Communal)
+ // Scene times out back to blank after the screen timeout.
+ advanceTimeBy(SCREEN_TIMEOUT.milliseconds)
+ assertThat(scene).isEqualTo(CommunalScenes.Communal)
- advanceTimeBy(SCREEN_TIMEOUT.milliseconds)
- assertThat(scene).isEqualTo(CommunalScenes.Blank)
- assertThat(uiEventLoggerFake.logs.first().eventId)
- .isEqualTo(CommunalUiEvent.COMMUNAL_HUB_TIMEOUT.id)
- assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1)
- }
+ advanceTimeBy(SCREEN_TIMEOUT.milliseconds)
+ assertThat(scene).isEqualTo(CommunalScenes.Blank)
+ assertThat(uiEventLoggerFake.logs.first().eventId)
+ .isEqualTo(CommunalUiEvent.COMMUNAL_HUB_TIMEOUT.id)
+ assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1)
}
@Test
@EnableFlags(FLAG_SCENE_CONTAINER)
+ @DisableFlags(FLAG_GLANCEABLE_HUB_V2)
fun hubTimeout_withSceneContainer_whenDreaming_goesToBlank() =
- with(kosmos) {
- testScope.runTest {
- // Device is dreaming and on communal.
- updateDreaming(true)
- sceneInteractor.changeScene(Scenes.Communal, "test")
+ kosmos.runTest {
+ // Device is dreaming and on communal.
+ fakeKeyguardRepository.setDreaming(true)
+ sceneInteractor.changeScene(Scenes.Communal, "test")
- val scene by collectLastValue(sceneInteractor.currentScene)
- assertThat(scene).isEqualTo(Scenes.Communal)
+ val scene by collectLastValue(sceneInteractor.currentScene)
+ assertThat(scene).isEqualTo(Scenes.Communal)
- // Scene times out back to blank after the screen timeout.
- advanceTimeBy(SCREEN_TIMEOUT.milliseconds)
- assertThat(scene).isEqualTo(Scenes.Dream)
- }
+ // Scene times out back to blank after the screen timeout.
+ advanceTimeBy(SCREEN_TIMEOUT.milliseconds)
+ assertThat(scene).isEqualTo(Scenes.Dream)
}
@Test
@EnableFlags(FLAG_SCENE_CONTAINER)
+ @DisableFlags(FLAG_GLANCEABLE_HUB_V2)
fun hubTimeout_withSceneContainer_notDreaming_staysOnCommunal() =
- with(kosmos) {
- testScope.runTest {
- // Device is not dreaming and on communal.
- updateDreaming(false)
- sceneInteractor.changeScene(Scenes.Communal, "test")
+ kosmos.runTest {
+ // Device is not dreaming and on communal.
+ fakeKeyguardRepository.setDreaming(false)
+ sceneInteractor.changeScene(Scenes.Communal, "test")
- // Scene stays as Communal
- advanceTimeBy(SCREEN_TIMEOUT.milliseconds)
- val scene by collectLastValue(sceneInteractor.currentScene)
- assertThat(scene).isEqualTo(Scenes.Communal)
- }
+ // Scene stays as Communal
+ advanceTimeBy(SCREEN_TIMEOUT.milliseconds)
+ val scene by collectLastValue(sceneInteractor.currentScene)
+ assertThat(scene).isEqualTo(Scenes.Communal)
}
@Test
@EnableFlags(FLAG_SCENE_CONTAINER)
+ @DisableFlags(FLAG_GLANCEABLE_HUB_V2)
fun hubTimeout_withSceneContainer_dreamStopped_staysOnCommunal() =
- with(kosmos) {
- testScope.runTest {
- // Device is dreaming and on communal.
- updateDreaming(true)
- sceneInteractor.changeScene(Scenes.Communal, "test")
+ kosmos.runTest {
+ // Device is dreaming and on communal.
+ fakeKeyguardRepository.setDreaming(true)
+ sceneInteractor.changeScene(Scenes.Communal, "test")
- val scene by collectLastValue(sceneInteractor.currentScene)
- assertThat(scene).isEqualTo(Scenes.Communal)
+ val scene by collectLastValue(sceneInteractor.currentScene)
+ assertThat(scene).isEqualTo(Scenes.Communal)
- // Wait a bit, but not long enough to timeout.
- advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds)
- assertThat(scene).isEqualTo(Scenes.Communal)
+ // Wait a bit, but not long enough to timeout.
+ advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds)
+ assertThat(scene).isEqualTo(Scenes.Communal)
- // Dream stops, timeout is cancelled and device stays on hub, because the regular
- // screen timeout will take effect at this point.
- updateDreaming(false)
- advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds)
- assertThat(scene).isEqualTo(Scenes.Communal)
- }
+ // Dream stops, timeout is cancelled and device stays on hub, because the regular
+ // screen timeout will take effect at this point.
+ fakeKeyguardRepository.setDreaming(false)
+ advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds)
+ assertThat(scene).isEqualTo(Scenes.Communal)
}
@Test
@EnableFlags(FLAG_SCENE_CONTAINER)
+ @DisableFlags(FLAG_GLANCEABLE_HUB_V2)
fun hubTimeout_withSceneContainer_dreamStartedHalfway_goesToCommunal() =
- with(kosmos) {
- testScope.runTest {
- // Device is on communal, but not dreaming.
- updateDreaming(false)
- sceneInteractor.changeScene(Scenes.Communal, "test")
+ kosmos.runTest {
+ // Device is on communal, but not dreaming.
+ fakeKeyguardRepository.setDreaming(false)
+ sceneInteractor.changeScene(Scenes.Communal, "test")
- val scene by collectLastValue(sceneInteractor.currentScene)
- assertThat(scene).isEqualTo(Scenes.Communal)
+ val scene by collectLastValue(sceneInteractor.currentScene)
+ assertThat(scene).isEqualTo(Scenes.Communal)
- // Wait a bit, but not long enough to timeout, then start dreaming.
- advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds)
- updateDreaming(true)
- assertThat(scene).isEqualTo(Scenes.Communal)
+ // Wait a bit, but not long enough to timeout, then start dreaming.
+ advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds)
+ fakeKeyguardRepository.setDreaming(true)
+ assertThat(scene).isEqualTo(Scenes.Communal)
- // Device times out after one screen timeout interval, dream doesn't reset timeout.
- advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds)
- assertThat(scene).isEqualTo(Scenes.Dream)
- }
+ // Device times out after one screen timeout interval, dream doesn't reset timeout.
+ advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds)
+ assertThat(scene).isEqualTo(Scenes.Dream)
}
@Test
@EnableFlags(FLAG_SCENE_CONTAINER)
+ @DisableFlags(FLAG_GLANCEABLE_HUB_V2)
fun hubTimeout_withSceneContainer_dreamAfterInitialTimeout_goesToBlank() =
- with(kosmos) {
- testScope.runTest {
- // Device is on communal.
- sceneInteractor.changeScene(Scenes.Communal, "test")
+ kosmos.runTest {
+ // Device is on communal.
+ sceneInteractor.changeScene(Scenes.Communal, "test")
- // Device stays on the hub after the timeout since we're not dreaming.
- advanceTimeBy(SCREEN_TIMEOUT.milliseconds * 2)
- val scene by collectLastValue(sceneInteractor.currentScene)
- assertThat(scene).isEqualTo(Scenes.Communal)
+ // Device stays on the hub after the timeout since we're not dreaming.
+ advanceTimeBy(SCREEN_TIMEOUT.milliseconds * 2)
+ val scene by collectLastValue(sceneInteractor.currentScene)
+ assertThat(scene).isEqualTo(Scenes.Communal)
- // Start dreaming.
- updateDreaming(true)
- advanceTimeBy(KeyguardInteractor.IS_ABLE_TO_DREAM_DELAY_MS)
+ // Start dreaming.
+ fakeKeyguardRepository.setDreaming(true)
+ advanceTimeBy(KeyguardInteractor.IS_ABLE_TO_DREAM_DELAY_MS.milliseconds)
- // Hub times out immediately.
- assertThat(scene).isEqualTo(Scenes.Dream)
- }
+ // Hub times out immediately.
+ assertThat(scene).isEqualTo(Scenes.Dream)
}
@Test
@EnableFlags(FLAG_SCENE_CONTAINER)
+ @DisableFlags(FLAG_GLANCEABLE_HUB_V2)
fun hubTimeout_withSceneContainer_userActivityTriggered_resetsTimeout() =
- with(kosmos) {
- testScope.runTest {
- // Device is dreaming and on communal.
- updateDreaming(true)
- sceneInteractor.changeScene(Scenes.Communal, "test")
+ kosmos.runTest {
+ // Device is dreaming and on communal.
+ fakeKeyguardRepository.setDreaming(true)
+ sceneInteractor.changeScene(Scenes.Communal, "test")
- val scene by collectLastValue(sceneInteractor.currentScene)
- assertThat(scene).isEqualTo(Scenes.Communal)
+ val scene by collectLastValue(sceneInteractor.currentScene)
+ assertThat(scene).isEqualTo(Scenes.Communal)
- // Wait a bit, but not long enough to timeout.
- advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds)
+ // Wait a bit, but not long enough to timeout.
+ advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds)
- // Send user interaction to reset timeout.
- communalInteractor.signalUserInteraction()
+ // Send user interaction to reset timeout.
+ communalInteractor.signalUserInteraction()
- // If user activity didn't reset timeout, we would have gone back to Blank by now.
- advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds)
- assertThat(scene).isEqualTo(Scenes.Communal)
+ // If user activity didn't reset timeout, we would have gone back to Blank by now.
+ advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds)
+ assertThat(scene).isEqualTo(Scenes.Communal)
- // Timeout happens one interval after the user interaction.
- advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds)
- assertThat(scene).isEqualTo(Scenes.Dream)
- }
+ // Timeout happens one interval after the user interaction.
+ advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds)
+ assertThat(scene).isEqualTo(Scenes.Dream)
}
@Test
@EnableFlags(FLAG_SCENE_CONTAINER)
+ @DisableFlags(FLAG_GLANCEABLE_HUB_V2)
fun hubTimeout_withSceneContainer_screenTimeoutChanged() =
- with(kosmos) {
- testScope.runTest {
- fakeSettings.putInt(Settings.System.SCREEN_OFF_TIMEOUT, SCREEN_TIMEOUT * 2)
+ kosmos.runTest {
+ fakeSettings.putInt(Settings.System.SCREEN_OFF_TIMEOUT, SCREEN_TIMEOUT * 2)
- // Device is dreaming and on communal.
- updateDreaming(true)
- sceneInteractor.changeScene(Scenes.Communal, "test")
+ // Device is dreaming and on communal.
+ fakeKeyguardRepository.setDreaming(true)
+ sceneInteractor.changeScene(Scenes.Communal, "test")
- val scene by collectLastValue(sceneInteractor.currentScene)
- assertThat(scene).isEqualTo(Scenes.Communal)
+ val scene by collectLastValue(sceneInteractor.currentScene)
+ assertThat(scene).isEqualTo(Scenes.Communal)
- // Scene times out back to blank after the screen timeout.
- advanceTimeBy(SCREEN_TIMEOUT.milliseconds)
- assertThat(scene).isEqualTo(Scenes.Communal)
+ // Scene times out back to blank after the screen timeout.
+ advanceTimeBy(SCREEN_TIMEOUT.milliseconds)
+ assertThat(scene).isEqualTo(Scenes.Communal)
- advanceTimeBy(SCREEN_TIMEOUT.milliseconds)
- assertThat(scene).isEqualTo(Scenes.Dream)
- assertThat(uiEventLoggerFake.logs.first().eventId)
- .isEqualTo(CommunalUiEvent.COMMUNAL_HUB_TIMEOUT.id)
- assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1)
- }
+ advanceTimeBy(SCREEN_TIMEOUT.milliseconds)
+ assertThat(scene).isEqualTo(Scenes.Dream)
+ assertThat(uiEventLoggerFake.logs.first().eventId)
+ .isEqualTo(CommunalUiEvent.COMMUNAL_HUB_TIMEOUT.id)
+ assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1)
}
- @Test
- @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR, FLAG_SCENE_CONTAINER)
- fun transitionFromDozingToGlanceableHub_forcesCommunal() =
- with(kosmos) {
- testScope.runTest {
- val scene by collectLastValue(communalSceneInteractor.currentScene)
- communalSceneInteractor.changeScene(CommunalScenes.Blank, "test")
- assertThat(scene).isEqualTo(CommunalScenes.Blank)
-
- fakeKeyguardTransitionRepository.sendTransitionSteps(
- from = KeyguardState.DOZING,
- to = KeyguardState.GLANCEABLE_HUB,
- testScope = this,
- )
-
- assertThat(scene).isEqualTo(CommunalScenes.Communal)
- }
- }
-
- private fun TestScope.updateDocked(docked: Boolean) =
- with(kosmos) {
- runCurrent()
- fakeDockManager.setIsDocked(docked)
- // TODO(b/322787129): uncomment once custom animations are in place
- // fakeDockManager.setDockEvent(DockManager.STATE_DOCKED)
- runCurrent()
- }
-
- private fun TestScope.updateDreaming(dreaming: Boolean) =
- with(kosmos) {
- fakeKeyguardRepository.setDreaming(dreaming)
- runCurrent()
- }
+ /**
+ * Advances time by duration + 1 millisecond, to ensure that tasks scheduled to run at
+ * currentTime + duration are scheduled.
+ */
+ private fun Kosmos.advanceTimeBy(duration: Duration) =
+ testScope.advanceTimeBy(duration + 1.milliseconds)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractorTest.kt
index ff722bf..aa96073 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractorTest.kt
@@ -22,7 +22,6 @@
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.compose.animation.scene.ObservableTransitionState.Idle
import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
-import com.android.systemui.Flags.FLAG_COMMUNAL_SCENE_KTF_REFACTOR
import com.android.systemui.SysuiTestCase
import com.android.systemui.communal.data.repository.communalSceneRepository
import com.android.systemui.communal.shared.model.CommunalScenes
@@ -64,7 +63,7 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
-@EnableFlags(FLAG_COMMUNAL_HUB, FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
+@EnableFlags(FLAG_COMMUNAL_HUB)
@DisableSceneContainer
class CommunalSceneTransitionInteractorTest : SysuiTestCase() {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelTest.kt
index 1a3606e..da25bca 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelTest.kt
@@ -35,6 +35,7 @@
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.flow.MutableStateFlow
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
@@ -44,6 +45,7 @@
import platform.test.runner.parameterized.ParameterizedAndroidJunit4
import platform.test.runner.parameterized.Parameters
+@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(ParameterizedAndroidJunit4::class)
class CommunalTransitionViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
@@ -65,7 +67,7 @@
private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
private val communalSceneRepository = kosmos.fakeCommunalSceneRepository
- private val sceneInteractor = kosmos.sceneInteractor
+ private val sceneInteractor by lazy { kosmos.sceneInteractor }
private val underTest: CommunalTransitionViewModel by lazy {
kosmos.communalTransitionViewModel
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
index 18cc8bf..f5f5dd8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
@@ -23,24 +23,19 @@
import android.content.pm.UserInfo
import android.provider.Settings
import android.view.accessibility.AccessibilityEvent
-import android.view.accessibility.AccessibilityManager
import android.view.accessibility.accessibilityManager
import android.widget.RemoteViews
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import com.android.internal.logging.UiEventLogger
+import com.android.internal.logging.uiEventLogger
+import com.android.internal.logging.uiEventLoggerFake
import com.android.systemui.SysuiTestCase
import com.android.systemui.communal.data.model.CommunalSmartspaceTimer
-import com.android.systemui.communal.data.repository.FakeCommunalMediaRepository
-import com.android.systemui.communal.data.repository.FakeCommunalSmartspaceRepository
-import com.android.systemui.communal.data.repository.FakeCommunalTutorialRepository
-import com.android.systemui.communal.data.repository.FakeCommunalWidgetRepository
import com.android.systemui.communal.data.repository.fakeCommunalMediaRepository
import com.android.systemui.communal.data.repository.fakeCommunalSmartspaceRepository
import com.android.systemui.communal.data.repository.fakeCommunalTutorialRepository
import com.android.systemui.communal.data.repository.fakeCommunalWidgetRepository
import com.android.systemui.communal.domain.interactor.CommunalInteractor
-import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
import com.android.systemui.communal.domain.interactor.communalInteractor
import com.android.systemui.communal.domain.interactor.communalSceneInteractor
import com.android.systemui.communal.domain.interactor.communalSettingsInteractor
@@ -49,12 +44,15 @@
import com.android.systemui.communal.shared.log.CommunalUiEvent
import com.android.systemui.communal.shared.model.EditModeState
import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel
-import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.Flags
import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.collectLastValue
+import com.android.systemui.kosmos.runTest
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.media.controls.ui.controller.mediaCarouselController
import com.android.systemui.media.controls.ui.view.MediaHost
@@ -62,73 +60,45 @@
import com.android.systemui.testKosmos
import com.android.systemui.user.data.repository.fakeUserRepository
import com.google.common.truth.Truth.assertThat
+import kotlin.time.Duration.Companion.milliseconds
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.advanceTimeBy
-import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
import org.mockito.kotlin.any
import org.mockito.kotlin.argumentCaptor
-import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
-import org.mockito.kotlin.spy
import org.mockito.kotlin.whenever
+@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
class CommunalEditModeViewModelTest : SysuiTestCase() {
- @Mock private lateinit var mediaHost: MediaHost
- @Mock private lateinit var uiEventLogger: UiEventLogger
- @Mock private lateinit var packageManager: PackageManager
- @Mock private lateinit var metricsLogger: CommunalMetricsLogger
-
- private val kosmos = testKosmos()
- private val testScope = kosmos.testScope
-
- private lateinit var tutorialRepository: FakeCommunalTutorialRepository
- private lateinit var widgetRepository: FakeCommunalWidgetRepository
- private lateinit var smartspaceRepository: FakeCommunalSmartspaceRepository
- private lateinit var mediaRepository: FakeCommunalMediaRepository
- private lateinit var communalSceneInteractor: CommunalSceneInteractor
- private lateinit var communalInteractor: CommunalInteractor
- private lateinit var accessibilityManager: AccessibilityManager
+ private val kosmos = testKosmos().useUnconfinedTestDispatcher()
private val testableResources = context.orCreateTestableResources
- private lateinit var underTest: CommunalEditModeViewModel
+ private val Kosmos.packageManager by Kosmos.Fixture { mock<PackageManager>() }
- @Before
- fun setUp() {
- MockitoAnnotations.initMocks(this)
+ private val Kosmos.metricsLogger by Kosmos.Fixture { mock<CommunalMetricsLogger>() }
- tutorialRepository = kosmos.fakeCommunalTutorialRepository
- widgetRepository = kosmos.fakeCommunalWidgetRepository
- smartspaceRepository = kosmos.fakeCommunalSmartspaceRepository
- mediaRepository = kosmos.fakeCommunalMediaRepository
- communalSceneInteractor = kosmos.communalSceneInteractor
- communalInteractor = spy(kosmos.communalInteractor)
- kosmos.fakeUserRepository.setUserInfos(listOf(MAIN_USER_INFO))
- kosmos.fakeUserTracker.set(userInfos = listOf(MAIN_USER_INFO), selectedUserIndex = 0)
- kosmos.fakeFeatureFlagsClassic.set(Flags.COMMUNAL_SERVICE_ENABLED, true)
- accessibilityManager = kosmos.accessibilityManager
-
- underTest =
+ private val Kosmos.underTest by
+ Kosmos.Fixture {
CommunalEditModeViewModel(
communalSceneInteractor,
communalInteractor,
- kosmos.communalSettingsInteractor,
- kosmos.keyguardTransitionInteractor,
- mediaHost,
+ communalSettingsInteractor,
+ keyguardTransitionInteractor,
+ mock<MediaHost>(),
uiEventLogger,
logcatLogBuffer("CommunalEditModeViewModelTest"),
- kosmos.testDispatcher,
+ testDispatcher,
metricsLogger,
context,
accessibilityManager,
@@ -136,19 +106,28 @@
WIDGET_PICKER_PACKAGE_NAME,
kosmos.mediaCarouselController,
)
+ }
+
+ @Before
+ fun setUp() {
+ kosmos.fakeUserRepository.setUserInfos(listOf(MAIN_USER_INFO))
+ kosmos.fakeUserTracker.set(userInfos = listOf(MAIN_USER_INFO), selectedUserIndex = 0)
+ kosmos.fakeFeatureFlagsClassic.set(Flags.COMMUNAL_SERVICE_ENABLED, true)
}
@Test
fun communalContent_onlyWidgetsAndCtaTileAreShownInEditMode() =
- testScope.runTest {
- tutorialRepository.setTutorialSettingState(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED)
+ kosmos.runTest {
+ fakeCommunalTutorialRepository.setTutorialSettingState(
+ Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED
+ )
// Widgets available.
- widgetRepository.addWidget(appWidgetId = 0, rank = 30)
- widgetRepository.addWidget(appWidgetId = 1, rank = 20)
+ fakeCommunalWidgetRepository.addWidget(appWidgetId = 0, rank = 30)
+ fakeCommunalWidgetRepository.addWidget(appWidgetId = 1, rank = 20)
// Smartspace available.
- smartspaceRepository.setTimers(
+ fakeCommunalSmartspaceRepository.setTimers(
listOf(
CommunalSmartspaceTimer(
smartspaceTargetId = "target",
@@ -159,7 +138,7 @@
)
// Media playing.
- mediaRepository.mediaActive()
+ fakeCommunalMediaRepository.mediaActive()
val communalContent by collectLastValue(underTest.communalContent)
@@ -173,7 +152,7 @@
@Test
fun selectedKey_onReorderWidgets_isSet() =
- testScope.runTest {
+ kosmos.runTest {
val selectedKey by collectLastValue(underTest.selectedKey)
underTest.setSelectedKey(null)
@@ -186,7 +165,7 @@
@Test
fun isCommunalContentVisible_isTrue_whenEditModeShowing() =
- testScope.runTest {
+ kosmos.runTest {
val isCommunalContentVisible by collectLastValue(underTest.isCommunalContentVisible)
communalSceneInteractor.setEditModeState(EditModeState.SHOWING)
assertThat(isCommunalContentVisible).isEqualTo(true)
@@ -194,7 +173,7 @@
@Test
fun isCommunalContentVisible_isFalse_whenEditModeNotShowing() =
- testScope.runTest {
+ kosmos.runTest {
val isCommunalContentVisible by collectLastValue(underTest.isCommunalContentVisible)
communalSceneInteractor.setEditModeState(null)
assertThat(isCommunalContentVisible).isEqualTo(false)
@@ -202,12 +181,14 @@
@Test
fun deleteWidget() =
- testScope.runTest {
- tutorialRepository.setTutorialSettingState(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED)
+ kosmos.runTest {
+ fakeCommunalTutorialRepository.setTutorialSettingState(
+ Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED
+ )
// Widgets available.
- widgetRepository.addWidget(appWidgetId = 0, rank = 30)
- widgetRepository.addWidget(appWidgetId = 1, rank = 20)
+ fakeCommunalWidgetRepository.addWidget(appWidgetId = 0, rank = 30)
+ fakeCommunalWidgetRepository.addWidget(appWidgetId = 1, rank = 20)
val communalContent by collectLastValue(underTest.communalContent)
@@ -220,6 +201,7 @@
underTest.onDeleteWidget(
id = 0,
+ key = "key_0",
componentName = ComponentName("test_package", "test_class"),
rank = 30,
)
@@ -233,26 +215,56 @@
}
@Test
- fun reorderWidget_uiEventLogging_start() {
- underTest.onReorderWidgetStart(CommunalContentModel.KEY.widget(123))
- verify(uiEventLogger).log(CommunalUiEvent.COMMUNAL_HUB_REORDER_WIDGET_START)
- }
+ fun deleteWidget_clearsSelectedKey() =
+ kosmos.runTest {
+ val selectedKey by collectLastValue(underTest.selectedKey)
+ underTest.setSelectedKey("test_key")
+ assertThat(selectedKey).isEqualTo("test_key")
+
+ // Selected key is deleted.
+ underTest.onDeleteWidget(
+ id = 0,
+ key = "test_key",
+ componentName = ComponentName("test_package", "test_class"),
+ rank = 30,
+ )
+
+ assertThat(selectedKey).isNull()
+ }
@Test
- fun reorderWidget_uiEventLogging_end() {
- underTest.onReorderWidgetEnd()
- verify(uiEventLogger).log(CommunalUiEvent.COMMUNAL_HUB_REORDER_WIDGET_FINISH)
- }
+ fun reorderWidget_uiEventLogging_start() =
+ kosmos.runTest {
+ underTest.onReorderWidgetStart(CommunalContentModel.KEY.widget(123))
+
+ assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1)
+ assertThat(uiEventLoggerFake.logs[0].eventId)
+ .isEqualTo(CommunalUiEvent.COMMUNAL_HUB_REORDER_WIDGET_START.id)
+ }
@Test
- fun reorderWidget_uiEventLogging_cancel() {
- underTest.onReorderWidgetCancel()
- verify(uiEventLogger).log(CommunalUiEvent.COMMUNAL_HUB_REORDER_WIDGET_CANCEL)
- }
+ fun reorderWidget_uiEventLogging_end() =
+ kosmos.runTest {
+ underTest.onReorderWidgetEnd()
+
+ assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1)
+ assertThat(uiEventLoggerFake.logs[0].eventId)
+ .isEqualTo(CommunalUiEvent.COMMUNAL_HUB_REORDER_WIDGET_FINISH.id)
+ }
+
+ @Test
+ fun reorderWidget_uiEventLogging_cancel() =
+ kosmos.runTest {
+ underTest.onReorderWidgetCancel()
+
+ assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1)
+ assertThat(uiEventLoggerFake.logs[0].eventId)
+ .isEqualTo(CommunalUiEvent.COMMUNAL_HUB_REORDER_WIDGET_CANCEL.id)
+ }
@Test
fun onOpenWidgetPicker_launchesWidgetPickerActivity() {
- testScope.runTest {
+ kosmos.runTest {
var activityStarted = false
val success =
underTest.onOpenWidgetPicker(testableResources.resources) { _ ->
@@ -266,7 +278,7 @@
@Test
fun onOpenWidgetPicker_activityLaunchThrowsException_failure() {
- testScope.runTest {
+ kosmos.runTest {
val success =
underTest.onOpenWidgetPicker(testableResources.resources) { _ ->
run { throw ActivityNotFoundException() }
@@ -278,7 +290,7 @@
@Test
fun showDisclaimer_trueAfterEditModeShowing() =
- testScope.runTest {
+ kosmos.runTest {
val showDisclaimer by collectLastValue(underTest.showDisclaimer)
assertThat(showDisclaimer).isFalse()
@@ -288,9 +300,9 @@
@Test
fun showDisclaimer_falseWhenDismissed() =
- testScope.runTest {
+ kosmos.runTest {
underTest.setEditModeState(EditModeState.SHOWING)
- kosmos.fakeUserRepository.setSelectedUserInfo(MAIN_USER_INFO)
+ fakeUserRepository.setSelectedUserInfo(MAIN_USER_INFO)
val showDisclaimer by collectLastValue(underTest.showDisclaimer)
@@ -301,63 +313,67 @@
@Test
fun showDisclaimer_trueWhenTimeout() =
- testScope.runTest {
+ kosmos.runTest {
underTest.setEditModeState(EditModeState.SHOWING)
- kosmos.fakeUserRepository.setSelectedUserInfo(MAIN_USER_INFO)
+ fakeUserRepository.setSelectedUserInfo(MAIN_USER_INFO)
val showDisclaimer by collectLastValue(underTest.showDisclaimer)
assertThat(showDisclaimer).isTrue()
underTest.onDisclaimerDismissed()
assertThat(showDisclaimer).isFalse()
- advanceTimeBy(CommunalInteractor.DISCLAIMER_RESET_MILLIS)
+ testScope.advanceTimeBy(CommunalInteractor.DISCLAIMER_RESET_MILLIS + 1.milliseconds)
assertThat(showDisclaimer).isTrue()
}
@Test
- fun scrollPosition_persistedOnEditCleanup() {
- val index = 2
- val offset = 30
- underTest.onScrollPositionUpdated(index, offset)
- underTest.cleanupEditModeState()
+ fun scrollPosition_persistedOnEditCleanup() =
+ kosmos.runTest {
+ val index = 2
+ val offset = 30
+ underTest.onScrollPositionUpdated(index, offset)
+ underTest.cleanupEditModeState()
- verify(communalInteractor).setScrollPosition(eq(index), eq(offset))
- }
+ assertThat(communalInteractor.firstVisibleItemIndex).isEqualTo(index)
+ assertThat(communalInteractor.firstVisibleItemOffset).isEqualTo(offset)
+ }
@Test
- fun onNewWidgetAdded_accessibilityDisabled_doNothing() {
- whenever(accessibilityManager.isEnabled).thenReturn(false)
+ fun onNewWidgetAdded_accessibilityDisabled_doNothing() =
+ kosmos.runTest {
+ whenever(accessibilityManager.isEnabled).thenReturn(false)
- val provider =
- mock<AppWidgetProviderInfo> {
- on { loadLabel(packageManager) }.thenReturn("Test Clock")
- }
- underTest.onNewWidgetAdded(provider)
+ val provider =
+ mock<AppWidgetProviderInfo> {
+ on { loadLabel(packageManager) }.thenReturn("Test Clock")
+ }
+ underTest.onNewWidgetAdded(provider)
- verify(accessibilityManager, never()).sendAccessibilityEvent(any())
- }
+ verify(accessibilityManager, never()).sendAccessibilityEvent(any())
+ }
@Test
- fun onNewWidgetAdded_accessibilityEnabled_sendAccessibilityAnnouncement() {
- whenever(accessibilityManager.isEnabled).thenReturn(true)
+ fun onNewWidgetAdded_accessibilityEnabled_sendAccessibilityAnnouncement() =
+ kosmos.runTest {
+ whenever(accessibilityManager.isEnabled).thenReturn(true)
- val provider =
- mock<AppWidgetProviderInfo> {
- on { loadLabel(packageManager) }.thenReturn("Test Clock")
- }
- underTest.onNewWidgetAdded(provider)
+ val provider =
+ mock<AppWidgetProviderInfo> {
+ on { loadLabel(packageManager) }.thenReturn("Test Clock")
+ }
+ underTest.onNewWidgetAdded(provider)
- val captor = argumentCaptor<AccessibilityEvent>()
- verify(accessibilityManager).sendAccessibilityEvent(captor.capture())
+ val captor = argumentCaptor<AccessibilityEvent>()
+ verify(accessibilityManager).sendAccessibilityEvent(captor.capture())
- val event = captor.firstValue
- assertThat(event.eventType).isEqualTo(AccessibilityEvent.TYPE_ANNOUNCEMENT)
- assertThat(event.contentDescription).isEqualTo("Test Clock widget added to lock screen")
- }
+ val event = captor.firstValue
+ assertThat(event.eventType).isEqualTo(AccessibilityEvent.TYPE_ANNOUNCEMENT)
+ assertThat(event.contentDescription).isEqualTo("Test Clock widget added to lock screen")
+ }
@Test
fun onResizeWidget_logsMetrics() =
- testScope.runTest {
+ kosmos.runTest {
val appWidgetId = 123
val spanY = 2
val widgetIdToRankMap = mapOf(appWidgetId to 1)
@@ -372,7 +388,6 @@
rank = rank,
)
- verify(communalInteractor).resizeWidget(appWidgetId, spanY, widgetIdToRankMap)
verify(metricsLogger)
.logResizeWidget(
componentName = componentName.flattenToString(),
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 8515515..799054a 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
@@ -78,6 +78,7 @@
import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager
import com.android.systemui.media.controls.ui.controller.mediaCarouselController
+import com.android.systemui.media.controls.ui.view.MediaCarouselScrollHandler
import com.android.systemui.media.controls.ui.view.MediaHost
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
import com.android.systemui.power.domain.interactor.powerInteractor
@@ -120,6 +121,7 @@
@RunWith(ParameterizedAndroidJunit4::class)
class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
@Mock private lateinit var mediaHost: MediaHost
+ @Mock private lateinit var mediaCarouselScrollHandler: MediaCarouselScrollHandler
@Mock private lateinit var metricsLogger: CommunalMetricsLogger
private val kosmos = testKosmos()
@@ -161,6 +163,8 @@
kosmos.fakeUserTracker.set(userInfos = listOf(MAIN_USER_INFO), selectedUserIndex = 0)
whenever(mediaHost.visible).thenReturn(true)
+ whenever(kosmos.mediaCarouselController.mediaCarouselScrollHandler)
+ .thenReturn(mediaCarouselScrollHandler)
kosmos.powerInteractor.setAwakeForTest()
@@ -187,6 +191,7 @@
metricsLogger,
kosmos.mediaCarouselController,
kosmos.blurConfig,
+ false,
)
}
@@ -903,6 +908,20 @@
}
@Test
+ fun onShowPreviousMedia_scrollHandler_isCalled() =
+ testScope.runTest {
+ underTest.onShowPreviousMedia()
+ verify(mediaCarouselScrollHandler).scrollByStep(-1)
+ }
+
+ @Test
+ fun onShowNextMedia_scrollHandler_isCalled() =
+ testScope.runTest {
+ underTest.onShowNextMedia()
+ verify(mediaCarouselScrollHandler).scrollByStep(1)
+ }
+
+ @Test
@EnableFlags(FLAG_BOUNCER_UI_REVAMP)
fun uiIsBlurred_whenPrimaryBouncerIsShowing() =
testScope.runTest {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt
index 329627a..e36d245 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt
@@ -61,6 +61,7 @@
import com.android.systemui.user.data.repository.fakeUserRepository
import com.android.systemui.util.mockito.eq
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.TestScope
@@ -73,6 +74,7 @@
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
+@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
class DeviceEntryFaceAuthInteractorTest : SysuiTestCase() {
@@ -80,21 +82,26 @@
private val testScope: TestScope = kosmos.testScope
private lateinit var underTest: SystemUIDeviceEntryFaceAuthInteractor
+
private val bouncerRepository = kosmos.fakeKeyguardBouncerRepository
private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
- private val keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor
private val faceAuthRepository = kosmos.fakeDeviceEntryFaceAuthRepository
private val fakeUserRepository = kosmos.fakeUserRepository
private val facePropertyRepository = kosmos.facePropertyRepository
- private val fakeDeviceEntryFingerprintAuthInteractor =
- kosmos.deviceEntryFingerprintAuthInteractor
- private val powerInteractor = kosmos.powerInteractor
private val fakeBiometricSettingsRepository = kosmos.fakeBiometricSettingsRepository
- private val keyguardUpdateMonitor = kosmos.keyguardUpdateMonitor
+ private val keyguardUpdateMonitor by lazy { kosmos.keyguardUpdateMonitor }
private val faceWakeUpTriggersConfig = kosmos.fakeFaceWakeUpTriggersConfig
private val trustManager = kosmos.trustManager
- private val deviceEntryFaceAuthStatusInteractor = kosmos.deviceEntryFaceAuthStatusInteractor
+
+ private val keyguardTransitionInteractor by lazy { kosmos.keyguardTransitionInteractor }
+ private val fakeDeviceEntryFingerprintAuthInteractor by lazy {
+ kosmos.deviceEntryFingerprintAuthInteractor
+ }
+ private val powerInteractor by lazy { kosmos.powerInteractor }
+ private val deviceEntryFaceAuthStatusInteractor by lazy {
+ kosmos.deviceEntryFaceAuthStatusInteractor
+ }
@Before
fun setup() {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialNotificationCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialNotificationCoordinatorTest.kt
index e0082da..ff5fa39 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialNotificationCoordinatorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialNotificationCoordinatorTest.kt
@@ -18,6 +18,7 @@
import android.app.Notification
import android.app.NotificationManager
+import android.service.notification.StatusBarNotification
import androidx.annotation.StringRes
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
@@ -28,6 +29,7 @@
import com.android.systemui.keyboard.data.repository.FakeKeyboardRepository
import com.android.systemui.kosmos.backgroundScope
import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
import com.android.systemui.res.R
import com.android.systemui.settings.userTracker
import com.android.systemui.statusbar.commandline.commandRegistry
@@ -35,6 +37,7 @@
import com.android.systemui.touchpad.data.repository.FakeTouchpadRepository
import com.google.common.truth.Truth.assertThat
import kotlin.time.Duration.Companion.hours
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -44,23 +47,29 @@
import org.mockito.ArgumentCaptor
import org.mockito.Captor
import org.mockito.Mock
+import org.mockito.Mockito.times
import org.mockito.junit.MockitoJUnit
import org.mockito.kotlin.any
import org.mockito.kotlin.eq
+import org.mockito.kotlin.firstValue
import org.mockito.kotlin.never
+import org.mockito.kotlin.secondValue
import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+@OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
class TutorialNotificationCoordinatorTest : SysuiTestCase() {
private lateinit var underTest: TutorialNotificationCoordinator
- private val kosmos = testKosmos()
+ private val kosmos = testKosmos().useUnconfinedTestDispatcher()
private val testScope = kosmos.testScope
private val keyboardRepository = FakeKeyboardRepository()
private val touchpadRepository = FakeTouchpadRepository()
private lateinit var repository: TutorialSchedulerRepository
@Mock private lateinit var notificationManager: NotificationManager
+ @Mock private lateinit var notification: StatusBarNotification
@Captor private lateinit var notificationCaptor: ArgumentCaptor<Notification>
@get:Rule val rule = MockitoJUnit.rule()
@@ -107,6 +116,7 @@
fun showTouchpadNotification() = runTestAndClear {
touchpadRepository.setIsAnyTouchpadConnected(true)
testScope.advanceTimeBy(LAUNCH_DELAY)
+ mockExistingNotification()
verifyNotification(
R.string.launch_touchpad_tutorial_notification_title,
R.string.launch_touchpad_tutorial_notification_content,
@@ -131,6 +141,45 @@
.notifyAsUser(eq(TAG), eq(NOTIFICATION_ID), any(), any())
}
+ @Test
+ fun showKeyboardNotificationThenDisconnectKeyboard() = runTestAndClear {
+ keyboardRepository.setIsAnyKeyboardConnected(true)
+ testScope.advanceTimeBy(LAUNCH_DELAY)
+ verifyNotification(
+ R.string.launch_keyboard_tutorial_notification_title,
+ R.string.launch_keyboard_tutorial_notification_content,
+ )
+ mockExistingNotification()
+
+ // After the keyboard is disconnected, i.e. there is nothing connected, the notification
+ // should be cancelled
+ keyboardRepository.setIsAnyKeyboardConnected(false)
+ verify(notificationManager).cancelAsUser(eq(TAG), eq(NOTIFICATION_ID), any())
+ }
+
+ @Test
+ fun showKeyboardTouchpadNotificationThenDisconnectKeyboard() = runTestAndClear {
+ keyboardRepository.setIsAnyKeyboardConnected(true)
+ touchpadRepository.setIsAnyTouchpadConnected(true)
+ testScope.advanceTimeBy(LAUNCH_DELAY)
+ mockExistingNotification()
+ keyboardRepository.setIsAnyKeyboardConnected(false)
+
+ verify(notificationManager, times(2))
+ .notifyAsUser(eq(TAG), eq(NOTIFICATION_ID), notificationCaptor.capture(), any())
+ // Connect both device and the first notification is for both
+ notificationCaptor.firstValue.verify(
+ R.string.launch_keyboard_touchpad_tutorial_notification_title,
+ R.string.launch_keyboard_touchpad_tutorial_notification_content,
+ )
+ // After the keyboard is disconnected, i.e. with only the touchpad left, the notification
+ // should be update to the one for only touchpad
+ notificationCaptor.secondValue.verify(
+ R.string.launch_touchpad_tutorial_notification_title,
+ R.string.launch_touchpad_tutorial_notification_content,
+ )
+ }
+
private fun runTestAndClear(block: suspend () -> Unit) =
testScope.runTest {
try {
@@ -140,12 +189,21 @@
}
}
+ // Assume that there's an existing notification when the updater checks activeNotifications
+ private fun mockExistingNotification() {
+ whenever(notification.id).thenReturn(NOTIFICATION_ID)
+ whenever(notificationManager.activeNotifications).thenReturn(arrayOf(notification))
+ }
+
private fun verifyNotification(@StringRes titleResId: Int, @StringRes contentResId: Int) {
verify(notificationManager)
.notifyAsUser(eq(TAG), eq(NOTIFICATION_ID), notificationCaptor.capture(), any())
- val notification = notificationCaptor.value
- val actualTitle = notification.getString(Notification.EXTRA_TITLE)
- val actualContent = notification.getString(Notification.EXTRA_TEXT)
+ notificationCaptor.value.verify(titleResId, contentResId)
+ }
+
+ private fun Notification.verify(@StringRes titleResId: Int, @StringRes contentResId: Int) {
+ val actualTitle = getString(Notification.EXTRA_TITLE)
+ val actualContent = getString(Notification.EXTRA_TEXT)
assertThat(actualTitle).isEqualTo(context.getString(titleResId))
assertThat(actualContent).isEqualTo(context.getString(contentResId))
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt
index e13f3f1..9be786f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt
@@ -20,18 +20,23 @@
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.FlagsParameterization
+import android.provider.Settings
import android.service.dream.dreamManager
import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
-import com.android.systemui.Flags.FLAG_COMMUNAL_SCENE_KTF_REFACTOR
+import com.android.systemui.Flags.FLAG_GLANCEABLE_HUB_V2
import com.android.systemui.Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR
import com.android.systemui.Flags.FLAG_SCENE_CONTAINER
+import com.android.systemui.Flags.glanceableHubV2
import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.data.repository.batteryRepository
+import com.android.systemui.common.data.repository.fake
import com.android.systemui.communal.data.repository.FakeCommunalSceneRepository
import com.android.systemui.communal.data.repository.communalSceneRepository
import com.android.systemui.communal.data.repository.fakeCommunalSceneRepository
import com.android.systemui.communal.domain.interactor.setCommunalAvailable
+import com.android.systemui.communal.domain.interactor.setCommunalV2ConfigEnabled
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepositorySpy
@@ -56,6 +61,8 @@
import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.testKosmos
+import com.android.systemui.user.data.repository.fakeUserRepository
+import com.android.systemui.util.settings.fakeSettings
import com.google.common.truth.Truth
import junit.framework.Assert.assertEquals
import kotlinx.coroutines.flow.flowOf
@@ -93,7 +100,7 @@
@JvmStatic
@Parameters(name = "{0}")
fun getParams(): List<FlagsParameterization> {
- return FlagsParameterization.allCombinationsOf(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
+ return FlagsParameterization.allCombinationsOf(FLAG_GLANCEABLE_HUB_V2)
}
}
@@ -107,6 +114,7 @@
// Transition to DOZING and set the power interactor asleep.
kosmos.powerInteractor.setAsleepForTest()
+ kosmos.setCommunalV2ConfigEnabled(true)
runBlocking {
kosmos.transitionRepository.sendTransitionSteps(
from = KeyguardState.LOCKSCREEN,
@@ -160,26 +168,20 @@
@Test
@EnableFlags(FLAG_KEYGUARD_WM_STATE_REFACTOR)
- @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
- fun testTransitionToLockscreen_onWake_canDream_glanceableHubAvailable() =
- kosmos.runTest {
- whenever(dreamManager.canStartDreaming(anyBoolean())).thenReturn(true)
- setCommunalAvailable(true)
-
- powerInteractor.setAwakeForTest()
-
- // If dreaming is possible and communal is available, then we should transition to
- // GLANCEABLE_HUB when waking up due to power button press.
- assertThat(transitionRepository)
- .startedTransition(from = KeyguardState.DOZING, to = KeyguardState.GLANCEABLE_HUB)
- }
-
- @Test
- @EnableFlags(FLAG_KEYGUARD_WM_STATE_REFACTOR, FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
fun testTransitionToLockscreen_onWake_canDream_ktfRefactor() =
kosmos.runTest {
setCommunalAvailable(true)
- whenever(dreamManager.canStartDreaming(anyBoolean())).thenReturn(true)
+ if (glanceableHubV2()) {
+ val user = fakeUserRepository.asMainUser()
+ fakeSettings.putIntForUser(
+ Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP,
+ 1,
+ user.id,
+ )
+ batteryRepository.fake.setDevicePluggedIn(true)
+ } else {
+ whenever(dreamManager.canStartDreaming(anyBoolean())).thenReturn(true)
+ }
clearInvocations(fakeCommunalSceneRepository)
powerInteractor.setAwakeForTest()
@@ -219,28 +221,21 @@
}
@Test
- @EnableFlags(FLAG_KEYGUARD_WM_STATE_REFACTOR)
- @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
- fun testTransitionToGlanceableHub_onWakeup_ifIdleOnCommunal_noOccludingActivity() =
- kosmos.runTest {
- fakeCommunalSceneRepository.setTransitionState(
- flowOf(ObservableTransitionState.Idle(CommunalScenes.Communal))
- )
-
- powerInteractor.setAwakeForTest()
-
- // Under default conditions, we should transition to LOCKSCREEN when waking up.
- assertThat(transitionRepository)
- .startedTransition(from = KeyguardState.DOZING, to = KeyguardState.GLANCEABLE_HUB)
- }
-
- @Test
@DisableFlags(FLAG_KEYGUARD_WM_STATE_REFACTOR, FLAG_SCENE_CONTAINER)
- @EnableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
fun testTransitionToGlanceableHub_onWakeup_ifAvailable() =
kosmos.runTest {
setCommunalAvailable(true)
- whenever(dreamManager.canStartDreaming(anyBoolean())).thenReturn(true)
+ if (glanceableHubV2()) {
+ val user = fakeUserRepository.asMainUser()
+ fakeSettings.putIntForUser(
+ Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP,
+ 1,
+ user.id,
+ )
+ batteryRepository.fake.setDevicePluggedIn(true)
+ } else {
+ whenever(dreamManager.canStartDreaming(anyBoolean())).thenReturn(true)
+ }
// Device turns on.
powerInteractor.setAwakeForTest()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorTest.kt
index 8e10682..898a5c1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorTest.kt
@@ -19,14 +19,19 @@
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.FlagsParameterization
+import android.provider.Settings
import android.service.dream.dreamManager
import androidx.test.filters.SmallTest
import com.android.systemui.Flags
-import com.android.systemui.Flags.FLAG_COMMUNAL_SCENE_KTF_REFACTOR
+import com.android.systemui.Flags.FLAG_GLANCEABLE_HUB_V2
+import com.android.systemui.Flags.glanceableHubV2
import com.android.systemui.SysuiTestCase
import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository
+import com.android.systemui.common.data.repository.batteryRepository
+import com.android.systemui.common.data.repository.fake
import com.android.systemui.communal.data.repository.communalSceneRepository
import com.android.systemui.communal.domain.interactor.setCommunalAvailable
+import com.android.systemui.communal.domain.interactor.setCommunalV2ConfigEnabled
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.flags.andSceneContainer
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
@@ -46,6 +51,8 @@
import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.statusbar.domain.interactor.keyguardOcclusionInteractor
import com.android.systemui.testKosmos
+import com.android.systemui.user.data.repository.fakeUserRepository
+import com.android.systemui.util.settings.fakeSettings
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.advanceTimeBy
@@ -66,7 +73,7 @@
@JvmStatic
@Parameters(name = "{0}")
fun getParams(): List<FlagsParameterization> {
- return FlagsParameterization.allCombinationsOf(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
+ return FlagsParameterization.allCombinationsOf(FLAG_GLANCEABLE_HUB_V2)
.andSceneContainer()
}
}
@@ -101,6 +108,7 @@
)
reset(kosmos.transitionRepository)
kosmos.setCommunalAvailable(true)
+ kosmos.setCommunalV2ConfigEnabled(true)
}
kosmos.underTest.start()
}
@@ -190,7 +198,6 @@
}
@Test
- @EnableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
@DisableFlags(Flags.FLAG_SCENE_CONTAINER)
fun testTransitionToGlanceableHubOnWake() =
kosmos.runTest {
@@ -202,7 +209,17 @@
reset(transitionRepository)
setCommunalAvailable(true)
- whenever(dreamManager.canStartDreaming(anyBoolean())).thenReturn(true)
+ if (glanceableHubV2()) {
+ val user = fakeUserRepository.asMainUser()
+ fakeSettings.putIntForUser(
+ Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP,
+ 1,
+ user.id,
+ )
+ batteryRepository.fake.setDevicePluggedIn(true)
+ } else {
+ whenever(dreamManager.canStartDreaming(anyBoolean())).thenReturn(true)
+ }
// Device wakes up.
powerInteractor.setAwakeForTest()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractorTest.kt
index 63ed040..9a8e095 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractorTest.kt
@@ -32,16 +32,11 @@
package com.android.systemui.keyguard.domain.interactor
-import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import com.android.compose.animation.scene.ObservableTransitionState
import com.android.systemui.Flags
-import com.android.systemui.Flags.FLAG_COMMUNAL_SCENE_KTF_REFACTOR
import com.android.systemui.SysuiTestCase
-import com.android.systemui.communal.data.repository.fakeCommunalSceneRepository
-import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepositorySpy
import com.android.systemui.keyguard.data.repository.keyguardOcclusionRepository
import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
@@ -51,7 +46,6 @@
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.testKosmos
-import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
@@ -101,21 +95,4 @@
assertThat(transitionRepository)
.startedTransition(from = KeyguardState.OCCLUDED, to = KeyguardState.LOCKSCREEN)
}
-
- @Test
- @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
- @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
- fun testShowWhenLockedActivity_noLongerOnTop_transitionsToGlanceableHub_ifIdleOnCommunal() =
- testScope.runTest {
- kosmos.fakeCommunalSceneRepository.setTransitionState(
- flowOf(ObservableTransitionState.Idle(CommunalScenes.Communal))
- )
- runCurrent()
-
- kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(onTop = false)
- runCurrent()
-
- assertThat(transitionRepository)
- .startedTransition(from = KeyguardState.OCCLUDED, to = KeyguardState.GLANCEABLE_HUB)
- }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
index 29e95cd..fee2dfc 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
@@ -46,6 +46,7 @@
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import junit.framework.Assert.assertEquals
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
@@ -53,13 +54,15 @@
import org.junit.Test
import org.junit.runner.RunWith
+@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
class KeyguardTransitionInteractorTest : SysuiTestCase() {
val kosmos = testKosmos()
- val underTest = kosmos.keyguardTransitionInteractor
- val repository = kosmos.fakeKeyguardTransitionRepository
val testScope = kosmos.testScope
+ val repository = kosmos.fakeKeyguardTransitionRepository
+
+ val underTest by lazy { kosmos.keyguardTransitionInteractor }
@Test
fun transitionCollectorsReceivesOnlyAppropriateEvents() =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index d057f7a0..a090fab 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -17,7 +17,6 @@
package com.android.systemui.keyguard.domain.interactor
import android.app.StatusBarManager.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP
-import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.FlagsParameterization
import androidx.test.filters.SmallTest
@@ -26,7 +25,6 @@
import com.android.keyguard.KeyguardSecurityModel.SecurityMode.PIN
import com.android.systemui.Flags
import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
-import com.android.systemui.Flags.FLAG_COMMUNAL_SCENE_KTF_REFACTOR
import com.android.systemui.Flags.FLAG_GLANCEABLE_HUB_V2
import com.android.systemui.Flags.glanceableHubV2
import com.android.systemui.SysuiTestCase
@@ -135,8 +133,7 @@
@JvmStatic
@Parameters(name = "{0}")
fun getParams(): List<FlagsParameterization> {
- return FlagsParameterization.allCombinationsOf(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
- .andSceneContainer()
+ return FlagsParameterization.allCombinationsOf().andSceneContainer()
}
}
@@ -468,38 +465,6 @@
@Test
@DisableSceneContainer
- @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
- fun dozingToGlanceableHub() =
- testScope.runTest {
- // GIVEN a prior transition has run to DOZING
- runTransitionAndSetWakefulness(KeyguardState.GLANCEABLE_HUB, KeyguardState.DOZING)
- runCurrent()
-
- // GIVEN the device is idle on the glanceable hub
- val idleTransitionState =
- MutableStateFlow<ObservableTransitionState>(
- ObservableTransitionState.Idle(CommunalScenes.Communal)
- )
- communalInteractor.setTransitionState(idleTransitionState)
- runCurrent()
-
- // WHEN the device begins to wake
- keyguardRepository.setKeyguardShowing(true)
- powerInteractor.setAwakeForTest()
- advanceTimeBy(60L)
-
- assertThat(transitionRepository)
- .startedTransition(
- from = KeyguardState.DOZING,
- to = KeyguardState.GLANCEABLE_HUB,
- animatorAssertion = { it.isNotNull() },
- )
-
- coroutineContext.cancelChildren()
- }
-
- @Test
- @DisableSceneContainer
fun goneToDozing() =
testScope.runTest {
// GIVEN a device with AOD not available
@@ -625,40 +590,6 @@
@Test
@BrokenWithSceneContainer(339465026)
- @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
- fun goneToGlanceableHub() =
- testScope.runTest {
- // GIVEN a prior transition has run to GONE
- runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.GONE)
-
- // GIVEN the device is idle on the glanceable hub
- val idleTransitionState =
- MutableStateFlow<ObservableTransitionState>(
- ObservableTransitionState.Idle(CommunalScenes.Communal)
- )
- communalInteractor.setTransitionState(idleTransitionState)
- runCurrent()
-
- // WHEN the keyguard starts to show
- keyguardRepository.setKeyguardShowing(true)
- runCurrent()
-
- assertThat(transitionRepository)
- .startedTransition(
- to = KeyguardState.GLANCEABLE_HUB,
- from = KeyguardState.GONE,
- ownerName =
- FromGoneTransitionInteractor::class.simpleName +
- "(keyguard interactor says keyguard is showing)",
- animatorAssertion = { it.isNotNull() },
- )
-
- coroutineContext.cancelChildren()
- }
-
- @Test
- @BrokenWithSceneContainer(339465026)
- @EnableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
fun goneToGlanceableHub_communalKtfRefactor() =
testScope.runTest {
// GIVEN a prior transition has run to GONE
@@ -1070,84 +1001,6 @@
@Test
@BrokenWithSceneContainer(339465026)
- @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
- fun occludedToGlanceableHub() =
- testScope.runTest {
- // GIVEN a device on lockscreen
- keyguardRepository.setKeyguardShowing(true)
- runCurrent()
-
- // GIVEN the device is idle on the glanceable hub
- val idleTransitionState =
- MutableStateFlow<ObservableTransitionState>(
- ObservableTransitionState.Idle(CommunalScenes.Communal)
- )
- communalInteractor.setTransitionState(idleTransitionState)
- runCurrent()
-
- // GIVEN a prior transition has run to OCCLUDED
- runTransitionAndSetWakefulness(KeyguardState.GLANCEABLE_HUB, KeyguardState.OCCLUDED)
- keyguardRepository.setKeyguardOccluded(true)
- runCurrent()
-
- // WHEN occlusion ends
- keyguardRepository.setKeyguardOccluded(false)
- runCurrent()
-
- // THEN a transition to GLANCEABLE_HUB should occur
- assertThat(transitionRepository)
- .startedTransition(
- ownerName = FromOccludedTransitionInteractor::class.simpleName,
- from = KeyguardState.OCCLUDED,
- to = KeyguardState.GLANCEABLE_HUB,
- animatorAssertion = { it.isNotNull() },
- )
-
- coroutineContext.cancelChildren()
- }
-
- @Test
- @BrokenWithSceneContainer(339465026)
- @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
- fun occludedToGlanceableHubWhenInitiallyOnHub() =
- testScope.runTest {
- // GIVEN a device on lockscreen and communal is available
- keyguardRepository.setKeyguardShowing(true)
- kosmos.setCommunalAvailable(true)
- runCurrent()
-
- // GIVEN a prior transition has run to OCCLUDED from GLANCEABLE_HUB
- runTransitionAndSetWakefulness(KeyguardState.GLANCEABLE_HUB, KeyguardState.OCCLUDED)
- keyguardRepository.setKeyguardOccluded(true)
- runCurrent()
-
- // GIVEN on blank scene
- val idleTransitionState =
- MutableStateFlow<ObservableTransitionState>(
- ObservableTransitionState.Idle(CommunalScenes.Blank)
- )
- communalInteractor.setTransitionState(idleTransitionState)
- runCurrent()
-
- // WHEN occlusion ends
- keyguardRepository.setKeyguardOccluded(false)
- runCurrent()
-
- // THEN a transition to GLANCEABLE_HUB should occur
- assertThat(transitionRepository)
- .startedTransition(
- ownerName = FromOccludedTransitionInteractor::class.simpleName,
- from = KeyguardState.OCCLUDED,
- to = KeyguardState.GLANCEABLE_HUB,
- animatorAssertion = { it.isNotNull() },
- )
-
- coroutineContext.cancelChildren()
- }
-
- @Test
- @BrokenWithSceneContainer(339465026)
- @EnableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
fun occludedToGlanceableHub_communalKtfRefactor() =
testScope.runTest {
// GIVEN a device on lockscreen and communal is available
@@ -1390,48 +1243,6 @@
@Test
@DisableSceneContainer
- @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
- fun dreamingToGlanceableHub() =
- testScope.runTest {
- // GIVEN a prior transition has run to DREAMING
- keyguardRepository.setDreaming(true)
- runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.DREAMING)
- runCurrent()
-
- // WHEN a transition to the glanceable hub starts
- val currentScene = CommunalScenes.Blank
- val targetScene = CommunalScenes.Communal
-
- val progress = MutableStateFlow(0f)
- val transitionState =
- MutableStateFlow<ObservableTransitionState>(
- ObservableTransitionState.Transition(
- fromScene = currentScene,
- toScene = targetScene,
- currentScene = flowOf(targetScene),
- progress = progress,
- isInitiatedByUserInput = false,
- isUserInputOngoing = flowOf(false),
- )
- )
- communalInteractor.setTransitionState(transitionState)
- progress.value = .1f
- runCurrent()
-
- assertThat(transitionRepository)
- .startedTransition(
- ownerName = FromDreamingTransitionInteractor::class.simpleName,
- from = KeyguardState.DREAMING,
- to = KeyguardState.GLANCEABLE_HUB,
- animatorAssertion = { it.isNull() }, // transition should be manually animated
- )
-
- coroutineContext.cancelChildren()
- }
-
- @Test
- @DisableSceneContainer
- @EnableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
fun dreamingToGlanceableHub_communalKtfRefactor() =
testScope.runTest {
// GIVEN a prior transition has run to DREAMING
@@ -1596,66 +1407,6 @@
@Test
@DisableSceneContainer
- @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
- fun lockscreenToGlanceableHub() =
- testScope.runTest {
- // GIVEN a prior transition has run to LOCKSCREEN
- runTransitionAndSetWakefulness(KeyguardState.AOD, KeyguardState.LOCKSCREEN)
- runCurrent()
-
- // WHEN a glanceable hub transition starts
- val currentScene = CommunalScenes.Blank
- val targetScene = CommunalScenes.Communal
-
- val progress = MutableStateFlow(0f)
- val transitionState =
- MutableStateFlow<ObservableTransitionState>(
- ObservableTransitionState.Transition(
- fromScene = currentScene,
- toScene = targetScene,
- currentScene = flowOf(targetScene),
- progress = progress,
- isInitiatedByUserInput = false,
- isUserInputOngoing = flowOf(false),
- )
- )
- communalInteractor.setTransitionState(transitionState)
- progress.value = .1f
- runCurrent()
-
- // THEN a transition from LOCKSCREEN => GLANCEABLE_HUB should occur
- assertThat(transitionRepository)
- .startedTransition(
- ownerName = FromLockscreenTransitionInteractor::class.simpleName,
- from = KeyguardState.LOCKSCREEN,
- to = KeyguardState.GLANCEABLE_HUB,
- animatorAssertion = { it.isNull() }, // transition should be manually animated
- )
-
- // WHEN the user stops dragging and the glanceable hub opening is cancelled
- clearInvocations(transitionRepository)
- runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.GLANCEABLE_HUB)
- val idleTransitionState =
- MutableStateFlow<ObservableTransitionState>(
- ObservableTransitionState.Idle(currentScene)
- )
- communalInteractor.setTransitionState(idleTransitionState)
- runCurrent()
-
- // THEN a transition from LOCKSCREEN => GLANCEABLE_HUB should occur
- assertThat(transitionRepository)
- .startedTransition(
- ownerName = FromLockscreenTransitionInteractor::class.simpleName,
- from = KeyguardState.GLANCEABLE_HUB,
- to = KeyguardState.LOCKSCREEN,
- )
-
- coroutineContext.cancelChildren()
- }
-
- @Test
- @DisableSceneContainer
- @EnableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
fun lockscreenToGlanceableHub_communalKtfRefactor() =
testScope.runTest {
// GIVEN a prior transition has run to LOCKSCREEN
@@ -1696,86 +1447,6 @@
@Test
@DisableSceneContainer
- @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
- fun glanceableHubToLockscreen() =
- testScope.runTest {
- // GIVEN a prior transition has run to GLANCEABLE_HUB
- runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.GLANCEABLE_HUB)
- runCurrent()
-
- // WHEN a transition away from glanceable hub starts
- val currentScene = CommunalScenes.Communal
- val targetScene = CommunalScenes.Blank
-
- val progress = MutableStateFlow(0f)
- val transitionState =
- MutableStateFlow<ObservableTransitionState>(
- ObservableTransitionState.Transition(
- fromScene = currentScene,
- toScene = targetScene,
- currentScene = flowOf(targetScene),
- progress = progress,
- isInitiatedByUserInput = false,
- isUserInputOngoing = flowOf(false),
- )
- )
- communalInteractor.setTransitionState(transitionState)
- progress.value = .1f
- runCurrent()
-
- assertThat(transitionRepository)
- .startedTransition(
- ownerName = FromGlanceableHubTransitionInteractor::class.simpleName,
- from = KeyguardState.GLANCEABLE_HUB,
- to = KeyguardState.LOCKSCREEN,
- animatorAssertion = { it.isNull() }, // transition should be manually animated
- )
-
- // WHEN the user stops dragging and the glanceable hub closing is cancelled
- clearInvocations(transitionRepository)
- runTransitionAndSetWakefulness(KeyguardState.GLANCEABLE_HUB, KeyguardState.LOCKSCREEN)
- val idleTransitionState =
- MutableStateFlow<ObservableTransitionState>(
- ObservableTransitionState.Idle(currentScene)
- )
- communalInteractor.setTransitionState(idleTransitionState)
- runCurrent()
-
- assertThat(transitionRepository)
- .startedTransition(
- from = KeyguardState.LOCKSCREEN,
- to = KeyguardState.GLANCEABLE_HUB,
- )
-
- coroutineContext.cancelChildren()
- }
-
- @Test
- @DisableSceneContainer
- @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
- fun glanceableHubToDozing() =
- testScope.runTest {
- // GIVEN a prior transition has run to GLANCEABLE_HUB
- runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.GLANCEABLE_HUB)
-
- // WHEN the device begins to sleep
- powerInteractor.setAsleepForTest()
- runCurrent()
-
- assertThat(transitionRepository)
- .startedTransition(
- ownerName = FromGlanceableHubTransitionInteractor::class.simpleName,
- from = KeyguardState.GLANCEABLE_HUB,
- to = KeyguardState.DOZING,
- animatorAssertion = { it.isNotNull() },
- )
-
- coroutineContext.cancelChildren()
- }
-
- @Test
- @DisableSceneContainer
- @EnableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
fun glanceableHubToDozing_communalKtfRefactor() =
testScope.runTest {
// GIVEN a prior transition has run to GLANCEABLE_HUB
@@ -1844,39 +1515,6 @@
@Test
@BrokenWithSceneContainer(339465026)
- @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
- fun glanceableHubToOccluded() =
- testScope.runTest {
- // GIVEN a prior transition has run to GLANCEABLE_HUB
- runTransitionAndSetWakefulness(KeyguardState.GONE, KeyguardState.GLANCEABLE_HUB)
- runCurrent()
-
- // GIVEN the device is idle on the glanceable hub
- val idleTransitionState =
- MutableStateFlow<ObservableTransitionState>(
- ObservableTransitionState.Idle(CommunalScenes.Communal)
- )
- communalInteractor.setTransitionState(idleTransitionState)
- runCurrent()
-
- // WHEN the keyguard is occluded
- keyguardRepository.setKeyguardOccluded(true)
- runCurrent()
-
- assertThat(transitionRepository)
- .startedTransition(
- ownerName = FromGlanceableHubTransitionInteractor::class.simpleName,
- from = KeyguardState.GLANCEABLE_HUB,
- to = KeyguardState.OCCLUDED,
- animatorAssertion = { it.isNotNull() },
- )
-
- coroutineContext.cancelChildren()
- }
-
- @Test
- @BrokenWithSceneContainer(339465026)
- @EnableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
fun glanceableHubToOccluded_communalKtfRefactor() =
testScope.runTest {
// GIVEN device is not dreaming
@@ -1905,30 +1543,6 @@
@Test
@DisableSceneContainer
- @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
- fun glanceableHubToGone() =
- testScope.runTest {
- // GIVEN a prior transition has run to GLANCEABLE_HUB
- runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.GLANCEABLE_HUB)
-
- // WHEN keyguard goes away
- keyguardRepository.setKeyguardGoingAway(true)
- runCurrent()
-
- assertThat(transitionRepository)
- .startedTransition(
- ownerName = FromGlanceableHubTransitionInteractor::class.simpleName,
- from = KeyguardState.GLANCEABLE_HUB,
- to = KeyguardState.GONE,
- animatorAssertion = { it.isNotNull() },
- )
-
- coroutineContext.cancelChildren()
- }
-
- @Test
- @DisableSceneContainer
- @EnableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
fun glanceableHubToGone_communalKtfRefactor() =
testScope.runTest {
// GIVEN a prior transition has run to GLANCEABLE_HUB
@@ -1953,7 +1567,7 @@
@Test
@DisableSceneContainer
- @EnableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR, FLAG_GLANCEABLE_HUB_V2)
+ @EnableFlags(FLAG_GLANCEABLE_HUB_V2)
fun glanceableHubToDreaming_v2() =
testScope.runTest {
kosmos.setCommunalV2Enabled(true)
@@ -1985,55 +1599,7 @@
}
@Test
- @DisableSceneContainer
- @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
- fun glanceableHubToDreaming() =
- testScope.runTest {
- runCurrent()
-
- // GIVEN that we are dreaming and not dozing
- keyguardRepository.setDreaming(true)
- keyguardRepository.setDozeTransitionModel(
- DozeTransitionModel(from = DozeStateModel.DOZE, to = DozeStateModel.FINISH)
- )
- advanceTimeBy(600L)
-
- // GIVEN a prior transition has run to GLANCEABLE_HUB
- runTransitionAndSetWakefulness(KeyguardState.DREAMING, KeyguardState.GLANCEABLE_HUB)
- runCurrent()
-
- // WHEN a transition away from glanceable hub starts
- val currentScene = CommunalScenes.Communal
- val targetScene = CommunalScenes.Blank
-
- val transitionState =
- MutableStateFlow<ObservableTransitionState>(
- ObservableTransitionState.Transition(
- fromScene = currentScene,
- toScene = targetScene,
- currentScene = flowOf(targetScene),
- progress = flowOf(0f, 0.1f),
- isInitiatedByUserInput = false,
- isUserInputOngoing = flowOf(false),
- )
- )
- communalSceneInteractor.setTransitionState(transitionState)
- runCurrent()
-
- assertThat(transitionRepository)
- .startedTransition(
- ownerName = FromGlanceableHubTransitionInteractor::class.simpleName,
- from = KeyguardState.GLANCEABLE_HUB,
- to = KeyguardState.DREAMING,
- animatorAssertion = { it.isNull() }, // transition should be manually animated
- )
-
- coroutineContext.cancelChildren()
- }
-
- @Test
@BrokenWithSceneContainer(339465026)
- @EnableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
fun glanceableHubToOccludedDoesNotTriggerWhenDreamStateChanges_communalKtfRefactor() =
testScope.runTest {
// GIVEN that we are dreaming and not dozing
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlowTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlowTest.kt
index a0fed6b..0ec3173 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlowTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlowTest.kt
@@ -57,9 +57,7 @@
duration = 1000.milliseconds,
edge = Edge.create(from = Scenes.Gone, to = DREAMING),
)
- .setupWithoutSceneContainer(
- edge = Edge.create(from = GONE, to = DREAMING),
- )
+ .setupWithoutSceneContainer(edge = Edge.create(from = GONE, to = DREAMING))
}
@Test(expected = IllegalArgumentException::class)
@@ -75,7 +73,7 @@
underTest.sharedFlow(
startTime = 300.milliseconds,
duration = 800.milliseconds,
- onStep = { it }
+ onStep = { it },
)
}
@@ -112,6 +110,17 @@
}
@Test
+ fun onStepReturnsNullEmitsNothing() =
+ testScope.runTest {
+ val flow = underTest.sharedFlow(duration = 100.milliseconds, onStep = { null })
+ var animationValues = collectLastValue(flow)
+ runCurrent()
+
+ repository.sendTransitionStep(step(0.5f, TransitionState.RUNNING))
+ assertThat(animationValues()).isNull()
+ }
+
+ @Test
fun usesStartTime() =
testScope.runTest {
val flow =
@@ -166,11 +175,7 @@
@Test
fun usesOnStepToDoubleValue() =
testScope.runTest {
- val flow =
- underTest.sharedFlow(
- duration = 1000.milliseconds,
- onStep = { it * 2 },
- )
+ val flow = underTest.sharedFlow(duration = 1000.milliseconds, onStep = { it * 2 })
val animationValues by collectLastValue(flow)
runCurrent()
@@ -190,10 +195,7 @@
fun usesOnStepToDoubleValueWithState() =
testScope.runTest {
val flow =
- underTest.sharedFlowWithState(
- duration = 1000.milliseconds,
- onStep = { it * 2 },
- )
+ underTest.sharedFlowWithState(duration = 1000.milliseconds, onStep = { it * 2 })
val animationValues by collectLastValue(flow)
runCurrent()
@@ -204,7 +206,7 @@
from = GONE,
to = DREAMING,
transitionState = TransitionState.STARTED,
- value = 0f
+ value = 0f,
)
)
repository.sendTransitionStep(step(0.3f, TransitionState.RUNNING))
@@ -214,7 +216,7 @@
from = GONE,
to = DREAMING,
transitionState = TransitionState.RUNNING,
- value = 0.6f
+ value = 0.6f,
)
)
repository.sendTransitionStep(step(0.6f, TransitionState.RUNNING))
@@ -224,7 +226,7 @@
from = GONE,
to = DREAMING,
transitionState = TransitionState.RUNNING,
- value = 1.2f
+ value = 1.2f,
)
)
repository.sendTransitionStep(step(0.8f, TransitionState.RUNNING))
@@ -234,7 +236,7 @@
from = GONE,
to = DREAMING,
transitionState = TransitionState.RUNNING,
- value = 1.6f
+ value = 1.6f,
)
)
repository.sendTransitionStep(step(1f, TransitionState.RUNNING))
@@ -244,7 +246,7 @@
from = GONE,
to = DREAMING,
transitionState = TransitionState.RUNNING,
- value = 2f
+ value = 2f,
)
)
repository.sendTransitionStep(step(1f, TransitionState.FINISHED))
@@ -254,7 +256,7 @@
from = GONE,
to = DREAMING,
transitionState = TransitionState.FINISHED,
- value = null
+ value = null,
)
)
}
@@ -262,11 +264,7 @@
@Test
fun sameFloatValueWithTheSameTransitionStateDoesNotEmitTwice() =
testScope.runTest {
- val flow =
- underTest.sharedFlow(
- duration = 1000.milliseconds,
- onStep = { it },
- )
+ val flow = underTest.sharedFlow(duration = 1000.milliseconds, onStep = { it })
val values by collectValues(flow)
runCurrent()
@@ -280,11 +278,7 @@
@Test
fun sameFloatValueWithADifferentTransitionStateDoesEmitTwice() =
testScope.runTest {
- val flow =
- underTest.sharedFlow(
- duration = 1000.milliseconds,
- onStep = { it },
- )
+ val flow = underTest.sharedFlow(duration = 1000.milliseconds, onStep = { it })
val values by collectValues(flow)
runCurrent()
@@ -302,14 +296,14 @@
private fun step(
value: Float,
- state: TransitionState = TransitionState.RUNNING
+ state: TransitionState = TransitionState.RUNNING,
): TransitionStep {
return TransitionStep(
from = GONE,
to = DREAMING,
value = value,
transitionState = state,
- ownerName = "GoneToDreamingTransitionViewModelTest"
+ ownerName = "GoneToDreamingTransitionViewModelTest",
)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModelTest.kt
index c8fade3..6648ae2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModelTest.kt
@@ -24,6 +24,7 @@
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionState.FINISHED
import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING
import com.android.systemui.keyguard.shared.model.TransitionState.STARTED
import com.android.systemui.keyguard.shared.model.TransitionStep
@@ -63,7 +64,15 @@
)
runCurrent()
- assertThat(alpha).isEqualTo(0.0f)
+ assertThat(alpha).isEqualTo(1.0f)
+
+ kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps(
+ listOf(lockscreenToDozing(1f, FINISHED)),
+ testScope,
+ )
+ runCurrent()
+
+ assertThat(alpha).isEqualTo(0f)
}
private fun lockscreenToDozing(value: Float, state: TransitionState = RUNNING): TransitionStep {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenUserActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenUserActionsViewModelTest.kt
index 47ca4b1..f357d0c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenUserActionsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenUserActionsViewModelTest.kt
@@ -69,7 +69,7 @@
@Parameters(
name =
"canSwipeToEnter={0}, downWithTwoPointers={1}, downFromEdge={2}," +
- " isSingleShade={3}, isShadeTouchable={4}, isOccluded={6}"
+ " isSingleShade={3}, isShadeTouchable={4}, isOccluded={5}"
)
@JvmStatic
fun combinations() = buildList {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/view/MediaCarouselScrollHandlerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/view/MediaCarouselScrollHandlerTest.kt
index d073cf1..c2f0ab9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/view/MediaCarouselScrollHandlerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/view/MediaCarouselScrollHandlerTest.kt
@@ -16,8 +16,11 @@
package com.android.systemui.media.controls.ui.view
+import android.content.res.Resources
import android.testing.TestableLooper
import android.view.MotionEvent
+import android.view.View
+import android.view.ViewGroup
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -25,16 +28,21 @@
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.qs.PageIndicator
import com.android.systemui.util.concurrency.FakeExecutor
-import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
+import com.android.wm.shell.shared.animation.PhysicsAnimatorTestUtils
+import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyFloat
import org.mockito.Mock
import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.eq
+import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
@SmallTest
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@@ -42,7 +50,9 @@
class MediaCarouselScrollHandlerTest : SysuiTestCase() {
private val carouselWidth = 1038
+ private val settingsButtonWidth = 200
private val motionEventUp = MotionEvent.obtain(0, 0, MotionEvent.ACTION_UP, 0f, 0f, 0)
+ private lateinit var testableLooper: TestableLooper
@Mock lateinit var mediaCarousel: MediaScrollView
@Mock lateinit var pageIndicator: PageIndicator
@@ -53,6 +63,9 @@
@Mock lateinit var falsingManager: FalsingManager
@Mock lateinit var logSmartspaceImpression: (Boolean) -> Unit
@Mock lateinit var logger: MediaUiEventLogger
+ @Mock lateinit var contentContainer: ViewGroup
+ @Mock lateinit var settingsButton: View
+ @Mock lateinit var resources: Resources
lateinit var executor: FakeExecutor
private val clock = FakeSystemClock()
@@ -63,6 +76,11 @@
fun setup() {
MockitoAnnotations.initMocks(this)
executor = FakeExecutor(clock)
+ testableLooper = TestableLooper.get(this)
+ PhysicsAnimatorTestUtils.prepareForTest()
+ PhysicsAnimatorTestUtils.setAllAnimationsBlock(true)
+
+ whenever(mediaCarousel.contentContainer).thenReturn(contentContainer)
mediaCarouselScrollHandler =
MediaCarouselScrollHandler(
mediaCarousel,
@@ -74,13 +92,17 @@
closeGuts,
falsingManager,
logSmartspaceImpression,
- logger
+ logger,
)
mediaCarouselScrollHandler.playerWidthPlusPadding = carouselWidth
-
whenever(mediaCarousel.touchListener).thenReturn(mediaCarouselScrollHandler.touchListener)
}
+ @After
+ fun tearDown() {
+ PhysicsAnimatorTestUtils.tearDown()
+ }
+
@Test
fun testCarouselScroll_shortScroll() {
whenever(mediaCarousel.isLayoutRtl).thenReturn(false)
@@ -128,4 +150,109 @@
verify(mediaCarousel).smoothScrollTo(eq(0), anyInt())
}
+
+ @Test
+ fun testCarouselScrollByStep_scrollRight() {
+ setupMediaContainer(visibleIndex = 0)
+
+ mediaCarouselScrollHandler.scrollByStep(1)
+ clock.advanceTime(DISMISS_DELAY)
+ executor.runAllReady()
+
+ verify(mediaCarousel).smoothScrollTo(eq(carouselWidth), anyInt())
+ }
+
+ @Test
+ fun testCarouselScrollByStep_scrollLeft() {
+ setupMediaContainer(visibleIndex = 1)
+
+ mediaCarouselScrollHandler.scrollByStep(-1)
+ clock.advanceTime(DISMISS_DELAY)
+ executor.runAllReady()
+
+ verify(mediaCarousel).smoothScrollTo(eq(0), anyInt())
+ }
+
+ @Test
+ fun testCarouselScrollByStep_scrollRight_alreadyAtEnd() {
+ setupMediaContainer(visibleIndex = 1)
+
+ mediaCarouselScrollHandler.scrollByStep(1)
+ clock.advanceTime(DISMISS_DELAY)
+ executor.runAllReady()
+
+ verify(mediaCarousel, never()).smoothScrollTo(anyInt(), anyInt())
+ verify(mediaCarousel).animationTargetX = eq(-settingsButtonWidth.toFloat())
+ }
+
+ @Test
+ fun testCarouselScrollByStep_scrollLeft_alreadyAtStart() {
+ setupMediaContainer(visibleIndex = 0)
+
+ mediaCarouselScrollHandler.scrollByStep(-1)
+ clock.advanceTime(DISMISS_DELAY)
+ executor.runAllReady()
+
+ verify(mediaCarousel, never()).smoothScrollTo(anyInt(), anyInt())
+ verify(mediaCarousel).animationTargetX = eq(settingsButtonWidth.toFloat())
+ }
+
+ @Test
+ fun testCarouselScrollByStep_scrollLeft_alreadyAtStart_isRTL() {
+ setupMediaContainer(visibleIndex = 0)
+ PhysicsAnimatorTestUtils.setAllAnimationsBlock(true)
+ whenever(mediaCarousel.isLayoutRtl).thenReturn(true)
+
+ mediaCarouselScrollHandler.scrollByStep(-1)
+ clock.advanceTime(DISMISS_DELAY)
+ executor.runAllReady()
+
+ verify(mediaCarousel, never()).smoothScrollTo(anyInt(), anyInt())
+ verify(mediaCarousel).animationTargetX = eq(-settingsButtonWidth.toFloat())
+ }
+
+ @Test
+ fun testCarouselScrollByStep_scrollRight_alreadyAtEnd_isRTL() {
+ setupMediaContainer(visibleIndex = 1)
+ PhysicsAnimatorTestUtils.setAllAnimationsBlock(true)
+ whenever(mediaCarousel.isLayoutRtl).thenReturn(true)
+
+ mediaCarouselScrollHandler.scrollByStep(1)
+ clock.advanceTime(DISMISS_DELAY)
+ executor.runAllReady()
+
+ verify(mediaCarousel, never()).smoothScrollTo(anyInt(), anyInt())
+ verify(mediaCarousel).animationTargetX = eq(settingsButtonWidth.toFloat())
+ }
+
+ @Test
+ fun testScrollByStep_noScroll_notDismissible() {
+ setupMediaContainer(visibleIndex = 1, showsSettingsButton = false)
+
+ mediaCarouselScrollHandler.scrollByStep(1)
+ clock.advanceTime(DISMISS_DELAY)
+ executor.runAllReady()
+
+ verify(mediaCarousel, never()).smoothScrollTo(anyInt(), anyInt())
+ verify(mediaCarousel, never()).animationTargetX = anyFloat()
+ }
+
+ private fun setupMediaContainer(visibleIndex: Int, showsSettingsButton: Boolean = true) {
+ whenever(contentContainer.childCount).thenReturn(2)
+ val child1: View = mock()
+ val child2: View = mock()
+ whenever(child1.left).thenReturn(0)
+ whenever(child2.left).thenReturn(carouselWidth)
+ whenever(contentContainer.getChildAt(0)).thenReturn(child1)
+ whenever(contentContainer.getChildAt(1)).thenReturn(child2)
+
+ whenever(settingsButton.width).thenReturn(settingsButtonWidth)
+ whenever(settingsButton.context).thenReturn(context)
+ whenever(settingsButton.resources).thenReturn(resources)
+ whenever(settingsButton.resources.getDimensionPixelSize(anyInt())).thenReturn(20)
+ mediaCarouselScrollHandler.onSettingsButtonUpdated(settingsButton)
+
+ mediaCarouselScrollHandler.visibleMediaIndex = visibleIndex
+ mediaCarouselScrollHandler.showsSettingsButton = showsSettingsButton
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
index 847044a..95d9c8f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
@@ -386,6 +386,7 @@
verify(mMediaSwitchingController).logInteractionAdjustVolume(mMediaDevice1);
}
+ @DisableFlags(Flags.FLAG_ENABLE_OUTPUT_SWITCHER_DEVICE_GROUPING)
@Test
public void onBindViewHolder_bindSelectableDevice_verifyView() {
List<MediaDevice> selectableDevices = new ArrayList<>();
@@ -396,10 +397,55 @@
assertThat(mViewHolder.mProgressBar.getVisibility()).isEqualTo(View.GONE);
assertThat(mViewHolder.mEndTouchArea.getVisibility()).isEqualTo(View.VISIBLE);
assertThat(mViewHolder.mCheckBox.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mViewHolder.mCheckBox.isChecked()).isFalse();
assertThat(mViewHolder.mEndClickIcon.getVisibility()).isEqualTo(View.GONE);
assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.VISIBLE);
assertThat(mViewHolder.mTitleText.getText().toString()).isEqualTo(TEST_DEVICE_NAME_2);
assertThat(mViewHolder.mContainerLayout.isFocusable()).isTrue();
+
+ mViewHolder.mCheckBox.performClick();
+ verify(mMediaSwitchingController).addDeviceToPlayMedia(mMediaDevice2);
+ }
+
+ @DisableFlags(Flags.FLAG_ENABLE_OUTPUT_SWITCHER_DEVICE_GROUPING)
+ @Test
+ public void onBindViewHolder_bindDeselectableDevice_verifyView() {
+ when(mMediaSwitchingController.getSelectedMediaDevice()).thenReturn(
+ List.of(mMediaDevice1, mMediaDevice2));
+ when(mMediaSwitchingController.getDeselectableMediaDevice()).thenReturn(
+ List.of(mMediaDevice1, mMediaDevice2));
+ mMediaOutputAdapter.onBindViewHolder(mViewHolder, 1);
+
+ assertThat(mViewHolder.mEndTouchArea.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mViewHolder.mCheckBox.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mViewHolder.mCheckBox.isChecked()).isTrue();
+ assertThat(mViewHolder.mEndClickIcon.getVisibility()).isEqualTo(View.GONE);
+
+ mViewHolder.mCheckBox.performClick();
+ verify(mMediaSwitchingController).removeDeviceFromPlayMedia(mMediaDevice2);
+ }
+
+ @DisableFlags(Flags.FLAG_ENABLE_OUTPUT_SWITCHER_DEVICE_GROUPING)
+ @Test
+ public void onBindViewHolder_changingSelectedValue_doesntTriggerChangeListener() {
+ List<MediaDevice> selectableDevices = List.of(mMediaDevice2);
+ List<MediaDevice> selectedDevices = new ArrayList<>();
+ selectedDevices.add(mMediaDevice1);
+ when(mMediaSwitchingController.getSelectableMediaDevice()).thenReturn(selectableDevices);
+ when(mMediaSwitchingController.getSelectedMediaDevice()).thenReturn(selectedDevices);
+
+ // mMediaDevice2 is selected
+ mMediaOutputAdapter.onBindViewHolder(mViewHolder, 1);
+ assertThat(mViewHolder.mCheckBox.isChecked()).isFalse();
+
+ // changing the selected state programmatically (not a user click)
+ selectedDevices.add(mMediaDevice2);
+ mMediaOutputAdapter.onBindViewHolder(mViewHolder, 1);
+ assertThat(mViewHolder.mCheckBox.isChecked()).isTrue();
+
+ // The onCheckedChangeListener is not invoked
+ verify(mMediaSwitchingController, never()).addDeviceToPlayMedia(mMediaDevice2);
+ verify(mMediaSwitchingController, never()).removeDeviceFromPlayMedia(mMediaDevice2);
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt
index 896ca26..3c8857f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt
@@ -17,11 +17,14 @@
package com.android.systemui.qs
import android.content.res.Configuration
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
import android.view.ContextThemeWrapper
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.logging.MetricsLogger
import com.android.internal.logging.testing.UiEventLoggerFake
+import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
import com.android.systemui.haptics.qs.QSLongPressEffect
@@ -33,6 +36,7 @@
import com.android.systemui.qs.logging.QSLogger
import com.android.systemui.res.R
import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener
import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController
import com.android.systemui.util.leak.RotationUtils
import javax.inject.Provider
@@ -63,6 +67,7 @@
@Mock private lateinit var tile: QSTile
@Mock private lateinit var tileLayout: TileLayout
@Captor private lateinit var captor: ArgumentCaptor<QSPanel.OnConfigurationChangedListener>
+ @Captor private lateinit var configCaptor: ArgumentCaptor<ConfigurationListener>
@Mock private lateinit var longPressEffectProvider: Provider<QSLongPressEffect>
@Mock private lateinit var mediaCarouselInteractor: MediaCarouselInteractor
@Mock private lateinit var configurationController: ConfigurationController
@@ -135,7 +140,8 @@
}
@Test
- fun mediaExpansion_afterConfigChange_inLandscape_collapsedInLandscapeTrue_updatesToCollapsed() {
+ @DisableFlags(Flags.FLAG_SHADE_WINDOW_GOES_AROUND)
+ fun mediaExpansion_afterConfigChange_inLandscape_collapsedInLandscapeTrue_updatesToCollapsed_old() {
verify(quickQSPanel).addOnConfigurationChangedListener(captor.capture())
// verify that media starts in the expanded state by default
@@ -150,7 +156,24 @@
}
@Test
- fun mediaExpansion_afterConfigChange_landscape_collapsedInLandscapeFalse_remainsExpanded() {
+ @EnableFlags(Flags.FLAG_SHADE_WINDOW_GOES_AROUND)
+ fun mediaExpansion_afterConfigChange_inLandscape_collapsedInLandscapeTrue_updatesToCollapsed() {
+ verify(configurationController).addCallback(configCaptor.capture())
+
+ // verify that media starts in the expanded state by default
+ verify(mediaHost).expansion = MediaHostState.EXPANDED
+
+ // Rotate device, verify media size updated to collapsed
+ usingCollapsedLandscapeMedia = true
+ controller.setRotation(RotationUtils.ROTATION_LANDSCAPE)
+ configCaptor.allValues.forEach { it.onConfigChanged(Configuration.EMPTY) }
+
+ verify(mediaHost).expansion = MediaHostState.COLLAPSED
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_SHADE_WINDOW_GOES_AROUND)
+ fun mediaExpansion_afterConfigChange_landscape_collapsedInLandscapeFalse_remainsExpanded_old() {
verify(quickQSPanel).addOnConfigurationChangedListener(captor.capture())
reset(mediaHost)
@@ -161,6 +184,19 @@
verify(mediaHost).expansion = MediaHostState.EXPANDED
}
+ @Test
+ @EnableFlags(Flags.FLAG_SHADE_WINDOW_GOES_AROUND)
+ fun mediaExpansion_afterConfigChange_landscape_collapsedInLandscapeFalse_remainsExpanded() {
+ verify(configurationController).addCallback(configCaptor.capture())
+ reset(mediaHost)
+
+ usingCollapsedLandscapeMedia = false
+ controller.setRotation(RotationUtils.ROTATION_LANDSCAPE)
+ configCaptor.allValues.forEach { it.onConfigChanged(Configuration.EMPTY) }
+
+ verify(mediaHost).expansion = MediaHostState.EXPANDED
+ }
+
class TestQuickQSPanelController(
view: QuickQSPanel,
qsHost: QSHost,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/QSPreferencesRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/QSPreferencesRepositoryTest.kt
index 264eda5..668c606 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/QSPreferencesRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/QSPreferencesRepositoryTest.kt
@@ -25,6 +25,7 @@
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.testScope
import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.pipeline.shared.TilesUpgradePath
import com.android.systemui.settings.userFileManager
import com.android.systemui.testKosmos
import com.android.systemui.user.data.repository.fakeUserRepository
@@ -76,11 +77,11 @@
@Test
fun setLargeTilesSpecs_inSharedPreferences() {
val setA = setOf("tileA", "tileB")
- underTest.setLargeTilesSpecs(setA.toTileSpecs())
+ underTest.writeLargeTileSpecs(setA.toTileSpecs())
assertThat(getLargeTilesSpecsFromSharedPreferences()).isEqualTo(setA)
val setB = setOf("tileA", "tileB")
- underTest.setLargeTilesSpecs(setB.toTileSpecs())
+ underTest.writeLargeTileSpecs(setB.toTileSpecs())
assertThat(getLargeTilesSpecsFromSharedPreferences()).isEqualTo(setB)
}
@@ -92,12 +93,12 @@
fakeUserRepository.setSelectedUserInfo(PRIMARY_USER)
val setA = setOf("tileA", "tileB")
- underTest.setLargeTilesSpecs(setA.toTileSpecs())
+ underTest.writeLargeTileSpecs(setA.toTileSpecs())
assertThat(getLargeTilesSpecsFromSharedPreferences()).isEqualTo(setA)
fakeUserRepository.setSelectedUserInfo(ANOTHER_USER)
val setB = setOf("tileA", "tileB")
- underTest.setLargeTilesSpecs(setB.toTileSpecs())
+ underTest.writeLargeTileSpecs(setB.toTileSpecs())
assertThat(getLargeTilesSpecsFromSharedPreferences()).isEqualTo(setB)
fakeUserRepository.setSelectedUserInfo(PRIMARY_USER)
@@ -106,7 +107,7 @@
}
@Test
- fun setInitialTilesFromSettings_noLargeTiles_tilesSet() =
+ fun setUpgradePathFromSettings_noLargeTiles_tilesSet() =
with(kosmos) {
testScope.runTest {
val largeTiles by collectLastValue(underTest.largeTilesSpecs)
@@ -117,14 +118,17 @@
assertThat(getSharedPreferences().contains(LARGE_TILES_SPECS_KEY)).isFalse()
- underTest.setInitialLargeTilesSpecs(tiles, PRIMARY_USER_ID)
+ underTest.setInitialOrUpgradeLargeTiles(
+ TilesUpgradePath.ReadFromSettings(tiles),
+ PRIMARY_USER_ID,
+ )
assertThat(largeTiles).isEqualTo(tiles)
}
}
@Test
- fun setInitialTilesFromSettings_alreadyLargeTiles_tilesNotSet() =
+ fun setUpgradePathFromSettings_alreadyLargeTiles_tilesNotSet() =
with(kosmos) {
testScope.runTest {
val largeTiles by collectLastValue(underTest.largeTilesSpecs)
@@ -133,14 +137,17 @@
fakeUserRepository.setSelectedUserInfo(ANOTHER_USER)
setLargeTilesSpecsInSharedPreferences(setOf("tileC"))
- underTest.setInitialLargeTilesSpecs(setOf("tileA").toTileSpecs(), ANOTHER_USER_ID)
+ underTest.setInitialOrUpgradeLargeTiles(
+ TilesUpgradePath.ReadFromSettings(setOf("tileA").toTileSpecs()),
+ ANOTHER_USER_ID,
+ )
assertThat(largeTiles).isEqualTo(setOf("tileC").toTileSpecs())
}
}
@Test
- fun setInitialTilesFromSettings_emptyLargeTiles_tilesNotSet() =
+ fun setUpgradePathFromSettings_emptyLargeTiles_tilesNotSet() =
with(kosmos) {
testScope.runTest {
val largeTiles by collectLastValue(underTest.largeTilesSpecs)
@@ -149,14 +156,17 @@
fakeUserRepository.setSelectedUserInfo(ANOTHER_USER)
setLargeTilesSpecsInSharedPreferences(emptySet())
- underTest.setInitialLargeTilesSpecs(setOf("tileA").toTileSpecs(), ANOTHER_USER_ID)
+ underTest.setInitialOrUpgradeLargeTiles(
+ TilesUpgradePath.ReadFromSettings(setOf("tileA").toTileSpecs()),
+ ANOTHER_USER_ID,
+ )
assertThat(largeTiles).isEmpty()
}
}
@Test
- fun setInitialTilesFromSettings_nonCurrentUser_tilesSetForCorrectUser() =
+ fun setUpgradePathFromSettings_nonCurrentUser_tilesSetForCorrectUser() =
with(kosmos) {
testScope.runTest {
val largeTiles by collectLastValue(underTest.largeTilesSpecs)
@@ -164,7 +174,10 @@
fakeUserRepository.setUserInfos(USERS)
fakeUserRepository.setSelectedUserInfo(PRIMARY_USER)
- underTest.setInitialLargeTilesSpecs(setOf("tileA").toTileSpecs(), ANOTHER_USER_ID)
+ underTest.setInitialOrUpgradeLargeTiles(
+ TilesUpgradePath.ReadFromSettings(setOf("tileA").toTileSpecs()),
+ ANOTHER_USER_ID,
+ )
assertThat(largeTiles).isEqualTo(defaultLargeTilesRepository.defaultLargeTiles)
@@ -174,7 +187,7 @@
}
@Test
- fun setInitialTiles_afterDefaultRead_noSetOnRepository_initialTilesCorrect() =
+ fun setUpgradePath_afterDefaultRead_noSetOnRepository_initialTilesCorrect() =
with(kosmos) {
testScope.runTest {
val largeTiles by collectLastValue(underTest.largeTilesSpecs)
@@ -186,14 +199,17 @@
assertThat(currentLargeTiles).isNotEmpty()
val tiles = setOf("tileA", "tileB")
- underTest.setInitialLargeTilesSpecs(tiles.toTileSpecs(), PRIMARY_USER_ID)
+ underTest.setInitialOrUpgradeLargeTiles(
+ TilesUpgradePath.ReadFromSettings(tiles.toTileSpecs()),
+ PRIMARY_USER_ID,
+ )
assertThat(largeTiles).isEqualTo(tiles.toTileSpecs())
}
}
@Test
- fun setInitialTiles_afterDefaultRead_largeTilesSetOnRepository_initialTilesCorrect() =
+ fun setUpgradePath_afterDefaultRead_largeTilesSetOnRepository_initialTilesCorrect() =
with(kosmos) {
testScope.runTest {
val largeTiles by collectLastValue(underTest.largeTilesSpecs)
@@ -204,15 +220,80 @@
assertThat(currentLargeTiles).isNotEmpty()
- underTest.setLargeTilesSpecs(setOf(TileSpec.create("tileC")))
+ underTest.writeLargeTileSpecs(setOf(TileSpec.create("tileC")))
val tiles = setOf("tileA", "tileB")
- underTest.setInitialLargeTilesSpecs(tiles.toTileSpecs(), PRIMARY_USER_ID)
+ underTest.setInitialOrUpgradeLargeTiles(
+ TilesUpgradePath.ReadFromSettings(tiles.toTileSpecs()),
+ PRIMARY_USER_ID,
+ )
assertThat(largeTiles).isEqualTo(setOf(TileSpec.create("tileC")))
}
}
+ @Test
+ fun setTilesRestored_noLargeTiles_tilesSet() =
+ with(kosmos) {
+ testScope.runTest {
+ val largeTiles by collectLastValue(underTest.largeTilesSpecs)
+
+ fakeUserRepository.setUserInfos(USERS)
+ fakeUserRepository.setSelectedUserInfo(PRIMARY_USER)
+ val tiles = setOf("tileA", "tileB").toTileSpecs()
+
+ assertThat(getSharedPreferences().contains(LARGE_TILES_SPECS_KEY)).isFalse()
+
+ underTest.setInitialOrUpgradeLargeTiles(
+ TilesUpgradePath.RestoreFromBackup(tiles),
+ PRIMARY_USER_ID,
+ )
+
+ assertThat(largeTiles).isEqualTo(tiles)
+ }
+ }
+
+ @Test
+ fun setDefaultTilesInitial_defaultSetLarge() =
+ with(kosmos) {
+ testScope.runTest {
+ val largeTiles by collectLastValue(underTest.largeTilesSpecs)
+
+ fakeUserRepository.setUserInfos(USERS)
+ fakeUserRepository.setSelectedUserInfo(PRIMARY_USER)
+
+ underTest.setInitialOrUpgradeLargeTiles(
+ TilesUpgradePath.DefaultSet,
+ PRIMARY_USER_ID,
+ )
+
+ assertThat(largeTiles).isEqualTo(defaultLargeTilesRepository.defaultLargeTiles)
+ }
+ }
+
+ @Test
+ fun setTilesRestored_afterDefaultSet_tilesSet() =
+ with(kosmos) {
+ testScope.runTest {
+ underTest.setInitialOrUpgradeLargeTiles(
+ TilesUpgradePath.DefaultSet,
+ PRIMARY_USER_ID,
+ )
+ val largeTiles by collectLastValue(underTest.largeTilesSpecs)
+
+ fakeUserRepository.setUserInfos(USERS)
+ fakeUserRepository.setSelectedUserInfo(PRIMARY_USER)
+ val tiles = setOf("tileA", "tileB").toTileSpecs()
+
+ underTest.setInitialOrUpgradeLargeTiles(
+ TilesUpgradePath.RestoreFromBackup(tiles),
+ PRIMARY_USER_ID,
+ )
+
+ assertThat(largeTiles).isEqualTo(tiles)
+ }
+ }
+
private fun getSharedPreferences(): SharedPreferences =
with(kosmos) {
return userFileManager.getSharedPreferences(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/LargeTilesUpgradePathsTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/LargeTilesUpgradePathsTest.kt
new file mode 100644
index 0000000..f3c1f0c
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/LargeTilesUpgradePathsTest.kt
@@ -0,0 +1,328 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.panels.domain
+
+import android.content.Context
+import android.content.Intent
+import android.content.SharedPreferences
+import android.content.res.mainResources
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.backup.BackupHelper.Companion.ACTION_RESTORE_FINISHED
+import com.android.systemui.broadcast.broadcastDispatcher
+import com.android.systemui.common.shared.model.PackageChangeModel.Empty.packageName
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.collectLastValue
+import com.android.systemui.kosmos.runTest
+import com.android.systemui.qs.panels.data.repository.QSPreferencesRepository
+import com.android.systemui.qs.panels.data.repository.defaultLargeTilesRepository
+import com.android.systemui.qs.panels.domain.interactor.qsPreferencesInteractor
+import com.android.systemui.qs.pipeline.data.repository.DefaultTilesQSHostRepository
+import com.android.systemui.qs.pipeline.data.repository.defaultTilesRepository
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.pipeline.shared.TilesUpgradePath
+import com.android.systemui.settings.userFileManager
+import com.android.systemui.testKosmos
+import com.android.systemui.user.data.repository.userRepository
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class LargeTilesUpgradePathsTest : SysuiTestCase() {
+
+ private val kosmos =
+ testKosmos().apply { defaultTilesRepository = DefaultTilesQSHostRepository(mainResources) }
+
+ private val defaultTiles = kosmos.defaultTilesRepository.defaultTiles.toSet()
+
+ private val underTest = kosmos.qsPreferencesInteractor
+
+ private val Kosmos.userId
+ get() = userRepository.getSelectedUserInfo().id
+
+ private val Kosmos.intent
+ get() =
+ Intent(ACTION_RESTORE_FINISHED).apply {
+ `package` = packageName
+ putExtra(Intent.EXTRA_USER_ID, kosmos.userId)
+ flags = Intent.FLAG_RECEIVER_REGISTERED_ONLY
+ }
+
+ /**
+ * This test corresponds to the case of a fresh start.
+ *
+ * The resulting large tiles are the default set of large tiles.
+ */
+ @Test
+ fun defaultTiles_noDataInSharedPreferences_defaultLargeTiles() =
+ kosmos.runTest {
+ val largeTiles by collectLastValue(underTest.largeTilesSpecs)
+
+ underTest.setInitialOrUpgradeLargeTilesSpecs(TilesUpgradePath.DefaultSet, userId)
+
+ assertThat(largeTiles).isEqualTo(defaultLargeTilesRepository.defaultLargeTiles)
+ }
+
+ /**
+ * This test corresponds to a user that upgraded in place from a build that didn't support large
+ * tiles to one that does. The current tiles of the user are read from settings.
+ *
+ * The resulting large tiles are those that were read from Settings.
+ */
+ @Test
+ fun upgradeInPlace_noDataInSharedPreferences_allLargeTiles() =
+ kosmos.runTest {
+ val largeTiles by collectLastValue(underTest.largeTilesSpecs)
+ val tiles = setOf("a", "b", "c").toTileSpecs()
+
+ underTest.setInitialOrUpgradeLargeTilesSpecs(
+ TilesUpgradePath.ReadFromSettings(tiles),
+ userId,
+ )
+
+ assertThat(largeTiles).isEqualTo(tiles)
+ }
+
+ /**
+ * This test corresponds to a fresh start, and then the user restarts the device, without ever
+ * having modified the set of large tiles.
+ *
+ * The resulting large tiles are the default large tiles that were set on the fresh start
+ */
+ @Test
+ fun defaultSet_restartDevice_largeTilesDontChange() =
+ kosmos.runTest {
+ val largeTiles by collectLastValue(underTest.largeTilesSpecs)
+
+ underTest.setInitialOrUpgradeLargeTilesSpecs(TilesUpgradePath.DefaultSet, userId)
+
+ // User restarts the device, this will send a read from settings with the default
+ // set of tiles
+
+ underTest.setInitialOrUpgradeLargeTilesSpecs(
+ TilesUpgradePath.ReadFromSettings(defaultTiles),
+ userId,
+ )
+
+ assertThat(largeTiles).isEqualTo(defaultLargeTilesRepository.defaultLargeTiles)
+ }
+
+ /**
+ * This test corresponds to a fresh start, following the user changing the sizes of some tiles.
+ * After that, the user restarts the device.
+ *
+ * The resulting set of large tiles are those that the user determined before restarting the
+ * device.
+ */
+ @Test
+ fun defaultSet_someSizeChanges_restart_correctSet() =
+ kosmos.runTest {
+ val largeTiles by collectLastValue(underTest.largeTilesSpecs)
+ underTest.setInitialOrUpgradeLargeTilesSpecs(TilesUpgradePath.DefaultSet, userId)
+
+ underTest.setLargeTilesSpecs(largeTiles!! + setOf("a", "b").toTileSpecs())
+ val largeTilesBeforeRestart = largeTiles!!
+
+ // Restart
+
+ underTest.setInitialOrUpgradeLargeTilesSpecs(
+ TilesUpgradePath.ReadFromSettings(defaultTiles),
+ userId,
+ )
+ assertThat(largeTiles).isEqualTo(largeTilesBeforeRestart)
+ }
+
+ /**
+ * This test corresponds to a user that upgraded, and after that performed some size changes.
+ * After that, the user restarts the device.
+ *
+ * The resulting set of large tiles are those that the user determined before restarting the
+ * device.
+ */
+ @Test
+ fun readFromSettings_changeSizes_restart_newLargeSet() =
+ kosmos.runTest {
+ val largeTiles by collectLastValue(underTest.largeTilesSpecs)
+ val readTiles = setOf("a", "b", "c").toTileSpecs()
+
+ underTest.setInitialOrUpgradeLargeTilesSpecs(
+ TilesUpgradePath.ReadFromSettings(readTiles),
+ userId,
+ )
+ underTest.setLargeTilesSpecs(emptySet())
+
+ assertThat(largeTiles).isEmpty()
+
+ // Restart
+ underTest.setInitialOrUpgradeLargeTilesSpecs(
+ TilesUpgradePath.ReadFromSettings(readTiles),
+ userId,
+ )
+ assertThat(largeTiles).isEmpty()
+ }
+
+ /**
+ * This test corresponds to a user that upgraded from a build that didn't support tile sizes to
+ * one that does, via restore from backup. Note that there's no file in SharedPreferences to
+ * restore.
+ *
+ * The resulting set of large tiles are those that were restored from the backup.
+ */
+ @Test
+ fun restoreFromBackup_noDataInSharedPreferences_allLargeTiles() =
+ kosmos.runTest {
+ val largeTiles by collectLastValue(underTest.largeTilesSpecs)
+ val tiles = setOf("a", "b", "c").toTileSpecs()
+
+ underTest.setInitialOrUpgradeLargeTilesSpecs(
+ TilesUpgradePath.RestoreFromBackup(tiles),
+ userId,
+ )
+
+ assertThat(largeTiles).isEqualTo(tiles)
+ }
+
+ /**
+ * This test corresponds to a user that upgraded from a build that didn't support tile sizes to
+ * one that does, via restore from backup. However, the restore happens after SystemUI's
+ * initialization has set the tiles to default. Note that there's no file in SharedPreferences
+ * to restore.
+ *
+ * The resulting set of large tiles are those that were restored from the backup.
+ */
+ @Test
+ fun restoreFromBackup_afterDefault_noDataInSharedPreferences_allLargeTiles() =
+ kosmos.runTest {
+ val largeTiles by collectLastValue(underTest.largeTilesSpecs)
+ underTest.setInitialOrUpgradeLargeTilesSpecs(TilesUpgradePath.DefaultSet, userId)
+
+ val tiles = setOf("a", "b", "c").toTileSpecs()
+
+ underTest.setInitialOrUpgradeLargeTilesSpecs(
+ TilesUpgradePath.RestoreFromBackup(tiles),
+ userId,
+ )
+
+ assertThat(largeTiles).isEqualTo(tiles)
+ }
+
+ /**
+ * This test corresponds to a user that restored from a build that supported different sizes
+ * tiles. First the list of tiles is restored in Settings and then a file containing some large
+ * tiles overrides the current shared preferences file
+ *
+ * The resulting set of large tiles are those that were restored from the shared preferences
+ * backup (and not the full list).
+ */
+ @Test
+ fun restoreFromBackup_thenRestoreOfSharedPrefs_sharedPrefsAreLarge() =
+ kosmos.runTest {
+ val largeTiles by collectLastValue(underTest.largeTilesSpecs)
+ val tiles = setOf("a", "b", "c").toTileSpecs()
+ underTest.setInitialOrUpgradeLargeTilesSpecs(
+ TilesUpgradePath.RestoreFromBackup(tiles),
+ userId,
+ )
+
+ val tilesFromBackupOfSharedPrefs = setOf("a")
+ setLargeTilesSpecsInSharedPreferences(tilesFromBackupOfSharedPrefs)
+ broadcastDispatcher.sendIntentToMatchingReceiversOnly(context, intent)
+
+ assertThat(largeTiles).isEqualTo(tilesFromBackupOfSharedPrefs.toTileSpecs())
+ }
+
+ /**
+ * This test corresponds to a user that restored from a build that supported different sizes
+ * tiles. However, this restore of settings happened after SystemUI's restore of the SharedPrefs
+ * containing the user's previous selections to large/small tiles.
+ *
+ * The resulting set of large tiles are those that were restored from the shared preferences
+ * backup (and not the full list).
+ */
+ @Test
+ fun restoreFromBackup_afterRestoreOfSharedPrefs_sharedPrefsAreLarge() =
+ kosmos.runTest {
+ val largeTiles by collectLastValue(underTest.largeTilesSpecs)
+ val tiles = setOf("a", "b", "c").toTileSpecs()
+ val tilesFromBackupOfSharedPrefs = setOf("a")
+
+ setLargeTilesSpecsInSharedPreferences(tilesFromBackupOfSharedPrefs)
+ broadcastDispatcher.sendIntentToMatchingReceiversOnly(context, intent)
+
+ underTest.setInitialOrUpgradeLargeTilesSpecs(
+ TilesUpgradePath.RestoreFromBackup(tiles),
+ userId,
+ )
+
+ assertThat(largeTiles).isEqualTo(tilesFromBackupOfSharedPrefs.toTileSpecs())
+ }
+
+ /**
+ * This test corresponds to a user that upgraded from a build that didn't support tile sizes to
+ * one that does, via restore from backup. After that, the user modifies the size of some tiles
+ * and then restarts the device.
+ *
+ * The resulting set of large tiles are those after the user modifications.
+ */
+ @Test
+ fun restoreFromBackup_changeSizes_restart_newLargeSet() =
+ kosmos.runTest {
+ val largeTiles by collectLastValue(underTest.largeTilesSpecs)
+ val readTiles = setOf("a", "b", "c").toTileSpecs()
+
+ underTest.setInitialOrUpgradeLargeTilesSpecs(
+ TilesUpgradePath.RestoreFromBackup(readTiles),
+ userId,
+ )
+ underTest.setLargeTilesSpecs(emptySet())
+
+ assertThat(largeTiles).isEmpty()
+
+ // Restart
+ underTest.setInitialOrUpgradeLargeTilesSpecs(
+ TilesUpgradePath.ReadFromSettings(readTiles),
+ userId,
+ )
+ assertThat(largeTiles).isEmpty()
+ }
+
+ private companion object {
+ private const val LARGE_TILES_SPECS_KEY = "large_tiles_specs"
+
+ private fun Kosmos.getSharedPreferences(): SharedPreferences =
+ userFileManager.getSharedPreferences(
+ QSPreferencesRepository.FILE_NAME,
+ Context.MODE_PRIVATE,
+ userRepository.getSelectedUserInfo().id,
+ )
+
+ private fun Kosmos.setLargeTilesSpecsInSharedPreferences(specs: Set<String>) {
+ getSharedPreferences().edit().putStringSet(LARGE_TILES_SPECS_KEY, specs).apply()
+ }
+
+ private fun Kosmos.getLargeTilesSpecsFromSharedPreferences(): Set<String> {
+ return getSharedPreferences().getStringSet(LARGE_TILES_SPECS_KEY, emptySet())!!
+ }
+
+ private fun Set<String>.toTileSpecs(): Set<TileSpec> {
+ return map { TileSpec.create(it) }.toSet()
+ }
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractorTest.kt
index c775bfd..9e400a6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractorTest.kt
@@ -19,6 +19,7 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.collectLastValue
import com.android.systemui.kosmos.runTest
@@ -34,6 +35,7 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
+@EnableSceneContainer
class GridLayoutTypeInteractorTest : SysuiTestCase() {
val kosmos = testKosmos()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractorTest.kt
index 79acfda..9838bcb 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractorTest.kt
@@ -66,7 +66,7 @@
runCurrent()
// Resize it to large
- qsPreferencesRepository.setLargeTilesSpecs(setOf(spec))
+ qsPreferencesRepository.writeLargeTileSpecs(setOf(spec))
runCurrent()
// Assert that the new tile was added to the large tiles set
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/QSColumnsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/QSColumnsInteractorTest.kt
index 2e7aeb4..9fe783b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/QSColumnsInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/QSColumnsInteractorTest.kt
@@ -22,6 +22,7 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.ui.data.repository.configurationRepository
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.kosmos.testCase
import com.android.systemui.kosmos.testScope
import com.android.systemui.qs.panels.data.repository.QSColumnsRepository
@@ -76,6 +77,7 @@
}
@Test
+ @EnableSceneContainer
fun withDualShade_returnsCorrectValue() =
with(kosmos) {
testScope.runTest {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/MediaInRowInLandscapeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/MediaInRowInLandscapeViewModelTest.kt
index fdbf42c..d5e502e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/MediaInRowInLandscapeViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/MediaInRowInLandscapeViewModelTest.kt
@@ -21,6 +21,7 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
+import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.kosmos.testScope
import com.android.systemui.lifecycle.activateIn
import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager.Companion.LOCATION_QQS
@@ -36,6 +37,7 @@
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlin.test.Test
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -43,6 +45,7 @@
import platform.test.runner.parameterized.ParameterizedAndroidJunit4
import platform.test.runner.parameterized.Parameters
+@OptIn(ExperimentalCoroutinesApi::class)
@RunWith(ParameterizedAndroidJunit4::class)
@SmallTest
class MediaInRowInLandscapeViewModelTest(private val testData: TestData) : SysuiTestCase() {
@@ -63,6 +66,7 @@
}
@Test
+ @EnableSceneContainer
fun shouldMediaShowInRow() =
with(kosmos) {
testScope.runTest {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/QSColumnsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/QSColumnsViewModelTest.kt
index 241cdbf..4912c31 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/QSColumnsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/QSColumnsViewModelTest.kt
@@ -21,6 +21,7 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.ui.data.repository.configurationRepository
+import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.runCurrent
import com.android.systemui.kosmos.testCase
@@ -88,6 +89,7 @@
}
@Test
+ @EnableSceneContainer
fun mediaLocationNull_dualShade_alwaysDualShadeColumns() =
with(kosmos) {
testScope.runTest {
@@ -111,6 +113,7 @@
}
@Test
+ @EnableSceneContainer
fun mediaLocationQS_dualShade_alwaysDualShadeColumns() =
with(kosmos) {
testScope.runTest {
@@ -133,6 +136,7 @@
}
@Test
+ @EnableSceneContainer
fun mediaLocationQQS_dualShade_alwaysDualShadeColumns() =
with(kosmos) {
testScope.runTest {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt
index 4b8cd37..d9b3926 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt
@@ -24,6 +24,7 @@
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.pipeline.shared.TilesUpgradePath
import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger
import com.android.systemui.res.R
import com.android.systemui.retail.data.repository.FakeRetailModeRepository
@@ -242,9 +243,12 @@
storeTilesForUser(startingTiles, userId)
val tiles by collectLastValue(underTest.tilesSpecs(userId))
- val tilesRead by collectLastValue(underTest.tilesReadFromSetting.consumeAsFlow())
+ val tilesRead by collectLastValue(underTest.tilesUpgradePath.consumeAsFlow())
- assertThat(tilesRead).isEqualTo(startingTiles.toTileSpecs().toSet() to userId)
+ assertThat(tilesRead)
+ .isEqualTo(
+ TilesUpgradePath.ReadFromSettings(startingTiles.toTileSpecs().toSet()) to userId
+ )
}
@Test
@@ -258,13 +262,13 @@
val tiles10 by collectLastValue(underTest.tilesSpecs(10))
val tiles11 by collectLastValue(underTest.tilesSpecs(11))
- val tilesRead by collectValues(underTest.tilesReadFromSetting.consumeAsFlow())
+ val tilesRead by collectValues(underTest.tilesUpgradePath.consumeAsFlow())
assertThat(tilesRead).hasSize(2)
assertThat(tilesRead)
.containsExactly(
- startingTiles10.toTileSpecs().toSet() to 10,
- startingTiles11.toTileSpecs().toSet() to 11,
+ TilesUpgradePath.ReadFromSettings(startingTiles10.toTileSpecs().toSet()) to 10,
+ TilesUpgradePath.ReadFromSettings(startingTiles11.toTileSpecs().toSet()) to 11,
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepositoryTest.kt
index 1945f75..29bd18d3 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepositoryTest.kt
@@ -7,8 +7,8 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.qs.pipeline.data.model.RestoreData
-import com.android.systemui.qs.pipeline.data.repository.UserTileSpecRepositoryTest.Companion.toTilesSet
import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.pipeline.shared.TilesUpgradePath
import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger
import com.android.systemui.util.settings.FakeSettings
import com.google.common.truth.Truth.assertThat
@@ -352,11 +352,11 @@
@Test
fun noSettingsStored_noTilesReadFromSettings() =
testScope.runTest {
- val tilesRead by collectLastValue(underTest.tilesReadFromSettings.consumeAsFlow())
+ val tilesRead by collectLastValue(underTest.tilesUpgradePath.consumeAsFlow())
val tiles by collectLastValue(underTest.tiles())
assertThat(tiles).isEqualTo(getDefaultTileSpecs())
- assertThat(tilesRead).isEqualTo(null)
+ assertThat(tilesRead).isEqualTo(TilesUpgradePath.DefaultSet)
}
@Test
@@ -365,19 +365,20 @@
val storedTiles = "a,b"
storeTiles(storedTiles)
val tiles by collectLastValue(underTest.tiles())
- val tilesRead by collectLastValue(underTest.tilesReadFromSettings.consumeAsFlow())
+ val tilesRead by collectLastValue(underTest.tilesUpgradePath.consumeAsFlow())
- assertThat(tilesRead).isEqualTo(storedTiles.toTilesSet())
+ assertThat(tilesRead)
+ .isEqualTo(TilesUpgradePath.ReadFromSettings(storedTiles.toTilesSet()))
}
@Test
fun noSettingsStored_tilesChanged_tilesReadFromSettingsNotChanged() =
testScope.runTest {
- val tilesRead by collectLastValue(underTest.tilesReadFromSettings.consumeAsFlow())
+ val tilesRead by collectLastValue(underTest.tilesUpgradePath.consumeAsFlow())
val tiles by collectLastValue(underTest.tiles())
underTest.addTile(TileSpec.create("a"))
- assertThat(tilesRead).isEqualTo(null)
+ assertThat(tilesRead).isEqualTo(TilesUpgradePath.DefaultSet)
}
@Test
@@ -386,10 +387,34 @@
val storedTiles = "a,b"
storeTiles(storedTiles)
val tiles by collectLastValue(underTest.tiles())
- val tilesRead by collectLastValue(underTest.tilesReadFromSettings.consumeAsFlow())
+ val tilesRead by collectLastValue(underTest.tilesUpgradePath.consumeAsFlow())
underTest.addTile(TileSpec.create("c"))
- assertThat(tilesRead).isEqualTo(storedTiles.toTilesSet())
+ assertThat(tilesRead)
+ .isEqualTo(TilesUpgradePath.ReadFromSettings(storedTiles.toTilesSet()))
+ }
+
+ @Test
+ fun tilesRestoredFromBackup() =
+ testScope.runTest {
+ val specsBeforeRestore = "a,b,c,d,e"
+ val restoredSpecs = "a,c,d,f"
+ val autoAddedBeforeRestore = "b,d"
+ val restoredAutoAdded = "d,e"
+
+ storeTiles(specsBeforeRestore)
+ val tiles by collectLastValue(underTest.tiles())
+ val tilesRead by collectLastValue(underTest.tilesUpgradePath.consumeAsFlow())
+ runCurrent()
+
+ val restoreData =
+ RestoreData(restoredSpecs.toTileSpecs(), restoredAutoAdded.toTilesSet(), USER)
+ underTest.reconcileRestore(restoreData, autoAddedBeforeRestore.toTilesSet())
+ runCurrent()
+
+ val expected = "a,b,c,d,f"
+ assertThat(tilesRead)
+ .isEqualTo(TilesUpgradePath.RestoreFromBackup(expected.toTilesSet()))
}
private fun getDefaultTileSpecs(): List<TileSpec> {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
index adaebbd..5527393 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
@@ -25,8 +25,10 @@
import com.android.compose.animation.scene.DefaultEdgeDetector
import com.android.systemui.SysuiTestCase
import com.android.systemui.classifier.fakeFalsingManager
-import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.EnableSceneContainer
+import com.android.systemui.kosmos.collectLastValue
+import com.android.systemui.kosmos.runCurrent
+import com.android.systemui.kosmos.runTest
import com.android.systemui.kosmos.testScope
import com.android.systemui.lifecycle.activateIn
import com.android.systemui.power.data.repository.fakePowerRepository
@@ -44,17 +46,15 @@
import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.statusbar.data.repository.fakeRemoteInputRepository
import com.android.systemui.testKosmos
-import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.Job
-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.kotlin.doReturn
+import org.mockito.kotlin.mock
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@@ -64,10 +64,6 @@
private val kosmos = testKosmos()
private val testScope by lazy { kosmos.testScope }
- private val sceneInteractor by lazy { kosmos.sceneInteractor }
- private val fakeSceneDataSource by lazy { kosmos.fakeSceneDataSource }
- private val sceneContainerConfig by lazy { kosmos.sceneContainerConfig }
- private val fakeRemoteInputRepository by lazy { kosmos.fakeRemoteInputRepository }
private val falsingManager by lazy { kosmos.fakeFalsingManager }
private val view = mock<View>()
@@ -91,14 +87,14 @@
@Test
fun activate_setsMotionEventHandler() =
- testScope.runTest {
+ kosmos.runTest {
runCurrent()
assertThat(motionEventHandler).isNotNull()
}
@Test
fun deactivate_clearsMotionEventHandler() =
- testScope.runTest {
+ kosmos.runTest {
activationJob.cancel()
runCurrent()
@@ -107,7 +103,7 @@
@Test
fun isVisible() =
- testScope.runTest {
+ kosmos.runTest {
assertThat(underTest.isVisible).isTrue()
sceneInteractor.setVisible(false, "reason")
@@ -121,7 +117,7 @@
@Test
fun sceneTransition() =
- testScope.runTest {
+ kosmos.runTest {
val currentScene by collectLastValue(underTest.currentScene)
assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
@@ -132,7 +128,7 @@
@Test
fun canChangeScene_whenAllowed_switchingFromGone_returnsTrue() =
- testScope.runTest {
+ kosmos.runTest {
val currentScene by collectLastValue(underTest.currentScene)
fakeSceneDataSource.changeScene(toScene = Scenes.Gone)
runCurrent()
@@ -149,7 +145,7 @@
@Test
fun canChangeScene_whenAllowed_switchingFromLockscreen_returnsTrue() =
- testScope.runTest {
+ kosmos.runTest {
val currentScene by collectLastValue(underTest.currentScene)
fakeSceneDataSource.changeScene(toScene = Scenes.Lockscreen)
runCurrent()
@@ -166,7 +162,7 @@
@Test
fun canChangeScene_whenNotAllowed_fromLockscreen_toFalsingProtectedScenes_returnsFalse() =
- testScope.runTest {
+ kosmos.runTest {
falsingManager.setIsFalseTouch(true)
val currentScene by collectLastValue(underTest.currentScene)
fakeSceneDataSource.changeScene(toScene = Scenes.Lockscreen)
@@ -188,7 +184,7 @@
@Test
fun canChangeScene_whenNotAllowed_fromLockscreen_toFalsingUnprotectedScenes_returnsTrue() =
- testScope.runTest {
+ kosmos.runTest {
falsingManager.setIsFalseTouch(true)
val currentScene by collectLastValue(underTest.currentScene)
fakeSceneDataSource.changeScene(toScene = Scenes.Lockscreen)
@@ -209,7 +205,7 @@
@Test
fun canChangeScene_whenNotAllowed_fromGone_toAnyOtherScene_returnsTrue() =
- testScope.runTest {
+ kosmos.runTest {
falsingManager.setIsFalseTouch(true)
val currentScene by collectLastValue(underTest.currentScene)
fakeSceneDataSource.changeScene(toScene = Scenes.Gone)
@@ -226,8 +222,70 @@
}
@Test
+ fun canShowOrReplaceOverlay_whenAllowed_showingWhileOnGone_returnsTrue() =
+ kosmos.runTest {
+ val currentScene by collectLastValue(underTest.currentScene)
+ fakeSceneDataSource.changeScene(toScene = Scenes.Gone)
+ runCurrent()
+ assertThat(currentScene).isEqualTo(Scenes.Gone)
+
+ sceneContainerConfig.overlayKeys.forEach { overlay ->
+ assertWithMessage("Overlay $overlay incorrectly protected when allowed")
+ .that(underTest.canShowOrReplaceOverlay(newlyShown = overlay))
+ .isTrue()
+ }
+ }
+
+ @Test
+ fun canShowOrReplaceOverlay_whenAllowed_showingWhileOnLockscreen_returnsTrue() =
+ kosmos.runTest {
+ val currentScene by collectLastValue(underTest.currentScene)
+ fakeSceneDataSource.changeScene(toScene = Scenes.Lockscreen)
+ runCurrent()
+ assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
+
+ sceneContainerConfig.overlayKeys.forEach { overlay ->
+ assertWithMessage("Overlay $overlay incorrectly protected when allowed")
+ .that(underTest.canShowOrReplaceOverlay(newlyShown = overlay))
+ .isTrue()
+ }
+ }
+
+ @Test
+ fun canShowOrReplaceOverlay_whenNotAllowed_whileOnLockscreen_returnsFalse() =
+ kosmos.runTest {
+ falsingManager.setIsFalseTouch(true)
+ val currentScene by collectLastValue(underTest.currentScene)
+ fakeSceneDataSource.changeScene(toScene = Scenes.Lockscreen)
+ runCurrent()
+ assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
+
+ sceneContainerConfig.overlayKeys.forEach { overlay ->
+ assertWithMessage("Protected overlay $overlay not properly protected")
+ .that(underTest.canShowOrReplaceOverlay(newlyShown = overlay))
+ .isFalse()
+ }
+ }
+
+ @Test
+ fun canShowOrReplaceOverlay_whenNotAllowed_whileOnGone_returnsTrue() =
+ kosmos.runTest {
+ falsingManager.setIsFalseTouch(true)
+ val currentScene by collectLastValue(underTest.currentScene)
+ fakeSceneDataSource.changeScene(toScene = Scenes.Gone)
+ runCurrent()
+ assertThat(currentScene).isEqualTo(Scenes.Gone)
+
+ sceneContainerConfig.overlayKeys.forEach { overlay ->
+ assertWithMessage("Protected overlay $overlay not properly protected")
+ .that(underTest.canShowOrReplaceOverlay(newlyShown = overlay))
+ .isTrue()
+ }
+ }
+
+ @Test
fun userInput() =
- testScope.runTest {
+ kosmos.runTest {
assertThat(kosmos.fakePowerRepository.userTouchRegistered).isFalse()
underTest.onMotionEvent(mock())
assertThat(kosmos.fakePowerRepository.userTouchRegistered).isTrue()
@@ -235,7 +293,7 @@
@Test
fun userInputOnEmptySpace_insideEvent() =
- testScope.runTest {
+ kosmos.runTest {
assertThat(fakeRemoteInputRepository.areRemoteInputsClosed).isFalse()
val insideMotionEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0f, 0f, 0)
underTest.onEmptySpaceMotionEvent(insideMotionEvent)
@@ -244,7 +302,7 @@
@Test
fun userInputOnEmptySpace_outsideEvent_remoteInputActive() =
- testScope.runTest {
+ kosmos.runTest {
fakeRemoteInputRepository.isRemoteInputActive.value = true
assertThat(fakeRemoteInputRepository.areRemoteInputsClosed).isFalse()
val outsideMotionEvent = MotionEvent.obtain(0, 0, ACTION_OUTSIDE, 0f, 0f, 0)
@@ -254,7 +312,7 @@
@Test
fun userInputOnEmptySpace_outsideEvent_remoteInputInactive() =
- testScope.runTest {
+ kosmos.runTest {
fakeRemoteInputRepository.isRemoteInputActive.value = false
assertThat(fakeRemoteInputRepository.areRemoteInputsClosed).isFalse()
val outsideMotionEvent = MotionEvent.obtain(0, 0, ACTION_OUTSIDE, 0f, 0f, 0)
@@ -264,7 +322,7 @@
@Test
fun remoteUserInteraction_keepsContainerVisible() =
- testScope.runTest {
+ kosmos.runTest {
sceneInteractor.setVisible(false, "reason")
runCurrent()
assertThat(underTest.isVisible).isFalse()
@@ -272,9 +330,7 @@
runCurrent()
assertThat(underTest.isVisible).isTrue()
- underTest.onMotionEvent(
- mock { whenever(actionMasked).thenReturn(MotionEvent.ACTION_UP) }
- )
+ underTest.onMotionEvent(mock { on { actionMasked } doReturn MotionEvent.ACTION_UP })
runCurrent()
assertThat(underTest.isVisible).isFalse()
@@ -282,7 +338,7 @@
@Test
fun getActionableContentKey_noOverlays_returnsCurrentScene() =
- testScope.runTest {
+ kosmos.runTest {
val currentScene by collectLastValue(underTest.currentScene)
val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
@@ -300,7 +356,7 @@
@Test
fun getActionableContentKey_multipleOverlays_returnsTopOverlay() =
- testScope.runTest {
+ kosmos.runTest {
val currentScene by collectLastValue(underTest.currentScene)
val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
fakeSceneDataSource.showOverlay(Overlays.QuickSettingsShade)
@@ -321,7 +377,7 @@
@Test
fun edgeDetector_singleShade_usesDefaultEdgeDetector() =
- testScope.runTest {
+ kosmos.runTest {
val shadeMode by collectLastValue(kosmos.shadeMode)
kosmos.enableSingleShade()
@@ -331,7 +387,7 @@
@Test
fun edgeDetector_splitShade_usesDefaultEdgeDetector() =
- testScope.runTest {
+ kosmos.runTest {
val shadeMode by collectLastValue(kosmos.shadeMode)
kosmos.enableSplitShade()
@@ -341,7 +397,7 @@
@Test
fun edgeDetector_dualShade_narrowScreen_usesSceneContainerSwipeDetector() =
- testScope.runTest {
+ kosmos.runTest {
val shadeMode by collectLastValue(kosmos.shadeMode)
kosmos.enableDualShade(wideLayout = false)
@@ -352,7 +408,7 @@
@Test
fun edgeDetector_dualShade_wideScreen_usesSceneContainerSwipeDetector() =
- testScope.runTest {
+ kosmos.runTest {
val shadeMode by collectLastValue(kosmos.shadeMode)
kosmos.enableDualShade(wideLayout = true)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/policy/WorkProfilePolicyTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/policy/WorkProfilePolicyTest.kt
index c1477fe..a9f3a65 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/policy/WorkProfilePolicyTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/policy/WorkProfilePolicyTest.kt
@@ -80,6 +80,8 @@
// Set desktop mode supported
whenever(mContext.resources).thenReturn(mResources)
whenever(mResources.getBoolean(R.bool.config_isDesktopModeSupported)).thenReturn(true)
+ whenever(mResources.getBoolean(R.bool.config_canInternalDisplayHostDesktops))
+ .thenReturn(true)
policy = WorkProfilePolicy(kosmos.profileTypeRepository, mContext)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractorImplTest.kt
index 668f568..d26e195 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractorImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractorImplTest.kt
@@ -20,6 +20,7 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.kosmos.testScope
import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.testKosmos
@@ -31,6 +32,7 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
+@EnableSceneContainer
class ShadeModeInteractorImplTest : SysuiTestCase() {
private val kosmos = testKosmos()
@@ -80,7 +82,7 @@
}
@Test
- fun isDualShade_settingEnabled_returnsTrue() =
+ fun isDualShade_settingEnabledSceneContainerEnabled_returnsTrue() =
testScope.runTest {
// TODO(b/391578667): Add a test case for user switching once the bug is fixed.
val shadeMode by collectLastValue(underTest.shadeMode)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/startable/ShadeStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/startable/ShadeStartableTest.kt
index b8f66ac..dde8678 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/startable/ShadeStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/startable/ShadeStartableTest.kt
@@ -48,6 +48,7 @@
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.TestScope
@@ -59,6 +60,7 @@
import platform.test.runner.parameterized.ParameterizedAndroidJunit4
import platform.test.runner.parameterized.Parameters
+@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(ParameterizedAndroidJunit4::class)
class ShadeStartableTest(flags: FlagsParameterization) : SysuiTestCase() {
@@ -103,6 +105,7 @@
}
@Test
+ @EnableSceneContainer
fun hydrateShadeMode_dualShadeEnabled() =
testScope.runTest {
overrideResource(R.bool.config_use_split_notification_shade, false)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt
index dbe8f82..c7b3175 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt
@@ -31,7 +31,6 @@
import com.android.systemui.plugins.activityStarter
import com.android.systemui.res.R
import com.android.systemui.statusbar.StatusBarIconView
-import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
import com.android.systemui.statusbar.chips.ui.model.ColorsModel
import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer
@@ -265,91 +264,25 @@
}
@Test
- fun chip_positiveStartTime_notPromoted_colorsAreThemed() =
+ fun chip_positiveStartTime_colorsAreAccentThemed() =
testScope.runTest {
val latest by collectLastValue(underTest.chip)
repo.setOngoingCallState(inCallModel(startTimeMs = 1000, promotedContent = null))
assertThat((latest as OngoingActivityChipModel.Active).colors)
- .isEqualTo(ColorsModel.Themed)
+ .isEqualTo(ColorsModel.AccentThemed)
}
@Test
- fun chip_zeroStartTime_notPromoted_colorsAreThemed() =
+ fun chip_zeroStartTime_colorsAreAccentThemed() =
testScope.runTest {
val latest by collectLastValue(underTest.chip)
repo.setOngoingCallState(inCallModel(startTimeMs = 0, promotedContent = null))
assertThat((latest as OngoingActivityChipModel.Active).colors)
- .isEqualTo(ColorsModel.Themed)
- }
-
- @Test
- @DisableFlags(StatusBarNotifChips.FLAG_NAME)
- fun chip_positiveStartTime_promoted_notifChipsFlagOff_colorsAreThemed() =
- testScope.runTest {
- val latest by collectLastValue(underTest.chip)
-
- repo.setOngoingCallState(
- inCallModel(startTimeMs = 1000, promotedContent = PROMOTED_CONTENT_WITH_COLOR)
- )
-
- assertThat((latest as OngoingActivityChipModel.Active).colors)
- .isEqualTo(ColorsModel.Themed)
- }
-
- @Test
- @DisableFlags(StatusBarNotifChips.FLAG_NAME)
- fun chip_zeroStartTime_promoted_notifChipsFlagOff_colorsAreThemed() =
- testScope.runTest {
- val latest by collectLastValue(underTest.chip)
-
- repo.setOngoingCallState(
- inCallModel(startTimeMs = 0, promotedContent = PROMOTED_CONTENT_WITH_COLOR)
- )
-
- assertThat((latest as OngoingActivityChipModel.Active).colors)
- .isEqualTo(ColorsModel.Themed)
- }
-
- @Test
- @EnableFlags(StatusBarNotifChips.FLAG_NAME)
- fun chip_positiveStartTime_promoted_notifChipsFlagOn_colorsAreCustom() =
- testScope.runTest {
- val latest by collectLastValue(underTest.chip)
-
- repo.setOngoingCallState(
- inCallModel(startTimeMs = 1000, promotedContent = PROMOTED_CONTENT_WITH_COLOR)
- )
-
- assertThat((latest as OngoingActivityChipModel.Active).colors)
- .isEqualTo(
- ColorsModel.Custom(
- backgroundColorInt = PROMOTED_BACKGROUND_COLOR,
- primaryTextColorInt = PROMOTED_PRIMARY_TEXT_COLOR,
- )
- )
- }
-
- @Test
- @EnableFlags(StatusBarNotifChips.FLAG_NAME)
- fun chip_zeroStartTime_promoted_notifChipsFlagOff_colorsAreCustom() =
- testScope.runTest {
- val latest by collectLastValue(underTest.chip)
-
- repo.setOngoingCallState(
- inCallModel(startTimeMs = 0, promotedContent = PROMOTED_CONTENT_WITH_COLOR)
- )
-
- assertThat((latest as OngoingActivityChipModel.Active).colors)
- .isEqualTo(
- ColorsModel.Custom(
- backgroundColorInt = PROMOTED_BACKGROUND_COLOR,
- primaryTextColorInt = PROMOTED_PRIMARY_TEXT_COLOR,
- )
- )
+ .isEqualTo(ColorsModel.AccentThemed)
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt
index 192ad87..aaa9b58 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt
@@ -186,7 +186,7 @@
@Test
@DisableFlags(FLAG_PROMOTE_NOTIFICATIONS_AUTOMATICALLY)
- fun chips_onePromotedNotif_colorMatches() =
+ fun chips_onePromotedNotif_colorIsSystemThemed() =
kosmos.runTest {
val latest by collectLastValue(underTest.chips)
@@ -209,10 +209,7 @@
)
assertThat(latest).hasSize(1)
- val colors = latest!![0].colors
- assertThat(colors).isInstanceOf(ColorsModel.Custom::class.java)
- assertThat((colors as ColorsModel.Custom).backgroundColorInt).isEqualTo(56)
- assertThat((colors as ColorsModel.Custom).primaryTextColorInt).isEqualTo(89)
+ assertThat(latest!![0].colors).isEqualTo(ColorsModel.SystemThemed)
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/view/ChipTextTruncationHelperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/view/ChipTextTruncationHelperTest.kt
index d727089..9ec5a42 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/view/ChipTextTruncationHelperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/view/ChipTextTruncationHelperTest.kt
@@ -52,24 +52,13 @@
}
@Test
- fun shouldShowText_desiredSlightlyLargerThanMax_true() {
+ fun shouldShowText_desiredMoreThanMax_false() {
val result =
underTest.shouldShowText(
desiredTextWidthPx = (MAX_WIDTH * 1.1).toInt(),
widthMeasureSpec = UNLIMITED_WIDTH_SPEC,
)
- assertThat(result).isTrue()
- }
-
- @Test
- fun shouldShowText_desiredMoreThanTwiceMax_false() {
- val result =
- underTest.shouldShowText(
- desiredTextWidthPx = (MAX_WIDTH * 2.2).toInt(),
- widthMeasureSpec = UNLIMITED_WIDTH_SPEC,
- )
-
assertThat(result).isFalse()
}
@@ -80,8 +69,8 @@
View.MeasureSpec.makeMeasureSpec(MAX_WIDTH / 2, View.MeasureSpec.AT_MOST)
)
- // WHEN desired is more than twice the smallerWidthSpec
- val desiredWidth = (MAX_WIDTH * 1.1).toInt()
+ // WHEN desired is more than the smallerWidthSpec
+ val desiredWidth = ((MAX_WIDTH / 2) * 1.1).toInt()
val result =
underTest.shouldShowText(
@@ -100,8 +89,8 @@
View.MeasureSpec.makeMeasureSpec(MAX_WIDTH * 3, View.MeasureSpec.AT_MOST)
)
- // WHEN desired is more than twice the max
- val desiredWidth = (MAX_WIDTH * 2.2).toInt()
+ // WHEN desired is more than the max
+ val desiredWidth = (MAX_WIDTH * 1.1).toInt()
val result =
underTest.shouldShowText(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/ChipTransitionHelperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/ChipTransitionHelperTest.kt
index 60030ad..e3a84fd 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/ChipTransitionHelperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/ChipTransitionHelperTest.kt
@@ -54,7 +54,7 @@
OngoingActivityChipModel.Active.Timer(
key = KEY,
icon = createIcon(R.drawable.ic_cake),
- colors = ColorsModel.Themed,
+ colors = ColorsModel.AccentThemed,
startTimeMs = 100L,
onClickListenerLegacy = null,
clickBehavior = OngoingActivityChipModel.ClickBehavior.None,
@@ -68,7 +68,7 @@
OngoingActivityChipModel.Active.IconOnly(
key = KEY,
icon = createIcon(R.drawable.ic_hotspot),
- colors = ColorsModel.Themed,
+ colors = ColorsModel.AccentThemed,
onClickListenerLegacy = null,
clickBehavior = OngoingActivityChipModel.ClickBehavior.None,
)
@@ -90,7 +90,7 @@
OngoingActivityChipModel.Active.Timer(
key = KEY,
icon = createIcon(R.drawable.ic_cake),
- colors = ColorsModel.Themed,
+ colors = ColorsModel.AccentThemed,
startTimeMs = 100L,
onClickListenerLegacy = null,
clickBehavior = OngoingActivityChipModel.ClickBehavior.None,
@@ -132,7 +132,7 @@
OngoingActivityChipModel.Active.Timer(
key = KEY,
icon = createIcon(R.drawable.ic_cake),
- colors = ColorsModel.Themed,
+ colors = ColorsModel.AccentThemed,
startTimeMs = 100L,
onClickListenerLegacy = null,
clickBehavior = OngoingActivityChipModel.ClickBehavior.None,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt
index 403ac32..20637cd 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt
@@ -293,7 +293,7 @@
@DisableFlags(StatusBarChipsModernization.FLAG_NAME, StatusBarRootModernization.FLAG_NAME)
@Test
- fun chipsLegacy_twoTimerChips_isSmallPortrait_andChipsModernizationDisabled_bothSquished() =
+ fun chipsLegacy_twoTimerChips_isSmallPortrait_bothSquished() =
kosmos.runTest {
screenRecordState.value = ScreenRecordModel.Recording
addOngoingCallState(key = "call")
@@ -307,6 +307,22 @@
.isInstanceOf(OngoingActivityChipModel.Active.IconOnly::class.java)
}
+ @EnableFlags(StatusBarChipsModernization.FLAG_NAME, StatusBarRootModernization.FLAG_NAME)
+ @Test
+ fun chips_twoTimerChips_isSmallPortrait_bothSquished() =
+ kosmos.runTest {
+ screenRecordState.value = ScreenRecordModel.Recording
+ addOngoingCallState(key = "call")
+
+ val latest by collectLastValue(underTest.chips)
+
+ // Squished chips are icon only
+ assertThat(latest!!.active[0])
+ .isInstanceOf(OngoingActivityChipModel.Active.IconOnly::class.java)
+ assertThat(latest!!.active[1])
+ .isInstanceOf(OngoingActivityChipModel.Active.IconOnly::class.java)
+ }
+
@DisableFlags(StatusBarChipsModernization.FLAG_NAME, StatusBarRootModernization.FLAG_NAME)
@Test
fun chipsLegacy_countdownChipAndTimerChip_countdownNotSquished_butTimerSquished() =
@@ -324,6 +340,23 @@
.isInstanceOf(OngoingActivityChipModel.Active.IconOnly::class.java)
}
+ @EnableFlags(StatusBarChipsModernization.FLAG_NAME, StatusBarRootModernization.FLAG_NAME)
+ @Test
+ fun chips_countdownChipAndTimerChip_countdownNotSquished_butTimerSquished() =
+ kosmos.runTest {
+ screenRecordState.value = ScreenRecordModel.Starting(millisUntilStarted = 2000)
+ addOngoingCallState(key = "call")
+
+ val latest by collectLastValue(underTest.chips)
+
+ // The screen record countdown isn't squished to icon-only
+ assertThat(latest!!.active[0])
+ .isInstanceOf(OngoingActivityChipModel.Active.Countdown::class.java)
+ // But the call chip *is* squished
+ assertThat(latest!!.active[1])
+ .isInstanceOf(OngoingActivityChipModel.Active.IconOnly::class.java)
+ }
+
@DisableFlags(StatusBarChipsModernization.FLAG_NAME, StatusBarRootModernization.FLAG_NAME)
@Test
fun chipsLegacy_numberOfChipsChanges_chipsGetSquishedAndUnsquished() =
@@ -360,6 +393,38 @@
.isInstanceOf(OngoingActivityChipModel.Inactive::class.java)
}
+ @EnableFlags(StatusBarChipsModernization.FLAG_NAME, StatusBarRootModernization.FLAG_NAME)
+ @Test
+ fun chips_numberOfChipsChanges_chipsGetSquishedAndUnsquished() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.chips)
+
+ // WHEN there's only one chip
+ screenRecordState.value = ScreenRecordModel.Recording
+ removeOngoingCallState(key = "call")
+
+ // The screen record isn't squished because it's the only one
+ assertThat(latest!!.active[0])
+ .isInstanceOf(OngoingActivityChipModel.Active.Timer::class.java)
+
+ // WHEN there's 2 chips
+ addOngoingCallState(key = "call")
+
+ // THEN they both become squished
+ assertThat(latest!!.active[0])
+ .isInstanceOf(OngoingActivityChipModel.Active.IconOnly::class.java)
+ // But the call chip *is* squished
+ assertThat(latest!!.active[1])
+ .isInstanceOf(OngoingActivityChipModel.Active.IconOnly::class.java)
+
+ // WHEN we go back down to 1 chip
+ screenRecordState.value = ScreenRecordModel.DoingNothing
+
+ // THEN the remaining chip unsquishes
+ assertThat(latest!!.active[0])
+ .isInstanceOf(OngoingActivityChipModel.Active.Timer::class.java)
+ }
+
@DisableFlags(StatusBarChipsModernization.FLAG_NAME, StatusBarRootModernization.FLAG_NAME)
@Test
fun chipsLegacy_twoChips_isLandscape_notSquished() =
@@ -383,6 +448,29 @@
.isInstanceOf(OngoingActivityChipModel.Active.Timer::class.java)
}
+ @EnableFlags(StatusBarChipsModernization.FLAG_NAME, StatusBarRootModernization.FLAG_NAME)
+ @Test
+ fun chips_twoChips_isLandscape_notSquished() =
+ kosmos.runTest {
+ screenRecordState.value = ScreenRecordModel.Recording
+ addOngoingCallState(key = "call")
+
+ // WHEN we're in landscape
+ val config =
+ Configuration(kosmos.mainResources.configuration).apply {
+ orientation = Configuration.ORIENTATION_LANDSCAPE
+ }
+ kosmos.fakeConfigurationRepository.onConfigurationChange(config)
+
+ val latest by collectLastValue(underTest.chips)
+
+ // THEN the chips aren't squished (squished chips would be icon only)
+ assertThat(latest!!.active[0])
+ .isInstanceOf(OngoingActivityChipModel.Active.Timer::class.java)
+ assertThat(latest!!.active[1])
+ .isInstanceOf(OngoingActivityChipModel.Active.Timer::class.java)
+ }
+
@DisableFlags(StatusBarChipsModernization.FLAG_NAME, StatusBarRootModernization.FLAG_NAME)
@Test
fun chipsLegacy_twoChips_isLargeScreen_notSquished() =
@@ -402,16 +490,19 @@
.isInstanceOf(OngoingActivityChipModel.Active.Timer::class.java)
}
- @Test
@EnableFlags(StatusBarChipsModernization.FLAG_NAME, StatusBarRootModernization.FLAG_NAME)
- fun chips_twoChips_chipsModernizationEnabled_notSquished() =
+ @Test
+ fun chips_twoChips_isLargeScreen_notSquished() =
kosmos.runTest {
screenRecordState.value = ScreenRecordModel.Recording
addOngoingCallState(key = "call")
+ // WHEN we're on a large screen
+ kosmos.displayStateRepository.setIsLargeScreen(true)
+
val latest by collectLastValue(underTest.chips)
- // Squished chips would be icon only
+ // THEN the chips aren't squished (squished chips would be icon only)
assertThat(latest!!.active[0])
.isInstanceOf(OngoingActivityChipModel.Active.Timer::class.java)
assertThat(latest!!.active[1])
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/ConversationNotificationProcessorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/ConversationNotificationProcessorTest.kt
new file mode 100644
index 0000000..67d0ade
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/ConversationNotificationProcessorTest.kt
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification
+
+import android.app.Flags
+import android.app.Notification
+import android.app.Notification.EXTRA_SUMMARIZED_CONTENT
+import android.app.Person
+import android.content.pm.LauncherApps
+import android.content.pm.launcherApps
+import android.graphics.drawable.Icon
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.testing.TestableLooper.RunWithLooper
+import android.text.SpannableStringBuilder
+import android.text.style.ImageSpan
+import android.text.style.StyleSpan
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.RankingBuilder
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
+import com.android.systemui.statusbar.notification.row.NotificationRowContentBinderLogger
+import com.android.systemui.statusbar.notification.row.NotificationTestHelper
+import com.android.systemui.statusbar.notification.row.notificationRowContentBinderLogger
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@RunWithLooper
+class ConversationNotificationProcessorTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private lateinit var conversationNotificationProcessor: ConversationNotificationProcessor
+ private lateinit var testHelper: NotificationTestHelper
+ private lateinit var launcherApps: LauncherApps
+ private lateinit var logger: NotificationRowContentBinderLogger
+ private lateinit var conversationNotificationManager: ConversationNotificationManager
+
+ @Before
+ fun setup() {
+ launcherApps = kosmos.launcherApps
+ conversationNotificationManager = kosmos.conversationNotificationManager
+ logger = kosmos.notificationRowContentBinderLogger
+ testHelper = NotificationTestHelper(mContext, mDependency)
+
+ conversationNotificationProcessor =
+ ConversationNotificationProcessor(
+ context,
+ launcherApps,
+ conversationNotificationManager,
+ )
+ }
+
+ @Test
+ fun processNotification_notMessagingStyle() {
+ val nb = Notification.Builder(mContext).setSmallIcon(R.drawable.ic_person)
+ val newRow: ExpandableNotificationRow = testHelper.createRow(nb.build())
+
+ assertThat(conversationNotificationProcessor.processNotification(newRow.entry, nb, logger))
+ .isNull()
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_NM_SUMMARIZATION, Flags.FLAG_NM_SUMMARIZATION_UI)
+ fun processNotification_messagingStyleWithSummarization_flagOff() {
+ val summarization = "hello"
+ val nb = getMessagingNotification()
+ val newRow: ExpandableNotificationRow = testHelper.createRow(nb.build())
+ newRow.entry.setRanking(
+ RankingBuilder(newRow.entry.ranking).setSummarization(summarization).build()
+ )
+
+ assertThat(conversationNotificationProcessor.processNotification(newRow.entry, nb, logger))
+ .isNotNull()
+ assertThat(nb.build().extras.getCharSequence(EXTRA_SUMMARIZED_CONTENT)).isNull()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_NM_SUMMARIZATION)
+ fun processNotification_messagingStyleWithSummarization() {
+ val summarization = "hello"
+ val nb = getMessagingNotification()
+ val newRow: ExpandableNotificationRow = testHelper.createRow(nb.build())
+ newRow.entry.setRanking(
+ RankingBuilder(newRow.entry.ranking).setSummarization(summarization).build()
+ )
+
+ assertThat(conversationNotificationProcessor.processNotification(newRow.entry, nb, logger))
+ .isNotNull()
+
+ val processedSummary = nb.build().extras.getCharSequence(EXTRA_SUMMARIZED_CONTENT)
+ assertThat(processedSummary.toString()).isEqualTo("x$summarization")
+
+ val checkSpans = SpannableStringBuilder(processedSummary)
+ assertThat(
+ checkSpans.getSpans(
+ /* queryStart = */ 0,
+ /* queryEnd = */ 1,
+ /* kind = */ ImageSpan::class.java,
+ )
+ )
+ .isNotNull()
+
+ assertThat(
+ processedSummary?.let {
+ checkSpans.getSpans(
+ /* queryStart = */ 0,
+ /* queryEnd = */ it.length,
+ /* kind = */ StyleSpan::class.java,
+ )
+ }
+ )
+ .isNotNull()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_NM_SUMMARIZATION)
+ fun processNotification_messagingStyleWithoutSummarization() {
+ val nb = getMessagingNotification()
+ val newRow: ExpandableNotificationRow = testHelper.createRow(nb.build())
+ assertThat(conversationNotificationProcessor.processNotification(newRow.entry, nb, logger))
+ .isNotNull()
+ assertThat(nb.build().extras.getCharSequence(EXTRA_SUMMARIZED_CONTENT)).isNull()
+ }
+
+ private fun getMessagingNotification(): Notification.Builder {
+ val displayName = "Display Name"
+ val messageText = "Message Text"
+ val personIcon = Icon.createWithResource(mContext, R.drawable.ic_person)
+ val testPerson = Person.Builder().setName(displayName).setIcon(personIcon).build()
+ val messagingStyle = Notification.MessagingStyle(testPerson)
+ messagingStyle.addMessage(
+ Notification.MessagingStyle.Message(messageText, System.currentTimeMillis(), testPerson)
+ )
+ return Notification.Builder(mContext)
+ .setSmallIcon(R.drawable.ic_person)
+ .setStyle(messagingStyle)
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
index da31cd9..7d406b4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
@@ -209,6 +209,26 @@
}
@Test
+ public void testInflationProcessesMessagingStyle() throws Exception {
+ String displayName = "Display Name";
+ String messageText = "Message Text";
+ Icon personIcon = Icon.createWithResource(
+ mContext, com.android.systemui.res.R.drawable.ic_person);
+ Person testPerson = new Person.Builder().setName(displayName).setIcon(personIcon).build();
+ Notification.MessagingStyle messagingStyle = new Notification.MessagingStyle(testPerson);
+ messagingStyle.addMessage(new Notification.MessagingStyle.Message(
+ messageText, System.currentTimeMillis(), testPerson));
+ Notification messageNotif = new Notification.Builder(mContext)
+ .setSmallIcon(com.android.systemui.res.R.drawable.ic_person)
+ .setStyle(messagingStyle)
+ .build();
+ ExpandableNotificationRow newRow = mHelper.createRow(messageNotif);
+ inflateAndWait(mNotificationInflater, FLAG_CONTENT_VIEW_ALL, newRow);
+
+ verify(mConversationNotificationProcessor).processNotification(any(), any(), any());
+ }
+
+ @Test
@Ignore
public void testInflationIsRetriedIfAsyncFails() throws Exception {
NotificationContentInflater.InflationProgress result =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt
index c77b09a..82eca37 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt
@@ -587,6 +587,35 @@
Assert.assertFalse(hasText(publicView, contentTitle))
}
+ @Test
+ @Throws(java.lang.Exception::class)
+ @EnableFlags(android.app.Flags.FLAG_NM_SUMMARIZATION)
+ fun testAllMessagingStyleProcessedAsConversations() {
+ val displayName = "Display Name"
+ val messageText = "Message Text"
+ val personIcon = Icon.createWithResource(mContext, R.drawable.ic_person)
+ val testPerson = Person.Builder().setName(displayName).setIcon(personIcon).build()
+ val messagingStyle = Notification.MessagingStyle(testPerson)
+ messagingStyle.addMessage(
+ Notification.MessagingStyle.Message(messageText, System.currentTimeMillis(), testPerson)
+ )
+ val messageNotif =
+ Notification.Builder(mContext)
+ .setSmallIcon(R.drawable.ic_person)
+ .setStyle(messagingStyle)
+ .build()
+ val newRow: ExpandableNotificationRow = testHelper.createRow(messageNotif)
+
+ inflateAndWait(
+ false,
+ notificationInflater,
+ FLAG_CONTENT_VIEW_ALL,
+ REDACTION_TYPE_NONE,
+ newRow,
+ )
+ verify(conversationNotificationProcessor).processNotification(any(), any(), any())
+ }
+
private class ExceptionHolder {
var exception: Exception? = null
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelTest.kt
index d570f18..6381b4e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelTest.kt
@@ -57,11 +57,12 @@
statusBarStateController = mock()
whenever(screenOffAnimationController.allowWakeUpIfDozing()).thenReturn(true)
}
- private val underTest = kosmos.notificationShelfViewModel
private val deviceEntryFaceAuthRepository = kosmos.fakeDeviceEntryFaceAuthRepository
private val keyguardRepository = kosmos.fakeKeyguardRepository
- private val keyguardTransitionController = kosmos.lockscreenShadeTransitionController
private val powerRepository = kosmos.fakePowerRepository
+ private val keyguardTransitionController by lazy { kosmos.lockscreenShadeTransitionController }
+
+ private val underTest by lazy { kosmos.notificationShelfViewModel }
@Test
fun canModifyColorOfNotifications_whenKeyguardNotShowing() =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.kt
index 57b7df7..31f8590 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.kt
@@ -51,11 +51,13 @@
import com.android.systemui.shade.ShadeViewStateProvider
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.core.NewStatusBarIcons
import com.android.systemui.statusbar.data.repository.StatusBarContentInsetsProviderStore
import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler
import com.android.systemui.statusbar.layout.mockStatusBarContentInsetsProvider
import com.android.systemui.statusbar.phone.ui.StatusBarIconController
import com.android.systemui.statusbar.phone.ui.TintedIconManager
+import com.android.systemui.statusbar.pipeline.battery.ui.viewmodel.batteryViewModelFactory
import com.android.systemui.statusbar.policy.BatteryController
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -85,6 +87,7 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
@RunWithLooper(setAsMainLooper = true)
+@DisableFlags(NewStatusBarIcons.FLAG_NAME)
class KeyguardStatusBarViewControllerTest : SysuiTestCase() {
private lateinit var kosmos: Kosmos
private lateinit var testScope: TestScope
@@ -190,6 +193,7 @@
statusBarIconController,
iconManagerFactory,
batteryMeterViewController,
+ kosmos.batteryViewModelFactory,
shadeViewStateProvider,
keyguardStateController,
keyguardBypassController,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt
index 4759c08..183cd8f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt
@@ -19,6 +19,9 @@
import android.graphics.Color
import android.graphics.Rect
import android.view.View
+import androidx.compose.runtime.getValue
+import com.android.systemui.lifecycle.ExclusiveActivatable
+import com.android.systemui.lifecycle.Hydrator
import com.android.systemui.plugins.DarkIconDispatcher
import com.android.systemui.statusbar.chips.mediaprojection.domain.model.MediaProjectionStopDialogModel
import com.android.systemui.statusbar.chips.ui.model.MultipleOngoingActivityChipsModel
@@ -26,15 +29,20 @@
import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
import com.android.systemui.statusbar.events.shared.model.SystemEventAnimationState.Idle
import com.android.systemui.statusbar.featurepods.popups.shared.model.PopupChipModel
+import com.android.systemui.statusbar.phone.domain.interactor.IsAreaDark
+import com.android.systemui.statusbar.pipeline.battery.ui.viewmodel.BatteryViewModel
import com.android.systemui.statusbar.pipeline.shared.ui.model.SystemInfoCombinedVisibilityModel
import com.android.systemui.statusbar.pipeline.shared.ui.model.VisibilityModel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
+import org.mockito.Mockito.mock
class FakeHomeStatusBarViewModel(
override val operatorNameViewModel: StatusBarOperatorNameViewModel
-) : HomeStatusBarViewModel {
+) : HomeStatusBarViewModel, ExclusiveActivatable() {
+ private val hydrator = Hydrator("FakeHomeStatusBarViewModel.hydrator")
+
override val areNotificationsLightsOut = MutableStateFlow(false)
override val isTransitioningFromLockscreenToOccluded = MutableStateFlow(false)
@@ -56,6 +64,11 @@
override val isHomeStatusBarAllowedByScene = MutableStateFlow(false)
+ override val batteryViewModelFactory: BatteryViewModel.Factory =
+ object : BatteryViewModel.Factory {
+ override fun create(): BatteryViewModel = mock(BatteryViewModel::class.java)
+ }
+
override val shouldShowOperatorNameView = MutableStateFlow(false)
override val isClockVisible =
@@ -80,6 +93,7 @@
var darkIconTint = Color.BLACK
var lightIconTint = Color.WHITE
+ var darkIntensity = 0f
override val areaTint: Flow<StatusBarTintColor> =
MutableStateFlow(
@@ -91,4 +105,22 @@
}
}
)
+
+ val isAreaDarkSource =
+ MutableStateFlow(
+ IsAreaDark { viewBounds ->
+ if (DarkIconDispatcher.isInAreas(darkRegions, viewBounds)) {
+ darkIntensity < 0.5f
+ } else {
+ false
+ }
+ }
+ )
+
+ override val areaDark: IsAreaDark by
+ hydrator.hydratedStateOf(traceName = "areaDark", source = isAreaDarkSource)
+
+ override suspend fun onActivated(): Nothing {
+ hydrator.activate()
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/DisplaySwitchLatencyTrackerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/DisplaySwitchLatencyTrackerTest.kt
index 354edac..36e18e6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/DisplaySwitchLatencyTrackerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/DisplaySwitchLatencyTrackerTest.kt
@@ -56,7 +56,6 @@
import com.android.systemui.unfold.domain.interactor.UnfoldTransitionInteractor
import com.android.systemui.unfoldedDeviceState
import com.android.systemui.util.animation.data.repository.fakeAnimationStatusRepository
-import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.capture
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
@@ -79,8 +78,10 @@
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.any
import org.mockito.kotlin.mock
import org.mockito.kotlin.times
+import org.mockito.kotlin.verifyNoMoreInteractions
@RunWith(AndroidJUnit4::class)
@SmallTest
@@ -603,12 +604,39 @@
}
}
+ @Test
+ fun foldingStarted_screenStillOn_eventSentOnlyAfterScreenSwitches() {
+ // can happen for both folding and unfolding (with animations off) but it's more likely to
+ // happen when folding as waiting for screen on is the default case then
+ testScope.runTest {
+ startInUnfoldedState(displaySwitchLatencyTracker)
+ setDeviceState(FOLDED)
+ powerInteractor.setScreenPowerState(SCREEN_ON)
+ runCurrent()
+
+ verifyNoMoreInteractions(displaySwitchLatencyLogger)
+
+ powerInteractor.setScreenPowerState(SCREEN_OFF)
+ runCurrent()
+ powerInteractor.setScreenPowerState(SCREEN_ON)
+ runCurrent()
+
+ verify(displaySwitchLatencyLogger).log(any())
+ }
+ }
+
private suspend fun TestScope.startInFoldedState(tracker: DisplaySwitchLatencyTracker) {
setDeviceState(FOLDED)
tracker.start()
runCurrent()
}
+ private suspend fun TestScope.startInUnfoldedState(tracker: DisplaySwitchLatencyTracker) {
+ setDeviceState(UNFOLDED)
+ tracker.start()
+ runCurrent()
+ }
+
private suspend fun TestScope.startUnfolding() {
setDeviceState(HALF_FOLDED)
powerInteractor.setScreenPowerState(SCREEN_OFF)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
index 75f3386..b8e1924 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
@@ -47,6 +47,7 @@
import androidx.test.filters.SmallTest;
import com.android.keyguard.TestScopeProvider;
+import com.android.settingslib.volume.MediaSessions;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.SysuiTestCaseExtKt;
import com.android.systemui.broadcast.BroadcastDispatcher;
@@ -268,13 +269,15 @@
@Test
public void testOnRemoteVolumeChanged_newStream_noNullPointer() {
MediaSession.Token token = new MediaSession.Token(Process.myUid(), null);
- mVolumeController.mMediaSessionsCallbacksW.onRemoteVolumeChanged(token, 0);
+ var sessionId = MediaSessions.SessionId.Companion.from(token);
+ mVolumeController.mMediaSessionsCallbacksW.onRemoteVolumeChanged(sessionId, 0);
}
@Test
public void testOnRemoteRemove_newStream_noNullPointer() {
MediaSession.Token token = new MediaSession.Token(Process.myUid(), null);
- mVolumeController.mMediaSessionsCallbacksW.onRemoteRemoved(token);
+ var sessionId = MediaSessions.SessionId.Companion.from(token);
+ mVolumeController.mMediaSessionsCallbacksW.onRemoteRemoved(sessionId);
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/window/ui/viewmodel/WindowRootViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/window/ui/viewmodel/WindowRootViewModelTest.kt
index 390518f..a023b3c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/window/ui/viewmodel/WindowRootViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/window/ui/viewmodel/WindowRootViewModelTest.kt
@@ -25,6 +25,8 @@
import com.android.systemui.kosmos.testScope
import com.android.systemui.lifecycle.activateIn
import com.android.systemui.testKosmos
+import com.android.systemui.window.data.repository.fakeWindowRootViewBlurRepository
+import com.android.systemui.window.data.repository.windowRootViewBlurRepository
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
@@ -34,7 +36,7 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
-@EnableFlags(Flags.FLAG_BOUNCER_UI_REVAMP)
+@EnableFlags(Flags.FLAG_BOUNCER_UI_REVAMP, Flags.FLAG_GLANCEABLE_HUB_BLURRED_BACKGROUND)
class WindowRootViewModelTest : SysuiTestCase() {
val kosmos = testKosmos()
val testScope = kosmos.testScope
@@ -49,6 +51,7 @@
@Test
fun bouncerTransitionChangesWindowBlurRadius() =
testScope.runTest {
+ kosmos.fakeWindowRootViewBlurRepository.isBlurSupported.value = true
val blurRadius by collectLastValue(underTest.blurRadius)
val isBlurOpaque by collectLastValue(underTest.isBlurOpaque)
runCurrent()
@@ -59,4 +62,27 @@
assertThat(blurRadius).isEqualTo(30)
assertThat(isBlurOpaque).isEqualTo(false)
}
+
+ @Test
+ fun blurRadiusDoesNotChangeWhenBlurIsNotSupported() =
+ testScope.runTest {
+ kosmos.fakeWindowRootViewBlurRepository.isBlurSupported.value = false
+ val blurRadius by collectLastValue(underTest.blurRadius)
+ runCurrent()
+
+ kosmos.fakeBouncerTransitions.first().windowBlurRadius.value = 30.0f
+ runCurrent()
+
+ assertThat(blurRadius).isEqualTo(0f)
+
+ kosmos.fakeGlanceableHubTransitions.first().windowBlurRadius.value = 50.0f
+ runCurrent()
+
+ assertThat(blurRadius).isEqualTo(0f)
+
+ kosmos.windowRootViewBlurRepository.blurRadius.value = 60
+ runCurrent()
+
+ assertThat(blurRadius).isEqualTo(0f)
+ }
}
diff --git a/packages/SystemUI/plugin_core/proguard.flags b/packages/SystemUI/plugin_core/proguard.flags
index 6240898..8b78ba4 100644
--- a/packages/SystemUI/plugin_core/proguard.flags
+++ b/packages/SystemUI/plugin_core/proguard.flags
@@ -8,4 +8,7 @@
-keep interface com.android.systemui.plugins.annotations.** {
*;
}
--keep,allowshrinking,allowoptimization,allowobfuscation,allowaccessmodification @com.android.systemui.plugins.annotations.** class *
+# TODO(b/373579455): Evaluate if <init> needs to be kept.
+-keep,allowshrinking,allowoptimization,allowobfuscation,allowaccessmodification @com.android.systemui.plugins.annotations.** class * {
+ void <init>();
+}
diff --git a/packages/SystemUI/proguard_common.flags b/packages/SystemUI/proguard_common.flags
index e152c98..2b908a7 100644
--- a/packages/SystemUI/proguard_common.flags
+++ b/packages/SystemUI/proguard_common.flags
@@ -12,8 +12,14 @@
# Note that we restrict this to SysUISingleton classes, as other registering
# classes should either *always* unregister or *never* register from their
# constructor. We also keep callback class names for easier debugging.
--keepnames @com.android.systemui.util.annotations.WeaklyReferencedCallback class *
--keepnames class * extends @com.android.systemui.util.annotations.WeaklyReferencedCallback **
+# TODO(b/373579455): Evaluate if <init> needs to be kept.
+-keepnames @com.android.systemui.util.annotations.WeaklyReferencedCallback class * {
+ void <init>();
+}
+# TODO(b/373579455): Evaluate if <init> needs to be kept.
+-keepnames class * extends @com.android.systemui.util.annotations.WeaklyReferencedCallback ** {
+ void <init>();
+}
-if @com.android.systemui.util.annotations.WeaklyReferencedCallback class *
-keepclassmembers,allowaccessmodification @com.android.systemui.dagger.SysUISingleton class * {
<1> *;
@@ -23,10 +29,16 @@
<1> *;
}
--keep class androidx.core.app.CoreComponentFactory
+# TODO(b/373579455): Evaluate if <init> needs to be kept.
+-keep class androidx.core.app.CoreComponentFactory {
+ void <init>();
+}
# Keep the wm shell lib
--keep class com.android.wm.shell.*
+# TODO(b/373579455): Evaluate if <init> needs to be kept.
+-keep class com.android.wm.shell.* {
+ void <init>();
+}
# Keep the protolog group methods that are called by the generated code
-keepclassmembers class com.android.wm.shell.protolog.ShellProtoLogGroup {
*;
diff --git a/packages/SystemUI/res/drawable/clipboard_minimized_background_inset.xml b/packages/SystemUI/res/drawable/clipboard_minimized_background_inset.xml
new file mode 100644
index 0000000..1ba637f
--- /dev/null
+++ b/packages/SystemUI/res/drawable/clipboard_minimized_background_inset.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2025 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<inset
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:drawable="@drawable/clipboard_minimized_background"
+ android:inset="4dp"/>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/clipboard_overlay.xml b/packages/SystemUI/res/layout/clipboard_overlay.xml
index 448b3e7..915563b 100644
--- a/packages/SystemUI/res/layout/clipboard_overlay.xml
+++ b/packages/SystemUI/res/layout/clipboard_overlay.xml
@@ -171,12 +171,12 @@
android:layout_height="wrap_content"
android:visibility="gone"
android:elevation="7dp"
- android:padding="8dp"
+ android:padding="12dp"
app:layout_constraintBottom_toTopOf="@id/indication_container"
app:layout_constraintStart_toStartOf="parent"
- android:layout_marginStart="@dimen/overlay_action_container_margin_horizontal"
- android:layout_marginBottom="@dimen/overlay_action_container_margin_bottom"
- android:background="@drawable/clipboard_minimized_background">
+ android:layout_marginStart="4dp"
+ android:layout_marginBottom="2dp"
+ android:background="@drawable/clipboard_minimized_background_inset">
<ImageView
android:src="@drawable/ic_content_paste"
android:tint="?attr/overlayButtonTextColor"
diff --git a/packages/SystemUI/res/layout/combined_qs_header.xml b/packages/SystemUI/res/layout/combined_qs_header.xml
index b9ef88e..32407c6 100644
--- a/packages/SystemUI/res/layout/combined_qs_header.xml
+++ b/packages/SystemUI/res/layout/combined_qs_header.xml
@@ -147,6 +147,7 @@
android:id="@+id/batteryRemainingIcon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:visibility="gone"
app:textAppearance="@style/TextAppearance.QS.Status" />
</LinearLayout>
</FrameLayout>
diff --git a/packages/SystemUI/res/layout/ongoing_activity_chip_content.xml b/packages/SystemUI/res/layout/ongoing_activity_chip_content.xml
index 6f42286..b66a88a 100644
--- a/packages/SystemUI/res/layout/ongoing_activity_chip_content.xml
+++ b/packages/SystemUI/res/layout/ongoing_activity_chip_content.xml
@@ -43,9 +43,6 @@
ongoing_activity_chip_short_time_delta] will ever be shown at one time. -->
<!-- Shows a timer, like 00:01. -->
- <!-- Don't use the LimitedWidth style for the timer because the end of the timer is often
- the most important value. ChipChronometer has the correct logic for when the timer is
- too large for the space allowed. -->
<com.android.systemui.statusbar.chips.ui.view.ChipChronometer
android:id="@+id/ongoing_activity_chip_time"
style="@style/StatusBar.Chip.Text"
@@ -54,14 +51,14 @@
<!-- Shows generic text. -->
<com.android.systemui.statusbar.chips.ui.view.ChipTextView
android:id="@+id/ongoing_activity_chip_text"
- style="@style/StatusBar.Chip.Text.LimitedWidth"
+ style="@style/StatusBar.Chip.Text"
android:visibility="gone"
/>
<!-- Shows a time delta in short form, like "15min" or "1hr". -->
<com.android.systemui.statusbar.chips.ui.view.ChipDateTimeView
android:id="@+id/ongoing_activity_chip_short_time_delta"
- style="@style/StatusBar.Chip.Text.LimitedWidth"
+ style="@style/StatusBar.Chip.Text"
android:visibility="gone"
/>
diff --git a/packages/SystemUI/res/layout/system_icons.xml b/packages/SystemUI/res/layout/system_icons.xml
index c28dc50..bb99d58 100644
--- a/packages/SystemUI/res/layout/system_icons.xml
+++ b/packages/SystemUI/res/layout/system_icons.xml
@@ -34,12 +34,17 @@
android:orientation="horizontal"/>
<!-- PaddingEnd is added to balance hover padding, compensating for paddingStart in statusIcons.
- See b/339589733 -->
+ See b/339589733.
+
+ Default visibility is now "gone" to make space for the new battery icon
+ -->
<com.android.systemui.battery.BatteryMeterView android:id="@+id/battery"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:clipToPadding="false"
android:clipChildren="false"
android:paddingEnd="@dimen/status_bar_battery_end_padding"
+ android:visibility="gone"
systemui:textAppearance="@style/TextAppearance.StatusBar.Clock" />
+
</LinearLayout>
diff --git a/packages/SystemUI/res/layout/volume_dialog.xml b/packages/SystemUI/res/layout/volume_dialog.xml
index 67f620f..8ad99ab 100644
--- a/packages/SystemUI/res/layout/volume_dialog.xml
+++ b/packages/SystemUI/res/layout/volume_dialog.xml
@@ -16,7 +16,7 @@
<androidx.constraintlayout.motion.widget.MotionLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
- android:id="@+id/volume_dialog_root"
+ android:id="@+id/volume_dialog"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:alpha="0"
diff --git a/packages/SystemUI/res/layout/volume_ringer_button.xml b/packages/SystemUI/res/layout/volume_ringer_button.xml
index 6748cfa..4e3c8cc 100644
--- a/packages/SystemUI/res/layout/volume_ringer_button.xml
+++ b/packages/SystemUI/res/layout/volume_ringer_button.xml
@@ -13,20 +13,13 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<ImageButton xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content" >
-
- <ImageButton
- android:id="@+id/volume_drawer_button"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:padding="@dimen/volume_dialog_ringer_drawer_button_icon_radius"
- android:contentDescription="@string/volume_ringer_mode"
- android:gravity="center"
- android:tint="@androidprv:color/materialColorOnSurface"
- android:src="@drawable/volume_ringer_item_bg"
- android:background="@drawable/volume_ringer_item_bg"/>
-
-</FrameLayout>
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@drawable/volume_ringer_item_bg"
+ android:contentDescription="@string/volume_ringer_mode"
+ android:gravity="center"
+ android:padding="@dimen/volume_dialog_ringer_drawer_button_icon_radius"
+ android:src="@drawable/volume_ringer_item_bg"
+ android:tint="@androidprv:color/materialColorOnSurface" />
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 9b8926e..09aa224 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -1110,4 +1110,7 @@
<!-- Configuration for wallpaper focal area -->
<bool name="center_align_focal_area_shape">false</bool>
<string name="focal_area_target" translatable="false" />
+
+ <!-- Configuration to swipe to open glanceable hub -->
+ <bool name="config_swipeToOpenGlanceableHub">false</bool>
</resources>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 2d3c07b..648e4c2 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1811,6 +1811,7 @@
<dimen name="ongoing_activity_chip_text_end_padding_for_embedded_padding_icon">6dp</dimen>
<dimen name="ongoing_activity_chip_text_fading_edge_length">12dp</dimen>
<dimen name="ongoing_activity_chip_corner_radius">28dp</dimen>
+ <dimen name="ongoing_activity_chip_outline_width">2px</dimen>
<!-- Status bar user chip -->
<dimen name="status_bar_user_chip_avatar_size">16dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index d18a90a..8629203 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1351,6 +1351,10 @@
<string name="accessibility_action_label_shrink_widget">Decrease height</string>
<!-- Label for accessibility action to expand a widget in edit mode. [CHAR LIMIT=NONE] -->
<string name="accessibility_action_label_expand_widget">Increase height</string>
+ <!-- Label for accessibility action to show the next media player. [CHAR LIMIT=NONE] -->
+ <string name="accessibility_action_label_umo_show_next">Show next</string>
+ <!-- Label for accessibility action to show the previous media player. [CHAR LIMIT=NONE] -->
+ <string name="accessibility_action_label_umo_show_previous">Show previous</string>
<!-- Title shown above information regarding lock screen widgets. [CHAR LIMIT=50] -->
<string name="communal_widgets_disclaimer_title">Lock screen widgets</string>
<!-- Information about lock screen widgets presented to the user. [CHAR LIMIT=NONE] -->
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 7f2c893..4961a7e 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -93,15 +93,6 @@
<item name="android:textColor">?android:attr/colorPrimary</item>
</style>
- <!-- Style for a status bar chip text that has a maximum width. Since there's so little room in
- the status bar chip area, don't ellipsize the text and instead just fade it out a bit at
- the end. -->
- <style name="StatusBar.Chip.Text.LimitedWidth">
- <item name="android:ellipsize">none</item>
- <item name="android:requiresFadingEdge">horizontal</item>
- <item name="android:fadingEdgeLength">@dimen/ongoing_activity_chip_text_fading_edge_length</item>
- </style>
-
<style name="Chipbar" />
<style name="Chipbar.Text" parent="@*android:style/TextAppearance.DeviceDefault.Notification.Title">
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index 335a910..73dc282 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -98,7 +98,6 @@
import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
import com.android.settingslib.Utils;
import com.android.settingslib.drawable.CircleFramedDrawable;
-import com.android.systemui.Flags;
import com.android.systemui.FontStyles;
import com.android.systemui.Gefingerpoken;
import com.android.systemui.classifier.FalsingA11yDelegate;
@@ -121,6 +120,7 @@
static final int USER_TYPE_PRIMARY = 1;
static final int USER_TYPE_WORK_PROFILE = 2;
static final int USER_TYPE_SECONDARY_USER = 3;
+ private boolean mTransparentModeEnabled = false;
@IntDef({MODE_UNINITIALIZED, MODE_DEFAULT, MODE_ONE_HANDED, MODE_USER_SWITCHER})
public @interface Mode {}
@@ -814,15 +814,30 @@
mDisappearAnimRunning = false;
}
+ /**
+ * Make the bouncer background transparent
+ */
+ public void enableTransparentMode() {
+ mTransparentModeEnabled = true;
+ reloadBackgroundColor();
+ }
+
+ /**
+ * Make the bouncer background opaque
+ */
+ public void disableTransparentMode() {
+ mTransparentModeEnabled = false;
+ reloadBackgroundColor();
+ }
+
private void reloadBackgroundColor() {
- if (Flags.bouncerUiRevamp()) {
- // Keep the background transparent, otherwise the background color looks like a box
- // while scaling the bouncer for back animation or while transitioning to the bouncer.
+ if (mTransparentModeEnabled) {
setBackgroundColor(Color.TRANSPARENT);
} else {
setBackgroundColor(
getContext().getColor(com.android.internal.R.color.materialColorSurfaceDim));
}
+ invalidate();
}
void reloadColors() {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index ff7b2b0..d10fce4 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -70,6 +70,7 @@
import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
import com.android.keyguard.dagger.KeyguardBouncerScope;
import com.android.settingslib.utils.ThreadUtils;
+import com.android.systemui.Flags;
import com.android.systemui.Gefingerpoken;
import com.android.systemui.biometrics.FaceAuthAccessibilityDelegate;
import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor;
@@ -96,6 +97,8 @@
import com.android.systemui.util.ViewController;
import com.android.systemui.util.kotlin.JavaAdapter;
import com.android.systemui.util.settings.GlobalSettings;
+import com.android.systemui.window.data.repository.WindowRootViewBlurRepository;
+import com.android.systemui.window.domain.interactor.WindowRootViewBlurInteractor;
import dagger.Lazy;
@@ -134,6 +137,7 @@
private final FalsingA11yDelegate mFalsingA11yDelegate;
private final DeviceEntryFaceAuthInteractor mDeviceEntryFaceAuthInteractor;
private final BouncerMessageInteractor mBouncerMessageInteractor;
+ private final Lazy<WindowRootViewBlurInteractor> mRootViewBlurInteractor;
private int mTranslationY;
private final KeyguardDismissTransitionInteractor mKeyguardDismissTransitionInteractor;
private final DevicePolicyManager mDevicePolicyManager;
@@ -431,6 +435,7 @@
private final Executor mBgExecutor;
@Nullable
private Job mSceneTransitionCollectionJob;
+ private Job mBlurEnabledCollectionJob;
@Inject
public KeyguardSecurityContainerController(KeyguardSecurityContainer view,
@@ -463,9 +468,11 @@
KeyguardDismissTransitionInteractor keyguardDismissTransitionInteractor,
Lazy<PrimaryBouncerInteractor> primaryBouncerInteractor,
@Background Executor bgExecutor,
- Provider<DeviceEntryInteractor> deviceEntryInteractor
+ Provider<DeviceEntryInteractor> deviceEntryInteractor,
+ Lazy<WindowRootViewBlurInteractor> rootViewBlurInteractorProvider
) {
super(view);
+ mRootViewBlurInteractor = rootViewBlurInteractorProvider;
view.setAccessibilityDelegate(faceAuthAccessibilityDelegate);
mLockPatternUtils = lockPatternUtils;
mUpdateMonitor = keyguardUpdateMonitor;
@@ -539,6 +546,32 @@
}
);
}
+
+ if (Flags.bouncerUiRevamp()) {
+ mBlurEnabledCollectionJob = mJavaAdapter.get().alwaysCollectFlow(
+ mRootViewBlurInteractor.get().isBlurCurrentlySupported(),
+ this::handleBlurSupportedChanged);
+ }
+ }
+
+ private void handleBlurSupportedChanged(boolean isWindowBlurSupported) {
+ if (isWindowBlurSupported) {
+ mView.enableTransparentMode();
+ } else {
+ mView.disableTransparentMode();
+ }
+ }
+
+ private void refreshBouncerBackground() {
+ // This is present solely for screenshot tests that disable blur by invoking setprop to
+ // disable blurs, however the mRootViewBlurInteractor#isBlurCurrentlySupported doesn't emit
+ // an updated value because sysui doesn't have a way to register for changes to setprop.
+ // KeyguardSecurityContainer view is inflated only once and doesn't re-inflate so it has to
+ // check the sysprop every time bouncer is about to be shown.
+ if (Flags.bouncerUiRevamp() && (ActivityManager.isRunningInUserTestHarness()
+ || ActivityManager.isRunningInTestHarness())) {
+ handleBlurSupportedChanged(!WindowRootViewBlurRepository.isDisableBlurSysPropSet());
+ }
}
@Override
@@ -552,6 +585,11 @@
mSceneTransitionCollectionJob.cancel(null);
mSceneTransitionCollectionJob = null;
}
+
+ if (mBlurEnabledCollectionJob != null) {
+ mBlurEnabledCollectionJob.cancel(null);
+ mBlurEnabledCollectionJob = null;
+ }
}
/** */
@@ -718,6 +756,8 @@
if (bouncerUserSwitcher != null) {
bouncerUserSwitcher.setAlpha(0f);
}
+
+ refreshBouncerBackground();
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
index a67ec65..8734d05 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
@@ -296,6 +296,7 @@
mGestureDetector =
new MagnificationGestureDetector(mContext, handler, this);
mWindowInsetChangeRunnable = this::onWindowInsetChanged;
+ mWindowInsetChangeRunnable.run();
// Initialize listeners.
mMirrorViewRunnable = new Runnable() {
@@ -367,8 +368,12 @@
private boolean updateSystemGestureInsetsTop() {
final WindowMetrics windowMetrics = mWm.getCurrentWindowMetrics();
final Insets insets = windowMetrics.getWindowInsets().getInsets(systemGestures());
- final int gestureTop =
- insets.bottom != 0 ? windowMetrics.getBounds().bottom - insets.bottom : -1;
+ final int gestureTop;
+ if (Flags.updateWindowMagnifierBottomBoundary()) {
+ gestureTop = windowMetrics.getBounds().bottom - insets.bottom;
+ } else {
+ gestureTop = insets.bottom != 0 ? windowMetrics.getBounds().bottom - insets.bottom : -1;
+ }
if (gestureTop != mSystemGestureTop) {
mSystemGestureTop = gestureTop;
return true;
@@ -953,7 +958,6 @@
? mSystemGestureTop - height + mOuterBorderSize
: mWindowBounds.bottom - height + mOuterBorderSize;
final int y = MathUtils.clamp(mMagnificationFrame.top - mMirrorSurfaceMargin, minY, maxY);
-
if (computeWindowSize) {
LayoutParams params = (LayoutParams) mMirrorView.getLayoutParams();
params.width = width;
diff --git a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterViewController.java b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterViewController.java
index 9a30c21..fcf5105 100644
--- a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterViewController.java
@@ -46,7 +46,10 @@
import javax.inject.Inject;
-/** Controller for {@link BatteryMeterView}. **/
+/**
+ * Controller for {@link BatteryMeterView}.
+ * @deprecated once [NewStatusBarIcons] is rolled out, this class is no longer needed
+ */
public class BatteryMeterViewController extends ViewController<BatteryMeterView> {
private final ConfigurationController mConfigurationController;
private final TunerService mTunerService;
diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
index fc589b2..e36e855 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
@@ -19,11 +19,8 @@
import android.os.UserHandle
import android.provider.Settings
import com.android.app.tracing.coroutines.launchTraced as launch
-import com.android.compose.animation.scene.SceneKey
-import com.android.compose.animation.scene.TransitionKey
import com.android.internal.logging.UiEventLogger
import com.android.systemui.CoreStartable
-import com.android.systemui.Flags.communalSceneKtfRefactor
import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
@@ -31,36 +28,24 @@
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.communal.shared.model.CommunalScenes.isCommunal
import com.android.systemui.communal.shared.model.CommunalTransitionKeys
-import com.android.systemui.communal.shared.model.EditModeState
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.dock.DockManager
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.keyguard.shared.model.TransitionStep
import com.android.systemui.statusbar.NotificationShadeWindowController
-import com.android.systemui.statusbar.phone.CentralSurfaces
import com.android.systemui.util.kotlin.emitOnStart
-import com.android.systemui.util.kotlin.getValue
import com.android.systemui.util.kotlin.sample
import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
import com.android.systemui.util.settings.SystemSettings
-import java.util.Optional
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
-import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.withContext
@@ -72,16 +57,12 @@
class CommunalSceneStartable
@Inject
constructor(
- private val dockManager: DockManager,
private val communalInteractor: CommunalInteractor,
private val communalSettingsInteractor: CommunalSettingsInteractor,
private val communalSceneInteractor: CommunalSceneInteractor,
- private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
private val keyguardInteractor: KeyguardInteractor,
private val systemSettings: SystemSettings,
- centralSurfacesOpt: Optional<CentralSurfaces>,
private val notificationShadeWindowController: NotificationShadeWindowController,
- @Application private val applicationScope: CoroutineScope,
@Background private val bgScope: CoroutineScope,
@Main private val mainDispatcher: CoroutineDispatcher,
private val uiEventLogger: UiEventLogger,
@@ -92,110 +73,11 @@
private var isDreaming: Boolean = false
- private val centralSurfaces: CentralSurfaces? by centralSurfacesOpt
-
override fun start() {
if (!communalSettingsInteractor.isCommunalFlagEnabled()) {
return
}
- if (!communalSceneKtfRefactor()) {
- // Handle automatically switching based on keyguard state.
- keyguardTransitionInteractor.startedKeyguardTransitionStep
- .mapLatest(::determineSceneAfterTransition)
- .filterNotNull()
- .onEach { (nextScene, nextTransition) ->
- // When launching a widget, we don't want to animate the scene change or the
- // Communal Hub will reveal the wallpaper even though it shouldn't. Instead we
- // snap to the new scene as part of the launch animation, once the activity
- // launch is done, so we don't change scene here.
- val delaySceneTransition =
- communalSceneInteractor.editModeState.value == EditModeState.STARTING ||
- communalSceneInteractor.isLaunchingWidget.value
- if (!delaySceneTransition) {
- communalSceneInteractor.changeScene(
- newScene = nextScene,
- loggingReason = "KTF syncing",
- transitionKey = nextTransition,
- )
- }
- }
- .launchIn(applicationScope)
- }
-
- // TODO(b/322787129): re-enable once custom animations are in place
- // Handle automatically switching to communal when docked.
- // dockManager
- // .retrieveIsDocked()
- // // Allow some time after docking to ensure the dream doesn't start. If the
- // dream
- // // starts, then we don't want to automatically transition to glanceable hub.
- // .debounce(DOCK_DEBOUNCE_DELAY)
- // .sample(keyguardTransitionInteractor.startedKeyguardState, ::Pair)
- // .onEach { (docked, lastStartedState) ->
- // if (docked && lastStartedState == KeyguardState.LOCKSCREEN) {
- // communalInteractor.onSceneChanged(CommunalScenes.Communal)
- // }
- // }
- // .launchIn(bgScope)
-
- systemSettings
- .observerFlow(Settings.System.SCREEN_OFF_TIMEOUT)
- // Read the setting value on start.
- .emitOnStart()
- .onEach {
- screenTimeout =
- systemSettings.getIntForUser(
- Settings.System.SCREEN_OFF_TIMEOUT,
- DEFAULT_SCREEN_TIMEOUT,
- UserHandle.USER_CURRENT,
- )
- }
- .launchIn(bgScope)
-
- // The hub mode timeout should start as soon as the user enters hub mode. At the end of the
- // timer, if the device is dreaming, hub mode should closed and reveal the dream. If the
- // dream is not running, nothing will happen. However if the dream starts again underneath
- // hub mode after the initial timeout expires, such as if the device is docked or the dream
- // app is updated by the Play store, a new timeout should be started.
- bgScope.launch {
- combine(
- communalSceneInteractor.currentScene,
- // Emit a value on start so the combine starts.
- communalInteractor.userActivity.emitOnStart(),
- ) { scene, _ ->
- // Only timeout if we're on the hub is open.
- scene.isCommunal()
- }
- .collectLatest { shouldTimeout ->
- cancelHubTimeout()
- if (shouldTimeout) {
- startHubTimeout()
- }
- }
- }
- bgScope.launch {
- keyguardInteractor.isDreaming
- .sample(communalSceneInteractor.currentScene, ::Pair)
- .collectLatest { (isDreaming, scene) ->
- this@CommunalSceneStartable.isDreaming = isDreaming
- if (scene.isCommunal() && isDreaming && timeoutJob == null) {
- // If dreaming starts after timeout has expired, ex. if dream restarts under
- // the hub, wait for IS_ABLE_TO_DREAM_DELAY_MS and then close the hub. The
- // delay is necessary so the KeyguardInteractor.isAbleToDream flow passes
- // through that same amount of delay and publishes a new value which is then
- // picked up by the HomeSceneFamilyResolver such that the next call to
- // SceneInteractor.changeScene(Home) will resolve "Home" to "Dream".
- delay(KeyguardInteractor.IS_ABLE_TO_DREAM_DELAY_MS)
- communalSceneInteractor.changeScene(
- CommunalScenes.Blank,
- "dream started after timeout",
- )
- uiEventLogger.log(CommunalUiEvent.COMMUNAL_HUB_TIMEOUT)
- }
- }
- }
-
bgScope.launch {
communalSceneInteractor.isIdleOnCommunal.collectLatest {
withContext(mainDispatcher) {
@@ -203,6 +85,75 @@
}
}
}
+
+ // In V2, the timeout is handled by PowerManagerService since we no longer keep the dream
+ // active underneath the hub.
+ if (!communalSettingsInteractor.isV2FlagEnabled()) {
+ systemSettings
+ .observerFlow(Settings.System.SCREEN_OFF_TIMEOUT)
+ // Read the setting value on start.
+ .emitOnStart()
+ .onEach {
+ screenTimeout =
+ systemSettings.getIntForUser(
+ Settings.System.SCREEN_OFF_TIMEOUT,
+ DEFAULT_SCREEN_TIMEOUT,
+ UserHandle.USER_CURRENT,
+ )
+ }
+ .launchIn(bgScope)
+
+ // The hub mode timeout should start as soon as the user enters hub mode. At the end of
+ // the
+ // timer, if the device is dreaming, hub mode should closed and reveal the dream. If the
+ // dream is not running, nothing will happen. However if the dream starts again
+ // underneath
+ // hub mode after the initial timeout expires, such as if the device is docked or the
+ // dream
+ // app is updated by the Play store, a new timeout should be started.
+ bgScope.launch {
+ combine(
+ communalSceneInteractor.currentScene,
+ // Emit a value on start so the combine starts.
+ communalInteractor.userActivity.emitOnStart(),
+ ) { scene, _ ->
+ // Only timeout if we're on the hub is open.
+ scene.isCommunal()
+ }
+ .collectLatest { shouldTimeout ->
+ cancelHubTimeout()
+ if (shouldTimeout) {
+ startHubTimeout()
+ }
+ }
+ }
+
+ bgScope.launch {
+ keyguardInteractor.isDreaming
+ .sample(communalSceneInteractor.currentScene, ::Pair)
+ .collectLatest { (isDreaming, scene) ->
+ this@CommunalSceneStartable.isDreaming = isDreaming
+ if (scene.isCommunal() && isDreaming && timeoutJob == null) {
+ // If dreaming starts after timeout has expired, ex. if dream restarts
+ // under
+ // the hub, wait for IS_ABLE_TO_DREAM_DELAY_MS and then close the hub.
+ // The
+ // delay is necessary so the KeyguardInteractor.isAbleToDream flow
+ // passes
+ // through that same amount of delay and publishes a new value which is
+ // then
+ // picked up by the HomeSceneFamilyResolver such that the next call to
+ // SceneInteractor.changeScene(Home) will resolve "Home" to "Dream".
+ delay(KeyguardInteractor.IS_ABLE_TO_DREAM_DELAY_MS)
+ communalSceneInteractor.changeScene(
+ CommunalScenes.Blank,
+ "dream started after timeout",
+ )
+ uiEventLogger.log(CommunalUiEvent.COMMUNAL_HUB_TIMEOUT)
+ }
+ }
+ }
+ }
}
private fun cancelHubTimeout() {
@@ -231,49 +182,7 @@
}
}
- private suspend fun determineSceneAfterTransition(
- lastStartedTransition: TransitionStep
- ): Pair<SceneKey, TransitionKey>? {
- val to = lastStartedTransition.to
- val from = lastStartedTransition.from
- val docked = dockManager.isDocked
- val launchingActivityOverLockscreen =
- centralSurfaces?.isLaunchingActivityOverLockscreen ?: false
-
- return when {
- to == KeyguardState.OCCLUDED && !launchingActivityOverLockscreen -> {
- // Hide communal when an activity is started on keyguard, to ensure the activity
- // underneath the hub is shown. When launching activities over lockscreen, we only
- // change scenes once the activity launch animation is finished, so avoid
- // changing the scene here.
- Pair(CommunalScenes.Blank, CommunalTransitionKeys.SimpleFade)
- }
- to == KeyguardState.GLANCEABLE_HUB && from == KeyguardState.OCCLUDED -> {
- // When transitioning to the hub from an occluded state, fade out the hub without
- // doing any translation.
- Pair(CommunalScenes.Communal, CommunalTransitionKeys.SimpleFade)
- }
- // Transitioning to Blank scene when entering the edit mode will be handled separately
- // with custom animations.
- to == KeyguardState.GONE && !communalInteractor.editModeOpen.value ->
- Pair(CommunalScenes.Blank, CommunalTransitionKeys.SimpleFade)
- !docked && !KeyguardState.deviceIsAwakeInState(to) -> {
- // If the user taps the screen and wakes the device within this timeout, we don't
- // want to dismiss the hub
- delay(AWAKE_DEBOUNCE_DELAY)
- Pair(CommunalScenes.Blank, CommunalTransitionKeys.SimpleFade)
- }
- from == KeyguardState.DOZING && to == KeyguardState.GLANCEABLE_HUB -> {
- // Make sure the communal hub is showing when transitioning from dozing to hub.
- Pair(CommunalScenes.Communal, CommunalTransitionKeys.SimpleFade)
- }
- else -> null
- }
- }
-
companion object {
- val AWAKE_DEBOUNCE_DELAY = 5.seconds
- val DOCK_DEBOUNCE_DELAY = 1.seconds
val DEFAULT_SCREEN_TIMEOUT = 15000
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
index f01a6db..ff74162 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
@@ -104,6 +104,7 @@
companion object {
const val LOGGABLE_PREFIXES = "loggable_prefixes"
const val LAUNCHER_PACKAGE = "launcher_package"
+ const val SWIPE_TO_HUB = "swipe_to_hub"
@Provides
@Communal
@@ -143,5 +144,11 @@
fun provideLauncherPackage(@Main resources: Resources): String {
return resources.getString(R.string.launcher_overlayable_package)
}
+
+ @Provides
+ @Named(SWIPE_TO_HUB)
+ fun provideSwipeToHub(@Main resources: Resources): Boolean {
+ return resources.getBoolean(R.bool.config_swipeToOpenGlanceableHub)
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractor.kt
index ec55401..740555f4 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractor.kt
@@ -16,11 +16,11 @@
package com.android.systemui.communal.domain.interactor
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.compose.animation.scene.SceneKey
import com.android.compose.animation.scene.SceneTransitionLayout
import com.android.systemui.CoreStartable
-import com.android.systemui.Flags.communalSceneKtfRefactor
import com.android.systemui.communal.data.repository.CommunalSceneTransitionRepository
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.dagger.SysUISingleton
@@ -44,7 +44,6 @@
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn
-import com.android.app.tracing.coroutines.launchTraced as launch
/**
* This class listens to [SceneTransitionLayout] transitions and manages keyguard transition
@@ -122,11 +121,7 @@
)
override fun start() {
- if (
- communalSceneKtfRefactor() &&
- settingsInteractor.isCommunalFlagEnabled() &&
- !SceneContainerFlag.isEnabled
- ) {
+ if (settingsInteractor.isCommunalFlagEnabled() && !SceneContainerFlag.isEnabled) {
sceneInteractor.registerSceneStateProcessor(this)
listenForSceneTransitionProgress()
}
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 49003a7..d061a33 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
@@ -153,7 +153,7 @@
) {}
/** Called as the UI requests deleting a widget. */
- open fun onDeleteWidget(id: Int, componentName: ComponentName, rank: Int) {}
+ open fun onDeleteWidget(id: Int, key: String, componentName: ComponentName, rank: Int) {}
/** Called as the UI detects a tap event on the widget. */
open fun onTapWidget(componentName: ComponentName, rank: Int) {}
@@ -202,6 +202,12 @@
/** Called as the user request to show the customize widget button. */
open fun onLongClick() {}
+ /** Called as the user requests to switch to the previous player in UMO. */
+ open fun onShowPreviousMedia() {}
+
+ /** Called as the user requests to switch to the next player in UMO. */
+ open fun onShowNextMedia() {}
+
/** Called as the UI determines that a new widget has been added to the grid. */
open fun onNewWidgetAdded(provider: AppWidgetProviderInfo) {}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
index 8aba111..59beb1e 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
@@ -141,7 +141,10 @@
metricsLogger.logAddWidget(componentName.flattenToString(), rank)
}
- override fun onDeleteWidget(id: Int, componentName: ComponentName, rank: Int) {
+ override fun onDeleteWidget(id: Int, key: String, componentName: ComponentName, rank: Int) {
+ if (selectedKey.value == key) {
+ setSelectedKey(null)
+ }
communalInteractor.deleteWidget(id)
metricsLogger.logRemoveWidget(componentName.flattenToString(), rank)
}
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 4bc4400..2169881 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
@@ -19,6 +19,7 @@
import android.content.ComponentName
import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.systemui.Flags
+import com.android.systemui.communal.dagger.CommunalModule.Companion.SWIPE_TO_HUB
import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
@@ -92,6 +93,7 @@
private val metricsLogger: CommunalMetricsLogger,
mediaCarouselController: MediaCarouselController,
blurConfig: BlurConfig,
+ @Named(SWIPE_TO_HUB) private val swipeToHub: Boolean,
) :
BaseCommunalViewModel(
communalSceneInteractor,
@@ -254,6 +256,14 @@
}
}
+ override fun onShowPreviousMedia() {
+ mediaCarouselController.mediaCarouselScrollHandler.scrollByStep(-1)
+ }
+
+ override fun onShowNextMedia() {
+ mediaCarouselController.mediaCarouselScrollHandler.scrollByStep(1)
+ }
+
override fun onTapWidget(componentName: ComponentName, rank: Int) {
metricsLogger.logTapWidget(componentName.flattenToString(), rank)
}
@@ -349,6 +359,8 @@
/** See [CommunalSettingsInteractor.isV2FlagEnabled] */
fun v2FlagEnabled(): Boolean = communalSettingsInteractor.isV2FlagEnabled()
+ fun swipeToHubEnabled(): Boolean = swipeToHub
+
companion object {
const val POPUP_AUTO_HIDE_TIMEOUT_MS = 12000L
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
index 3c68e3a..8bff090 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
@@ -74,6 +74,7 @@
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.dagger.CentralSurfacesModule;
import com.android.systemui.statusbar.dagger.StartCentralSurfacesModule;
+import com.android.systemui.statusbar.notification.dagger.NotificationStackModule;
import com.android.systemui.statusbar.notification.dagger.ReferenceNotificationsModule;
import com.android.systemui.statusbar.notification.headsup.HeadsUpModule;
import com.android.systemui.statusbar.phone.CentralSurfaces;
@@ -169,6 +170,7 @@
WallpaperModule.class,
ShortcutHelperModule.class,
ContextualEducationModule.class,
+ NotificationStackModule.class,
})
public abstract class ReferenceSystemUIModule {
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialSchedulerInteractor.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialSchedulerInteractor.kt
index 9607053..b712fde 100644
--- a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialSchedulerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialSchedulerInteractor.kt
@@ -39,6 +39,8 @@
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.drop
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flow
@@ -99,6 +101,25 @@
waitForDeviceConnection(deviceType)
}
+ // This flow is used by the notification updater once an initial notification is launched. It
+ // listens to the device connection changes for both keyboard and touchpad. When either of the
+ // device is disconnected, resolve the tutorial type base on the latest connection state.
+ // Dropping the initial state because it's the existing notification. Filtering out BOTH because
+ // we only care about disconnections.
+ val tutorialTypeUpdates: Flow<TutorialType> =
+ keyboardRepository.isAnyKeyboardConnected
+ .combine(touchpadRepository.isAnyTouchpadConnected, ::Pair)
+ .map { (keyboardConnected, touchpadConnected) ->
+ when {
+ keyboardConnected && touchpadConnected -> TutorialType.BOTH
+ keyboardConnected -> TutorialType.KEYBOARD
+ touchpadConnected -> TutorialType.TOUCHPAD
+ else -> TutorialType.NONE
+ }
+ }
+ .drop(1)
+ .filter { it != TutorialType.BOTH }
+
private suspend fun waitForDeviceConnection(deviceType: DeviceType) =
isAnyDeviceConnected[deviceType]!!.filter { it }.first()
@@ -172,6 +193,7 @@
pw.println(
" launch time = ${repo.getScheduledTutorialLaunchTime(TOUCHPAD)}"
)
+ pw.println("Delay time = ${LAUNCH_DELAY.seconds} sec")
}
"notify" -> {
if (args.size != 2) help(pw)
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/TutorialNotificationCoordinator.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/TutorialNotificationCoordinator.kt
index 3cba70e..a90c7ad 100644
--- a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/TutorialNotificationCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/TutorialNotificationCoordinator.kt
@@ -42,6 +42,9 @@
import com.android.systemui.settings.UserTracker
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.merge
/** When the scheduler is due, show a notification to launch tutorial */
@@ -55,19 +58,43 @@
private val notificationManager: NotificationManager,
private val userTracker: UserTracker,
) {
+ private var updaterJob: Job? = null
+
fun start() {
backgroundScope.launch {
merge(
tutorialSchedulerInteractor.tutorials,
tutorialSchedulerInteractor.commandTutorials,
)
- .collect { showNotification(it) }
+ .filter { it != TutorialType.NONE }
+ .collectLatest {
+ showNotification(it)
+ updaterJob?.cancel()
+ updaterJob = backgroundScope.launch { updateWhenDeviceDisconnects() }
+ }
}
}
+ private suspend fun updateWhenDeviceDisconnects() {
+ // Only update the notification when there is an active one (i.e. if the notification has
+ // been dismissed by the user, or if the tutorial has been launched, there's no need to
+ // update)
+ tutorialSchedulerInteractor.tutorialTypeUpdates
+ .filter { hasNotification() }
+ .collect {
+ if (it == TutorialType.NONE)
+ notificationManager.cancelAsUser(TAG, NOTIFICATION_ID, userTracker.userHandle)
+ else showNotification(it)
+ }
+ }
+
+ private fun hasNotification() =
+ notificationManager.activeNotifications.any { it.id == NOTIFICATION_ID }
+
// By sharing the same tag and id, we update the content of existing notification instead of
// creating multiple notifications
private fun showNotification(tutorialType: TutorialType) {
+ // Safe guard - but this should never been reached
if (tutorialType == TutorialType.NONE) return
if (notificationManager.getNotificationChannel(CHANNEL_ID) == null)
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/AccessibilityShortcutsSource.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/AccessibilityShortcutsSource.kt
index fdb80b2..978b873 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/AccessibilityShortcutsSource.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/AccessibilityShortcutsSource.kt
@@ -52,17 +52,15 @@
val shortcuts = mutableListOf<KeyboardShortcutInfo>()
if (keyboardA11yShortcutControl()) {
- if (InputSettings.isAccessibilityBounceKeysFeatureEnabled()) {
- shortcuts.add(
- // Toggle bounce keys:
- // - Meta + Alt + 3
- shortcutInfo(
- resources.getString(R.string.group_accessibility_toggle_bounce_keys)
- ) {
- command(META_META_ON or META_ALT_ON, KEYCODE_3)
- }
- )
- }
+ shortcuts.add(
+ // Toggle bounce keys:
+ // - Meta + Alt + 3
+ shortcutInfo(
+ resources.getString(R.string.group_accessibility_toggle_bounce_keys)
+ ) {
+ command(META_META_ON or META_ALT_ON, KEYCODE_3)
+ }
+ )
if (InputSettings.isAccessibilityMouseKeysFeatureFlagEnabled()) {
shortcuts.add(
// Toggle mouse keys:
@@ -74,28 +72,24 @@
}
)
}
- if (InputSettings.isAccessibilityStickyKeysFeatureEnabled()) {
- shortcuts.add(
- // Toggle sticky keys:
- // - Meta + Alt + 5
- shortcutInfo(
- resources.getString(R.string.group_accessibility_toggle_sticky_keys)
- ) {
- command(META_META_ON or META_ALT_ON, KEYCODE_5)
- }
- )
- }
- if (InputSettings.isAccessibilitySlowKeysFeatureFlagEnabled()) {
- shortcuts.add(
- // Toggle slow keys:
- // - Meta + Alt + 6
- shortcutInfo(
- resources.getString(R.string.group_accessibility_toggle_slow_keys)
- ) {
- command(META_META_ON or META_ALT_ON, KEYCODE_6)
- }
- )
- }
+ shortcuts.add(
+ // Toggle sticky keys:
+ // - Meta + Alt + 5
+ shortcutInfo(
+ resources.getString(R.string.group_accessibility_toggle_sticky_keys)
+ ) {
+ command(META_META_ON or META_ALT_ON, KEYCODE_5)
+ }
+ )
+ shortcuts.add(
+ // Toggle slow keys:
+ // - Meta + Alt + 6
+ shortcutInfo(
+ resources.getString(R.string.group_accessibility_toggle_slow_keys)
+ ) {
+ command(META_META_ON or META_ALT_ON, KEYCODE_6)
+ }
+ )
}
if (enableVoiceAccessKeyGestures()) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
index f85a23c..6f5f662 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
@@ -21,9 +21,9 @@
import android.app.DreamManager
import com.android.app.animation.Interpolators
import com.android.app.tracing.coroutines.launchTraced as launch
-import com.android.systemui.Flags.communalSceneKtfRefactor
import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
+import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
@@ -57,6 +57,7 @@
keyguardInteractor: KeyguardInteractor,
powerInteractor: PowerInteractor,
private val communalInteractor: CommunalInteractor,
+ private val communalSettingsInteractor: CommunalSettingsInteractor,
private val communalSceneInteractor: CommunalSceneInteractor,
keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
val deviceEntryInteractor: DeviceEntryInteractor,
@@ -116,6 +117,17 @@
}
}
+ @SuppressLint("MissingPermission")
+ private fun shouldTransitionToCommunal(
+ shouldShowCommunal: Boolean,
+ isCommunalAvailable: Boolean,
+ ) =
+ if (communalSettingsInteractor.isV2FlagEnabled()) {
+ shouldShowCommunal
+ } else {
+ isCommunalAvailable && dreamManager.canStartDreaming(false)
+ }
+
@OptIn(FlowPreview::class)
@SuppressLint("MissingPermission")
private fun listenForDozingToDreaming() {
@@ -141,9 +153,9 @@
.filterRelevantKeyguardStateAnd { isAwake -> isAwake }
.sample(
communalInteractor.isCommunalAvailable,
- communalSceneInteractor.isIdleOnCommunal,
+ communalInteractor.shouldShowCommunal,
)
- .collect { (_, isCommunalAvailable, isIdleOnCommunal) ->
+ .collect { (_, isCommunalAvailable, shouldShowCommunal) ->
val isKeyguardOccludedLegacy = keyguardInteractor.isKeyguardOccluded.value
val primaryBouncerShowing = keyguardInteractor.primaryBouncerShowing.value
val isKeyguardGoingAway = keyguardInteractor.isKeyguardGoingAway.value
@@ -173,15 +185,9 @@
}
} else if (isKeyguardOccludedLegacy) {
startTransitionTo(KeyguardState.OCCLUDED)
- } else if (isIdleOnCommunal && !communalSceneKtfRefactor()) {
- if (!SceneContainerFlag.isEnabled) {
- startTransitionTo(KeyguardState.GLANCEABLE_HUB)
- }
- } else if (isCommunalAvailable && dreamManager.canStartDreaming(false)) {
- // Using false for isScreenOn as canStartDreaming returns false if any
- // dream, including doze, is active.
- // This case handles tapping the power button to transition through
- // dream -> off -> hub.
+ } else if (
+ shouldTransitionToCommunal(shouldShowCommunal, isCommunalAvailable)
+ ) {
if (!SceneContainerFlag.isEnabled) {
transitionToGlanceableHub()
}
@@ -203,8 +209,8 @@
powerInteractor.detailedWakefulness
.filterRelevantKeyguardStateAnd { it.isAwake() }
.sample(
+ communalInteractor.shouldShowCommunal,
communalInteractor.isCommunalAvailable,
- communalSceneInteractor.isIdleOnCommunal,
keyguardInteractor.biometricUnlockState,
wakeToGoneInteractor.canWakeDirectlyToGone,
keyguardInteractor.primaryBouncerShowing,
@@ -212,8 +218,8 @@
.collect {
(
_,
+ shouldShowCommunal,
isCommunalAvailable,
- isIdleOnCommunal,
biometricUnlockState,
canWakeDirectlyToGone,
primaryBouncerShowing) ->
@@ -238,14 +244,9 @@
ownerReason = "waking from dozing",
)
}
- } else if (isIdleOnCommunal && !communalSceneKtfRefactor()) {
- if (!SceneContainerFlag.isEnabled) {
- startTransitionTo(
- KeyguardState.GLANCEABLE_HUB,
- ownerReason = "waking from dozing",
- )
- }
- } else if (isCommunalAvailable && dreamManager.canStartDreaming(true)) {
+ } else if (
+ shouldTransitionToCommunal(shouldShowCommunal, isCommunalAvailable)
+ ) {
if (!SceneContainerFlag.isEnabled) {
transitionToGlanceableHub()
}
@@ -260,15 +261,11 @@
}
}
- private suspend fun transitionToGlanceableHub() {
- if (communalSceneKtfRefactor()) {
- communalSceneInteractor.snapToScene(
- newScene = CommunalScenes.Communal,
- loggingReason = "from dozing to hub",
- )
- } else {
- startTransitionTo(KeyguardState.GLANCEABLE_HUB)
- }
+ private fun transitionToGlanceableHub() {
+ communalSceneInteractor.snapToScene(
+ newScene = CommunalScenes.Communal,
+ loggingReason = "from dozing to hub",
+ )
}
/** Dismisses keyguard from the DOZING state. */
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
index 251af11..0fb98ff 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
@@ -21,7 +21,6 @@
import android.app.DreamManager
import com.android.app.animation.Interpolators
import com.android.app.tracing.coroutines.launchTraced as launch
-import com.android.systemui.Flags.communalSceneKtfRefactor
import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
@@ -59,7 +58,6 @@
@Background bgDispatcher: CoroutineDispatcher,
@Main mainDispatcher: CoroutineDispatcher,
keyguardInteractor: KeyguardInteractor,
- private val glanceableHubTransitions: GlanceableHubTransitions,
private val communalInteractor: CommunalInteractor,
private val communalSceneInteractor: CommunalSceneInteractor,
private val communalSettingsInteractor: CommunalSettingsInteractor,
@@ -87,11 +85,7 @@
listenForDreamingToLockscreenOrGone()
listenForDreamingToAodOrDozing()
listenForTransitionToCamera(scope, keyguardInteractor)
- if (!communalSceneKtfRefactor()) {
- listenForDreamingToGlanceableHub()
- } else {
- listenForDreamingToGlanceableHubFromPowerButton()
- }
+ listenForDreamingToGlanceableHubFromPowerButton()
listenForDreamingToPrimaryBouncer()
}
@@ -105,18 +99,6 @@
}
}
- private fun listenForDreamingToGlanceableHub() {
- if (!communalSettingsInteractor.isCommunalFlagEnabled()) return
- if (SceneContainerFlag.isEnabled) return
- scope.launch("$TAG#listenForDreamingToGlanceableHub", mainDispatcher) {
- glanceableHubTransitions.listenForGlanceableHubTransition(
- transitionOwnerName = TAG,
- fromState = KeyguardState.DREAMING,
- toState = KeyguardState.GLANCEABLE_HUB,
- )
- }
- }
-
/**
* Normally when pressing power button from the dream, the devices goes from DREAMING to DOZING,
* then [FromDozingTransitionInteractor] handles the transition to GLANCEABLE_HUB. However if
@@ -129,20 +111,37 @@
if (!communalSettingsInteractor.isCommunalFlagEnabled()) return
if (SceneContainerFlag.isEnabled) return
scope.launch {
- powerInteractor.isAwake
- .debounce(50L)
- .filterRelevantKeyguardStateAnd { isAwake -> isAwake }
- .sample(communalInteractor.isCommunalAvailable)
- .collect { isCommunalAvailable ->
- if (isCommunalAvailable && dreamManager.canStartDreaming(false)) {
- // This case handles tapping the power button to transition through
- // dream -> off -> hub.
- communalSceneInteractor.snapToScene(
- newScene = CommunalScenes.Communal,
- loggingReason = "from dreaming to hub",
- )
+ if (communalSettingsInteractor.isV2FlagEnabled()) {
+ powerInteractor.isAwake
+ .debounce(50L)
+ .filterRelevantKeyguardStateAnd { isAwake -> isAwake }
+ .sample(communalInteractor.shouldShowCommunal)
+ .collect { shouldShowCommunal ->
+ if (shouldShowCommunal) {
+ // This case handles tapping the power button to transition through
+ // dream -> off -> hub.
+ communalSceneInteractor.snapToScene(
+ newScene = CommunalScenes.Communal,
+ loggingReason = "from dreaming to hub",
+ )
+ }
}
- }
+ } else {
+ powerInteractor.isAwake
+ .debounce(50L)
+ .filterRelevantKeyguardStateAnd { isAwake -> isAwake }
+ .sample(communalInteractor.isCommunalAvailable)
+ .collect { isCommunalAvailable ->
+ if (isCommunalAvailable && dreamManager.canStartDreaming(false)) {
+ // This case handles tapping the power button to transition through
+ // dream -> off -> hub.
+ communalSceneInteractor.snapToScene(
+ newScene = CommunalScenes.Communal,
+ loggingReason = "from dreaming to hub",
+ )
+ }
+ }
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
index c5d40a0..2eeba0f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
@@ -19,7 +19,6 @@
import android.animation.ValueAnimator
import com.android.app.animation.Interpolators
import com.android.app.tracing.coroutines.launchTraced as launch
-import com.android.systemui.Flags.communalSceneKtfRefactor
import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
import com.android.systemui.communal.shared.model.CommunalScenes
@@ -31,12 +30,10 @@
import com.android.systemui.keyguard.KeyguardWmStateRefactor
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
-import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.util.kotlin.BooleanFlowOperators.allOf
import com.android.systemui.util.kotlin.BooleanFlowOperators.noneOf
-import com.android.systemui.util.kotlin.BooleanFlowOperators.not
import com.android.systemui.util.kotlin.Utils.Companion.sampleFilter
import com.android.systemui.util.kotlin.sample
import javax.inject.Inject
@@ -45,10 +42,8 @@
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.FlowPreview
-import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.debounce
-import kotlinx.coroutines.withContext
@OptIn(FlowPreview::class)
@SysUISingleton
@@ -58,7 +53,6 @@
@Background private val scope: CoroutineScope,
@Main mainDispatcher: CoroutineDispatcher,
@Background bgDispatcher: CoroutineDispatcher,
- private val glanceableHubTransitions: GlanceableHubTransitions,
private val communalSettingsInteractor: CommunalSettingsInteractor,
keyguardInteractor: KeyguardInteractor,
private val communalSceneInteractor: CommunalSceneInteractor,
@@ -83,9 +77,6 @@
if (!communalSettingsInteractor.isCommunalFlagEnabled()) {
return
}
- if (!communalSceneKtfRefactor()) {
- listenForHubToLockscreenOrDreaming()
- }
listenForHubToDozing()
listenForHubToPrimaryBouncer()
listenForHubToAlternateBouncer()
@@ -108,30 +99,6 @@
}
}
- /**
- * Listens for the glanceable hub transition to lock screen and directly drives the keyguard
- * transition.
- */
- private fun listenForHubToLockscreenOrDreaming() {
- scope.launch("$TAG#listenForGlanceableHubToLockscreenOrDream") {
- keyguardInteractor.isDreaming.collectLatest { dreaming ->
- withContext(mainDispatcher) {
- val toState =
- if (dreaming) {
- KeyguardState.DREAMING
- } else {
- KeyguardState.LOCKSCREEN
- }
- glanceableHubTransitions.listenForGlanceableHubTransition(
- transitionOwnerName = TAG,
- fromState = KeyguardState.GLANCEABLE_HUB,
- toState = toState,
- )
- }
- }
- }
- }
-
private fun listenForHubToPrimaryBouncer() {
scope.launch("$TAG#listenForHubToPrimaryBouncer") {
keyguardInteractor.primaryBouncerShowing
@@ -161,18 +128,11 @@
powerInteractor.isAsleep
.filterRelevantKeyguardStateAnd { isAsleep -> isAsleep }
.collect {
- if (communalSceneKtfRefactor()) {
- communalSceneInteractor.snapToScene(
- newScene = CommunalScenes.Blank,
- loggingReason = "hub to dozing",
- keyguardState = KeyguardState.DOZING,
- )
- } else {
- startTransitionTo(
- toState = KeyguardState.DOZING,
- modeOnCanceled = TransitionModeOnCanceled.LAST_VALUE,
- )
- }
+ communalSceneInteractor.snapToScene(
+ newScene = CommunalScenes.Blank,
+ loggingReason = "hub to dozing",
+ keyguardState = KeyguardState.DOZING,
+ )
}
}
}
@@ -202,21 +162,17 @@
.filterRelevantKeyguardStateAnd { onTop -> onTop }
.collect {
maybeStartTransitionToOccludedOrInsecureCamera { state, reason ->
- if (communalSceneKtfRefactor()) {
- communalSceneInteractor.changeScene(
- newScene = CommunalScenes.Blank,
- loggingReason = "hub to occluded (KeyguardWmStateRefactor)",
- transitionKey = CommunalTransitionKeys.SimpleFade,
- keyguardState = state,
- )
- null
- } else {
- startTransitionTo(state, ownerReason = reason)
- }
+ communalSceneInteractor.changeScene(
+ newScene = CommunalScenes.Blank,
+ loggingReason = "hub to occluded (KeyguardWmStateRefactor)",
+ transitionKey = CommunalTransitionKeys.SimpleFade,
+ keyguardState = state,
+ )
+ null
}
}
}
- } else if (communalSceneKtfRefactor()) {
+ } else {
scope.launch {
combine(
keyguardInteractor.isKeyguardOccluded,
@@ -248,56 +204,40 @@
)
}
}
- } else {
- scope.launch {
- allOf(keyguardInteractor.isKeyguardOccluded, not(keyguardInteractor.isDreaming))
- .filterRelevantKeyguardStateAnd { isOccludedAndNotDreaming ->
- isOccludedAndNotDreaming
- }
- .collect { _ -> startTransitionTo(KeyguardState.OCCLUDED) }
- }
}
}
private fun listenForHubToGone() {
if (SceneContainerFlag.isEnabled) return
- if (communalSceneKtfRefactor()) {
- scope.launch {
- allOf(
- keyguardInteractor.isKeyguardGoingAway,
- // TODO(b/327225415): Handle edit mode opening here to avoid going to GONE
- // state until after edit mode is ready to be shown.
- noneOf(
- // When launching activities from widgets on the hub, we wait to change
- // scenes until the activity launch is complete.
- communalSceneInteractor.isLaunchingWidget
- ),
- )
- .filterRelevantKeyguardStateAnd { isKeyguardGoingAway -> isKeyguardGoingAway }
- .sample(communalSceneInteractor.editModeState, ::Pair)
- .collect { (_, editModeState) ->
- if (
- editModeState == EditModeState.STARTING ||
- editModeState == EditModeState.SHOWING
- ) {
- // Don't change scenes here as that is handled by the edit activity.
- startTransitionTo(KeyguardState.GONE)
- } else {
- communalSceneInteractor.changeScene(
- newScene = CommunalScenes.Blank,
- loggingReason = "hub to gone",
- transitionKey = CommunalTransitionKeys.SimpleFade,
- keyguardState = KeyguardState.GONE,
- )
- }
+ scope.launch {
+ allOf(
+ keyguardInteractor.isKeyguardGoingAway,
+ // TODO(b/327225415): Handle edit mode opening here to avoid going to GONE
+ // state until after edit mode is ready to be shown.
+ noneOf(
+ // When launching activities from widgets on the hub, we wait to change
+ // scenes until the activity launch is complete.
+ communalSceneInteractor.isLaunchingWidget
+ ),
+ )
+ .filterRelevantKeyguardStateAnd { isKeyguardGoingAway -> isKeyguardGoingAway }
+ .sample(communalSceneInteractor.editModeState, ::Pair)
+ .collect { (_, editModeState) ->
+ if (
+ editModeState == EditModeState.STARTING ||
+ editModeState == EditModeState.SHOWING
+ ) {
+ // Don't change scenes here as that is handled by the edit activity.
+ startTransitionTo(KeyguardState.GONE)
+ } else {
+ communalSceneInteractor.changeScene(
+ newScene = CommunalScenes.Blank,
+ loggingReason = "hub to gone",
+ transitionKey = CommunalTransitionKeys.SimpleFade,
+ keyguardState = KeyguardState.GONE,
+ )
}
- }
- } else {
- scope.launch {
- keyguardInteractor.isKeyguardGoingAway
- .filterRelevantKeyguardStateAnd { isKeyguardGoingAway -> isKeyguardGoingAway }
- .collect { startTransitionTo(KeyguardState.GONE) }
- }
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
index cf712f1..a01dc02 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
@@ -20,7 +20,6 @@
import android.util.MathUtils
import com.android.app.animation.Interpolators
import com.android.app.tracing.coroutines.launchTraced as launch
-import com.android.systemui.Flags.communalSceneKtfRefactor
import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
@@ -69,7 +68,6 @@
keyguardInteractor: KeyguardInteractor,
private val shadeRepository: ShadeRepository,
powerInteractor: PowerInteractor,
- private val glanceableHubTransitions: GlanceableHubTransitions,
private val communalSettingsInteractor: CommunalSettingsInteractor,
private val communalInteractor: CommunalInteractor,
private val communalSceneInteractor: CommunalSceneInteractor,
@@ -96,9 +94,6 @@
listenForLockscreenToPrimaryBouncerDragging()
listenForLockscreenToAlternateBouncer()
listenForLockscreenTransitionToCamera()
- if (!communalSceneKtfRefactor()) {
- listenForLockscreenToGlanceableHub()
- }
if (communalSettingsInteractor.isV2FlagEnabled()) {
listenForLockscreenToGlanceableHubV2()
}
@@ -358,24 +353,6 @@
}
}
- /**
- * Listens for transition from glanceable hub back to lock screen and directly drives the
- * keyguard transition.
- */
- private fun listenForLockscreenToGlanceableHub() {
- if (SceneContainerFlag.isEnabled) return
- if (!communalSettingsInteractor.isCommunalFlagEnabled()) {
- return
- }
- scope.launch(context = mainDispatcher) {
- glanceableHubTransitions.listenForGlanceableHubTransition(
- transitionOwnerName = TAG,
- fromState = KeyguardState.LOCKSCREEN,
- toState = KeyguardState.GLANCEABLE_HUB,
- )
- }
- }
-
private fun listenForLockscreenToGlanceableHubV2() {
scope.launch {
communalInteractor.shouldShowCommunal
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
index 284298d..a2cfdc1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
@@ -19,7 +19,6 @@
import android.animation.ValueAnimator
import com.android.app.animation.Interpolators
import com.android.app.tracing.coroutines.launchTraced as launch
-import com.android.systemui.Flags.communalSceneKtfRefactor
import com.android.systemui.Flags.restartDreamOnUnocclude
import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
@@ -143,15 +142,11 @@
startTransitionTo(KeyguardState.DREAMING)
} else if (isIdleOnCommunal || showCommunalFromOccluded) {
if (SceneContainerFlag.isEnabled) return
- if (communalSceneKtfRefactor()) {
- communalSceneInteractor.changeScene(
- newScene = CommunalScenes.Communal,
- loggingReason = "occluded to hub",
- transitionKey = CommunalTransitionKeys.SimpleFade,
- )
- } else {
- startTransitionTo(KeyguardState.GLANCEABLE_HUB)
- }
+ communalSceneInteractor.changeScene(
+ newScene = CommunalScenes.Communal,
+ loggingReason = "occluded to hub",
+ transitionKey = CommunalTransitionKeys.SimpleFade,
+ )
} else {
startTransitionTo(KeyguardState.LOCKSCREEN)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
index 402c152..30c1aac 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
@@ -18,8 +18,8 @@
import android.animation.ValueAnimator
import android.util.Log
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.keyguard.KeyguardSecurityModel
-import com.android.systemui.Flags.communalSceneKtfRefactor
import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.dagger.SysUISingleton
@@ -35,7 +35,6 @@
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.android.systemui.util.kotlin.Utils.Companion.sample
-import com.android.systemui.util.kotlin.sample
import com.android.wm.shell.shared.animation.Interpolators
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
@@ -45,7 +44,6 @@
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
-import com.android.app.tracing.coroutines.launchTraced as launch
@SysUISingleton
class FromPrimaryBouncerTransitionInteractor
@@ -173,8 +171,7 @@
// If the hub is showing, and we are not animating a widget launch nor transitioning to
// edit mode, then close the hub immediately.
if (
- communalSceneKtfRefactor() &&
- communalSceneInteractor.isIdleOnCommunal.value &&
+ communalSceneInteractor.isIdleOnCommunal.value &&
!communalSceneInteractor.isLaunchingWidget.value &&
communalSceneInteractor.editModeState.value == null
) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitions.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitions.kt
deleted file mode 100644
index bde0f56..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitions.kt
+++ /dev/null
@@ -1,146 +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.keyguard.domain.interactor
-
-import android.animation.ValueAnimator
-import com.android.app.animation.Interpolators
-import com.android.systemui.communal.domain.interactor.CommunalInteractor
-import com.android.systemui.communal.domain.model.CommunalTransitionProgressModel
-import com.android.systemui.communal.shared.model.CommunalScenes
-import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
-import com.android.systemui.keyguard.shared.model.KeyguardState
-import com.android.systemui.keyguard.shared.model.TransitionInfo
-import com.android.systemui.keyguard.shared.model.TransitionState
-import com.android.systemui.scene.shared.flag.SceneContainerFlag
-import com.android.systemui.util.kotlin.sample
-import java.util.UUID
-import javax.inject.Inject
-
-class GlanceableHubTransitions
-@Inject
-constructor(
- private val transitionInteractor: KeyguardTransitionInteractor,
- private val transitionRepository: KeyguardTransitionRepository,
- private val communalInteractor: CommunalInteractor,
-) {
- /**
- * Listens for the glanceable hub transition to the specified scene and directly drives the
- * keyguard transition between the lockscreen and the hub.
- *
- * The glanceable hub transition progress is used as the source of truth as it cannot be driven
- * externally. The progress is used for both transitions caused by user touch input or by
- * programmatic changes.
- */
- suspend fun listenForGlanceableHubTransition(
- transitionOwnerName: String,
- fromState: KeyguardState,
- toState: KeyguardState,
- ) {
- if (SceneContainerFlag.isEnabled) return
- val toScene =
- if (fromState == KeyguardState.GLANCEABLE_HUB) {
- CommunalScenes.Blank
- } else {
- CommunalScenes.Communal
- }
- var transitionId: UUID? = null
-
- communalInteractor
- .transitionProgressToScene(toScene)
- .sample(
- transitionInteractor.startedKeyguardTransitionStep,
- ::Pair,
- )
- .collect { (transitionProgress, lastStartedStep) ->
- val id = transitionId
- if (id == null) {
- // No transition started.
- if (
- transitionProgress is CommunalTransitionProgressModel.Transition &&
- lastStartedStep.to == fromState
- ) {
- transitionId =
- transitionRepository.startTransition(
- TransitionInfo(
- ownerName = transitionOwnerName,
- from = fromState,
- to = toState,
- animator = null, // transition will be manually controlled
- )
- )
- }
- } else {
- if (lastStartedStep.to != toState) {
- return@collect
- }
- // An existing `id` means a transition is started, and calls to
- // `updateTransition` will control it until FINISHED or CANCELED
- val nextState: TransitionState
- val progressFraction: Float
- when (transitionProgress) {
- is CommunalTransitionProgressModel.Idle -> {
- if (transitionProgress.scene == toScene) {
- nextState = TransitionState.FINISHED
- progressFraction = 1f
- } else {
- nextState = TransitionState.CANCELED
- progressFraction = 0f
- }
- }
- is CommunalTransitionProgressModel.Transition -> {
- nextState = TransitionState.RUNNING
- progressFraction = transitionProgress.progress
- }
- is CommunalTransitionProgressModel.OtherTransition -> {
- // Shouldn't happen but if another transition starts during the
- // current one, mark the current one as canceled.
- nextState = TransitionState.CANCELED
- progressFraction = 0f
- }
- }
- transitionRepository.updateTransition(
- id,
- progressFraction,
- nextState,
- )
-
- if (
- nextState == TransitionState.CANCELED ||
- nextState == TransitionState.FINISHED
- ) {
- transitionId = null
- }
-
- // If canceled, just put the state back.
- if (nextState == TransitionState.CANCELED) {
- transitionRepository.startTransition(
- TransitionInfo(
- ownerName = transitionOwnerName,
- from = toState,
- to = fromState,
- animator =
- ValueAnimator().apply {
- interpolator = Interpolators.LINEAR
- duration = 0
- }
- )
- )
- }
- }
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt
index 8f68158..c5127a5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt
@@ -66,11 +66,14 @@
* range. This function maps the [startTime] and [duration] into [0, 1], when this subset is
* valid.
*
+ * Note that [onStep] accepts a null return value. When null, no animation information will
+ * be emitted, effectively saying "do not change the value on this frame"
+ *
* Note that [onCancel] isn't used when the scene framework is enabled.
*/
fun sharedFlow(
duration: Duration = transitionDuration,
- onStep: (Float) -> Float,
+ onStep: (Float) -> Float?,
startTime: Duration = 0.milliseconds,
onStart: (() -> Unit)? = null,
onCancel: (() -> Float)? = null,
@@ -102,7 +105,7 @@
*/
fun sharedFlowWithState(
duration: Duration,
- onStep: (Float) -> Float,
+ onStep: (Float) -> Float?,
startTime: Duration = 0.milliseconds,
onStart: (() -> Unit)? = null,
onCancel: (() -> Float)? = null,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDozingTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDozingTransitionViewModel.kt
index dc7fefa..65c0a4f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDozingTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDozingTransitionViewModel.kt
@@ -54,7 +54,12 @@
)
val deviceEntryBackgroundViewAlpha: Flow<Float> =
- transitionAnimation.immediatelyTransitionTo(0f)
+ transitionAnimation.sharedFlow(
+ duration = TO_DOZING_DURATION,
+ onStep = { null },
+ onFinish = { 0f },
+ onCancel = { 0f },
+ )
override val deviceEntryParentViewAlpha: Flow<Float> =
deviceEntryUdfpsInteractor.isUdfpsEnrolledAndEnabled.flatMapLatest {
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaCarouselScrollHandler.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaCarouselScrollHandler.kt
index d63c2e0..0107a52 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaCarouselScrollHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaCarouselScrollHandler.kt
@@ -23,11 +23,11 @@
import android.view.View
import android.view.ViewGroup
import android.view.ViewOutlineProvider
+import androidx.annotation.VisibleForTesting
import androidx.core.view.GestureDetectorCompat
import androidx.dynamicanimation.animation.FloatPropertyCompat
import androidx.dynamicanimation.animation.SpringForce
import com.android.app.tracing.TraceStateLogger
-import com.android.internal.annotations.VisibleForTesting
import com.android.settingslib.Utils
import com.android.systemui.Gefingerpoken
import com.android.systemui.classifier.Classifier.NOTIFICATION_DISMISS
@@ -38,9 +38,10 @@
import com.android.systemui.util.animation.TransitionLayout
import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.wm.shell.shared.animation.PhysicsAnimator
+import kotlin.math.sign
private const val FLING_SLOP = 1000000
-private const val DISMISS_DELAY = 100L
+@VisibleForTesting const val DISMISS_DELAY = 100L
private const val SCROLL_DELAY = 100L
private const val RUBBERBAND_FACTOR = 0.2f
private const val SETTINGS_BUTTON_TRANSLATION_FRACTION = 0.3f
@@ -64,7 +65,7 @@
private val closeGuts: (immediate: Boolean) -> Unit,
private val falsingManager: FalsingManager,
private val logSmartspaceImpression: (Boolean) -> Unit,
- private val logger: MediaUiEventLogger
+ private val logger: MediaUiEventLogger,
) {
/** Trace state logger for media carousel visibility */
private val visibleStateLogger = TraceStateLogger("$TAG#visibleToUser")
@@ -96,7 +97,7 @@
/** What's the currently visible player index? */
var visibleMediaIndex: Int = 0
- private set
+ @VisibleForTesting set
/** How much are we scrolled into the current media? */
private var scrollIntoCurrentMedia: Int = 0
@@ -137,14 +138,14 @@
eStart: MotionEvent?,
eCurrent: MotionEvent,
vX: Float,
- vY: Float
+ vY: Float,
) = onFling(vX, vY)
override fun onScroll(
down: MotionEvent?,
lastMotion: MotionEvent,
distanceX: Float,
- distanceY: Float
+ distanceY: Float,
) = onScroll(down!!, lastMotion, distanceX)
override fun onDown(e: MotionEvent): Boolean {
@@ -157,6 +158,7 @@
val touchListener =
object : Gefingerpoken {
override fun onTouchEvent(motionEvent: MotionEvent?) = onTouch(motionEvent!!)
+
override fun onInterceptTouchEvent(ev: MotionEvent?) = onInterceptTouch(ev!!)
}
@@ -168,7 +170,7 @@
scrollX: Int,
scrollY: Int,
oldScrollX: Int,
- oldScrollY: Int
+ oldScrollY: Int,
) {
if (playerWidthPlusPadding == 0) {
return
@@ -177,7 +179,7 @@
val relativeScrollX = scrollView.relativeScrollX
onMediaScrollingChanged(
relativeScrollX / playerWidthPlusPadding,
- relativeScrollX % playerWidthPlusPadding
+ relativeScrollX % playerWidthPlusPadding,
)
}
}
@@ -209,7 +211,7 @@
0,
carouselWidth,
carouselHeight,
- cornerRadius.toFloat()
+ cornerRadius.toFloat(),
)
}
}
@@ -235,7 +237,7 @@
getMaxTranslation().toFloat(),
0.0f,
1.0f,
- Math.abs(contentTranslation)
+ Math.abs(contentTranslation),
)
val settingsTranslation =
(1.0f - settingsOffset) *
@@ -323,7 +325,7 @@
CONTENT_TRANSLATION,
newTranslation,
startVelocity = 0.0f,
- config = translationConfig
+ config = translationConfig,
)
.start()
scrollView.animationTargetX = newTranslation
@@ -391,7 +393,7 @@
CONTENT_TRANSLATION,
newTranslation,
startVelocity = 0.0f,
- config = translationConfig
+ config = translationConfig,
)
.start()
} else {
@@ -430,7 +432,7 @@
CONTENT_TRANSLATION,
newTranslation,
startVelocity = vX,
- config = translationConfig
+ config = translationConfig,
)
.start()
scrollView.animationTargetX = newTranslation
@@ -583,10 +585,35 @@
// We need to post this to wait for the active player becomes visible.
mainExecutor.executeDelayed(
{ scrollView.smoothScrollTo(view.left, scrollView.scrollY) },
- SCROLL_DELAY
+ SCROLL_DELAY,
)
}
+ /**
+ * Scrolls the media carousel by the number of players specified by [step]. If scrolling beyond
+ * the carousel's bounds:
+ * - If the carousel is not dismissible, the settings button is displayed.
+ * - If the carousel is dismissible, no action taken.
+ *
+ * @param step A positive number means next, and negative means previous.
+ */
+ fun scrollByStep(step: Int) {
+ val destIndex = visibleMediaIndex + step
+ if (destIndex >= mediaContent.childCount || destIndex < 0) {
+ if (!showsSettingsButton) return
+ var translation = getMaxTranslation() * sign(-step.toFloat())
+ translation = if (isRtl) -translation else translation
+ PhysicsAnimator.getInstance(this)
+ .spring(CONTENT_TRANSLATION, translation, config = translationConfig)
+ .start()
+ scrollView.animationTargetX = translation
+ } else if (scrollView.getContentTranslation() != 0.0f) {
+ resetTranslation(true)
+ } else {
+ scrollToPlayer(destIndex = destIndex)
+ }
+ }
+
companion object {
private val CONTENT_TRANSLATION =
object : FloatPropertyCompat<MediaCarouselScrollHandler>("contentTranslation") {
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
index be814ae..3d2aafe 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
@@ -440,6 +440,7 @@
updateEndAreaColor(groupStatus.selected() ? mController.getColorSeekbarProgress()
: mController.getColorItemBackground());
mEndTouchArea.setContentDescription(getDeviceItemContentDescription(device));
+ mCheckBox.setOnCheckedChangeListener(null);
mCheckBox.setChecked(groupStatus.selected());
mCheckBox.setOnCheckedChangeListener(
isEnabled ? (buttonView, isChecked) -> onGroupActionTriggered(
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/QSPreferencesRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/QSPreferencesRepository.kt
index 16dff7d..11b014c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/QSPreferencesRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/QSPreferencesRepository.kt
@@ -28,6 +28,7 @@
import com.android.systemui.log.core.Logger
import com.android.systemui.qs.panels.shared.model.PanelsLog
import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.pipeline.shared.TilesUpgradePath
import com.android.systemui.settings.UserFileManager
import com.android.systemui.user.data.repository.UserRepository
import com.android.systemui.util.kotlin.SharedPreferencesExt.observe
@@ -83,34 +84,78 @@
.flowOn(backgroundDispatcher)
/** Sets for the current user the set of [TileSpec] to display as large tiles. */
- fun setLargeTilesSpecs(specs: Set<TileSpec>) {
- setLargeTilesSpecsForUser(specs, userRepository.getSelectedUserInfo().id)
- }
-
- private fun setLargeTilesSpecsForUser(specs: Set<TileSpec>, userId: Int) {
- with(getSharedPrefs(userId)) {
- edit().putStringSet(LARGE_TILES_SPECS_KEY, specs.map { it.spec }.toSet()).apply()
+ fun writeLargeTileSpecs(specs: Set<TileSpec>) {
+ with(getSharedPrefs(userRepository.getSelectedUserInfo().id)) {
+ writeLargeTileSpecs(specs)
+ setLargeTilesDefault(false)
}
}
+ suspend fun deleteLargeTileDataJob() {
+ userRepository.selectedUserInfo.collect { userInfo ->
+ getSharedPrefs(userInfo.id)
+ .edit()
+ .remove(LARGE_TILES_SPECS_KEY)
+ .remove(LARGE_TILES_DEFAULT_KEY)
+ .apply()
+ }
+ }
+
+ private fun SharedPreferences.writeLargeTileSpecs(specs: Set<TileSpec>) {
+ edit().putStringSet(LARGE_TILES_SPECS_KEY, specs.map { it.spec }.toSet()).apply()
+ }
+
/**
- * Sets the initial tiles as large, if there is no set in SharedPrefs for the [userId]. This is
- * to be used when upgrading to a build that supports large/small tiles.
+ * Sets the initial set of large tiles. One of the following cases will happen:
+ * * If we are setting the default set (no value stored in settings for the list of tiles), set
+ * the large tiles based on [defaultLargeTilesRepository]. We do this to signal future reboots
+ * that we have performed the upgrade path once. In this case, we will mark that we set them
+ * as the default in case a restore needs to modify them later.
+ * * If we got a list of tiles restored from a device and nothing has modified the list of
+ * tiles, set all the restored tiles to large. Note that if we also restored a set of large
+ * tiles before this was called, [LARGE_TILES_DEFAULT_KEY] will be false and we won't
+ * overwrite it.
+ * * If we got a list of tiles from settings, we consider that we upgraded in place and then we
+ * will set all those tiles to large IF there's no current set of large tiles.
*
* Even if largeTilesSpec is read Eagerly before we know if we are in an initial state, because
* we are not writing the default values to the SharedPreferences, the file will not contain the
* key and this call will succeed, as long as there hasn't been any calls to setLargeTilesSpecs
* for that user before.
*/
- fun setInitialLargeTilesSpecs(specs: Set<TileSpec>, userId: Int) {
+ fun setInitialOrUpgradeLargeTiles(upgradePath: TilesUpgradePath, userId: Int) {
with(getSharedPrefs(userId)) {
- if (!contains(LARGE_TILES_SPECS_KEY)) {
- logger.i("Setting upgraded large tiles for user $userId: $specs")
- setLargeTilesSpecsForUser(specs, userId)
+ when (upgradePath) {
+ is TilesUpgradePath.DefaultSet -> {
+ writeLargeTileSpecs(defaultLargeTilesRepository.defaultLargeTiles)
+ logger.i("Large tiles set to default on init")
+ setLargeTilesDefault(true)
+ }
+ is TilesUpgradePath.RestoreFromBackup -> {
+ if (
+ getBoolean(LARGE_TILES_DEFAULT_KEY, false) ||
+ !contains(LARGE_TILES_SPECS_KEY)
+ ) {
+ writeLargeTileSpecs(upgradePath.value)
+ logger.i("Tiles restored from backup set to large: ${upgradePath.value}")
+ setLargeTilesDefault(false)
+ }
+ }
+ is TilesUpgradePath.ReadFromSettings -> {
+ if (!contains(LARGE_TILES_SPECS_KEY)) {
+ writeLargeTileSpecs(upgradePath.value)
+ logger.i("Tiles read from settings set to large: ${upgradePath.value}")
+ setLargeTilesDefault(false)
+ }
+ }
}
}
}
+ private fun SharedPreferences.setLargeTilesDefault(value: Boolean) {
+ edit().putBoolean(LARGE_TILES_DEFAULT_KEY, value).apply()
+ }
+
private fun getSharedPrefs(userId: Int): SharedPreferences {
return userFileManager.getSharedPreferences(FILE_NAME, Context.MODE_PRIVATE, userId)
}
@@ -118,6 +163,7 @@
companion object {
private const val TAG = "QSPreferencesRepository"
private const val LARGE_TILES_SPECS_KEY = "large_tiles_specs"
+ private const val LARGE_TILES_DEFAULT_KEY = "large_tiles_default"
const val FILE_NAME = "quick_settings_prefs"
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/QSPreferencesInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/QSPreferencesInteractor.kt
index 86838b4..9b98797 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/QSPreferencesInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/QSPreferencesInteractor.kt
@@ -19,6 +19,7 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.qs.panels.data.repository.QSPreferencesRepository
import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.pipeline.shared.TilesUpgradePath
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
@@ -27,10 +28,20 @@
val largeTilesSpecs: Flow<Set<TileSpec>> = repo.largeTilesSpecs
fun setLargeTilesSpecs(specs: Set<TileSpec>) {
- repo.setLargeTilesSpecs(specs)
+ repo.writeLargeTileSpecs(specs)
}
- fun setInitialLargeTilesSpecs(specs: Set<TileSpec>, user: Int) {
- repo.setInitialLargeTilesSpecs(specs, user)
+ /**
+ * This method should be called to indicate that a "new" set of tiles has been determined for a
+ * particular user coming from different upgrade sources.
+ *
+ * @see TilesUpgradePath for more information
+ */
+ fun setInitialOrUpgradeLargeTilesSpecs(specs: TilesUpgradePath, user: Int) {
+ repo.setInitialOrUpgradeLargeTiles(specs, user)
+ }
+
+ suspend fun deleteLargeTilesDataJob() {
+ repo.deleteLargeTileDataJob()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/startable/QSPanelsCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/startable/QSPanelsCoreStartable.kt
index a8ac5c3..e279735 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/startable/QSPanelsCoreStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/startable/QSPanelsCoreStartable.kt
@@ -19,11 +19,13 @@
import com.android.app.tracing.coroutines.launchTraced
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.qs.flags.QsInCompose
import com.android.systemui.qs.panels.domain.interactor.QSPreferencesInteractor
import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.receiveAsFlow
+import kotlinx.coroutines.launch
class QSPanelsCoreStartable
@Inject
@@ -33,10 +35,14 @@
@Background private val backgroundApplicationScope: CoroutineScope,
) : CoreStartable {
override fun start() {
- backgroundApplicationScope.launchTraced("QSPanelsCoreStartable.startingLargeTiles") {
- tileSpecRepository.tilesReadFromSetting.receiveAsFlow().collect { (tiles, userId) ->
- preferenceInteractor.setInitialLargeTilesSpecs(tiles, userId)
+ if (QsInCompose.isEnabled) {
+ backgroundApplicationScope.launchTraced("QSPanelsCoreStartable.startingLargeTiles") {
+ tileSpecRepository.tilesUpgradePath.receiveAsFlow().collect { (tiles, userId) ->
+ preferenceInteractor.setInitialOrUpgradeLargeTilesSpecs(tiles, userId)
+ }
}
+ } else {
+ backgroundApplicationScope.launch { preferenceInteractor.deleteLargeTilesDataJob() }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt
index 6b7dd38..c50d5da 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt
@@ -24,6 +24,7 @@
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.qs.pipeline.data.model.RestoreData
import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.pipeline.shared.TilesUpgradePath
import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger
import com.android.systemui.res.R
import com.android.systemui.retail.data.repository.RetailModeRepository
@@ -78,7 +79,7 @@
/** Reset the current set of tiles to the default list of tiles */
suspend fun resetToDefault(userId: Int)
- val tilesReadFromSetting: ReceiveChannel<Pair<Set<TileSpec>, Int>>
+ val tilesUpgradePath: ReceiveChannel<Pair<TilesUpgradePath, Int>>
companion object {
/** Position to indicate the end of the list */
@@ -112,8 +113,8 @@
.filter { it !is TileSpec.Invalid }
}
- private val _tilesReadFromSetting = Channel<Pair<Set<TileSpec>, Int>>(capacity = 5)
- override val tilesReadFromSetting = _tilesReadFromSetting
+ private val _tilesUpgradePath = Channel<Pair<TilesUpgradePath, Int>>(capacity = 5)
+ override val tilesUpgradePath = _tilesUpgradePath
private val userTileRepositories = SparseArray<UserTileSpecRepository>()
@@ -122,8 +123,8 @@
val userTileRepository = userTileSpecRepositoryFactory.create(userId)
userTileRepositories.put(userId, userTileRepository)
applicationScope.launchTraced("TileSpecRepository.aggregateTilesPerUser") {
- for (tilesFromSettings in userTileRepository.tilesReadFromSettings) {
- _tilesReadFromSetting.send(tilesFromSettings to userId)
+ for (tileUpgrade in userTileRepository.tilesUpgradePath) {
+ _tilesUpgradePath.send(tileUpgrade to userId)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepository.kt
index 7b56cd9..5aa5eda 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepository.kt
@@ -9,6 +9,7 @@
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.qs.pipeline.data.model.RestoreData
import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.pipeline.shared.TilesUpgradePath
import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger
import com.android.systemui.util.settings.SecureSettings
import dagger.assisted.Assisted
@@ -49,8 +50,8 @@
@Background private val backgroundDispatcher: CoroutineDispatcher,
) {
- private val _tilesReadFromSettings = Channel<Set<TileSpec>>(capacity = 2)
- val tilesReadFromSettings: ReceiveChannel<Set<TileSpec>> = _tilesReadFromSettings
+ private val _tilesUpgradePath = Channel<TilesUpgradePath>(capacity = 3)
+ val tilesUpgradePath: ReceiveChannel<TilesUpgradePath> = _tilesUpgradePath
private val defaultTiles: List<TileSpec>
get() = defaultTilesRepository.defaultTiles
@@ -67,14 +68,23 @@
.scan(loadTilesFromSettingsAndParse(userId)) { current, change ->
change
.apply(current)
- .also {
- if (current != it) {
+ .also { afterRestore ->
+ if (current != afterRestore) {
if (change is RestoreTiles) {
- logger.logTilesRestoredAndReconciled(current, it, userId)
+ logger.logTilesRestoredAndReconciled(
+ current,
+ afterRestore,
+ userId,
+ )
} else {
- logger.logProcessTileChange(change, it, userId)
+ logger.logProcessTileChange(change, afterRestore, userId)
}
}
+ if (change is RestoreTiles) {
+ _tilesUpgradePath.send(
+ TilesUpgradePath.RestoreFromBackup(afterRestore.toSet())
+ )
+ }
}
// Distinct preserves the order of the elements removing later
// duplicates,
@@ -154,7 +164,9 @@
private suspend fun loadTilesFromSettingsAndParse(userId: Int): List<TileSpec> {
val loadedTiles = loadTilesFromSettings(userId)
if (loadedTiles.isNotEmpty()) {
- _tilesReadFromSettings.send(loadedTiles.toSet())
+ _tilesUpgradePath.send(TilesUpgradePath.ReadFromSettings(loadedTiles.toSet()))
+ } else {
+ _tilesUpgradePath.send(TilesUpgradePath.DefaultSet)
}
return parseTileSpecs(loadedTiles, userId)
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/TilesUpgradePath.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/TilesUpgradePath.kt
new file mode 100644
index 0000000..98f30c2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/TilesUpgradePath.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.shared
+
+/** Upgrade paths indicating the source of the list of QS tiles. */
+sealed interface TilesUpgradePath {
+
+ sealed interface UpgradeWithTiles : TilesUpgradePath {
+ val value: Set<TileSpec>
+ }
+
+ /** This indicates a set of tiles that was read from Settings on user start */
+ @JvmInline value class ReadFromSettings(override val value: Set<TileSpec>) : UpgradeWithTiles
+
+ /** This indicates a set of tiles that was restored from backup */
+ @JvmInline value class RestoreFromBackup(override val value: Set<TileSpec>) : UpgradeWithTiles
+
+ /**
+ * This indicates that no tiles were read from Settings on user start so the default has been
+ * stored.
+ */
+ data object DefaultSet : TilesUpgradePath
+}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
index 01bcc24..efdf5be 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
@@ -40,6 +40,7 @@
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.logger.SceneLogger
+import com.android.systemui.scene.shared.model.Overlays
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.ui.composable.Overlay
import com.android.systemui.shade.domain.interactor.ShadeModeInteractor
@@ -218,39 +219,36 @@
* it being a false touch.
*/
fun canChangeScene(toScene: SceneKey): Boolean {
- val interactionTypeOrNull =
- when (toScene) {
- Scenes.Bouncer -> Classifier.BOUNCER_UNLOCK
- Scenes.Gone -> Classifier.UNLOCK
- Scenes.Shade -> Classifier.NOTIFICATION_DRAG_DOWN
- Scenes.QuickSettings -> Classifier.QUICK_SETTINGS
- else -> null
- }
-
- val fromScene = currentScene.value
- val isAllowed =
- interactionTypeOrNull?.let { interactionType ->
- // It's important that the falsing system is always queried, even if no enforcement
- // will occur. This helps build up the right signal in the system.
- val isFalseTouch = falsingInteractor.isFalseTouch(interactionType)
-
- // Only enforce falsing if moving from the lockscreen scene to a new scene.
- val fromLockscreenScene = fromScene == Scenes.Lockscreen
-
- !fromLockscreenScene || !isFalseTouch
- } ?: true
-
- if (isAllowed) {
+ return isInteractionAllowedByFalsing(toScene).also {
// A scene change is guaranteed; log it.
logger.logSceneChanged(
- from = fromScene,
+ from = currentScene.value,
to = toScene,
sceneState = null,
reason = "user interaction",
isInstant = false,
)
}
- return isAllowed
+ }
+
+ /**
+ * Returns `true` if showing the [newlyShown] overlay is currently allowed; `false` otherwise.
+ *
+ * This is invoked only for user-initiated transitions. The goal is to check with the falsing
+ * system whether the overlay change should be rejected due to it being a false touch.
+ */
+ fun canShowOrReplaceOverlay(
+ newlyShown: OverlayKey,
+ beingReplaced: OverlayKey? = null,
+ ): Boolean {
+ return isInteractionAllowedByFalsing(newlyShown).also {
+ // An overlay change is guaranteed; log it.
+ logger.logOverlayChangeRequested(
+ from = beingReplaced,
+ to = newlyShown,
+ reason = "user interaction",
+ )
+ }
}
/**
@@ -313,6 +311,34 @@
return sceneInteractor.filteredUserActions(unfiltered)
}
+ /**
+ * Returns `true` if transitioning to [content] is permissible by the falsing system; `false`
+ * otherwise.
+ */
+ private fun isInteractionAllowedByFalsing(content: ContentKey): Boolean {
+ val interactionTypeOrNull =
+ when (content) {
+ Scenes.Bouncer -> Classifier.BOUNCER_UNLOCK
+ Scenes.Gone -> Classifier.UNLOCK
+ Scenes.Shade,
+ Overlays.NotificationsShade -> Classifier.NOTIFICATION_DRAG_DOWN
+ Scenes.QuickSettings,
+ Overlays.QuickSettingsShade -> Classifier.QUICK_SETTINGS
+ else -> null
+ }
+
+ return interactionTypeOrNull?.let { interactionType ->
+ // It's important that the falsing system is always queried, even if no enforcement
+ // will occur. This helps build up the right signal in the system.
+ val isFalseTouch = falsingInteractor.isFalseTouch(interactionType)
+
+ // Only enforce falsing if moving from the lockscreen scene to new content.
+ val fromLockscreenScene = currentScene.value == Scenes.Lockscreen
+
+ !fromLockscreenScene || !isFalseTouch
+ } ?: true
+ }
+
/** Defines interface for classes that can handle externally-reported [MotionEvent]s. */
interface MotionEventHandler {
/** Notifies that a [MotionEvent] has occurred. */
diff --git a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
index a379ef7..305e71e 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
@@ -520,7 +520,10 @@
val glanceableHubV2 = communalSettingsInteractor.isV2FlagEnabled()
if (
!hubShowing &&
- (touchOnNotifications || touchOnUmo || touchOnSmartspace || glanceableHubV2)
+ (touchOnNotifications ||
+ touchOnUmo ||
+ touchOnSmartspace ||
+ !communalViewModel.swipeToHubEnabled())
) {
logger.d({
"Lockscreen touch ignored: touchOnNotifications: $bool1, touchOnUmo: $bool2, " +
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
index 9a79e1a..ce48c85 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
@@ -31,16 +31,26 @@
import android.provider.AlarmClock
import android.view.DisplayCutout
import android.view.View
+import android.view.ViewGroup
import android.view.WindowInsets
import android.widget.TextView
import androidx.annotation.VisibleForTesting
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.wrapContentWidth
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.ComposeView
+import androidx.compose.ui.unit.dp
import androidx.constraintlayout.motion.widget.MotionLayout
import androidx.core.view.doOnLayout
+import androidx.core.view.isVisible
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.app.animation.Interpolators
import com.android.settingslib.Utils
import com.android.systemui.Dumpable
import com.android.systemui.animation.ShadeInterpolation
import com.android.systemui.battery.BatteryMeterView
+import com.android.systemui.battery.BatteryMeterView.MODE_ESTIMATE
import com.android.systemui.battery.BatteryMeterViewController
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.demomode.DemoMode
@@ -60,12 +70,15 @@
import com.android.systemui.shade.carrier.ShadeCarrierGroupController
import com.android.systemui.shade.data.repository.ShadeDisplaysRepository
import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround
+import com.android.systemui.statusbar.core.NewStatusBarIcons
import com.android.systemui.statusbar.data.repository.StatusBarContentInsetsProviderStore
import com.android.systemui.statusbar.phone.StatusBarLocation
import com.android.systemui.statusbar.phone.StatusIconContainer
import com.android.systemui.statusbar.phone.StatusOverlayHoverListenerFactory
import com.android.systemui.statusbar.phone.ui.StatusBarIconController
import com.android.systemui.statusbar.phone.ui.TintedIconManager
+import com.android.systemui.statusbar.pipeline.battery.ui.composable.BatteryWithEstimate
+import com.android.systemui.statusbar.pipeline.battery.ui.viewmodel.BatteryViewModel
import com.android.systemui.statusbar.policy.Clock
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.NextAlarmController
@@ -76,6 +89,7 @@
import java.io.PrintWriter
import javax.inject.Inject
import javax.inject.Named
+import kotlinx.coroutines.flow.MutableStateFlow
/**
* Controller for QS header.
@@ -100,6 +114,7 @@
private val shadeDisplaysRepositoryLazy: Lazy<ShadeDisplaysRepository>,
private val variableDateViewControllerFactory: VariableDateViewController.Factory,
@Named(SHADE_HEADER) private val batteryMeterViewController: BatteryMeterViewController,
+ private val batteryViewModelFactory: BatteryViewModel.Factory,
private val dumpManager: DumpManager,
private val shadeCarrierGroupControllerBuilder: ShadeCarrierGroupController.Builder,
private val combinedShadeHeadersConstraintManager: CombinedShadeHeadersConstraintManager,
@@ -162,6 +177,8 @@
private var lastInsets: WindowInsets? = null
private var nextAlarmIntent: PendingIntent? = null
+ private val showBatteryEstimate = MutableStateFlow(false)
+
private var qsDisabled = false
private var visible = false
set(value) {
@@ -323,10 +340,6 @@
override fun onInit() {
variableDateViewControllerFactory.create(date as VariableDateView).init()
- batteryMeterViewController.init()
-
- // battery settings same as in QS icons
- batteryMeterViewController.ignoreTunerUpdates()
val fgColor =
Utils.getColorAttrDefaultColor(header.context, android.R.attr.textColorPrimary)
@@ -336,11 +349,36 @@
iconManager = tintedIconManagerFactory.create(iconContainer, StatusBarLocation.QS)
iconManager.setTint(fgColor, bgColor)
- batteryIcon.updateColors(
- fgColor /* foreground */,
- bgColor /* background */,
- fgColor, /* single tone (current default) */
- )
+ if (!NewStatusBarIcons.isEnabled) {
+ batteryMeterViewController.init()
+
+ // battery settings same as in QS icons
+ batteryMeterViewController.ignoreTunerUpdates()
+
+ batteryIcon.isVisible = true
+ batteryIcon.updateColors(
+ fgColor /* foreground */,
+ bgColor /* background */,
+ fgColor, /* single tone (current default) */
+ )
+ } else {
+ // Configure the compose battery view
+ val batteryComposeView =
+ ComposeView(mView.context).apply {
+ setContent {
+ val showBatteryEstimate by showBatteryEstimate.collectAsStateWithLifecycle()
+ BatteryWithEstimate(
+ modifier = Modifier.height(17.dp).wrapContentWidth(),
+ viewModelFactory = batteryViewModelFactory,
+ isDark = { true },
+ showEstimate = showBatteryEstimate,
+ )
+ }
+ }
+ mView.requireViewById<ViewGroup>(R.id.hover_system_icons_container).apply {
+ addView(batteryComposeView, -1)
+ }
+ }
carrierIconSlots =
listOf(header.context.getString(com.android.internal.R.string.status_bar_mobile))
@@ -474,7 +512,11 @@
private fun updateBatteryMode() {
qsBatteryModeController.getBatteryMode(cutout, qsExpandedFraction)?.let {
- batteryIcon.setPercentShowMode(it)
+ if (NewStatusBarIcons.isEnabled) {
+ showBatteryEstimate.value = it == MODE_ESTIMATE
+ } else {
+ batteryIcon.setPercentShowMode(it)
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
index b9df9f86..7d4b0ed 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
@@ -45,13 +45,17 @@
import com.android.systemui.shade.domain.interactor.ShadeLockscreenInteractorImpl
import com.android.systemui.shade.domain.interactor.ShadeModeInteractor
import com.android.systemui.shade.domain.interactor.ShadeModeInteractorImpl
+import com.android.systemui.window.dagger.WindowRootViewBlurModule
import dagger.Binds
import dagger.Module
import dagger.Provides
import javax.inject.Provider
/** Module for classes related to the notification shade. */
-@Module(includes = [StartShadeModule::class, ShadeViewProviderModule::class])
+@Module(
+ includes =
+ [StartShadeModule::class, ShadeViewProviderModule::class, WindowRootViewBlurModule::class]
+)
abstract class ShadeModule {
companion object {
@Provides
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractor.kt
index 8f4e870..1ab0b93 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractor.kt
@@ -22,6 +22,7 @@
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.log.table.logDiffsForTable
import com.android.systemui.scene.domain.SceneFrameworkTableLog
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.shade.data.repository.ShadeRepository
import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.shared.settings.data.repository.SecureSettingsRepository
@@ -32,6 +33,7 @@
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.stateIn
/**
@@ -89,10 +91,14 @@
) : ShadeModeInteractor {
private val isDualShadeEnabled: Flow<Boolean> =
- secureSettingsRepository.boolSetting(
- Settings.Secure.DUAL_SHADE,
- defaultValue = DUAL_SHADE_ENABLED_DEFAULT,
- )
+ if (SceneContainerFlag.isEnabled) {
+ secureSettingsRepository.boolSetting(
+ Settings.Secure.DUAL_SHADE,
+ defaultValue = DUAL_SHADE_ENABLED_DEFAULT,
+ )
+ } else {
+ flowOf(false)
+ }
override val isShadeLayoutWide: StateFlow<Boolean> = repository.isShadeLayoutWide
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index 25ebc8c..f06565f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
@@ -24,8 +24,10 @@
import static android.os.Flags.allowPrivateProfile;
import static android.os.UserHandle.USER_ALL;
import static android.os.UserHandle.USER_NULL;
+import static android.provider.Settings.Secure.REDACT_OTP_NOTIFICATION_IMMEDIATELY;
import static android.provider.Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS;
import static android.provider.Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS;
+import static android.provider.Settings.Secure.REDACT_OTP_NOTIFICATION_WHILE_CONNECTED_TO_WIFI;
import static com.android.systemui.DejankUtils.whitelistIpcs;
@@ -44,6 +46,7 @@
import android.database.ExecutorContentObserver;
import android.net.Uri;
import android.os.Looper;
+import android.os.Process;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
@@ -118,6 +121,11 @@
Settings.Secure.getUriFor(LOCK_SCREEN_SHOW_NOTIFICATIONS);
private static final Uri SHOW_PRIVATE_LOCKSCREEN =
Settings.Secure.getUriFor(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+ private static final Uri REDACT_OTP_ON_WIFI =
+ Settings.Secure.getUriFor(REDACT_OTP_NOTIFICATION_WHILE_CONNECTED_TO_WIFI);
+
+ private static final Uri REDACT_OTP_IMMEDIATELY =
+ Settings.Secure.getUriFor(REDACT_OTP_NOTIFICATION_IMMEDIATELY);
private static final long LOCK_TIME_FOR_SENSITIVE_REDACTION_MS =
TimeUnit.MINUTES.toMillis(10);
@@ -307,6 +315,9 @@
@VisibleForTesting
protected final AtomicBoolean mConnectedToWifi = new AtomicBoolean(false);
+ protected final AtomicBoolean mRedactOtpOnWifi = new AtomicBoolean(true);
+ protected final AtomicBoolean mRedactOtpImmediately = new AtomicBoolean(false);
+
protected int mCurrentUserId = 0;
protected NotificationPresenter mPresenter;
@@ -363,6 +374,8 @@
mLockScreenUris.add(SHOW_LOCKSCREEN);
mLockScreenUris.add(SHOW_PRIVATE_LOCKSCREEN);
+ mLockScreenUris.add(REDACT_OTP_ON_WIFI);
+ mLockScreenUris.add(REDACT_OTP_IMMEDIATELY);
dumpManager.registerDumpable(this);
@@ -432,6 +445,10 @@
changed |= updateUserShowSettings(user.getIdentifier());
} else if (SHOW_PRIVATE_LOCKSCREEN.equals(uri)) {
changed |= updateUserShowPrivateSettings(user.getIdentifier());
+ } else if (REDACT_OTP_ON_WIFI.equals(uri)) {
+ changed |= updateRedactOtpOnWifiSetting();
+ } else if (REDACT_OTP_IMMEDIATELY.equals(uri)) {
+ changed |= updateRedactOtpImmediatelySetting();
}
}
@@ -465,6 +482,14 @@
true,
mLockscreenSettingsObserver,
USER_ALL);
+ mSecureSettings.registerContentObserverAsync(
+ REDACT_OTP_ON_WIFI,
+ mLockscreenSettingsObserver
+ );
+ mSecureSettings.registerContentObserverAsync(
+ REDACT_OTP_IMMEDIATELY,
+ mLockscreenSettingsObserver
+ );
mBroadcastDispatcher.registerReceiver(mAllUsersReceiver,
@@ -602,6 +627,28 @@
}
@WorkerThread
+ private boolean updateRedactOtpOnWifiSetting() {
+ boolean originalValue = mRedactOtpOnWifi.get();
+ boolean newValue = mSecureSettings.getIntForUser(
+ REDACT_OTP_NOTIFICATION_WHILE_CONNECTED_TO_WIFI,
+ 0,
+ Process.myUserHandle().getIdentifier()) != 0;
+ mRedactOtpOnWifi.set(newValue);
+ return originalValue != newValue;
+ }
+
+ @WorkerThread
+ private boolean updateRedactOtpImmediatelySetting() {
+ boolean originalValue = mRedactOtpImmediately.get();
+ boolean newValue = mSecureSettings.getIntForUser(
+ REDACT_OTP_NOTIFICATION_IMMEDIATELY,
+ 0,
+ Process.myUserHandle().getIdentifier()) != 0;
+ mRedactOtpImmediately.set(newValue);
+ return originalValue != newValue;
+ }
+
+ @WorkerThread
private boolean updateGlobalKeyguardSettings() {
final boolean oldValue = mKeyguardAllowingNotifications;
mKeyguardAllowingNotifications = mKeyguardManager.getPrivateNotificationsAllowed();
@@ -769,23 +816,31 @@
return false;
}
- if (mConnectedToWifi.get()) {
- return false;
+ if (!mRedactOtpOnWifi.get()) {
+ if (mConnectedToWifi.get()) {
+ return false;
+ }
+
+ long lastWifiConnectTime = mLastWifiConnectionTime.get();
+ // If the device has connected to wifi since receiving the notification, do not redact
+ if (ent.getSbn().getPostTime() < lastWifiConnectTime) {
+ return false;
+ }
}
if (ent.getRanking() == null || !ent.getRanking().hasSensitiveContent()) {
return false;
}
- long lastWifiConnectTime = mLastWifiConnectionTime.get();
- // If the device has connected to wifi since receiving the notification, do not redact
- if (ent.getSbn().getPostTime() < lastWifiConnectTime) {
- return false;
+ long latestTimeForRedaction;
+ if (mRedactOtpImmediately.get()) {
+ latestTimeForRedaction = mLastLockTime.get();
+ } else {
+ // If the lock screen was not already locked for LOCK_TIME_FOR_SENSITIVE_REDACTION_MS
+ // when this notification arrived, do not redact
+ latestTimeForRedaction = mLastLockTime.get() + LOCK_TIME_FOR_SENSITIVE_REDACTION_MS;
}
- // If the lock screen was not already locked for LOCK_TIME_FOR_SENSITIVE_REDACTION_MS when
- // this notification arrived, do not redact
- long latestTimeForRedaction = mLastLockTime.get() + LOCK_TIME_FOR_SENSITIVE_REDACTION_MS;
if (ent.getSbn().getPostTime() < latestTimeForRedaction) {
return false;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt
index a2c0226..f466278 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt
@@ -32,9 +32,7 @@
import com.android.systemui.statusbar.chips.StatusBarChipLogTags.pad
import com.android.systemui.statusbar.chips.StatusBarChipsLog
import com.android.systemui.statusbar.chips.call.domain.interactor.CallChipInteractor
-import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
import com.android.systemui.statusbar.chips.ui.model.ColorsModel
-import com.android.systemui.statusbar.chips.ui.model.ColorsModel.Companion.toCustomColorsModel
import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer
import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel
@@ -86,12 +84,7 @@
OngoingActivityChipModel.ChipIcon.SingleColorIcon(phoneIcon)
}
- val colors =
- if (StatusBarNotifChips.isEnabled && state.promotedContent != null) {
- state.promotedContent.toCustomColorsModel()
- } else {
- ColorsModel.Themed
- }
+ val colors = ColorsModel.AccentThemed
// This block mimics OngoingCallController#updateChip.
if (state.startTimeMs <= 0L) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt
index 8357df4..2d6102e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt
@@ -27,7 +27,7 @@
import com.android.systemui.statusbar.chips.notification.domain.interactor.StatusBarNotificationChipsInteractor
import com.android.systemui.statusbar.chips.notification.domain.model.NotificationChipModel
import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
-import com.android.systemui.statusbar.chips.ui.model.ColorsModel.Companion.toCustomColorsModel
+import com.android.systemui.statusbar.chips.ui.model.ColorsModel
import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationInteractor
@@ -85,8 +85,7 @@
contentDescription,
)
}
- val colors = this.promotedContent.toCustomColorsModel()
-
+ val colors = ColorsModel.SystemThemed
val clickListener: () -> Unit = {
// The notification pipeline needs everything to run on the main thread, so keep
// this event on the main thread.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt
index 456cd12..d41353b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.chips.ui.binder
import android.annotation.IdRes
+import android.content.Context
import android.content.res.ColorStateList
import android.graphics.drawable.GradientDrawable
import android.view.View
@@ -32,6 +33,7 @@
import com.android.systemui.res.R
import com.android.systemui.statusbar.StatusBarIconView
import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
+import com.android.systemui.statusbar.chips.ui.model.ColorsModel
import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer
import com.android.systemui.statusbar.chips.ui.view.ChipChronometer
@@ -76,8 +78,10 @@
chipTimeView.setTextColor(textColor)
chipTextView.setTextColor(textColor)
chipShortTimeDeltaView.setTextColor(textColor)
- (chipBackgroundView.background as GradientDrawable).color =
- chipModel.colors.background(chipContext)
+ (chipBackgroundView.background as GradientDrawable).setBackgroundColors(
+ chipModel.colors,
+ chipContext,
+ )
}
is OngoingActivityChipModel.Inactive -> {
// The Chronometer should be stopped to prevent leaks -- see b/192243808 and
@@ -460,5 +464,20 @@
chipView.minimumWidth = minimumWidth
}
+ private fun GradientDrawable.setBackgroundColors(colors: ColorsModel, context: Context) {
+ this.color = colors.background(context)
+ val outline = colors.outline(context)
+ if (outline != null) {
+ this.setStroke(
+ context.resources.getDimensionPixelSize(
+ R.dimen.ongoing_activity_chip_outline_width
+ ),
+ outline,
+ )
+ } else {
+ this.setStroke(0, /* color= */ 0)
+ }
+ }
+
@IdRes private val CUSTOM_ICON_VIEW_ID = R.id.ongoing_activity_chip_custom_icon
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/ChipContent.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/ChipContent.kt
index 32de0fb..8443d10 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/ChipContent.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/ChipContent.kt
@@ -20,16 +20,9 @@
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.drawWithCache
-import androidx.compose.ui.graphics.BlendMode
-import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.CompositingStrategy
-import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.layout.Measurable
import androidx.compose.ui.layout.MeasureResult
import androidx.compose.ui.layout.MeasureScope
@@ -37,6 +30,8 @@
import androidx.compose.ui.node.ModifierNodeElement
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.dimensionResource
+import androidx.compose.ui.text.TextMeasurer
+import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.rememberTextMeasurer
import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.Dp
@@ -83,15 +78,14 @@
softWrap = false,
modifier =
modifier
- .customTextContentLayout(
+ .hideTextIfDoesNotFit(
+ text = text,
+ textStyle = textStyle,
+ textMeasurer = textMeasurer,
maxTextWidth = maxTextWidth,
startPadding = startPadding,
endPadding = endPadding,
- ) { constraintWidth ->
- val intrinsicWidth =
- textMeasurer.measure(text, textStyle, softWrap = false).size.width
- intrinsicWidth <= constraintWidth
- }
+ )
.neverDecreaseWidth(),
)
}
@@ -108,7 +102,6 @@
}
is OngoingActivityChipModel.Active.Text -> {
- var hasOverflow by remember { mutableStateOf(false) }
val text = viewModel.text
Text(
text = text,
@@ -116,24 +109,14 @@
style = textStyle,
softWrap = false,
modifier =
- modifier
- .customTextContentLayout(
- maxTextWidth = maxTextWidth,
- startPadding = startPadding,
- endPadding = endPadding,
- ) { constraintWidth ->
- val intrinsicWidth =
- textMeasurer.measure(text, textStyle, softWrap = false).size.width
- hasOverflow = intrinsicWidth > constraintWidth
- constraintWidth.toFloat() / intrinsicWidth.toFloat() > 0.5f
- }
- .overflowFadeOut(
- hasOverflow = { hasOverflow },
- fadeLength =
- dimensionResource(
- id = R.dimen.ongoing_activity_chip_text_fading_edge_length
- ),
- ),
+ modifier.hideTextIfDoesNotFit(
+ text = text,
+ textStyle = textStyle,
+ textMeasurer = textMeasurer,
+ maxTextWidth = maxTextWidth,
+ startPadding = startPadding,
+ endPadding = endPadding,
+ ),
)
}
@@ -180,45 +163,67 @@
}
/**
- * A custom layout modifier for text that ensures its text is only visible if a provided
- * [shouldShow] callback returns true. Imposes a provided [maxTextWidthPx]. Also, accounts for
- * provided padding values if provided and ensures its text is placed with the provided padding
- * included around it.
+ * A custom layout modifier for text that ensures the text is only visible if it completely fits
+ * within the constrained bounds. Imposes a provided [maxTextWidthPx]. Also, accounts for provided
+ * padding values if provided and ensures its text is placed with the provided padding included
+ * around it.
*/
-private fun Modifier.customTextContentLayout(
+private fun Modifier.hideTextIfDoesNotFit(
+ text: String,
+ textStyle: TextStyle,
+ textMeasurer: TextMeasurer,
maxTextWidth: Dp,
startPadding: Dp = 0.dp,
endPadding: Dp = 0.dp,
- shouldShow: (constraintWidth: Int) -> Boolean,
): Modifier {
return this.then(
- CustomTextContentLayoutElement(maxTextWidth, startPadding, endPadding, shouldShow)
+ HideTextIfDoesNotFitElement(
+ text,
+ textStyle,
+ textMeasurer,
+ maxTextWidth,
+ startPadding,
+ endPadding,
+ )
)
}
-private data class CustomTextContentLayoutElement(
+private data class HideTextIfDoesNotFitElement(
+ val text: String,
+ val textStyle: TextStyle,
+ val textMeasurer: TextMeasurer,
val maxTextWidth: Dp,
val startPadding: Dp,
val endPadding: Dp,
- val shouldShow: (constrainedWidth: Int) -> Boolean,
-) : ModifierNodeElement<CustomTextContentLayoutNode>() {
- override fun create(): CustomTextContentLayoutNode {
- return CustomTextContentLayoutNode(maxTextWidth, startPadding, endPadding, shouldShow)
+) : ModifierNodeElement<HideTextIfDoesNotFitNode>() {
+ override fun create(): HideTextIfDoesNotFitNode {
+ return HideTextIfDoesNotFitNode(
+ text,
+ textStyle,
+ textMeasurer,
+ maxTextWidth,
+ startPadding,
+ endPadding,
+ )
}
- override fun update(node: CustomTextContentLayoutNode) {
- node.shouldShow = shouldShow
+ override fun update(node: HideTextIfDoesNotFitNode) {
+ node.text = text
+ node.textStyle = textStyle
+ node.textMeasurer = textMeasurer
node.maxTextWidth = maxTextWidth
node.startPadding = startPadding
node.endPadding = endPadding
}
}
-private class CustomTextContentLayoutNode(
+private class HideTextIfDoesNotFitNode(
+ var text: String,
+ var textStyle: TextStyle,
+ var textMeasurer: TextMeasurer,
var maxTextWidth: Dp,
var startPadding: Dp,
var endPadding: Dp,
- var shouldShow: (constrainedWidth: Int) -> Boolean,
) : Modifier.Node(), LayoutModifierNode {
override fun MeasureScope.measure(
measurable: Measurable,
@@ -230,9 +235,10 @@
.coerceAtLeast(constraints.minWidth)
val placeable = measurable.measure(constraints.copy(maxWidth = maxWidth))
- val height = placeable.height
- val width = placeable.width
- return if (shouldShow(maxWidth)) {
+ val intrinsicWidth = textMeasurer.measure(text, textStyle, softWrap = false).size.width
+ return if (intrinsicWidth <= maxWidth) {
+ val height = placeable.height
+ val width = placeable.width
layout(width + horizontalPadding.roundToPx(), height) {
placeable.place(startPadding.roundToPx(), 0)
}
@@ -241,20 +247,3 @@
}
}
}
-
-private fun Modifier.overflowFadeOut(hasOverflow: () -> Boolean, fadeLength: Dp): Modifier {
- return graphicsLayer(compositingStrategy = CompositingStrategy.Offscreen).drawWithCache {
- val width = size.width
- val start = (width - fadeLength.toPx()).coerceAtLeast(0f)
- val gradient =
- Brush.horizontalGradient(
- colors = listOf(Color.Black, Color.Transparent),
- startX = start,
- endX = width,
- )
- onDrawWithContent {
- drawContent()
- if (hasOverflow()) drawRect(brush = gradient, blendMode = BlendMode.DstIn)
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChip.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChip.kt
index 76c5386..1cdf680 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChip.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChip.kt
@@ -19,6 +19,7 @@
import android.content.res.ColorStateList
import android.view.ViewGroup
import androidx.compose.foundation.background
+import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
@@ -103,6 +104,13 @@
} else {
dimensionResource(id = R.dimen.ongoing_activity_chip_min_text_width) + chipSidePadding
}
+
+ val outline = model.colors.outline(context)
+ val outlineWidth = dimensionResource(R.dimen.ongoing_activity_chip_outline_width)
+
+ val shape =
+ RoundedCornerShape(dimensionResource(id = R.dimen.ongoing_activity_chip_corner_radius))
+
// Use a Box with `fillMaxHeight` to create a larger click surface for the chip. The visible
// height of the chip is determined by the height of the background of the Row below.
Box(
@@ -121,12 +129,7 @@
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically,
modifier =
- Modifier.clip(
- RoundedCornerShape(
- dimensionResource(id = R.dimen.ongoing_activity_chip_corner_radius)
- )
- )
- .height(dimensionResource(R.dimen.ongoing_appops_chip_height))
+ Modifier.height(dimensionResource(R.dimen.ongoing_appops_chip_height))
.thenIf(isClickable) { Modifier.widthIn(min = minWidth) }
.layout { measurable, constraints ->
val placeable = measurable.measure(constraints)
@@ -136,7 +139,14 @@
}
}
}
- .background(Color(model.colors.background(context).defaultColor))
+ .background(Color(model.colors.background(context).defaultColor), shape = shape)
+ .thenIf(outline != null) {
+ Modifier.border(
+ width = outlineWidth,
+ color = Color(outline!!),
+ shape = shape,
+ )
+ }
.padding(
horizontal =
if (hasEmbeddedIcon) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/ColorsModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/ColorsModel.kt
index 25f90f9..4954cb0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/ColorsModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/ColorsModel.kt
@@ -21,7 +21,6 @@
import androidx.annotation.ColorInt
import com.android.settingslib.Utils
import com.android.systemui.res.R
-import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
/** Model representing how the chip in the status bar should be colored. */
sealed interface ColorsModel {
@@ -31,13 +30,38 @@
/** The color for the text (and icon) on the chip. */
@ColorInt fun text(context: Context): Int
- /** The chip should match the theme's primary color. */
- data object Themed : ColorsModel {
+ /** The color to use for the chip outline, or null if the chip shouldn't have an outline. */
+ @ColorInt fun outline(context: Context): Int?
+
+ /** The chip should match the theme's primary accent color. */
+ // TODO(b/347717946): The chip's color isn't getting updated when the user switches theme, it
+ // only gets updated when a different configuration change happens, like a rotation.
+ data object AccentThemed : ColorsModel {
override fun background(context: Context): ColorStateList =
Utils.getColorAttr(context, com.android.internal.R.attr.colorAccent)
override fun text(context: Context) =
Utils.getColorAttrDefaultColor(context, com.android.internal.R.attr.colorPrimary)
+
+ override fun outline(context: Context) = null
+ }
+
+ /** The chip should match the system theme main color. */
+ // TODO(b/347717946): The chip's color isn't getting updated when the user switches theme, it
+ // only gets updated when a different configuration change happens, like a rotation.
+ data object SystemThemed : ColorsModel {
+ override fun background(context: Context): ColorStateList =
+ ColorStateList.valueOf(
+ context.getColor(com.android.internal.R.color.materialColorSurfaceDim)
+ )
+
+ override fun text(context: Context) =
+ context.getColor(com.android.internal.R.color.materialColorOnSurface)
+
+ override fun outline(context: Context) =
+ // Outline is required on the SystemThemed chip to guarantee the chip doesn't completely
+ // blend in with the background.
+ context.getColor(com.android.internal.R.color.materialColorOutlineVariant)
}
/** The chip should have the given background color and primary text color. */
@@ -46,6 +70,8 @@
ColorStateList.valueOf(backgroundColorInt)
override fun text(context: Context): Int = primaryTextColorInt
+
+ override fun outline(context: Context) = null
}
/** The chip should have a red background with white text. */
@@ -55,15 +81,7 @@
}
override fun text(context: Context) = context.getColor(android.R.color.white)
- }
- companion object {
- /** Converts the promoted notification colors to a [Custom] colors model. */
- fun PromotedNotificationContentModel.toCustomColorsModel(): Custom {
- return Custom(
- backgroundColorInt = this.colors.backgroundColor,
- primaryTextColorInt = this.colors.primaryTextColor,
- )
- }
+ override fun outline(context: Context) = null
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/view/ChipTextTruncationHelper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/view/ChipTextTruncationHelper.kt
index 52495eb..c19b144 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/view/ChipTextTruncationHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/view/ChipTextTruncationHelper.kt
@@ -51,9 +51,8 @@
}
/**
- * Returns true if this view should show the text because there's enough room for a substantial
- * amount of text, and returns false if this view should hide the text because the text is much
- * too long.
+ * Returns true if this view should show the text because there's enough room for all the text,
+ * and returns false if this view should hide the text because not all of it fits.
*
* @param desiredTextWidthPx should be calculated by having the view measure itself with
* [unlimitedWidthMeasureSpec] and then sending its `measuredWidth` to this method. (This
@@ -82,9 +81,8 @@
enforcedTextWidth = maxWidthBasedOnDimension
}
- // Only show the text if at least 50% of it can show. (Assume that if < 50% of the text will
- // be visible, the text will be more confusing than helpful.)
- return desiredTextWidthPx <= enforcedTextWidth * 2
+ // Only show the text if all of it can show
+ return desiredTextWidthPx <= enforcedTextWidth
}
private fun fetchMaxWidth() =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModel.kt
index 3ba0ae3..1a30caf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModel.kt
@@ -214,7 +214,6 @@
if (
secondaryChip is InternalChipModel.Active &&
StatusBarNotifChips.isEnabled &&
- !StatusBarChipsModernization.isEnabled &&
!isScreenReasonablyLarge
) {
// If we have two showing chips and we don't have a ton of room
@@ -222,8 +221,10 @@
// possible so that we have the highest chance of showing both chips (as
// opposed to showing the primary chip with a lot of text and completely
// hiding the secondary chip).
- // Also: If StatusBarChipsModernization is enabled, then we'll do the
- // squishing in Compose instead.
+ // TODO(b/392895330): If StatusBarChipsModernization is enabled, do the
+ // squishing in Compose instead, and be smart about it (e.g. if we have
+ // room for the first chip to show text and the second chip to be icon-only,
+ // do that instead of always squishing both chips.)
InternalMultipleOngoingActivityChipsModel(
primaryChip.squish(),
secondaryChip.squish(),
@@ -237,24 +238,31 @@
/** Squishes the chip down to the smallest content possible. */
private fun InternalChipModel.Active.squish(): InternalChipModel.Active {
- return when (model) {
+ return if (model.shouldSquish()) {
+ InternalChipModel.Active(this.type, this.model.toIconOnly())
+ } else {
+ this
+ }
+ }
+
+ private fun OngoingActivityChipModel.Active.shouldSquish(): Boolean {
+ return when (this) {
// Icon-only is already maximum squished
- is OngoingActivityChipModel.Active.IconOnly -> this
+ is OngoingActivityChipModel.Active.IconOnly,
// Countdown shows just a single digit, so already maximum squished
- is OngoingActivityChipModel.Active.Countdown -> this
- // The other chips have icon+text, so we should hide the text
+ is OngoingActivityChipModel.Active.Countdown -> false
+ // The other chips have icon+text, so we can squish them by hiding text
is OngoingActivityChipModel.Active.Timer,
is OngoingActivityChipModel.Active.ShortTimeDelta,
- is OngoingActivityChipModel.Active.Text ->
- InternalChipModel.Active(this.type, this.model.toIconOnly())
+ is OngoingActivityChipModel.Active.Text -> true
}
}
private fun OngoingActivityChipModel.Active.toIconOnly(): OngoingActivityChipModel.Active {
// If this chip doesn't have an icon, then it only has text and we should continue showing
// its text. (This is theoretically impossible because
- // [OngoingActivityChipModel.Active.Countdown] is the only chip without an icon, but protect
- // against it just in case.)
+ // [OngoingActivityChipModel.Active.Countdown] is the only chip without an icon and
+ // [shouldSquish] returns false for that model, but protect against it just in case.)
val currentIcon = icon ?: return this
return OngoingActivityChipModel.Active.IconOnly(
key,
@@ -271,8 +279,38 @@
*/
val chips: StateFlow<MultipleOngoingActivityChipsModel> =
if (StatusBarChipsModernization.isEnabled) {
- incomingChipBundle
- .map { bundle -> rankChips(bundle) }
+ combine(
+ incomingChipBundle.map { bundle -> rankChips(bundle) },
+ isScreenReasonablyLarge,
+ ) { rankedChips, isScreenReasonablyLarge ->
+ if (
+ StatusBarNotifChips.isEnabled &&
+ !isScreenReasonablyLarge &&
+ rankedChips.active.filter { !it.isHidden }.size >= 2
+ ) {
+ // If we have at least two showing chips and we don't have a ton of room
+ // (!isScreenReasonablyLarge), then we want to make both of them as small as
+ // possible so that we have the highest chance of showing both chips (as
+ // opposed to showing the first chip with a lot of text and completely
+ // hiding the other chips).
+ val squishedActiveChips =
+ rankedChips.active.map {
+ if (!it.isHidden && it.shouldSquish()) {
+ it.toIconOnly()
+ } else {
+ it
+ }
+ }
+
+ MultipleOngoingActivityChipsModel(
+ active = squishedActiveChips,
+ overflow = rankedChips.overflow,
+ inactive = rankedChips.inactive,
+ )
+ } else {
+ rankedChips
+ }
+ }
.stateIn(scope, SharingStarted.Lazily, MultipleOngoingActivityChipsModel())
} else {
MutableStateFlow(MultipleOngoingActivityChipsModel()).asStateFlow()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt
index 89cb420..9bc5231 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt
@@ -69,7 +69,10 @@
messagingStyle.conversationType =
if (entry.ranking.channel.isImportantConversation)
Notification.MessagingStyle.CONVERSATION_TYPE_IMPORTANT
- else Notification.MessagingStyle.CONVERSATION_TYPE_NORMAL
+ else if (entry.ranking.isConversation)
+ Notification.MessagingStyle.CONVERSATION_TYPE_NORMAL
+ else
+ Notification.MessagingStyle.CONVERSATION_TYPE_LEGACY
entry.ranking.conversationShortcutInfo?.let { shortcutInfo ->
logger.logAsyncTaskProgress(entry, "getting shortcut icon")
messagingStyle.shortcutIcon = launcherApps.getShortcutIcon(shortcutInfo)
diff --git a/core/java/com/android/internal/app/IAppOpsCallback.aidl b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntry.java
similarity index 61%
copy from core/java/com/android/internal/app/IAppOpsCallback.aidl
copy to packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntry.java
index 3a9525c..37485fe 100644
--- a/core/java/com/android/internal/app/IAppOpsCallback.aidl
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntry.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013 The Android Open Source Project
+ * Copyright (C) 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,10 +14,13 @@
* limitations under the License.
*/
-package com.android.internal.app;
+package com.android.systemui.statusbar.notification.collection;
-// This interface is also used by native code, so must
-// be kept in sync with frameworks/native/libs/permission/include/binder/IAppOpsCallback.h
-oneway interface IAppOpsCallback {
- void opChanged(int op, int uid, String packageName, String persistentDeviceId);
+/**
+ * Abstract class to represent notification section bundled by AI.
+ */
+public class BundleEntry extends PipelineEntry {
+
+ public class BundleEntryAdapter implements EntryAdapter {
+ }
}
diff --git a/core/java/com/android/internal/app/IAppOpsCallback.aidl b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/EntryAdapter.java
similarity index 61%
copy from core/java/com/android/internal/app/IAppOpsCallback.aidl
copy to packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/EntryAdapter.java
index 3a9525c..b12b1c5 100644
--- a/core/java/com/android/internal/app/IAppOpsCallback.aidl
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/EntryAdapter.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013 The Android Open Source Project
+ * Copyright (C) 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,10 +14,10 @@
* limitations under the License.
*/
-package com.android.internal.app;
+package com.android.systemui.statusbar.notification.collection;
-// This interface is also used by native code, so must
-// be kept in sync with frameworks/native/libs/permission/include/binder/IAppOpsCallback.h
-oneway interface IAppOpsCallback {
- void opChanged(int op, int uid, String packageName, String persistentDeviceId);
+/**
+ * Adapter interface for UI to get relevant info.
+ */
+public interface EntryAdapter {
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java
index 915057f..c8e3be4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java
@@ -27,7 +27,7 @@
* Abstract superclass for top-level entries, i.e. things that can appear in the final notification
* list shown to users. In practice, this means either GroupEntries or NotificationEntries.
*/
-public abstract class ListEntry {
+public abstract class ListEntry extends PipelineEntry {
private final String mKey;
private final long mCreationTime;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
index 9dc651e..7dd82a6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
@@ -270,6 +270,9 @@
setRanking(ranking);
}
+ public class NotifEntryAdapter implements EntryAdapter {
+ }
+
@Override
public NotificationEntry getRepresentativeEntry() {
return this;
diff --git a/core/java/com/android/internal/app/IAppOpsCallback.aidl b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/PipelineEntry.java
similarity index 61%
rename from core/java/com/android/internal/app/IAppOpsCallback.aidl
rename to packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/PipelineEntry.java
index 3a9525c..efedfef 100644
--- a/core/java/com/android/internal/app/IAppOpsCallback.aidl
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/PipelineEntry.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013 The Android Open Source Project
+ * Copyright (C) 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,10 +14,10 @@
* limitations under the License.
*/
-package com.android.internal.app;
+package com.android.systemui.statusbar.notification.collection;
-// This interface is also used by native code, so must
-// be kept in sync with frameworks/native/libs/permission/include/binder/IAppOpsCallback.h
-oneway interface IAppOpsCallback {
- void opChanged(int op, int uid, String packageName, String persistentDeviceId);
+/**
+ * Class to represent a notification, group, or bundle in the pipeline.
+ */
+public class PipelineEntry {
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationStackModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationStackOptionalModule.kt
similarity index 96%
rename from packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationStackModule.kt
rename to packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationStackOptionalModule.kt
index 6ceeb6a..bcaf187 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationStackModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationStackOptionalModule.kt
@@ -26,7 +26,7 @@
* This is meant to be bound in SystemUI variants with [NotificationStackScrollLayoutController].
*/
@Module
-interface NotificationStackGoogleModule {
+interface NotificationStackModule {
@Binds
fun bindNotificationStackRebindingHider(
impl: NotificationStackRebindingHiderImpl
@@ -35,7 +35,7 @@
/** This is meant to be used by all SystemUI variants, also those without NSSL. */
@Module
-interface NotificationStackModule {
+interface NotificationStackOptionalModule {
@BindsOptionalOf
fun bindOptionalOfNotificationStackRebindingHider(): NotificationStackRebindingHider
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
index e10825b..34f4969 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
@@ -121,7 +121,7 @@
NotificationMemoryModule.class,
NotificationStatsLoggerModule.class,
NotificationsLogModule.class,
- NotificationStackModule.class,
+ NotificationStackOptionalModule.class,
})
public interface NotificationsModule {
@Binds
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/AvalancheController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/AvalancheController.kt
index de113d3..ccc2dff 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/AvalancheController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/AvalancheController.kt
@@ -49,7 +49,7 @@
) : Dumpable {
private val tag = "AvalancheController"
- private val debug = Compile.IS_DEBUG
+ private val debug = Compile.IS_DEBUG && Log.isLoggable(tag, Log.DEBUG)
var baseEntryMapStr: () -> String = { "baseEntryMapStr not initialized" }
var enableAtRuntime = true
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/AODPromotedNotification.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/AODPromotedNotification.kt
index 7c75983..777ffda 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/AODPromotedNotification.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/AODPromotedNotification.kt
@@ -231,6 +231,7 @@
) {
// Icon binding must be called in this order
updateImageView(icon, content.smallIcon)
+ icon?.setImageLevel(content.iconLevel)
icon?.setBackgroundColor(Background.colorInt)
icon?.originalIconColor = PrimaryText.colorInt
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractor.kt
index cd78722..39c7df0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractor.kt
@@ -96,6 +96,7 @@
contentBuilder.wasPromotedAutomatically =
notification.extras.getBoolean(EXTRA_WAS_AUTOMATICALLY_PROMOTED, false)
contentBuilder.smallIcon = notification.smallIconModel(imageModelProvider)
+ contentBuilder.iconLevel = notification.iconLevel
contentBuilder.appName = notification.loadHeaderAppName(context)
contentBuilder.subText = notification.subText()
contentBuilder.time = notification.extractWhen()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/shared/model/PromotedNotificationContentModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/shared/model/PromotedNotificationContentModel.kt
index af5a820..38d41e3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/shared/model/PromotedNotificationContentModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/shared/model/PromotedNotificationContentModel.kt
@@ -38,6 +38,7 @@
*/
val wasPromotedAutomatically: Boolean,
val smallIcon: ImageModel?,
+ val iconLevel: Int,
val appName: CharSequence?,
val subText: CharSequence?,
val shortCriticalText: String?,
@@ -67,6 +68,7 @@
class Builder(val key: String) {
var wasPromotedAutomatically: Boolean = false
var smallIcon: ImageModel? = null
+ var iconLevel: Int = 0
var appName: CharSequence? = null
var subText: CharSequence? = null
var time: When? = null
@@ -94,6 +96,7 @@
identity = Identity(key, style),
wasPromotedAutomatically = wasPromotedAutomatically,
smallIcon = smallIcon,
+ iconLevel = iconLevel,
appName = appName,
subText = subText,
shortCriticalText = shortCriticalText,
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 a2d563a..9bf0768 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
@@ -47,6 +47,7 @@
import android.os.SystemProperties;
import android.os.Trace;
import android.os.UserHandle;
+import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.FloatProperty;
import android.util.IndentingPrintWriter;
@@ -81,6 +82,8 @@
import com.android.internal.util.ContrastColorUtil;
import com.android.internal.widget.CachingIconView;
import com.android.internal.widget.CallLayout;
+import com.android.internal.widget.ConversationLayout;
+import com.android.internal.widget.MessagingLayout;
import com.android.systemui.Flags;
import com.android.systemui.flags.RefactorFlag;
import com.android.systemui.plugins.FalsingManager;
@@ -97,6 +100,7 @@
import com.android.systemui.statusbar.notification.ColorUpdateLogger;
import com.android.systemui.statusbar.notification.FeedbackIcon;
import com.android.systemui.statusbar.notification.LaunchAnimationParameters;
+import com.android.systemui.statusbar.notification.NmSummarizationUiFlag;
import com.android.systemui.statusbar.notification.NotificationFadeAware;
import com.android.systemui.statusbar.notification.NotificationTransitionAnimatorController;
import com.android.systemui.statusbar.notification.NotificationUtils;
@@ -205,6 +209,7 @@
private int mMaxSmallHeightBeforeN;
private int mMaxSmallHeightBeforeP;
private int mMaxSmallHeightBeforeS;
+ private int mMaxSmallHeightWithSummarization;
private int mMaxSmallHeight;
private int mMaxExpandedHeight;
private int mMaxExpandedHeightForPromotedOngoing;
@@ -856,6 +861,8 @@
int smallHeight;
boolean isCallLayout = contractedView instanceof CallLayout;
+ boolean isMessagingLayout = contractedView instanceof MessagingLayout
+ || contractedView instanceof ConversationLayout;
if (customView && beforeS && !mIsSummaryWithChildren) {
if (beforeN) {
@@ -867,6 +874,10 @@
}
} else if (isCallLayout) {
smallHeight = maxExpandedHeight;
+ } else if (NmSummarizationUiFlag.isEnabled()
+ && isMessagingLayout
+ && !TextUtils.isEmpty(mEntry.getRanking().getSummarization())) {
+ smallHeight = mMaxSmallHeightWithSummarization;
} else {
smallHeight = mMaxSmallHeight;
}
@@ -2111,6 +2122,8 @@
mMaxSmallHeight = NotificationUtils.getFontScaledHeight(mContext,
R.dimen.notification_min_height);
}
+ mMaxSmallHeightWithSummarization = NotificationUtils.getFontScaledHeight(mContext,
+ com.android.internal.R.dimen.notification_collapsed_height_with_summarization);
mMaxExpandedHeight = NotificationUtils.getFontScaledHeight(mContext,
R.dimen.notification_max_height);
mMaxExpandedHeightForPromotedOngoing = NotificationUtils.getFontScaledHeight(mContext,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
index 73e8246..e311b53 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
@@ -55,6 +55,7 @@
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.notification.ConversationNotificationProcessor;
import com.android.systemui.statusbar.notification.InflationException;
+import com.android.systemui.statusbar.notification.NmSummarizationUiFlag;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.promoted.PromotedNotificationContentExtractor;
import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUiForceExpanded;
@@ -201,13 +202,13 @@
mNotifLayoutInflaterFactoryProvider,
mHeadsUpStyleProvider,
mLogger);
-
result = inflateSmartReplyViews(result, reInflateFlags, entry, row.getContext(),
packageContext, row.getExistingSmartReplyState(), smartRepliesInflater, mLogger);
boolean isConversation = entry.getRanking().isConversation();
Notification.MessagingStyle messagingStyle = null;
- if (isConversation && (AsyncHybridViewInflation.isEnabled()
- || LockscreenOtpRedaction.isSingleLineViewEnabled())) {
+ if (NmSummarizationUiFlag.isEnabled()
+ || (isConversation && (AsyncHybridViewInflation.isEnabled()
+ || LockscreenOtpRedaction.isSingleLineViewEnabled()))) {
messagingStyle = mConversationProcessor
.processNotification(entry, builder, mLogger);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt
index 589e5b8..517fc3a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt
@@ -48,6 +48,7 @@
import com.android.systemui.statusbar.NotificationRemoteInputManager
import com.android.systemui.statusbar.notification.ConversationNotificationProcessor
import com.android.systemui.statusbar.notification.InflationException
+import com.android.systemui.statusbar.notification.NmSummarizationUiFlag
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.promoted.PromotedNotificationContentExtractor
import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
@@ -700,7 +701,7 @@
// process conversations and extract the messaging style
val messagingStyle =
- if (entry.ranking.isConversation) {
+ if (NmSummarizationUiFlag.isEnabled || entry.ranking.isConversation) {
conversationProcessor.processNotification(entry, builder, logger)
} else null
@@ -730,9 +731,8 @@
builder = builder,
systemUiContext = systemUiContext,
redactText = false,
- summarization = entry.sbn.notification.extras.getCharSequence(
- EXTRA_SUMMARIZED_CONTENT,
- )
+ summarization =
+ entry.sbn.notification.extras.getCharSequence(EXTRA_SUMMARIZED_CONTENT),
)
} else null
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
index 58326db..fa4fe46 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
@@ -47,6 +47,7 @@
import com.android.systemui.battery.BatteryMeterView;
import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
import com.android.systemui.res.R;
+import com.android.systemui.statusbar.core.NewStatusBarIcons;
import com.android.systemui.statusbar.layout.StatusBarContentInsetsProvider;
import com.android.systemui.statusbar.phone.SysuiDarkIconDispatcher.DarkChange;
import com.android.systemui.statusbar.phone.ui.TintedIconManager;
@@ -79,7 +80,7 @@
private TextView mCarrierLabel;
private ImageView mMultiUserAvatar;
- private BatteryMeterView mBatteryView;
+ @Nullable private BatteryMeterView mBatteryView;
private StatusIconContainer mStatusIconContainer;
private StatusBarUserSwitcherContainer mUserSwitcherContainer;
@@ -131,6 +132,11 @@
mMultiUserAvatar = findViewById(R.id.multi_user_avatar);
mCarrierLabel = findViewById(R.id.keyguard_carrier_text);
mBatteryView = mSystemIconsContainer.findViewById(R.id.battery);
+ if (NewStatusBarIcons.isEnabled()) {
+ // When this flag is rolled forward, this whole view can be removed
+ mBatteryView.setVisibility(View.GONE);
+ mBatteryView = null;
+ }
mCutoutSpace = findViewById(R.id.cutout_space_view);
mStatusIconArea = findViewById(R.id.status_icon_area);
mStatusIconContainer = findViewById(R.id.statusIcons);
@@ -259,7 +265,10 @@
mMultiUserAvatar.setVisibility(View.GONE);
}
}
- mBatteryView.setForceShowPercent(mBatteryCharging && mShowPercentAvailable);
+
+ if (mBatteryView != null) {
+ mBatteryView.setForceShowPercent(mBatteryCharging && mShowPercentAvailable);
+ }
}
private void updateSystemIconsLayoutParams() {
@@ -442,7 +451,9 @@
/** Should only be called from {@link KeyguardStatusBarViewController}. */
void onThemeChanged(TintedIconManager iconManager) {
- mBatteryView.setColorsFromContext(mContext);
+ if (mBatteryView != null) {
+ mBatteryView.setColorsFromContext(mContext);
+ }
updateIconsAndTextColors(iconManager);
}
@@ -450,7 +461,9 @@
void onOverlayChanged() {
final int carrierTheme = R.style.TextAppearance_StatusBar_Clock;
mCarrierLabel.setTextAppearance(carrierTheme);
- mBatteryView.updatePercentView();
+ if (mBatteryView != null) {
+ mBatteryView.updatePercentView();
+ }
final int userSwitcherTheme = R.style.TextAppearance_StatusBar_UserChip;
TextView userSwitcherName = mUserSwitcherContainer.findViewById(R.id.current_user_name);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
index 40245ae..de72154 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
@@ -34,10 +34,12 @@
import android.util.MathUtils;
import android.view.DisplayCutout;
import android.view.View;
+import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
+import androidx.compose.ui.platform.ComposeView;
import androidx.core.animation.Animator;
import androidx.core.animation.AnimatorListenerAdapter;
import androidx.core.animation.ValueAnimator;
@@ -62,6 +64,7 @@
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
+import com.android.systemui.statusbar.core.NewStatusBarIcons;
import com.android.systemui.statusbar.data.repository.StatusBarContentInsetsProviderStore;
import com.android.systemui.statusbar.disableflags.DisableStateTracker;
import com.android.systemui.statusbar.events.SystemStatusAnimationCallback;
@@ -71,10 +74,13 @@
import com.android.systemui.statusbar.notification.PropertyAnimator;
import com.android.systemui.statusbar.notification.stack.AnimationProperties;
import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
+import com.android.systemui.statusbar.phone.domain.interactor.DarkIconInteractor;
import com.android.systemui.statusbar.phone.fragment.StatusBarIconBlocklistKt;
import com.android.systemui.statusbar.phone.fragment.StatusBarSystemEventDefaultAnimator;
import com.android.systemui.statusbar.phone.ui.StatusBarIconController;
import com.android.systemui.statusbar.phone.ui.TintedIconManager;
+import com.android.systemui.statusbar.pipeline.battery.ui.binder.UnifiedBatteryViewBinder;
+import com.android.systemui.statusbar.pipeline.battery.ui.viewmodel.BatteryViewModel;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -125,6 +131,7 @@
private final StatusBarIconController mStatusBarIconController;
private final TintedIconManager.Factory mTintedIconManagerFactory;
private final BatteryMeterViewController mBatteryMeterViewController;
+ private final BatteryViewModel.Factory mBatteryViewModelFactory;
private final ShadeViewStateProvider mShadeViewStateProvider;
private final KeyguardStateController mKeyguardStateController;
private final KeyguardBypassController mKeyguardBypassController;
@@ -145,7 +152,7 @@
private final GlanceableHubToLockscreenTransitionViewModel mHubToLockscreenTransitionViewModel;
private final LockscreenToGlanceableHubTransitionViewModel mLockscreenToHubTransitionViewModel;
- private View mSystemIconsContainer;
+ private ViewGroup mSystemIconsContainer;
private final StatusOverlayHoverListenerFactory mStatusOverlayHoverListenerFactory;
private final ConfigurationController.ConfigurationListener mConfigurationListener =
@@ -327,6 +334,7 @@
StatusBarIconController statusBarIconController,
TintedIconManager.Factory tintedIconManagerFactory,
BatteryMeterViewController batteryMeterViewController,
+ BatteryViewModel.Factory batteryViewModelFactory,
ShadeViewStateProvider shadeViewStateProvider,
KeyguardStateController keyguardStateController,
KeyguardBypassController bypassController,
@@ -360,6 +368,7 @@
mStatusBarIconController = statusBarIconController;
mTintedIconManagerFactory = tintedIconManagerFactory;
mBatteryMeterViewController = batteryMeterViewController;
+ mBatteryViewModelFactory = batteryViewModelFactory;
mShadeViewStateProvider = shadeViewStateProvider;
mKeyguardStateController = keyguardStateController;
mKeyguardBypassController = bypassController;
@@ -417,7 +426,9 @@
protected void onInit() {
super.onInit();
mCarrierTextController.init();
- mBatteryMeterViewController.init();
+ if (!NewStatusBarIcons.isEnabled()) {
+ mBatteryMeterViewController.init();
+ }
if (isMigrationEnabled()) {
KeyguardStatusBarViewBinder.bind(mView, mKeyguardStatusBarViewModel);
}
@@ -469,6 +480,15 @@
mToGlanceableHubStatusBarAlphaConsumer, mCoroutineDispatcher);
collectFlow(mView, mHubToLockscreenTransitionViewModel.getStatusBarAlpha(),
mFromGlanceableHubStatusBarAlphaConsumer, mCoroutineDispatcher);
+ if (NewStatusBarIcons.isEnabled()) {
+ ComposeView batteryComposeView = new ComposeView(mContext);
+ UnifiedBatteryViewBinder.bind(
+ batteryComposeView,
+ mBatteryViewModelFactory,
+ DarkIconInteractor.toIsAreaDark(mView.darkChangeFlow()));
+
+ mSystemIconsContainer.addView(batteryComposeView, -1);
+ }
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index 4d222fd..b2d3377 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -81,6 +81,9 @@
import com.android.systemui.util.kotlin.JavaAdapter;
import com.android.systemui.util.wakelock.DelayedWakeLock;
import com.android.systemui.util.wakelock.WakeLock;
+import com.android.systemui.window.domain.interactor.WindowRootViewBlurInteractor;
+
+import dagger.Lazy;
import kotlinx.coroutines.CoroutineDispatcher;
@@ -226,7 +229,7 @@
private float mScrimBehindAlphaKeyguard = KEYGUARD_SCRIM_ALPHA;
static final float TRANSPARENT_BOUNCER_SCRIM_ALPHA = 0.54f;
- private final float mDefaultScrimAlpha;
+ private float mDefaultScrimAlpha;
private float mRawPanelExpansionFraction;
private float mPanelScrimMinFraction;
@@ -257,6 +260,7 @@
private final TriConsumer<ScrimState, Float, GradientColors> mScrimStateListener;
private final LargeScreenShadeInterpolator mLargeScreenShadeInterpolator;
private final BlurConfig mBlurConfig;
+ private final Lazy<WindowRootViewBlurInteractor> mWindowRootViewBlurInteractor;
private Consumer<Integer> mScrimVisibleListener;
private boolean mBlankScreen;
private boolean mScreenBlankingCallbackCalled;
@@ -339,14 +343,13 @@
KeyguardInteractor keyguardInteractor,
@Main CoroutineDispatcher mainDispatcher,
LargeScreenShadeInterpolator largeScreenShadeInterpolator,
- BlurConfig blurConfig) {
+ BlurConfig blurConfig,
+ Lazy<WindowRootViewBlurInteractor> windowRootViewBlurInteractor) {
mScrimStateListener = lightBarController::setScrimState;
mLargeScreenShadeInterpolator = largeScreenShadeInterpolator;
mBlurConfig = blurConfig;
- // All scrims default alpha need to match bouncer background alpha to make sure the
- // transitions involving the bouncer are smooth and don't overshoot the bouncer alpha.
- mDefaultScrimAlpha =
- Flags.bouncerUiRevamp() ? TRANSPARENT_BOUNCER_SCRIM_ALPHA : BUSY_SCRIM_ALPHA;
+ mWindowRootViewBlurInteractor = windowRootViewBlurInteractor;
+ mDefaultScrimAlpha = BUSY_SCRIM_ALPHA;
mKeyguardStateController = keyguardStateController;
mDarkenWhileDragging = !mKeyguardStateController.canDismissLockScreen();
@@ -407,7 +410,7 @@
final ScrimState[] states = ScrimState.values();
for (int i = 0; i < states.length; i++) {
- states[i].init(mScrimInFront, mScrimBehind, mDozeParameters, mDockManager, mBlurConfig);
+ states[i].init(mScrimInFront, mScrimBehind, mDozeParameters, mDockManager);
states[i].setScrimBehindAlphaKeyguard(mScrimBehindAlphaKeyguard);
states[i].setDefaultScrimAlpha(mDefaultScrimAlpha);
}
@@ -485,6 +488,30 @@
Edge.Companion.create(Scenes.Communal, LOCKSCREEN),
Edge.Companion.create(GLANCEABLE_HUB, LOCKSCREEN)),
mGlanceableHubConsumer, mMainDispatcher);
+
+ if (Flags.bouncerUiRevamp()) {
+ collectFlow(behindScrim,
+ mWindowRootViewBlurInteractor.get().isBlurCurrentlySupported(),
+ this::handleBlurSupportedChanged);
+ }
+ }
+
+ private void updateDefaultScrimAlpha(float alpha) {
+ mDefaultScrimAlpha = alpha;
+ for (ScrimState state : ScrimState.values()) {
+ state.setDefaultScrimAlpha(mDefaultScrimAlpha);
+ }
+ applyAndDispatchState();
+ }
+
+ private void handleBlurSupportedChanged(boolean isBlurSupported) {
+ if (isBlurSupported) {
+ updateDefaultScrimAlpha(TRANSPARENT_BOUNCER_SCRIM_ALPHA);
+ ScrimState.BOUNCER_SCRIMMED.setNotifBlurRadius(mBlurConfig.getMaxBlurRadiusPx());
+ } else {
+ ScrimState.BOUNCER_SCRIMMED.setNotifBlurRadius(0f);
+ updateDefaultScrimAlpha(BUSY_SCRIM_ALPHA);
+ }
}
// TODO(b/270984686) recompute scrim height accurately, based on shade contents.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
index 5f423cf..071a57a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
@@ -17,14 +17,12 @@
package com.android.systemui.statusbar.phone;
import static com.android.systemui.statusbar.phone.ScrimController.BUSY_SCRIM_ALPHA;
-import static com.android.systemui.statusbar.phone.ScrimController.TRANSPARENT_BOUNCER_SCRIM_ALPHA;
import android.graphics.Color;
import com.android.app.tracing.coroutines.TrackTracer;
import com.android.systemui.Flags;
import com.android.systemui.dock.DockManager;
-import com.android.systemui.keyguard.ui.transitions.BlurConfig;
import com.android.systemui.res.R;
import com.android.systemui.scrim.ScrimView;
import com.android.systemui.shade.ui.ShadeColors;
@@ -116,8 +114,8 @@
@Override
public void prepare(ScrimState previousState) {
if (Flags.bouncerUiRevamp()) {
- mBehindAlpha = mClipQsScrim ? 0.0f : TRANSPARENT_BOUNCER_SCRIM_ALPHA;
- mNotifAlpha = mClipQsScrim ? TRANSPARENT_BOUNCER_SCRIM_ALPHA : 0;
+ mBehindAlpha = mDefaultScrimAlpha;
+ mNotifAlpha = 0f;
mBehindTint = mNotifTint = mSurfaceColor;
mFrontAlpha = 0f;
return;
@@ -153,12 +151,11 @@
if (previousState == SHADE_LOCKED) {
mBehindAlpha = previousState.getBehindAlpha();
mNotifAlpha = previousState.getNotifAlpha();
- mNotifBlurRadius = mBlurConfig.getMaxBlurRadiusPx();
} else {
mNotifAlpha = 0f;
mBehindAlpha = 0f;
}
- mFrontAlpha = TRANSPARENT_BOUNCER_SCRIM_ALPHA;
+ mFrontAlpha = mDefaultScrimAlpha;
mFrontTint = mSurfaceColor;
return;
}
@@ -403,7 +400,6 @@
DozeParameters mDozeParameters;
DockManager mDockManager;
boolean mDisplayRequiresBlanking;
- protected BlurConfig mBlurConfig;
boolean mLaunchingAffordanceWithPreview;
boolean mOccludeAnimationPlaying;
boolean mWakeLockScreenSensorActive;
@@ -417,7 +413,7 @@
protected float mNotifBlurRadius = 0.0f;
public void init(ScrimView scrimInFront, ScrimView scrimBehind, DozeParameters dozeParameters,
- DockManager dockManager, BlurConfig blurConfig) {
+ DockManager dockManager) {
mBackgroundColor = scrimBehind.getContext().getColor(R.color.shade_scrim_background_dark);
mScrimInFront = scrimInFront;
mScrimBehind = scrimBehind;
@@ -425,7 +421,6 @@
mDozeParameters = dozeParameters;
mDockManager = dockManager;
mDisplayRequiresBlanking = dozeParameters.getDisplayNeedsBlanking();
- mBlurConfig = blurConfig;
}
/** Prepare state for transition. */
@@ -536,4 +531,8 @@
public float getNotifBlurRadius() {
return mNotifBlurRadius;
}
+
+ public void setNotifBlurRadius(float value) {
+ mNotifBlurRadius = value;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
index e33baf7..ded964d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -57,11 +57,11 @@
import com.android.systemui.assist.AssistManager;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
-import com.android.systemui.dagger.qualifiers.DisplayId;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.power.domain.interactor.PowerInteractor;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.shade.ShadeController;
+import com.android.systemui.shade.ShadeDisplayAware;
import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor;
import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractor;
import com.android.systemui.statusbar.CommandQueue;
@@ -76,11 +76,11 @@
import com.android.systemui.statusbar.notification.collection.provider.LaunchFullScreenIntentProvider;
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
import com.android.systemui.statusbar.notification.emptyshade.shared.ModesEmptyShadeFix;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpUtil;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRowDragController;
import com.android.systemui.statusbar.notification.row.OnUserInteractionCallback;
-import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
-import com.android.systemui.statusbar.notification.headsup.HeadsUpUtil;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.wmshell.BubblesManager;
@@ -115,7 +115,6 @@
private final static String TAG = "StatusBarNotificationActivityStarter";
private final Context mContext;
- private final int mDisplayId;
private final Handler mMainThreadHandler;
private final Executor mUiBgExecutor;
@@ -155,8 +154,7 @@
@Inject
StatusBarNotificationActivityStarter(
- Context context,
- @DisplayId int displayId,
+ @ShadeDisplayAware Context context,
Handler mainThreadHandler,
@Background Executor uiBgExecutor,
NotificationVisibilityProvider visibilityProvider,
@@ -189,7 +187,6 @@
PowerInteractor powerInteractor,
UserTracker userTracker) {
mContext = context;
- mDisplayId = displayId;
mMainThreadHandler = mainThreadHandler;
mUiBgExecutor = uiBgExecutor;
mVisibilityProvider = visibilityProvider;
@@ -493,6 +490,7 @@
boolean animate,
boolean isActivityIntent) {
mLogger.logStartNotificationIntent(entry);
+ final int displayId = mContext.getDisplayId();
try {
ActivityTransitionAnimator.Controller animationController =
new StatusBarTransitionAnimatorController(
@@ -501,7 +499,7 @@
mShadeController,
mNotificationShadeWindowController,
mCommandQueue,
- mDisplayId,
+ displayId,
isActivityIntent);
mActivityTransitionAnimator.startPendingIntentWithAnimation(
animationController,
@@ -511,11 +509,11 @@
long eventTime = row.getAndResetLastActionUpTime();
Bundle options = eventTime > 0
? getActivityOptions(
- mDisplayId,
+ displayId,
adapter,
mKeyguardStateController.isShowing(),
eventTime)
- : getActivityOptions(mDisplayId, adapter);
+ : getActivityOptions(displayId, adapter);
int result = intent.sendAndReturnResult(mContext, 0, fillInIntent, null,
null, null, options);
mLogger.logSendPendingIntent(entry, intent, result);
@@ -533,6 +531,7 @@
public void startNotificationGutsIntent(@NonNull final Intent intent, final int appUid,
@NonNull ExpandableNotificationRow row) {
boolean animate = mActivityStarter.shouldAnimateLaunch(true /* isActivityIntent */);
+ final int displayId = mContext.getDisplayId();
ActivityStarter.OnDismissAction onDismissAction = new ActivityStarter.OnDismissAction() {
@Override
public boolean onDismiss() {
@@ -544,7 +543,7 @@
mShadeController,
mNotificationShadeWindowController,
mCommandQueue,
- mDisplayId,
+ displayId,
true /* isActivityIntent */);
mActivityTransitionAnimator.startIntentWithAnimation(
@@ -552,7 +551,7 @@
(adapter) -> TaskStackBuilder.create(mContext)
.addNextIntentWithParentStack(intent)
.startActivities(getActivityOptions(
- mDisplayId,
+ displayId,
adapter),
new UserHandle(UserHandle.getUserId(appUid))));
});
@@ -571,6 +570,7 @@
@Override
public void startHistoryIntent(View view, boolean showHistory) {
ModesEmptyShadeFix.assertInLegacyMode();
+ final int displayId = mContext.getDisplayId();
boolean animate = mActivityStarter.shouldAnimateLaunch(true /* isActivityIntent */);
ActivityStarter.OnDismissAction onDismissAction = new ActivityStarter.OnDismissAction() {
@Override
@@ -597,13 +597,13 @@
mShadeController,
mNotificationShadeWindowController,
mCommandQueue,
- mDisplayId,
+ displayId,
true /* isActivityIntent */);
mActivityTransitionAnimator.startIntentWithAnimation(
animationController, animate, intent.getPackage(),
(adapter) -> tsb.startActivities(
- getActivityOptions(mDisplayId, adapter),
+ getActivityOptions(displayId, adapter),
mUserTracker.getUserHandle()));
});
return true;
@@ -620,6 +620,7 @@
@Override
public void startSettingsIntent(@NonNull View view, @NonNull SettingsIntent intentInfo) {
+ final int displayId = mContext.getDisplayId();
boolean animate = mActivityStarter.shouldAnimateLaunch(true /* isActivityIntent */);
ActivityStarter.OnDismissAction onDismissAction = new ActivityStarter.OnDismissAction() {
@Override
@@ -642,13 +643,13 @@
mShadeController,
mNotificationShadeWindowController,
mCommandQueue,
- mDisplayId,
+ displayId,
true /* isActivityIntent */);
mActivityTransitionAnimator.startIntentWithAnimation(
animationController, animate, intentInfo.getTargetIntent().getPackage(),
(adapter) -> tsb.startActivities(
- getActivityOptions(mDisplayId, adapter),
+ getActivityOptions(displayId, adapter),
mUserTracker.getUserHandle()));
});
return true;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/HomeStatusBarComponent.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/HomeStatusBarComponent.java
index 7207d0a..4d531b5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/HomeStatusBarComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/HomeStatusBarComponent.java
@@ -20,6 +20,7 @@
import com.android.systemui.dagger.qualifiers.DisplaySpecific;
import com.android.systemui.dagger.qualifiers.RootView;
import com.android.systemui.plugins.DarkIconDispatcher;
+import com.android.systemui.statusbar.core.NewStatusBarIcons;
import com.android.systemui.statusbar.data.repository.StatusBarConfigurationController;
import com.android.systemui.statusbar.layout.StatusBarBoundsProvider;
import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor;
@@ -85,7 +86,9 @@
default void init() {
// No one accesses these controllers, so we need to make sure we reference them here so they
// do get initialized.
- getBatteryMeterViewController().init();
+ if (!NewStatusBarIcons.isEnabled()) {
+ getBatteryMeterViewController().init();
+ }
getHeadsUpAppearanceController().init();
getPhoneStatusBarViewController().init();
if (!NotificationsLiveDataStoreRefactor.isEnabled()) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/battery/ui/binder/UnifiedBatteryViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/battery/ui/binder/UnifiedBatteryViewBinder.kt
index 903844e..9665c33 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/battery/ui/binder/UnifiedBatteryViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/battery/ui/binder/UnifiedBatteryViewBinder.kt
@@ -16,7 +16,10 @@
package com.android.systemui.statusbar.pipeline.battery.ui.binder
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.width
import androidx.compose.runtime.getValue
+import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.platform.ViewCompositionStrategy
import androidx.core.view.isVisible
@@ -27,6 +30,8 @@
import com.android.systemui.statusbar.phone.domain.interactor.IsAreaDark
import com.android.systemui.statusbar.pipeline.battery.ui.composable.UnifiedBattery
import com.android.systemui.statusbar.pipeline.battery.ui.viewmodel.BatteryViewModel
+import com.android.systemui.statusbar.pipeline.battery.ui.viewmodel.BatteryViewModel.Companion.STATUS_BAR_BATTERY_HEIGHT
+import com.android.systemui.statusbar.pipeline.battery.ui.viewmodel.BatteryViewModel.Companion.STATUS_BAR_BATTERY_WIDTH
import kotlinx.coroutines.flow.Flow
/** In cases where the battery needs to be bound to an existing android view */
@@ -47,7 +52,13 @@
)
setContent {
val isDark by isAreaDark.collectAsStateWithLifecycle(IsAreaDark { true })
- UnifiedBattery(viewModelFactory = viewModelFactory, isDark = isDark)
+ UnifiedBattery(
+ modifier =
+ Modifier.height(STATUS_BAR_BATTERY_HEIGHT)
+ .width(STATUS_BAR_BATTERY_WIDTH),
+ viewModelFactory = viewModelFactory,
+ isDark = isDark,
+ )
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/battery/ui/composable/BatteryWithEstimate.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/battery/ui/composable/BatteryWithEstimate.kt
index 2ee86ee..ac793a9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/battery/ui/composable/BatteryWithEstimate.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/battery/ui/composable/BatteryWithEstimate.kt
@@ -54,7 +54,7 @@
)
if (showEstimate) {
viewModel.batteryTimeRemainingEstimate?.let {
- Spacer(modifier.width(2.dp))
+ Spacer(modifier.width(4.dp))
Text(text = it, color = Color.White)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/battery/ui/viewmodel/BatteryViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/battery/ui/viewmodel/BatteryViewModel.kt
index d0d099e7..afd4bb1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/battery/ui/viewmodel/BatteryViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/battery/ui/viewmodel/BatteryViewModel.kt
@@ -18,6 +18,7 @@
import android.content.Context
import androidx.compose.runtime.getValue
+import androidx.compose.ui.unit.dp
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.lifecycle.ExclusiveActivatable
@@ -223,6 +224,10 @@
}
companion object {
+ // Status bar battery height, based on a 21x12 base canvas
+ val STATUS_BAR_BATTERY_HEIGHT = 13.dp
+ val STATUS_BAR_BATTERY_WIDTH = 22.75.dp
+
fun Int.glyphRepresentation(): List<BatteryGlyph> = toString().map { it.toGlyph() }
private fun Char.toGlyph(): BatteryGlyph =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt
index 9d72daf..c34fa46 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt
@@ -23,6 +23,8 @@
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.width
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
@@ -34,9 +36,11 @@
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.compose.theme.PlatformTheme
import com.android.keyguard.AlphaOptimizedLinearLayout
+import com.android.systemui.lifecycle.rememberViewModel
import com.android.systemui.plugins.DarkIconDispatcher
import com.android.systemui.res.R
import com.android.systemui.statusbar.chips.ui.compose.OngoingActivityChips
+import com.android.systemui.statusbar.core.NewStatusBarIcons
import com.android.systemui.statusbar.core.StatusBarRootModernization
import com.android.systemui.statusbar.data.repository.DarkIconDispatcherStore
import com.android.systemui.statusbar.events.domain.interactor.SystemStatusEventAnimationInteractor
@@ -51,6 +55,9 @@
import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization
import com.android.systemui.statusbar.phone.ui.DarkIconManager
import com.android.systemui.statusbar.phone.ui.StatusBarIconController
+import com.android.systemui.statusbar.pipeline.battery.ui.composable.UnifiedBattery
+import com.android.systemui.statusbar.pipeline.battery.ui.viewmodel.BatteryViewModel.Companion.STATUS_BAR_BATTERY_HEIGHT
+import com.android.systemui.statusbar.pipeline.battery.ui.viewmodel.BatteryViewModel.Companion.STATUS_BAR_BATTERY_WIDTH
import com.android.systemui.statusbar.pipeline.shared.ui.binder.HomeStatusBarIconBlockListBinder
import com.android.systemui.statusbar.pipeline.shared.ui.binder.HomeStatusBarViewBinder
import com.android.systemui.statusbar.pipeline.shared.ui.model.VisibilityModel
@@ -73,14 +80,13 @@
) {
fun create(root: ViewGroup, andThen: (ViewGroup) -> Unit): ComposeView {
val composeView = ComposeView(root.context)
- val displayId = root.context.displayId
val darkIconDispatcher =
darkIconDispatcherStore.forDisplay(root.context.displayId) ?: return composeView
composeView.apply {
setContent {
StatusBarRoot(
parent = root,
- statusBarViewModel = homeStatusBarViewModelFactory.create(displayId),
+ statusBarViewModelFactory = homeStatusBarViewModelFactory,
statusBarViewBinder = homeStatusBarViewBinder,
notificationIconsBinder = notificationIconsBinder,
darkIconManagerFactory = darkIconManagerFactory,
@@ -110,7 +116,7 @@
@Composable
fun StatusBarRoot(
parent: ViewGroup,
- statusBarViewModel: HomeStatusBarViewModel,
+ statusBarViewModelFactory: HomeStatusBarViewModelFactory,
statusBarViewBinder: HomeStatusBarViewBinder,
notificationIconsBinder: NotificationIconContainerStatusBarViewBinder,
darkIconManagerFactory: DarkIconManager.Factory,
@@ -120,6 +126,10 @@
eventAnimationInteractor: SystemStatusEventAnimationInteractor,
onViewCreated: (ViewGroup) -> Unit,
) {
+ val displayId = parent.context.displayId
+ val statusBarViewModel =
+ rememberViewModel("HomeStatusBar") { statusBarViewModelFactory.create(displayId) }
+
Box(Modifier.fillMaxSize()) {
// TODO(b/364360986): remove this before rolling the flag forward
if (StatusBarRootModernization.SHOW_DISAMBIGUATION) {
@@ -159,10 +169,6 @@
LinearLayout.LayoutParams.WRAP_CONTENT,
)
- setViewCompositionStrategy(
- ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed
- )
-
setContent {
PlatformTheme {
val chips by
@@ -241,6 +247,12 @@
endSideContent.addView(composeView, 0)
}
+ // If the flag is enabled, create and add a compose battery view to the end
+ // of the system_icons container
+ if (NewStatusBarIcons.isEnabled) {
+ addBatteryComposable(phoneStatusBarView, statusBarViewModel)
+ }
+
notificationIconsBinder.bindWhileAttached(
notificationIconContainer,
context.displayId,
@@ -263,6 +275,27 @@
}
}
+/** Create a new [UnifiedBattery] and add it to the end of the system_icons container */
+private fun addBatteryComposable(
+ phoneStatusBarView: PhoneStatusBarView,
+ statusBarViewModel: HomeStatusBarViewModel,
+) {
+ val batteryComposeView =
+ ComposeView(phoneStatusBarView.context).apply {
+ setContent {
+ UnifiedBattery(
+ modifier =
+ Modifier.height(STATUS_BAR_BATTERY_HEIGHT).width(STATUS_BAR_BATTERY_WIDTH),
+ viewModelFactory = statusBarViewModel.batteryViewModelFactory,
+ isDark = statusBarViewModel.areaDark,
+ )
+ }
+ }
+ phoneStatusBarView.findViewById<ViewGroup>(R.id.system_icons).apply {
+ addView(batteryComposeView, -1)
+ }
+}
+
/**
* This is our analog of the flexi "ribbon", which just shows some text so we know if the flag is on
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt
index 1bc45a9..f396cbf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt
@@ -19,6 +19,7 @@
import android.annotation.ColorInt
import android.graphics.Rect
import android.view.View
+import androidx.compose.runtime.getValue
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
@@ -28,6 +29,8 @@
import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED
import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.lifecycle.ExclusiveActivatable
+import com.android.systemui.lifecycle.Hydrator
import com.android.systemui.log.table.TableLogBufferFactory
import com.android.systemui.log.table.logDiffsForTable
import com.android.systemui.plugins.DarkIconDispatcher
@@ -55,8 +58,10 @@
import com.android.systemui.statusbar.notification.headsup.PinnedStatus
import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor
import com.android.systemui.statusbar.phone.domain.interactor.DarkIconInteractor
+import com.android.systemui.statusbar.phone.domain.interactor.IsAreaDark
import com.android.systemui.statusbar.phone.domain.interactor.LightsOutInteractor
import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization
+import com.android.systemui.statusbar.pipeline.battery.ui.viewmodel.BatteryViewModel
import com.android.systemui.statusbar.pipeline.shared.domain.interactor.HomeStatusBarIconBlockListInteractor
import com.android.systemui.statusbar.pipeline.shared.domain.interactor.HomeStatusBarInteractor
import com.android.systemui.statusbar.pipeline.shared.ui.model.SystemInfoCombinedVisibilityModel
@@ -90,6 +95,9 @@
* so that it's all in one place and easily testable outside of the fragment.
*/
interface HomeStatusBarViewModel {
+ /** Factory to create the view model for the battery icon */
+ val batteryViewModelFactory: BatteryViewModel.Factory
+
/**
* True if the device is currently transitioning from lockscreen to occluded and false
* otherwise.
@@ -171,6 +179,9 @@
*/
val areaTint: Flow<StatusBarTintColor>
+ /** [IsAreaDark] applicable for this status bar's display and content area */
+ val areaDark: IsAreaDark
+
/** Interface for the assisted factory, to allow for providing a fake in tests */
interface HomeStatusBarViewModelFactory {
fun create(displayId: Int): HomeStatusBarViewModel
@@ -181,6 +192,7 @@
@AssistedInject
constructor(
@Assisted thisDisplayId: Int,
+ override val batteryViewModelFactory: BatteryViewModel.Factory,
tableLoggerFactory: TableLogBufferFactory,
homeStatusBarInteractor: HomeStatusBarInteractor,
homeStatusBarIconBlockListInteractor: HomeStatusBarIconBlockListInteractor,
@@ -201,7 +213,9 @@
statusBarContentInsetsViewModelStore: StatusBarContentInsetsViewModelStore,
@Background bgScope: CoroutineScope,
@Background bgDispatcher: CoroutineDispatcher,
-) : HomeStatusBarViewModel {
+) : HomeStatusBarViewModel, ExclusiveActivatable() {
+
+ private val hydrator = Hydrator(traceName = "HomeStatusBarViewModel.hydrator")
val tableLogger = tableLoggerFactory.getOrCreate(tableLogBufferName(thisDisplayId), 200)
@@ -294,6 +308,13 @@
.distinctUntilChanged()
.flowOn(bgDispatcher)
+ override val areaDark: IsAreaDark by
+ hydrator.hydratedStateOf(
+ traceName = "areaDark",
+ initialValue = IsAreaDark { true },
+ source = darkIconInteractor.isAreaDark(thisDisplayId),
+ )
+
/**
* True if the current SysUI state can show the home status bar (aka this status bar), and false
* if we shouldn't be showing any part of the home status bar.
@@ -473,6 +494,10 @@
@View.Visibility
private fun Boolean.toVisibleOrInvisible(): Int = if (this) View.VISIBLE else View.INVISIBLE
+ override suspend fun onActivated(): Nothing {
+ hydrator.activate()
+ }
+
/** Inject this to create the display-dependent view model */
@AssistedFactory
interface HomeStatusBarViewModelFactoryImpl :
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/DisplaySwitchLatencyTracker.kt b/packages/SystemUI/src/com/android/systemui/unfold/DisplaySwitchLatencyTracker.kt
index cd401d5..e1640cd 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/DisplaySwitchLatencyTracker.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/DisplaySwitchLatencyTracker.kt
@@ -214,7 +214,12 @@
private suspend fun waitForScreenTurnedOn() {
traceAsync(TAG, "waitForScreenTurnedOn()") {
- powerInteractor.screenPowerState.filter { it == ScreenPowerState.SCREEN_ON }.first()
+ // dropping first as it's stateFlow and will always emit latest value but we're
+ // only interested in new states
+ powerInteractor.screenPowerState
+ .drop(1)
+ .filter { it == ScreenPowerState.SCREEN_ON }
+ .first()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/NoCooldownDisplaySwitchLatencyTracker.kt b/packages/SystemUI/src/com/android/systemui/unfold/NoCooldownDisplaySwitchLatencyTracker.kt
index 6ac0bb1..91f1426 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/NoCooldownDisplaySwitchLatencyTracker.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/NoCooldownDisplaySwitchLatencyTracker.kt
@@ -50,6 +50,7 @@
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.TimeoutCancellationException
import kotlinx.coroutines.asCoroutineDispatcher
+import kotlinx.coroutines.flow.drop
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flatMapLatest
@@ -160,7 +161,12 @@
private suspend fun waitForScreenTurnedOn() {
traceAsync(TAG, "waitForScreenTurnedOn()") {
- powerInteractor.screenPowerState.filter { it == ScreenPowerState.SCREEN_ON }.first()
+ // dropping first as it's stateFlow and will always emit latest value but we're
+ // only interested in new states
+ powerInteractor.screenPowerState
+ .drop(1)
+ .filter { it == ScreenPowerState.SCREEN_ON }
+ .first()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
index 68bffeef..4d547705 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
@@ -37,8 +37,6 @@
import android.media.IVolumeController;
import android.media.MediaRouter2Manager;
import android.media.VolumePolicy;
-import android.media.session.MediaController.PlaybackInfo;
-import android.media.session.MediaSession.Token;
import android.net.Uri;
import android.os.Handler;
import android.os.HandlerExecutor;
@@ -61,6 +59,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.settingslib.volume.MediaSessions;
+import com.android.settingslib.volume.MediaSessions.SessionId;
import com.android.systemui.Dumpable;
import com.android.systemui.Flags;
import com.android.systemui.broadcast.BroadcastDispatcher;
@@ -1402,12 +1401,13 @@
}
protected final class MediaSessionsCallbacks implements MediaSessions.Callbacks {
- private final HashMap<Token, Integer> mRemoteStreams = new HashMap<>();
+ private final HashMap<SessionId, Integer> mRemoteStreams = new HashMap<>();
private int mNextStream = DYNAMIC_STREAM_REMOTE_START_INDEX;
@Override
- public void onRemoteUpdate(Token token, String name, PlaybackInfo pi) {
+ public void onRemoteUpdate(
+ SessionId token, String name, MediaSessions.VolumeInfo volumeInfo) {
addStream(token, "onRemoteUpdate");
int stream = 0;
@@ -1415,14 +1415,15 @@
stream = mRemoteStreams.get(token);
}
Slog.d(TAG,
- "onRemoteUpdate: stream: " + stream + " volume: " + pi.getCurrentVolume());
+ "onRemoteUpdate: stream: "
+ + stream + " volume: " + volumeInfo.getCurrentVolume());
boolean changed = mState.states.indexOfKey(stream) < 0;
final StreamState ss = streamStateW(stream);
ss.dynamic = true;
ss.levelMin = 0;
- ss.levelMax = pi.getMaxVolume();
- if (ss.level != pi.getCurrentVolume()) {
- ss.level = pi.getCurrentVolume();
+ ss.levelMax = volumeInfo.getMaxVolume();
+ if (ss.level != volumeInfo.getCurrentVolume()) {
+ ss.level = volumeInfo.getCurrentVolume();
changed = true;
}
if (!Objects.equals(ss.remoteLabel, name)) {
@@ -1437,11 +1438,11 @@
}
@Override
- public void onRemoteVolumeChanged(Token token, int flags) {
- addStream(token, "onRemoteVolumeChanged");
+ public void onRemoteVolumeChanged(SessionId sessionId, int flags) {
+ addStream(sessionId, "onRemoteVolumeChanged");
int stream = 0;
synchronized (mRemoteStreams) {
- stream = mRemoteStreams.get(token);
+ stream = mRemoteStreams.get(sessionId);
}
final boolean showUI = shouldShowUI(flags);
Slog.d(TAG, "onRemoteVolumeChanged: stream: " + stream + " showui? " + showUI);
@@ -1459,7 +1460,7 @@
}
@Override
- public void onRemoteRemoved(Token token) {
+ public void onRemoteRemoved(SessionId token) {
int stream;
synchronized (mRemoteStreams) {
if (!mRemoteStreams.containsKey(token)) {
@@ -1480,7 +1481,7 @@
}
public void setStreamVolume(int stream, int level) {
- final Token token = findToken(stream);
+ final SessionId token = findToken(stream);
if (token == null) {
Log.w(TAG, "setStreamVolume: No token found for stream: " + stream);
return;
@@ -1488,9 +1489,9 @@
mMediaSessions.setVolume(token, level);
}
- private Token findToken(int stream) {
+ private SessionId findToken(int stream) {
synchronized (mRemoteStreams) {
- for (Map.Entry<Token, Integer> entry : mRemoteStreams.entrySet()) {
+ for (Map.Entry<SessionId, Integer> entry : mRemoteStreams.entrySet()) {
if (entry.getValue().equals(stream)) {
return entry.getKey();
}
@@ -1499,7 +1500,7 @@
return null;
}
- private void addStream(Token token, String triggeringMethod) {
+ private void addStream(SessionId token, String triggeringMethod) {
synchronized (mRemoteStreams) {
if (!mRemoteStreams.containsKey(token)) {
mRemoteStreams.put(token, mNextStream);
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialog.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialog.kt
index 83b7c18..86defff 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialog.kt
@@ -68,7 +68,7 @@
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.volume_dialog)
- requireViewById<View>(R.id.volume_dialog_root).repeatWhenAttached {
+ requireViewById<View>(R.id.volume_dialog).repeatWhenAttached {
coroutineScopeTraced("[Volume]dialog") {
val component = componentFactory.create(this)
with(component.volumeDialogViewBinder()) { bind(this@VolumeDialog) }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogVisibilityInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogVisibilityInteractor.kt
index 20a74b0..afe3d7b 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogVisibilityInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogVisibilityInteractor.kt
@@ -17,7 +17,9 @@
package com.android.systemui.volume.dialog.domain.interactor
import android.annotation.SuppressLint
+import android.provider.Settings
import com.android.systemui.plugins.VolumeDialogController
+import com.android.systemui.shared.settings.data.repository.SecureSettingsRepository
import com.android.systemui.volume.Events
import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogPlugin
import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogPluginScope
@@ -28,8 +30,9 @@
import com.android.systemui.volume.dialog.shared.model.VolumeDialogVisibilityModel.Visible
import com.android.systemui.volume.dialog.utils.VolumeTracer
import javax.inject.Inject
-import kotlin.time.Duration
+import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.seconds
+import kotlin.time.DurationUnit
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
@@ -43,8 +46,6 @@
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
-private val MAX_DIALOG_SHOW_TIME: Duration = 3.seconds
-
/**
* Handles Volume Dialog visibility state. It might change from several sources:
* - [com.android.systemui.plugins.VolumeDialogController] requests visibility change;
@@ -60,8 +61,11 @@
private val tracer: VolumeTracer,
private val repository: VolumeDialogVisibilityRepository,
private val controller: VolumeDialogController,
+ private val secureSettingsRepository: SecureSettingsRepository,
) {
+ private val defaultTimeout = 3.seconds
+
@SuppressLint("SharedFlowCreation")
private val mutableDismissDialogEvents = MutableSharedFlow<Unit>(extraBufferCapacity = 1)
val dialogVisibility: Flow<VolumeDialogVisibilityModel> =
@@ -73,7 +77,14 @@
init {
merge(
mutableDismissDialogEvents.mapLatest {
- delay(MAX_DIALOG_SHOW_TIME)
+ delay(
+ secureSettingsRepository
+ .getInt(
+ Settings.Secure.VOLUME_DIALOG_DISMISS_TIMEOUT,
+ defaultTimeout.toInt(DurationUnit.MILLISECONDS),
+ )
+ .milliseconds
+ )
VolumeDialogEventModel.DismissRequested(Events.DISMISS_REASON_TIMEOUT)
},
callbacksInteractor.event,
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/binder/VolumeDialogRingerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/binder/VolumeDialogRingerViewBinder.kt
index 3d0c7d6..eb2b2f6 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/binder/VolumeDialogRingerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/binder/VolumeDialogRingerViewBinder.kt
@@ -120,7 +120,7 @@
drawerContainer.setTransitionListener(ringerDrawerTransitionListener)
volumeDialogBackgroundView.background = volumeDialogBackgroundView.background.mutate()
ringerBackgroundView.background = ringerBackgroundView.background.mutate()
- launch { dialogViewModel.addTouchableBounds(drawerContainer) }
+ launch { dialogViewModel.addTouchableBounds(ringerBackgroundView) }
viewModel.ringerViewModel
.mapLatest { ringerState ->
@@ -246,16 +246,12 @@
uiModel.drawerState.currentMode != uiModel.drawerState.previousMode
) {
val count = uiModel.availableButtons.size
- val selectedButton =
- getChildAt(count - uiModel.currentButtonIndex)
- .requireViewById<ImageButton>(R.id.volume_drawer_button)
+ val selectedButton = getChildAt(count - uiModel.currentButtonIndex) as ImageButton
val previousIndex =
uiModel.availableButtons.indexOfFirst {
it.ringerMode == uiModel.drawerState.previousMode
}
- val unselectedButton =
- getChildAt(count - previousIndex)
- .requireViewById<ImageButton>(R.id.volume_drawer_button)
+ val unselectedButton = getChildAt(count - previousIndex) as ImageButton
// We only need to execute on roundness animation end and volume dialog background
// progress update once because these changes should be applied once on volume dialog
// background and ringer drawer views.
@@ -306,7 +302,7 @@
) {
val count = uiModel.availableButtons.size
uiModel.availableButtons.fastForEachIndexed { index, ringerButton ->
- val view = getChildAt(count - index)
+ val view = getChildAt(count - index) as ImageButton
val isOpen = uiModel.drawerState is RingerDrawerState.Open
if (index == uiModel.currentButtonIndex) {
view.bindDrawerButton(
@@ -323,37 +319,37 @@
onAnimationEnd?.run()
}
- private fun View.bindDrawerButton(
+ private fun ImageButton.bindDrawerButton(
buttonViewModel: RingerButtonViewModel,
viewModel: VolumeDialogRingerDrawerViewModel,
isOpen: Boolean,
isSelected: Boolean = false,
isAnimated: Boolean = false,
) {
+ // id = buttonViewModel.viewId
+ setSelected(isSelected)
val ringerContentDesc = context.getString(buttonViewModel.contentDescriptionResId)
- with(requireViewById<ImageButton>(R.id.volume_drawer_button)) {
- setImageResource(buttonViewModel.imageResId)
- contentDescription =
- if (isSelected && !isOpen) {
- context.getString(
- R.string.volume_ringer_drawer_closed_content_description,
- ringerContentDesc,
- )
- } else {
- ringerContentDesc
- }
- if (isSelected && !isAnimated) {
- setBackgroundResource(R.drawable.volume_drawer_selection_bg)
- setColorFilter(context.getColor(internalR.color.materialColorOnPrimary))
- background = background.mutate()
- } else if (!isAnimated) {
- setBackgroundResource(R.drawable.volume_ringer_item_bg)
- setColorFilter(context.getColor(internalR.color.materialColorOnSurface))
- background = background.mutate()
+ setImageResource(buttonViewModel.imageResId)
+ contentDescription =
+ if (isSelected && !isOpen) {
+ context.getString(
+ R.string.volume_ringer_drawer_closed_content_description,
+ ringerContentDesc,
+ )
+ } else {
+ ringerContentDesc
}
- setOnClickListener {
- viewModel.onRingerButtonClicked(buttonViewModel.ringerMode, isSelected)
- }
+ if (isSelected && !isAnimated) {
+ setBackgroundResource(R.drawable.volume_drawer_selection_bg)
+ setColorFilter(context.getColor(internalR.color.materialColorOnPrimary))
+ background = background.mutate()
+ } else if (!isAnimated) {
+ setBackgroundResource(R.drawable.volume_ringer_item_bg)
+ setColorFilter(context.getColor(internalR.color.materialColorOnSurface))
+ background = background.mutate()
+ }
+ setOnClickListener {
+ viewModel.onRingerButtonClicked(buttonViewModel.ringerMode, isSelected)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt
index a51e33a..2c9ee54 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt
@@ -19,7 +19,6 @@
import android.graphics.drawable.Drawable
import android.view.View
import androidx.compose.animation.AnimatedVisibility
-import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
@@ -61,11 +60,9 @@
import javax.inject.Inject
import kotlin.math.round
import kotlin.math.roundToInt
-import kotlinx.coroutines.Job
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.currentCoroutineContext
import kotlinx.coroutines.isActive
-import kotlinx.coroutines.launch
@VolumeDialogSliderScope
class VolumeDialogSliderViewBinder
@@ -116,10 +113,6 @@
val steps = with(sliderStateModel.valueRange) { endInclusive - start - 1 }.toInt()
- var animateJob: Job? = null
- val animatedSliderValue =
- remember(sliderStateModel.value) { Animatable(sliderStateModel.value) }
-
val interactionSource = remember { MutableInteractionSource() }
val hapticsViewModel: SliderHapticsViewModel? =
hapticsViewModelFactory?.let {
@@ -149,16 +142,7 @@
hapticsViewModel?.onValueChangeEnded()
}
sliderState.onValueChange = { newValue ->
- if (newValue != animatedSliderValue.targetValue) {
- animateJob?.cancel()
- animateJob =
- coroutineScope.launch {
- animatedSliderValue.animateTo(newValue) {
- sliderState.value = value
- }
- }
- }
-
+ sliderState.value = newValue
hapticsViewModel?.addVelocityDataPoint(newValue)
overscrollViewModel.setSlider(
value = sliderState.value,
@@ -173,7 +157,7 @@
var lastDiscreteStep by remember { mutableFloatStateOf(round(sliderStateModel.value)) }
LaunchedEffect(sliderStateModel.value) {
val value = sliderStateModel.value
- launch { animatedSliderValue.animateTo(value) }
+ sliderState.value = value
if (value != lastDiscreteStep) {
lastDiscreteStep = value
hapticsViewModel?.onValueChange(value)
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinder.kt
index f2d7d95..7cc4bcc 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinder.kt
@@ -74,7 +74,7 @@
val insets: MutableStateFlow<WindowInsets> =
MutableStateFlow(WindowInsets.Builder().build())
// Root view of the Volume Dialog.
- val root: MotionLayout = dialog.requireViewById(R.id.volume_dialog_root)
+ val root: MotionLayout = dialog.requireViewById(R.id.volume_dialog)
animateVisibility(root, dialog, viewModel.dialogVisibilityModel)
diff --git a/packages/SystemUI/src/com/android/systemui/window/dagger/WindowRootViewBlurModule.kt b/packages/SystemUI/src/com/android/systemui/window/dagger/WindowRootViewBlurModule.kt
new file mode 100644
index 0000000..95b3b68
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/window/dagger/WindowRootViewBlurModule.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.window.dagger
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.window.data.repository.WindowRootViewBlurRepository
+import com.android.systemui.window.data.repository.WindowRootViewBlurRepositoryImpl
+import dagger.Binds
+import dagger.Module
+
+/**
+ * Module that can be installed in sysui variants where we support cross window blur.
+ */
+@Module
+interface WindowRootViewBlurModule {
+ @Binds
+ @SysUISingleton
+ fun bindWindowRootViewBlurRepository(
+ windowRootViewBlurRepositoryImpl: WindowRootViewBlurRepositoryImpl
+ ): WindowRootViewBlurRepository
+}
diff --git a/packages/SystemUI/src/com/android/systemui/window/dagger/WindowRootViewBlurNotSupportedModule.kt b/packages/SystemUI/src/com/android/systemui/window/dagger/WindowRootViewBlurNotSupportedModule.kt
new file mode 100644
index 0000000..ae917e0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/window/dagger/WindowRootViewBlurNotSupportedModule.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.window.dagger
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.window.data.repository.NoopWindowRootViewBlurRepository
+import com.android.systemui.window.data.repository.WindowRootViewBlurRepository
+import dagger.Binds
+import dagger.Module
+
+/**
+ * Module that can be installed in sysui variants where we don't support cross window blur.
+ */
+@Module
+interface WindowRootViewBlurNotSupportedModule {
+ @Binds
+ @SysUISingleton
+ fun bindWindowRootViewBlurRepository(
+ windowRootViewBlurRepositoryImpl: NoopWindowRootViewBlurRepository
+ ): WindowRootViewBlurRepository
+}
diff --git a/packages/SystemUI/src/com/android/systemui/window/data/repository/NoopWindowRootViewBlurRepository.kt b/packages/SystemUI/src/com/android/systemui/window/data/repository/NoopWindowRootViewBlurRepository.kt
new file mode 100644
index 0000000..80aa11a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/window/data/repository/NoopWindowRootViewBlurRepository.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.window.data.repository
+
+import com.android.systemui.window.data.repository.WindowRootViewBlurRepository
+import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+
+class NoopWindowRootViewBlurRepository @Inject constructor() : WindowRootViewBlurRepository {
+ override val blurRadius: MutableStateFlow<Int> = MutableStateFlow(0)
+ override val isBlurOpaque: MutableStateFlow<Boolean> = MutableStateFlow(true)
+ override val isBlurSupported: StateFlow<Boolean> = MutableStateFlow(false)
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/window/data/repository/WindowRootViewBlurRepository.kt b/packages/SystemUI/src/com/android/systemui/window/data/repository/WindowRootViewBlurRepository.kt
index 22a74c8..41ceda0 100644
--- a/packages/SystemUI/src/com/android/systemui/window/data/repository/WindowRootViewBlurRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/window/data/repository/WindowRootViewBlurRepository.kt
@@ -16,14 +16,77 @@
package com.android.systemui.window.data.repository
+import android.app.ActivityManager
+import android.os.SystemProperties
+import android.view.CrossWindowBlurListeners
+import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
+import com.android.systemui.window.data.repository.WindowRootViewBlurRepository.Companion.isDisableBlurSysPropSet
+import java.util.concurrent.Executor
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.stateIn
/** Repository that maintains state for the window blur effect. */
-@SysUISingleton
-class WindowRootViewBlurRepository @Inject constructor() {
- val blurRadius = MutableStateFlow(0)
+interface WindowRootViewBlurRepository {
+ val blurRadius: MutableStateFlow<Int>
+ val isBlurOpaque: MutableStateFlow<Boolean>
- val isBlurOpaque = MutableStateFlow(false)
+ /** Is blur supported based on settings toggle and battery power saver mode. */
+ val isBlurSupported: StateFlow<Boolean>
+
+ companion object {
+ /**
+ * Whether the `persist.sysui.disableBlur` is set, this is used to disable blur for tests.
+ */
+ @JvmStatic
+ fun isDisableBlurSysPropSet() = SystemProperties.getBoolean(DISABLE_BLUR_PROPERTY, false)
+
+ // property that can be used to disable the cross window blur for tests
+ private const val DISABLE_BLUR_PROPERTY = "persist.sysui.disableBlur"
+ }
+}
+
+@SysUISingleton
+class WindowRootViewBlurRepositoryImpl
+@Inject
+constructor(
+ crossWindowBlurListeners: CrossWindowBlurListeners,
+ @Main private val executor: Executor,
+ @Application private val scope: CoroutineScope,
+) : WindowRootViewBlurRepository {
+ override val blurRadius = MutableStateFlow(0)
+
+ override val isBlurOpaque = MutableStateFlow(false)
+
+ override val isBlurSupported: StateFlow<Boolean> =
+ conflatedCallbackFlow {
+ val sendUpdate = { value: Boolean ->
+ trySendWithFailureLogging(
+ isBlurAllowed() && value,
+ TAG,
+ "unable to send blur enabled/disable state change",
+ )
+ }
+ crossWindowBlurListeners.addListener(executor, sendUpdate)
+ sendUpdate(crossWindowBlurListeners.isCrossWindowBlurEnabled)
+
+ awaitClose { crossWindowBlurListeners.removeListener(sendUpdate) }
+ } // stateIn because this is backed by a binder call.
+ .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+
+ private fun isBlurAllowed(): Boolean {
+ return ActivityManager.isHighEndGfx() && !isDisableBlurSysPropSet()
+ }
+
+ companion object {
+ const val TAG = "WindowRootViewBlurRepository"
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/window/domain/interactor/WindowRootViewBlurInteractor.kt b/packages/SystemUI/src/com/android/systemui/window/domain/interactor/WindowRootViewBlurInteractor.kt
index 9e36934..7a88a2e 100644
--- a/packages/SystemUI/src/com/android/systemui/window/domain/interactor/WindowRootViewBlurInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/window/domain/interactor/WindowRootViewBlurInteractor.kt
@@ -75,6 +75,12 @@
_onBlurAppliedEvent.emit(appliedBlurRadius)
}
+ /**
+ * Whether blur is enabled or not based on settings toggle, critical thermal state, battery save
+ * state and multimedia tunneling state.
+ */
+ val isBlurCurrentlySupported: StateFlow<Boolean> = repository.isBlurSupported
+
/** Radius of blur to be applied on the window root view. */
val blurRadius: StateFlow<Int> = repository.blurRadius.asStateFlow()
diff --git a/packages/SystemUI/src/com/android/systemui/window/ui/viewmodel/WindowRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/window/ui/viewmodel/WindowRootViewModel.kt
index b18c39dc..1b42352 100644
--- a/packages/SystemUI/src/com/android/systemui/window/ui/viewmodel/WindowRootViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/window/ui/viewmodel/WindowRootViewModel.kt
@@ -26,11 +26,14 @@
import com.android.systemui.window.domain.interactor.WindowRootViewBlurInteractor
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onEach
@@ -38,6 +41,7 @@
typealias BlurAppliedUiEvent = Int
/** View model for window root view. */
+@OptIn(ExperimentalCoroutinesApi::class)
class WindowRootViewModel
@AssistedInject
constructor(
@@ -58,7 +62,7 @@
glanceableHubTransitions.map { it.windowBlurRadius.logIfPossible(it.javaClass.name) }
else emptyList()
- val blurRadius: Flow<Float> =
+ private val _blurRadius =
listOf(
*bouncerBlurRadiusFlows.toTypedArray(),
*glanceableHubBlurRadiusFlows.toTypedArray(),
@@ -66,8 +70,23 @@
)
.merge()
+ val blurRadius: Flow<Float> =
+ blurInteractor.isBlurCurrentlySupported.flatMapLatest { blurSupported ->
+ if (blurSupported) {
+ _blurRadius
+ } else {
+ flowOf(0f)
+ }
+ }
+
val isBlurOpaque =
- blurInteractor.isBlurOpaque.distinctUntilChanged().logIfPossible("isBlurOpaque")
+ blurInteractor.isBlurCurrentlySupported.flatMapLatest { blurSupported ->
+ if (blurSupported) {
+ blurInteractor.isBlurOpaque.distinctUntilChanged().logIfPossible("isBlurOpaque")
+ } else {
+ flowOf(false)
+ }
+ }
override suspend fun onActivated(): Nothing {
coroutineScope {
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 732561e0..944604f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
@@ -640,11 +640,11 @@
}
}
- @DisableFlags(FLAG_GLANCEABLE_HUB_V2)
@Test
fun onTouchEvent_shadeInteracting_movesNotDispatched() =
with(kosmos) {
testScope.runTest {
+ `whenever`(communalViewModel.swipeToHubEnabled()).thenReturn(true)
// On lockscreen.
goToScene(CommunalScenes.Blank)
whenever(
@@ -721,11 +721,11 @@
}
}
- @DisableFlags(FLAG_GLANCEABLE_HUB_V2)
@Test
fun onTouchEvent_bouncerInteracting_movesNotDispatched() =
with(kosmos) {
testScope.runTest {
+ `whenever`(communalViewModel.swipeToHubEnabled()).thenReturn(true)
// On lockscreen.
goToScene(CommunalScenes.Blank)
whenever(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt
index edb0f35..f3af794f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt
@@ -59,6 +59,7 @@
import com.android.systemui.statusbar.phone.StatusOverlayHoverListenerFactory
import com.android.systemui.statusbar.phone.ui.StatusBarIconController
import com.android.systemui.statusbar.phone.ui.TintedIconManager
+import com.android.systemui.statusbar.pipeline.battery.ui.viewmodel.batteryViewModelFactory
import com.android.systemui.statusbar.policy.Clock
import com.android.systemui.statusbar.policy.FakeConfigurationController
import com.android.systemui.statusbar.policy.NextAlarmController
@@ -202,6 +203,7 @@
Lazy { kosmos.shadeDisplaysRepository },
variableDateViewControllerFactory,
batteryMeterViewController,
+ kosmos.batteryViewModelFactory,
dumpManager,
mShadeCarrierGroupControllerBuilder,
combinedShadeHeadersConstraintManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
index a523488..9137215 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
@@ -63,6 +63,7 @@
import com.android.systemui.animation.ShadeInterpolation;
import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants;
import com.android.systemui.dock.DockManager;
+import com.android.systemui.flags.DisableSceneContainer;
import com.android.systemui.flags.EnableSceneContainer;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository;
@@ -118,10 +119,8 @@
@Rule public Expect mExpect = Expect.create();
private final KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
- private final FakeConfigurationController mConfigurationController =
- new FakeConfigurationController();
- private final LargeScreenShadeInterpolator
- mLinearLargeScreenShadeInterpolator = new LinearLargeScreenShadeInterpolator();
+ private FakeConfigurationController mConfigurationController;
+ private LargeScreenShadeInterpolator mLinearLargeScreenShadeInterpolator;
private final TestScope mTestScope = mKosmos.getTestScope();
private final JavaAdapter mJavaAdapter = new JavaAdapter(mTestScope.getBackgroundScope());
@@ -137,6 +136,7 @@
private boolean mAlwaysOnEnabled;
private TestableLooper mLooper;
private Context mContext;
+
@Mock private DozeParameters mDozeParameters;
@Mock private LightBarController mLightBarController;
@Mock private DelayedWakeLock.Factory mDelayedWakeLockFactory;
@@ -149,12 +149,11 @@
@Mock private PrimaryBouncerToGoneTransitionViewModel mPrimaryBouncerToGoneTransitionViewModel;
@Mock private AlternateBouncerToGoneTransitionViewModel
mAlternateBouncerToGoneTransitionViewModel;
- private final KeyguardTransitionInteractor mKeyguardTransitionInteractor =
- mKosmos.getKeyguardTransitionInteractor();
- private final FakeKeyguardTransitionRepository mKeyguardTransitionRepository =
- mKosmos.getKeyguardTransitionRepository();
@Mock private KeyguardInteractor mKeyguardInteractor;
+ private KeyguardTransitionInteractor mKeyguardTransitionInteractor;
+ private FakeKeyguardTransitionRepository mKeyguardTransitionRepository;
+
// TODO(b/204991468): Use a real PanelExpansionStateManager object once this bug is fixed. (The
// event-dispatch-on-registration pattern caused some of these unit tests to fail.)
@Mock private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
@@ -238,6 +237,9 @@
when(mContext.getColor(com.android.internal.R.color.materialColorSurface))
.thenAnswer(invocation -> mSurfaceColor);
+ mConfigurationController = new FakeConfigurationController();
+ mLinearLargeScreenShadeInterpolator = new LinearLargeScreenShadeInterpolator();
+
mScrimBehind = spy(new ScrimView(mContext));
mScrimInFront = new ScrimView(mContext);
mNotificationsScrim = new ScrimView(mContext);
@@ -270,6 +272,9 @@
when(mAlternateBouncerToGoneTransitionViewModel.getScrimAlpha())
.thenReturn(emptyFlow());
+ mKeyguardTransitionRepository = mKosmos.getKeyguardTransitionRepository();
+ mKeyguardTransitionInteractor = mKosmos.getKeyguardTransitionInteractor();
+
mScrimController = new ScrimController(
mLightBarController,
mDozeParameters,
@@ -290,7 +295,8 @@
mKeyguardInteractor,
mKosmos.getTestDispatcher(),
mLinearLargeScreenShadeInterpolator,
- new BlurConfig(0.0f, 0.0f));
+ new BlurConfig(0.0f, 0.0f),
+ mKosmos::getWindowRootViewBlurInteractor);
mScrimController.setScrimVisibleListener(visible -> mScrimVisibility = visible);
mScrimController.attachViews(mScrimBehind, mNotificationsScrim, mScrimInFront);
mScrimController.setAnimatorListener(mAnimatorListener);
@@ -321,6 +327,7 @@
}
@Test
+ @DisableSceneContainer
public void transitionToKeyguard() {
mScrimController.legacyTransitionTo(ScrimState.KEYGUARD);
finishAnimationsImmediately();
@@ -336,7 +343,7 @@
}
@Test
- public void transitionToShadeLocked() {
+@DisableSceneContainer void transitionToShadeLocked() {
mScrimController.legacyTransitionTo(SHADE_LOCKED);
mScrimController.setQsPosition(1f, 0);
finishAnimationsImmediately();
@@ -372,6 +379,7 @@
}
@Test
+ @DisableSceneContainer
public void transitionToShadeLocked_clippingQs() {
mScrimController.setClipsQsScrim(true);
mScrimController.legacyTransitionTo(SHADE_LOCKED);
@@ -390,6 +398,7 @@
}
@Test
+ @DisableSceneContainer
public void transitionToOff() {
mScrimController.legacyTransitionTo(ScrimState.OFF);
finishAnimationsImmediately();
@@ -405,6 +414,7 @@
}
@Test
+ @DisableSceneContainer
public void transitionToAod_withRegularWallpaper() {
mScrimController.legacyTransitionTo(ScrimState.AOD);
finishAnimationsImmediately();
@@ -420,6 +430,7 @@
}
@Test
+ @DisableSceneContainer
public void transitionToAod_withFrontAlphaUpdates() {
// Assert that setting the AOD front scrim alpha doesn't take effect in a non-AOD state.
mScrimController.legacyTransitionTo(ScrimState.KEYGUARD);
@@ -464,6 +475,7 @@
}
@Test
+ @DisableSceneContainer
public void transitionToAod_afterDocked_ignoresAlwaysOnAndUpdatesFrontAlpha() {
// Assert that setting the AOD front scrim alpha doesn't take effect in a non-AOD state.
mScrimController.legacyTransitionTo(ScrimState.KEYGUARD);
@@ -505,6 +517,7 @@
}
@Test
+ @DisableSceneContainer
public void transitionToPulsing_withFrontAlphaUpdates() {
// Pre-condition
// Need to go to AoD first because PULSING doesn't change
@@ -550,6 +563,7 @@
}
@Test
+ @DisableSceneContainer
public void transitionToKeyguardBouncer() {
mScrimController.legacyTransitionTo(BOUNCER);
finishAnimationsImmediately();
@@ -570,6 +584,7 @@
}
@Test
+ @DisableSceneContainer
public void lockscreenToHubTransition_setsBehindScrimAlpha() {
// Start on lockscreen.
mScrimController.legacyTransitionTo(ScrimState.KEYGUARD);
@@ -616,6 +631,7 @@
}
@Test
+ @DisableSceneContainer
public void hubToLockscreenTransition_setsViewAlpha() {
// Start on glanceable hub.
mScrimController.legacyTransitionTo(ScrimState.GLANCEABLE_HUB);
@@ -662,6 +678,7 @@
}
@Test
+ @DisableSceneContainer
public void transitionToHub() {
mScrimController.setRawPanelExpansionFraction(0f);
mScrimController.setBouncerHiddenFraction(KeyguardBouncerConstants.EXPANSION_HIDDEN);
@@ -676,6 +693,7 @@
}
@Test
+ @DisableSceneContainer
public void openBouncerOnHub() {
mScrimController.legacyTransitionTo(ScrimState.GLANCEABLE_HUB);
@@ -705,6 +723,7 @@
}
@Test
+ @DisableSceneContainer
public void openShadeOnHub() {
mScrimController.legacyTransitionTo(ScrimState.GLANCEABLE_HUB);
@@ -733,6 +752,7 @@
}
@Test
+ @DisableSceneContainer
public void transitionToHubOverDream() {
mScrimController.setRawPanelExpansionFraction(0f);
mScrimController.setBouncerHiddenFraction(KeyguardBouncerConstants.EXPANSION_HIDDEN);
@@ -747,6 +767,7 @@
}
@Test
+ @DisableSceneContainer
public void openBouncerOnHubOverDream() {
mScrimController.legacyTransitionTo(ScrimState.GLANCEABLE_HUB_OVER_DREAM);
@@ -776,6 +797,7 @@
}
@Test
+ @DisableSceneContainer
public void openShadeOnHubOverDream() {
mScrimController.legacyTransitionTo(ScrimState.GLANCEABLE_HUB_OVER_DREAM);
@@ -804,6 +826,7 @@
}
@Test
+ @DisableSceneContainer
public void onThemeChange_bouncerBehindTint_isUpdatedToSurfaceColor() {
assertEquals(BOUNCER.getBehindTint(), 0x112233);
mSurfaceColor = 0x223344;
@@ -812,6 +835,7 @@
}
@Test
+ @DisableSceneContainer
public void onThemeChangeWhileClipQsScrim_bouncerBehindTint_remainsBlack() {
mScrimController.setClipsQsScrim(true);
mScrimController.legacyTransitionTo(BOUNCER);
@@ -824,6 +848,7 @@
}
@Test
+ @DisableSceneContainer
public void transitionToKeyguardBouncer_clippingQs() {
mScrimController.setClipsQsScrim(true);
mScrimController.legacyTransitionTo(BOUNCER);
@@ -844,6 +869,7 @@
}
@Test
+ @DisableSceneContainer
public void disableClipQsScrimWithoutStateTransition_updatesTintAndAlpha() {
mScrimController.setClipsQsScrim(true);
mScrimController.legacyTransitionTo(BOUNCER);
@@ -866,6 +892,7 @@
}
@Test
+ @DisableSceneContainer
public void enableClipQsScrimWithoutStateTransition_updatesTintAndAlpha() {
mScrimController.setClipsQsScrim(false);
mScrimController.legacyTransitionTo(BOUNCER);
@@ -888,6 +915,7 @@
}
@Test
+ @DisableSceneContainer
public void transitionToBouncer() {
mScrimController.legacyTransitionTo(ScrimState.BOUNCER_SCRIMMED);
finishAnimationsImmediately();
@@ -901,6 +929,7 @@
}
@Test
+ @DisableSceneContainer
public void transitionToUnlocked_clippedQs() {
mScrimController.setClipsQsScrim(true);
mScrimController.setRawPanelExpansionFraction(0f);
@@ -959,6 +988,7 @@
}
@Test
+ @DisableSceneContainer
public void transitionToUnlocked_nonClippedQs_followsLargeScreensInterpolator() {
mScrimController.setClipsQsScrim(false);
mScrimController.setRawPanelExpansionFraction(0f);
@@ -998,6 +1028,7 @@
}
@Test
+ @DisableSceneContainer
public void scrimStateCallback() {
mScrimController.legacyTransitionTo(ScrimState.UNLOCKED);
finishAnimationsImmediately();
@@ -1013,6 +1044,7 @@
}
@Test
+ @DisableSceneContainer
public void panelExpansion() {
mScrimController.setRawPanelExpansionFraction(0f);
mScrimController.setRawPanelExpansionFraction(0.5f);
@@ -1035,6 +1067,7 @@
}
@Test
+ @DisableSceneContainer
public void qsExpansion() {
reset(mScrimBehind);
mScrimController.setQsPosition(1f, 999 /* value doesn't matter */);
@@ -1047,6 +1080,7 @@
}
@Test
+ @DisableSceneContainer
public void qsExpansion_clippingQs() {
reset(mScrimBehind);
mScrimController.setClipsQsScrim(true);
@@ -1060,6 +1094,7 @@
}
@Test
+ @DisableSceneContainer
public void qsExpansion_half_clippingQs() {
reset(mScrimBehind);
mScrimController.setClipsQsScrim(true);
@@ -1073,6 +1108,7 @@
}
@Test
+ @DisableSceneContainer
public void panelExpansionAffectsAlpha() {
mScrimController.setRawPanelExpansionFraction(0f);
mScrimController.setRawPanelExpansionFraction(0.5f);
@@ -1095,6 +1131,7 @@
}
@Test
+ @DisableSceneContainer
public void transitionToUnlockedFromOff() {
// Simulate unlock with fingerprint without AOD
mScrimController.legacyTransitionTo(ScrimState.OFF);
@@ -1117,6 +1154,7 @@
}
@Test
+ @DisableSceneContainer
public void transitionToUnlockedFromAod() {
// Simulate unlock with fingerprint
mScrimController.legacyTransitionTo(ScrimState.AOD);
@@ -1139,6 +1177,7 @@
}
@Test
+ @DisableSceneContainer
public void scrimBlanksBeforeLeavingAod() {
// Simulate unlock with fingerprint
mScrimController.legacyTransitionTo(ScrimState.AOD);
@@ -1162,6 +1201,7 @@
}
@Test
+ @DisableSceneContainer
public void scrimBlankCallbackWhenUnlockingFromPulse() {
boolean[] blanked = {false};
// Simulate unlock with fingerprint
@@ -1180,6 +1220,7 @@
}
@Test
+ @DisableSceneContainer
public void blankingNotRequired_leavingAoD() {
// GIVEN display does NOT need blanking
when(mDozeParameters.getDisplayNeedsBlanking()).thenReturn(false);
@@ -1204,7 +1245,8 @@
mKeyguardInteractor,
mKosmos.getTestDispatcher(),
mLinearLargeScreenShadeInterpolator,
- new BlurConfig(0.0f, 0.0f));
+ new BlurConfig(0.0f, 0.0f),
+ mKosmos::getWindowRootViewBlurInteractor);
mScrimController.setScrimVisibleListener(visible -> mScrimVisibility = visible);
mScrimController.attachViews(mScrimBehind, mNotificationsScrim, mScrimInFront);
mScrimController.setAnimatorListener(mAnimatorListener);
@@ -1234,6 +1276,7 @@
}
@Test
+ @DisableSceneContainer
public void testScrimCallback() {
int[] callOrder = {0, 0, 0};
int[] currentCall = {0};
@@ -1260,12 +1303,14 @@
}
@Test
+ @DisableSceneContainer
public void testScrimCallbacksWithoutAmbientDisplay() {
mAlwaysOnEnabled = false;
testScrimCallback();
}
@Test
+ @DisableSceneContainer
public void testScrimCallbackCancelled() {
boolean[] cancelledCalled = {false};
mScrimController.legacyTransitionTo(ScrimState.AOD, new ScrimController.Callback() {
@@ -1279,6 +1324,7 @@
}
@Test
+ @DisableSceneContainer
public void testHoldsWakeLock_whenAOD() {
mScrimController.legacyTransitionTo(ScrimState.AOD);
verify(mWakeLock).acquire(anyString());
@@ -1288,6 +1334,7 @@
}
@Test
+ @DisableSceneContainer
public void testDoesNotHoldWakeLock_whenUnlocking() {
mScrimController.legacyTransitionTo(ScrimState.UNLOCKED);
finishAnimationsImmediately();
@@ -1295,6 +1342,7 @@
}
@Test
+ @DisableSceneContainer
public void testCallbackInvokedOnSameStateTransition() {
mScrimController.legacyTransitionTo(ScrimState.UNLOCKED);
finishAnimationsImmediately();
@@ -1304,6 +1352,7 @@
}
@Test
+ @DisableSceneContainer
public void testConservesExpansionOpacityAfterTransition() {
mScrimController.legacyTransitionTo(ScrimState.UNLOCKED);
mScrimController.setRawPanelExpansionFraction(0.5f);
@@ -1321,6 +1370,7 @@
}
@Test
+ @DisableSceneContainer
public void testCancelsOldAnimationBeforeBlanking() {
mScrimController.legacyTransitionTo(ScrimState.AOD);
finishAnimationsImmediately();
@@ -1333,6 +1383,7 @@
}
@Test
+ @DisableSceneContainer
public void testScrimsAreNotFocusable() {
assertFalse("Behind scrim should not be focusable", mScrimBehind.isFocusable());
assertFalse("Front scrim should not be focusable", mScrimInFront.isFocusable());
@@ -1341,6 +1392,7 @@
}
@Test
+ @DisableSceneContainer
public void testEatsTouchEvent() {
HashSet<ScrimState> eatsTouches =
new HashSet<>(Collections.singletonList(ScrimState.AOD));
@@ -1357,6 +1409,7 @@
}
@Test
+ @DisableSceneContainer
public void testAnimatesTransitionToAod() {
when(mDozeParameters.shouldControlScreenOff()).thenReturn(false);
ScrimState.AOD.prepare(ScrimState.KEYGUARD);
@@ -1371,6 +1424,7 @@
}
@Test
+ @DisableSceneContainer
public void testIsLowPowerMode() {
HashSet<ScrimState> lowPowerModeStates = new HashSet<>(Arrays.asList(
ScrimState.OFF, ScrimState.AOD, ScrimState.PULSING));
@@ -1388,6 +1442,7 @@
}
@Test
+ @DisableSceneContainer
public void testScrimsOpaque_whenShadeFullyExpanded() {
mScrimController.legacyTransitionTo(ScrimState.UNLOCKED);
mScrimController.setRawPanelExpansionFraction(1);
@@ -1402,6 +1457,7 @@
}
@Test
+ @DisableSceneContainer
public void testScrimsVisible_whenShadeVisible() {
mScrimController.setClipsQsScrim(true);
mScrimController.legacyTransitionTo(ScrimState.UNLOCKED);
@@ -1417,6 +1473,7 @@
}
@Test
+ @DisableSceneContainer
public void testDoesntAnimate_whenUnlocking() {
// LightRevealScrim will animate the transition, we should only hide the keyguard scrims.
ScrimState.UNLOCKED.prepare(ScrimState.KEYGUARD);
@@ -1437,6 +1494,7 @@
}
@Test
+ @DisableSceneContainer
public void testScrimsVisible_whenShadeVisible_clippingQs() {
mScrimController.setClipsQsScrim(true);
mScrimController.legacyTransitionTo(ScrimState.UNLOCKED);
@@ -1452,6 +1510,7 @@
}
@Test
+ @DisableSceneContainer
public void testScrimsVisible_whenShadeVisibleOnLockscreen() {
mScrimController.legacyTransitionTo(ScrimState.KEYGUARD);
mScrimController.setQsPosition(0.25f, 300);
@@ -1463,6 +1522,7 @@
}
@Test
+ @DisableSceneContainer
public void testNotificationScrimTransparent_whenOnLockscreen() {
mScrimController.legacyTransitionTo(ScrimState.KEYGUARD);
// even if shade is not pulled down, panel has expansion of 1 on the lockscreen
@@ -1475,6 +1535,7 @@
}
@Test
+ @DisableSceneContainer
public void testNotificationScrimVisible_afterOpeningShadeFromLockscreen() {
mScrimController.setRawPanelExpansionFraction(1);
mScrimController.legacyTransitionTo(SHADE_LOCKED);
@@ -1486,6 +1547,7 @@
}
@Test
+ @DisableSceneContainer
public void qsExpansion_BehindTint_shadeLocked_bouncerActive_usesBouncerProgress() {
when(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).thenReturn(true);
// clipping doesn't change tested logic but allows to assert scrims more in line with
@@ -1502,6 +1564,7 @@
}
@Test
+ @DisableSceneContainer
public void expansionNotificationAlpha_shadeLocked_bouncerActive_usesBouncerInterpolator() {
when(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).thenReturn(true);
@@ -1518,6 +1581,7 @@
}
@Test
+ @DisableSceneContainer
public void expansionNotificationAlpha_shadeLocked_bouncerNotActive_usesShadeInterpolator() {
when(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).thenReturn(false);
@@ -1533,6 +1597,7 @@
}
@Test
+ @DisableSceneContainer
public void notificationAlpha_unnocclusionAnimating_bouncerNotActive_usesKeyguardNotifAlpha() {
when(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).thenReturn(false);
@@ -1552,6 +1617,7 @@
}
@Test
+ @DisableSceneContainer
public void notificationAlpha_inKeyguardState_bouncerActive_usesInvertedBouncerInterpolator() {
when(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).thenReturn(true);
mScrimController.setClipsQsScrim(true);
@@ -1572,6 +1638,7 @@
}
@Test
+ @DisableSceneContainer
public void notificationAlpha_inKeyguardState_bouncerNotActive_usesInvertedShadeInterpolator() {
when(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).thenReturn(false);
mScrimController.setClipsQsScrim(true);
@@ -1592,6 +1659,7 @@
}
@Test
+ @DisableSceneContainer
public void behindTint_inKeyguardState_bouncerNotActive_usesKeyguardBehindTint() {
when(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).thenReturn(false);
mScrimController.setClipsQsScrim(false);
@@ -1603,6 +1671,7 @@
}
@Test
+ @DisableSceneContainer
public void testNotificationTransparency_followsTransitionToFullShade() {
mScrimController.setClipsQsScrim(true);
@@ -1644,6 +1713,7 @@
}
@Test
+ @DisableSceneContainer
public void notificationTransparency_followsNotificationScrimProgress() {
mScrimController.legacyTransitionTo(SHADE_LOCKED);
mScrimController.setRawPanelExpansionFraction(1.0f);
@@ -1660,6 +1730,7 @@
}
@Test
+ @DisableSceneContainer
public void notificationAlpha_qsNotClipped_alphaMatchesNotificationExpansionProgress() {
mScrimController.setClipsQsScrim(false);
mScrimController.legacyTransitionTo(ScrimState.KEYGUARD);
@@ -1695,6 +1766,7 @@
}
@Test
+ @DisableSceneContainer
public void setNotificationsOverScrollAmount_setsTranslationYOnNotificationsScrim() {
int overScrollAmount = 10;
@@ -1704,6 +1776,7 @@
}
@Test
+ @DisableSceneContainer
public void setNotificationsOverScrollAmount_doesNotSetTranslationYOnBehindScrim() {
int overScrollAmount = 10;
@@ -1713,6 +1786,7 @@
}
@Test
+ @DisableSceneContainer
public void setNotificationsOverScrollAmount_doesNotSetTranslationYOnFrontScrim() {
int overScrollAmount = 10;
@@ -1722,6 +1796,7 @@
}
@Test
+ @DisableSceneContainer
public void notificationBoundsTopGetsPassedToKeyguard() {
mScrimController.legacyTransitionTo(SHADE_LOCKED);
mScrimController.setQsPosition(1f, 0);
@@ -1732,6 +1807,7 @@
}
@Test
+ @DisableSceneContainer
public void notificationBoundsTopDoesNotGetPassedToKeyguardWhenNotifScrimIsNotVisible() {
mScrimController.setKeyguardOccluded(true);
mScrimController.legacyTransitionTo(ScrimState.KEYGUARD);
@@ -1742,6 +1818,7 @@
}
@Test
+ @DisableSceneContainer
public void transitionToDreaming() {
mScrimController.setRawPanelExpansionFraction(0f);
mScrimController.setBouncerHiddenFraction(KeyguardBouncerConstants.EXPANSION_HIDDEN);
@@ -1761,6 +1838,7 @@
}
@Test
+ @DisableSceneContainer
public void keyguardGoingAwayUpdateScrims() {
when(mKeyguardStateController.isKeyguardGoingAway()).thenReturn(true);
mScrimController.updateScrims();
@@ -1770,6 +1848,7 @@
@Test
+ @DisableSceneContainer
public void setUnOccludingAnimationKeyguard() {
mScrimController.legacyTransitionTo(ScrimState.KEYGUARD);
finishAnimationsImmediately();
@@ -1784,6 +1863,7 @@
}
@Test
+ @DisableSceneContainer
public void testHidesScrimFlickerInActivity() {
mScrimController.setKeyguardOccluded(true);
mScrimController.legacyTransitionTo(ScrimState.KEYGUARD);
@@ -1802,6 +1882,7 @@
}
@Test
+ @DisableSceneContainer
public void notificationAlpha_inKeyguardState_bouncerNotActive_clipsQsScrimFalse() {
mScrimController.setClipsQsScrim(false);
mScrimController.legacyTransitionTo(ScrimState.KEYGUARD);
@@ -1811,6 +1892,7 @@
}
@Test
+ @DisableSceneContainer
public void aodStateSetsFrontScrimToNotBlend() {
mScrimController.legacyTransitionTo(ScrimState.AOD);
assertFalse("Front scrim should not blend with main color",
@@ -1818,6 +1900,7 @@
}
@Test
+ @DisableSceneContainer
public void applyState_unlocked_bouncerShowing() {
mScrimController.legacyTransitionTo(ScrimState.UNLOCKED);
mScrimController.setBouncerHiddenFraction(0.99f);
@@ -1827,6 +1910,7 @@
}
@Test
+ @DisableSceneContainer
public void ignoreTransitionRequestWhileKeyguardTransitionRunning() {
mScrimController.legacyTransitionTo(ScrimState.UNLOCKED);
mScrimController.mBouncerToGoneTransition.accept(
@@ -1839,6 +1923,7 @@
}
@Test
+ @DisableSceneContainer
public void primaryBouncerToGoneOnFinishCallsKeyguardFadedAway() {
when(mKeyguardStateController.isKeyguardFadingAway()).thenReturn(true);
mScrimController.mBouncerToGoneTransition.accept(
@@ -1849,6 +1934,7 @@
}
@Test
+ @DisableSceneContainer
public void primaryBouncerToGoneOnFinishCallsLightBarController() {
reset(mLightBarController);
mScrimController.mBouncerToGoneTransition.accept(
@@ -1860,6 +1946,7 @@
}
@Test
+ @DisableSceneContainer
public void testDoNotAnimateChangeIfOccludeAnimationPlaying() {
mScrimController.setOccludeAnimationPlaying(true);
mScrimController.legacyTransitionTo(ScrimState.UNLOCKED);
@@ -1868,6 +1955,7 @@
}
@Test
+ @DisableSceneContainer
public void testNotifScrimAlpha_1f_afterUnlockFinishedAndExpanded() {
mScrimController.legacyTransitionTo(ScrimState.KEYGUARD);
when(mKeyguardUnlockAnimationController.isPlayingCannedUnlockAnimation()).thenReturn(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
index 2f30b74..3190d3a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
@@ -91,11 +91,11 @@
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
import com.android.systemui.statusbar.notification.data.repository.NotificationLaunchAnimationRepository;
import com.android.systemui.statusbar.notification.domain.interactor.NotificationLaunchAnimationInteractor;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
import com.android.systemui.statusbar.notification.row.OnUserInteractionCallback;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
-import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.time.FakeSystemClock;
@@ -122,7 +122,6 @@
@TestableLooper.RunWithLooper(setAsMainLooper = true)
public class StatusBarNotificationActivityStarterTest extends SysuiTestCase {
- private static final int DISPLAY_ID = 0;
private final FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
@Mock
@@ -233,7 +232,6 @@
mNotificationActivityStarter =
new StatusBarNotificationActivityStarter(
getContext(),
- DISPLAY_ID,
mHandler,
mUiBgExecutor,
mVisibilityProvider,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorKosmos.kt
index fcdda9f..9da8e80 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorKosmos.kt
@@ -19,6 +19,7 @@
import android.service.dream.dreamManager
import com.android.systemui.communal.domain.interactor.communalInteractor
import com.android.systemui.communal.domain.interactor.communalSceneInteractor
+import com.android.systemui.communal.domain.interactor.communalSettingsInteractor
import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
import com.android.systemui.kosmos.Kosmos
@@ -44,5 +45,6 @@
deviceEntryInteractor = deviceEntryInteractor,
wakeToGoneInteractor = keyguardWakeDirectlyToGoneInteractor,
dreamManager = dreamManager,
+ communalSettingsInteractor = communalSettingsInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorKosmos.kt
index d995b86..0f7366d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorKosmos.kt
@@ -38,7 +38,6 @@
bgDispatcher = testDispatcher,
mainDispatcher = testDispatcher,
keyguardInteractor = keyguardInteractor,
- glanceableHubTransitions = glanceableHubTransitions,
communalInteractor = communalInteractor,
communalSceneInteractor = communalSceneInteractor,
communalSettingsInteractor = communalSettingsInteractor,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractorKosmos.kt
index 494f08b..bf72e48 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractorKosmos.kt
@@ -31,7 +31,6 @@
scope = applicationCoroutineScope,
mainDispatcher = testDispatcher,
bgDispatcher = testDispatcher,
- glanceableHubTransitions = glanceableHubTransitions,
communalSettingsInteractor = communalSettingsInteractor,
keyguardInteractor = keyguardInteractor,
transitionRepository = keyguardTransitionRepository,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorKosmos.kt
index ff7a06c..985044c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorKosmos.kt
@@ -39,7 +39,6 @@
keyguardInteractor = keyguardInteractor,
shadeRepository = shadeRepository,
powerInteractor = powerInteractor,
- glanceableHubTransitions = glanceableHubTransitions,
communalSettingsInteractor = communalSettingsInteractor,
swipeToDismissInteractor = swipeToDismissInteractor,
keyguardOcclusionInteractor = keyguardOcclusionInteractor,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitionsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitionsKosmos.kt
deleted file mode 100644
index a45b269..0000000
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitionsKosmos.kt
+++ /dev/null
@@ -1,30 +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.keyguard.domain.interactor
-
-import com.android.systemui.communal.domain.interactor.communalInteractor
-import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
-import com.android.systemui.kosmos.Kosmos
-
-val Kosmos.glanceableHubTransitions by
- Kosmos.Fixture {
- GlanceableHubTransitions(
- transitionRepository = keyguardTransitionRepository,
- transitionInteractor = keyguardTransitionInteractor,
- communalInteractor = communalInteractor,
- )
- }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt
index b255b51..0443329 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt
@@ -57,12 +57,10 @@
var Kosmos.testScope by Fixture { TestScope(testDispatcher) }
var Kosmos.backgroundScope by Fixture { testScope.backgroundScope }
-var Kosmos.applicationCoroutineScope by Fixture { backgroundScope }
+var Kosmos.applicationCoroutineScope by Fixture { testScope.backgroundScope }
var Kosmos.testCase: SysuiTestCase by Fixture()
-var Kosmos.backgroundCoroutineContext: CoroutineContext by Fixture {
- backgroundScope.coroutineContext
-}
-var Kosmos.mainCoroutineContext: CoroutineContext by Fixture { testScope.coroutineContext }
+var Kosmos.backgroundCoroutineContext: CoroutineContext by Fixture { testDispatcher }
+var Kosmos.mainCoroutineContext: CoroutineContext by Fixture { testDispatcher }
/**
* Run this test body with a [Kosmos] as receiver, and using the [testScope] currently installed in
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
index 446e106..60b371a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
@@ -90,6 +90,7 @@
import com.android.systemui.statusbar.ui.viewmodel.keyguardStatusBarViewModel
import com.android.systemui.util.time.systemClock
import com.android.systemui.volume.domain.interactor.volumeDialogInteractor
+import com.android.systemui.window.domain.interactor.windowRootViewBlurInteractor
/**
* Helper for using [Kosmos] from Java.
@@ -192,4 +193,5 @@
val disableFlagsInteractor by lazy { kosmos.disableFlagsInteractor }
val fakeDisableFlagsRepository by lazy { kosmos.fakeDisableFlagsRepository }
val mockWindowRootViewProvider by lazy { kosmos.mockWindowRootViewProvider }
+ val windowRootViewBlurInteractor by lazy { kosmos.windowRootViewBlurInteractor }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeTileSpecRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeTileSpecRepository.kt
index 5fc31f8..f287114 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeTileSpecRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeTileSpecRepository.kt
@@ -19,6 +19,7 @@
import com.android.systemui.qs.pipeline.data.model.RestoreData
import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository.Companion.POSITION_AT_END
import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.pipeline.shared.TilesUpgradePath
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
@@ -79,9 +80,9 @@
with(getFlow(userId)) { value = defaultTilesRepository.defaultTiles }
}
- override val tilesReadFromSetting: Channel<Pair<Set<TileSpec>, Int>> = Channel(capacity = 10)
+ override val tilesUpgradePath: Channel<Pair<TilesUpgradePath, Int>> = Channel(capacity = 10)
- suspend fun sendTilesReadFromSetting(tiles: Set<TileSpec>, userId: Int) {
- tilesReadFromSetting.send(tiles to userId)
+ suspend fun sendTilesFromUpgradePath(upgradePath: TilesUpgradePath, userId: Int) {
+ tilesUpgradePath.send(upgradePath to userId)
}
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/QSPipelineRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/QSPipelineRepositoryKosmos.kt
index 5ff44e5..c5de02a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/QSPipelineRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/QSPipelineRepositoryKosmos.kt
@@ -26,7 +26,7 @@
Kosmos.Fixture { fakeMinimumTilesRepository }
var Kosmos.fakeDefaultTilesRepository by Kosmos.Fixture { FakeDefaultTilesRepository() }
-val Kosmos.defaultTilesRepository: DefaultTilesRepository by
+var Kosmos.defaultTilesRepository: DefaultTilesRepository by
Kosmos.Fixture { fakeDefaultTilesRepository }
val Kosmos.fakeTileSpecRepository by
diff --git a/core/java/com/android/internal/app/IAppOpsCallback.aidl b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/ConversationNotificationManagerKosmos.kt
similarity index 60%
copy from core/java/com/android/internal/app/IAppOpsCallback.aidl
copy to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/ConversationNotificationManagerKosmos.kt
index 3a9525c..af53ae56 100644
--- a/core/java/com/android/internal/app/IAppOpsCallback.aidl
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/ConversationNotificationManagerKosmos.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013 The Android Open Source Project
+ * Copyright (C) 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,10 +14,10 @@
* limitations under the License.
*/
-package com.android.internal.app;
+package com.android.systemui.statusbar.notification
-// This interface is also used by native code, so must
-// be kept in sync with frameworks/native/libs/permission/include/binder/IAppOpsCallback.h
-oneway interface IAppOpsCallback {
- void opChanged(int op, int uid, String packageName, String persistentDeviceId);
-}
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
+
+val Kosmos.conversationNotificationManager: ConversationNotificationManager by
+ Kosmos.Fixture { mock<ConversationNotificationManager>() }
diff --git a/core/java/com/android/internal/app/IAppOpsCallback.aidl b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderLoggerKosmos.kt
similarity index 61%
copy from core/java/com/android/internal/app/IAppOpsCallback.aidl
copy to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderLoggerKosmos.kt
index 3a9525c..cc40222 100644
--- a/core/java/com/android/internal/app/IAppOpsCallback.aidl
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderLoggerKosmos.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013 The Android Open Source Project
+ * Copyright (C) 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,10 +14,10 @@
* limitations under the License.
*/
-package com.android.internal.app;
+package com.android.systemui.statusbar.notification.row
-// This interface is also used by native code, so must
-// be kept in sync with frameworks/native/libs/permission/include/binder/IAppOpsCallback.h
-oneway interface IAppOpsCallback {
- void opChanged(int op, int uid, String packageName, String persistentDeviceId);
-}
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
+
+val Kosmos.notificationRowContentBinderLogger: NotificationRowContentBinderLogger by
+ Kosmos.Fixture { mock<NotificationRowContentBinderLogger>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterKosmos.kt
index 0d6ac44..d787e2c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterKosmos.kt
@@ -52,7 +52,6 @@
Kosmos.Fixture {
StatusBarNotificationActivityStarter(
applicationContext,
- applicationContext.displayId,
fakeExecutorHandler,
fakeExecutor,
notificationVisibilityProvider,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/battery/ui/viewmodel/BatteryViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/battery/ui/viewmodel/BatteryViewModelKosmos.kt
index c6cf006..7dd0103 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/battery/ui/viewmodel/BatteryViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/battery/ui/viewmodel/BatteryViewModelKosmos.kt
@@ -22,3 +22,10 @@
val Kosmos.batteryViewModel by
Kosmos.Fixture { BatteryViewModel(batteryInteractor, testableContext) }
+
+val Kosmos.batteryViewModelFactory by
+ Kosmos.Fixture {
+ object : BatteryViewModel.Factory {
+ override fun create(): BatteryViewModel = batteryViewModel
+ }
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelKosmos.kt
index bc29dba..fbada93 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelKosmos.kt
@@ -35,6 +35,7 @@
import com.android.systemui.statusbar.notification.stack.domain.interactor.headsUpNotificationInteractor
import com.android.systemui.statusbar.phone.domain.interactor.darkIconInteractor
import com.android.systemui.statusbar.phone.domain.interactor.lightsOutInteractor
+import com.android.systemui.statusbar.pipeline.battery.ui.viewmodel.batteryViewModelFactory
import com.android.systemui.statusbar.pipeline.shared.domain.interactor.homeStatusBarIconBlockListInteractor
import com.android.systemui.statusbar.pipeline.shared.domain.interactor.homeStatusBarInteractor
@@ -42,6 +43,7 @@
Kosmos.Fixture {
HomeStatusBarViewModelImpl(
testableContext.displayId,
+ batteryViewModelFactory,
tableLogBufferFactory,
homeStatusBarInteractor,
homeStatusBarIconBlockListInteractor,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogVisibilityInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogVisibilityInteractorKosmos.kt
index 0d2aa4c..888b7e6 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogVisibilityInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogVisibilityInteractorKosmos.kt
@@ -19,6 +19,7 @@
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.plugins.volumeDialogController
+import com.android.systemui.shared.settings.data.repository.secureSettingsRepository
import com.android.systemui.volume.dialog.data.repository.volumeDialogVisibilityRepository
import com.android.systemui.volume.dialog.utils.volumeTracer
@@ -30,5 +31,6 @@
volumeTracer,
volumeDialogVisibilityRepository,
volumeDialogController,
+ secureSettingsRepository,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/window/data/repository/WindowRootViewBlurRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/window/data/repository/WindowRootViewBlurRepositoryKosmos.kt
index 7281e03..9699223 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/window/data/repository/WindowRootViewBlurRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/window/data/repository/WindowRootViewBlurRepositoryKosmos.kt
@@ -17,5 +17,16 @@
package com.android.systemui.window.data.repository
import com.android.systemui.kosmos.Kosmos
+import kotlinx.coroutines.flow.MutableStateFlow
-val Kosmos.windowRootViewBlurRepository by Kosmos.Fixture { WindowRootViewBlurRepository() }
+val Kosmos.fakeWindowRootViewBlurRepository: FakeWindowRootViewBlurRepository by
+ Kosmos.Fixture { FakeWindowRootViewBlurRepository() }
+
+val Kosmos.windowRootViewBlurRepository: WindowRootViewBlurRepository by
+ Kosmos.Fixture { fakeWindowRootViewBlurRepository }
+
+class FakeWindowRootViewBlurRepository : WindowRootViewBlurRepository {
+ override val blurRadius: MutableStateFlow<Int> = MutableStateFlow(0)
+ override val isBlurOpaque: MutableStateFlow<Boolean> = MutableStateFlow(false)
+ override val isBlurSupported: MutableStateFlow<Boolean> = MutableStateFlow(false)
+}
diff --git a/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java
index db8441d..5283df5 100644
--- a/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java
+++ b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java
@@ -17,11 +17,16 @@
package com.android.server.accessibility.autoclick;
import static android.view.MotionEvent.BUTTON_PRIMARY;
+import static android.view.MotionEvent.BUTTON_SECONDARY;
import static android.view.accessibility.AccessibilityManager.AUTOCLICK_CURSOR_AREA_SIZE_DEFAULT;
import static android.view.accessibility.AccessibilityManager.AUTOCLICK_DELAY_DEFAULT;
import static android.view.accessibility.AccessibilityManager.AUTOCLICK_IGNORE_MINOR_CURSOR_MOVEMENT_DEFAULT;
import static com.android.server.accessibility.autoclick.AutoclickIndicatorView.SHOW_INDICATOR_DELAY_TIME;
+import static com.android.server.accessibility.autoclick.AutoclickTypePanel.AUTOCLICK_TYPE_LEFT_CLICK;
+import static com.android.server.accessibility.autoclick.AutoclickTypePanel.AUTOCLICK_TYPE_RIGHT_CLICK;
+import static com.android.server.accessibility.autoclick.AutoclickTypePanel.AutoclickType;
+import static com.android.server.accessibility.autoclick.AutoclickTypePanel.ClickPanelControllerInterface;
import android.accessibilityservice.AccessibilityTrace;
import android.annotation.NonNull;
@@ -84,6 +89,23 @@
@VisibleForTesting AutoclickTypePanel mAutoclickTypePanel;
private WindowManager mWindowManager;
+ // Default click type is left-click.
+ private @AutoclickType int mActiveClickType = AUTOCLICK_TYPE_LEFT_CLICK;
+
+ @VisibleForTesting
+ final ClickPanelControllerInterface clickPanelController =
+ new ClickPanelControllerInterface() {
+ @Override
+ public void handleAutoclickTypeChange(@AutoclickType int clickType) {
+ mActiveClickType = clickType;
+ }
+
+ @Override
+ public void toggleAutoclickPause() {
+ // TODO(b/388872274): allows users to pause the autoclick.
+ }
+ };
+
public AutoclickController(Context context, int userId, AccessibilityTraceManager trace) {
mTrace = trace;
mContext = context;
@@ -124,7 +146,8 @@
mAutoclickIndicatorView = new AutoclickIndicatorView(mContext);
mWindowManager = mContext.getSystemService(WindowManager.class);
- mAutoclickTypePanel = new AutoclickTypePanel(mContext, mWindowManager);
+ mAutoclickTypePanel =
+ new AutoclickTypePanel(mContext, mWindowManager, clickPanelController);
mAutoclickTypePanel.show();
mWindowManager.addView(mAutoclickIndicatorView, mAutoclickIndicatorView.getLayoutParams());
@@ -644,6 +667,15 @@
final long now = SystemClock.uptimeMillis();
+ // TODO(b/395094903): always triggers left-click when the cursor hovers over the
+ // autoclick type panel, to always allow users to change a different click type.
+ // Otherwise, if one chooses the right-click, this user won't be able to rely on
+ // autoclick to select other click types.
+ final int actionButton =
+ mActiveClickType == AUTOCLICK_TYPE_RIGHT_CLICK
+ ? BUTTON_SECONDARY
+ : BUTTON_PRIMARY;
+
MotionEvent downEvent =
MotionEvent.obtain(
/* downTime= */ now,
@@ -653,7 +685,7 @@
mTempPointerProperties,
mTempPointerCoords,
mMetaState,
- BUTTON_PRIMARY,
+ actionButton,
/* xPrecision= */ 1.0f,
/* yPrecision= */ 1.0f,
mLastMotionEvent.getDeviceId(),
@@ -663,11 +695,11 @@
MotionEvent pressEvent = MotionEvent.obtain(downEvent);
pressEvent.setAction(MotionEvent.ACTION_BUTTON_PRESS);
- pressEvent.setActionButton(BUTTON_PRIMARY);
+ pressEvent.setActionButton(actionButton);
MotionEvent releaseEvent = MotionEvent.obtain(downEvent);
releaseEvent.setAction(MotionEvent.ACTION_BUTTON_RELEASE);
- releaseEvent.setActionButton(BUTTON_PRIMARY);
+ releaseEvent.setActionButton(actionButton);
releaseEvent.setButtonState(0);
MotionEvent upEvent = MotionEvent.obtain(downEvent);
diff --git a/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickIndicatorView.java b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickIndicatorView.java
index 557e1b2..01f359f 100644
--- a/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickIndicatorView.java
+++ b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickIndicatorView.java
@@ -34,6 +34,8 @@
import androidx.annotation.VisibleForTesting;
+import com.android.internal.R;
+
// A visual indicator for the autoclick feature.
public class AutoclickIndicatorView extends View {
private static final String TAG = AutoclickIndicatorView.class.getSimpleName();
@@ -67,8 +69,7 @@
super(context);
mPaint = new Paint();
- // TODO(b/383901288): update styling once determined by UX.
- mPaint.setARGB(255, 52, 103, 235);
+ mPaint.setColor(context.getColor(R.color.materialColorPrimary));
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(10);
diff --git a/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickTypePanel.java b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickTypePanel.java
index 2ef11f4..cf928e2 100644
--- a/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickTypePanel.java
+++ b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickTypePanel.java
@@ -18,6 +18,7 @@
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+import android.annotation.IntDef;
import android.content.Context;
import android.graphics.PixelFormat;
import android.graphics.drawable.Drawable;
@@ -39,12 +40,40 @@
private final String TAG = AutoclickTypePanel.class.getSimpleName();
+ public static final int AUTOCLICK_TYPE_LEFT_CLICK = 0;
+ public static final int AUTOCLICK_TYPE_RIGHT_CLICK = 1;
+ public static final int AUTOCLICK_TYPE_DOUBLE_CLICK = 2;
+ public static final int AUTOCLICK_TYPE_DRAG = 3;
+ public static final int AUTOCLICK_TYPE_SCROLL = 4;
+
+ // Types of click the AutoclickTypePanel supports.
+ @IntDef({
+ AUTOCLICK_TYPE_LEFT_CLICK,
+ AUTOCLICK_TYPE_RIGHT_CLICK,
+ AUTOCLICK_TYPE_DOUBLE_CLICK,
+ AUTOCLICK_TYPE_DRAG,
+ AUTOCLICK_TYPE_SCROLL,
+ })
+ public @interface AutoclickType {}
+
+ // An interface exposed to {@link AutoclickController) to handle different actions on the panel,
+ // including changing autoclick type, pausing/resuming autoclick.
+ public interface ClickPanelControllerInterface {
+ // Allows users to change a different autoclick type.
+ void handleAutoclickTypeChange(@AutoclickType int clickType);
+
+ // Allows users to pause/resume the autoclick.
+ void toggleAutoclickPause();
+ }
+
private final Context mContext;
private final View mContentView;
private final WindowManager mWindowManager;
+ private final ClickPanelControllerInterface mClickPanelController;
+
// Whether the panel is expanded or not.
private boolean mExpanded = false;
@@ -56,9 +85,13 @@
private LinearLayout mSelectedButton;
- public AutoclickTypePanel(Context context, WindowManager windowManager) {
+ public AutoclickTypePanel(
+ Context context,
+ WindowManager windowManager,
+ ClickPanelControllerInterface clickPanelController) {
mContext = context;
mWindowManager = windowManager;
+ mClickPanelController = clickPanelController;
mContentView =
LayoutInflater.from(context)
@@ -76,26 +109,35 @@
}
private void initializeButtonState() {
- mLeftClickButton.setOnClickListener(v -> togglePanelExpansion(mLeftClickButton));
- mRightClickButton.setOnClickListener(v -> togglePanelExpansion(mRightClickButton));
- mDoubleClickButton.setOnClickListener(v -> togglePanelExpansion(mDoubleClickButton));
- mScrollButton.setOnClickListener(v -> togglePanelExpansion(mScrollButton));
- mDragButton.setOnClickListener(v -> togglePanelExpansion(mDragButton));
+ mLeftClickButton.setOnClickListener(v -> togglePanelExpansion(AUTOCLICK_TYPE_LEFT_CLICK));
+ mRightClickButton.setOnClickListener(v -> togglePanelExpansion(AUTOCLICK_TYPE_RIGHT_CLICK));
+ mDoubleClickButton.setOnClickListener(
+ v -> togglePanelExpansion(AUTOCLICK_TYPE_DOUBLE_CLICK));
+ mScrollButton.setOnClickListener(v -> togglePanelExpansion(AUTOCLICK_TYPE_SCROLL));
+ mDragButton.setOnClickListener(v -> togglePanelExpansion(AUTOCLICK_TYPE_DRAG));
+
+ // TODO(b/388872274): registers listener for pause button and allows users to pause/resume
+ // the autoclick.
+ // TODO(b/388847771): registers listener for position button and allows users to move the
+ // panel to a different position.
// Initializes panel as collapsed state and only displays the left click button.
hideAllClickTypeButtons();
mLeftClickButton.setVisibility(View.VISIBLE);
- setSelectedButton(/* selectedButton= */ mLeftClickButton);
+ setSelectedClickType(AUTOCLICK_TYPE_LEFT_CLICK);
}
/** Sets the selected button and updates the newly and previously selected button styling. */
- private void setSelectedButton(@NonNull LinearLayout selectedButton) {
+ private void setSelectedClickType(@AutoclickType int clickType) {
+ final LinearLayout selectedButton = getButtonFromClickType(clickType);
+
// Updates the previously selected button styling.
if (mSelectedButton != null) {
toggleSelectedButtonStyle(mSelectedButton, /* isSelected= */ false);
}
mSelectedButton = selectedButton;
+ mClickPanelController.handleAutoclickTypeChange(clickType);
// Updates the newly selected button styling.
toggleSelectedButtonStyle(selectedButton, /* isSelected= */ true);
@@ -130,7 +172,9 @@
}
/** Toggles the panel expanded or collapsed state. */
- private void togglePanelExpansion(LinearLayout button) {
+ private void togglePanelExpansion(@AutoclickType int clickType) {
+ final LinearLayout button = getButtonFromClickType(clickType);
+
if (mExpanded) {
// If the panel is already in expanded state, we should collapse it by hiding all
// buttons except the one user selected.
@@ -138,7 +182,7 @@
button.setVisibility(View.VISIBLE);
// Sets the newly selected button.
- setSelectedButton(/* selectedButton= */ button);
+ setSelectedClickType(clickType);
} else {
// If the panel is already collapsed, we just need to expand it.
showAllClickTypeButtons();
@@ -166,6 +210,17 @@
mScrollButton.setVisibility(View.VISIBLE);
}
+ private LinearLayout getButtonFromClickType(@AutoclickType int clickType) {
+ return switch (clickType) {
+ case AUTOCLICK_TYPE_LEFT_CLICK -> mLeftClickButton;
+ case AUTOCLICK_TYPE_RIGHT_CLICK -> mRightClickButton;
+ case AUTOCLICK_TYPE_DOUBLE_CLICK -> mDoubleClickButton;
+ case AUTOCLICK_TYPE_DRAG -> mDragButton;
+ case AUTOCLICK_TYPE_SCROLL -> mScrollButton;
+ default -> throw new IllegalArgumentException("Unknown clickType " + clickType);
+ };
+ }
+
@VisibleForTesting
boolean getExpansionStateForTesting() {
return mExpanded;
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 414db37..05301fd 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -588,10 +588,10 @@
@Override
@EnforcePermission(DELIVER_COMPANION_MESSAGES)
public void attachSystemDataTransport(String packageName, int userId, int associationId,
- ParcelFileDescriptor fd) {
+ ParcelFileDescriptor fd, int flags) {
attachSystemDataTransport_enforcePermission();
- mTransportManager.attachSystemDataTransport(associationId, fd);
+ mTransportManager.attachSystemDataTransport(associationId, fd, flags);
}
@Override
diff --git a/services/companion/java/com/android/server/companion/securechannel/AttestationVerifier.java b/services/companion/java/com/android/server/companion/securechannel/AttestationVerifier.java
index df3071e..42af059 100644
--- a/services/companion/java/com/android/server/companion/securechannel/AttestationVerifier.java
+++ b/services/companion/java/com/android/server/companion/securechannel/AttestationVerifier.java
@@ -16,7 +16,9 @@
package com.android.server.companion.securechannel;
+import static android.companion.CompanionDeviceManager.TRANSPORT_FLAG_EXTEND_PATCH_DIFF;
import static android.security.attestationverification.AttestationVerificationManager.PARAM_CHALLENGE;
+import static android.security.attestationverification.AttestationVerificationManager.PARAM_MAX_PATCH_LEVEL_DIFF_MONTHS;
import static android.security.attestationverification.AttestationVerificationManager.PROFILE_PEER_DEVICE;
import static android.security.attestationverification.AttestationVerificationManager.TYPE_CHALLENGE;
@@ -34,15 +36,21 @@
/**
* Helper class to perform attestation verification synchronously.
+ *
+ * @hide
*/
public class AttestationVerifier {
private static final long ATTESTATION_VERIFICATION_TIMEOUT_SECONDS = 10; // 10 seconds
private static final String PARAM_OWNED_BY_SYSTEM = "android.key_owned_by_system";
- private final Context mContext;
+ private static final int EXTENDED_PATCH_LEVEL_DIFF_MONTHS = 24; // 2 years
- AttestationVerifier(Context context) {
+ private final Context mContext;
+ private final int mFlags;
+
+ AttestationVerifier(Context context, int flags) {
this.mContext = context;
+ this.mFlags = flags;
}
/**
@@ -59,10 +67,13 @@
@NonNull byte[] remoteAttestation,
@NonNull byte[] attestationChallenge
) throws SecureChannelException {
- Bundle requirements = new Bundle();
+ final Bundle requirements = new Bundle();
requirements.putByteArray(PARAM_CHALLENGE, attestationChallenge);
requirements.putBoolean(PARAM_OWNED_BY_SYSTEM, true); // Custom parameter for CDM
+ // Apply flags to verifier requirements
+ updateRequirements(requirements);
+
// Synchronously execute attestation verification.
AtomicInteger verificationResult = new AtomicInteger(0);
CountDownLatch verificationFinished = new CountDownLatch(1);
@@ -96,4 +107,15 @@
return verificationResult.get();
}
+
+ private void updateRequirements(Bundle requirements) {
+ if (mFlags == 0) {
+ return;
+ }
+
+ if ((mFlags & TRANSPORT_FLAG_EXTEND_PATCH_DIFF) > 0) {
+ requirements.putInt(PARAM_MAX_PATCH_LEVEL_DIFF_MONTHS,
+ EXTENDED_PATCH_LEVEL_DIFF_MONTHS);
+ }
+ }
}
diff --git a/services/companion/java/com/android/server/companion/securechannel/SecureChannel.java b/services/companion/java/com/android/server/companion/securechannel/SecureChannel.java
index 2d3782f..6c7c9b3 100644
--- a/services/companion/java/com/android/server/companion/securechannel/SecureChannel.java
+++ b/services/companion/java/com/android/server/companion/securechannel/SecureChannel.java
@@ -59,6 +59,7 @@
private final Callback mCallback;
private final byte[] mPreSharedKey;
private final AttestationVerifier mVerifier;
+ private final int mFlags;
private volatile boolean mStopped;
private volatile boolean mInProgress;
@@ -89,7 +90,7 @@
@NonNull Callback callback,
@NonNull byte[] preSharedKey
) {
- this(in, out, callback, preSharedKey, null);
+ this(in, out, callback, preSharedKey, null, 0);
}
/**
@@ -100,14 +101,16 @@
* @param out output stream from which data is sent out
* @param callback subscription to received messages from the channel
* @param context context for fetching the Attestation Verifier Framework system service
+ * @param flags flags for custom security settings on the channel
*/
public SecureChannel(
@NonNull final InputStream in,
@NonNull final OutputStream out,
@NonNull Callback callback,
- @NonNull Context context
+ @NonNull Context context,
+ int flags
) {
- this(in, out, callback, null, new AttestationVerifier(context));
+ this(in, out, callback, null, new AttestationVerifier(context, flags), flags);
}
public SecureChannel(
@@ -115,13 +118,15 @@
final OutputStream out,
Callback callback,
byte[] preSharedKey,
- AttestationVerifier verifier
+ AttestationVerifier verifier,
+ int flags
) {
this.mInput = in;
this.mOutput = out;
this.mCallback = callback;
this.mPreSharedKey = preSharedKey;
this.mVerifier = verifier;
+ this.mFlags = flags;
}
/**
diff --git a/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java b/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java
index 3608360..92d9fb0 100644
--- a/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java
+++ b/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java
@@ -16,7 +16,11 @@
package com.android.server.companion.transport;
+import static android.companion.AssociationRequest.DEVICE_PROFILE_WEARABLE_SENSING;
import static android.companion.CompanionDeviceManager.MESSAGE_REQUEST_PERMISSION_RESTORE;
+import static android.companion.CompanionDeviceManager.TRANSPORT_FLAG_EXTEND_PATCH_DIFF;
+
+import static com.android.server.companion.transport.TransportUtils.enforceAssociationCanUseTransportFlags;
import android.annotation.NonNull;
import android.annotation.SuppressLint;
@@ -152,10 +156,14 @@
/**
* Attach transport.
*/
- public void attachSystemDataTransport(int associationId, ParcelFileDescriptor fd) {
+ public void attachSystemDataTransport(int associationId, ParcelFileDescriptor fd,
+ int flags) {
Slog.i(TAG, "Attaching transport for association id=[" + associationId + "]...");
- mAssociationStore.getAssociationWithCallerChecks(associationId);
+ AssociationInfo association =
+ mAssociationStore.getAssociationWithCallerChecks(associationId);
+
+ enforceAssociationCanUseTransportFlags(association, flags);
synchronized (mTransports) {
if (mTransports.contains(associationId)) {
@@ -163,7 +171,7 @@
}
// TODO: Implement new API to pass a PSK
- initializeTransport(associationId, fd, null);
+ initializeTransport(association, fd, null, flags);
notifyOnTransportsChanged();
}
@@ -217,10 +225,12 @@
}
}
- private void initializeTransport(int associationId,
+ private void initializeTransport(AssociationInfo association,
ParcelFileDescriptor fd,
- byte[] preSharedKey) {
+ byte[] preSharedKey,
+ int flags) {
Slog.i(TAG, "Initializing transport");
+ int associationId = association.getId();
Transport transport;
if (!isSecureTransportEnabled()) {
// If secure transport is explicitly disabled for testing, use raw transport
@@ -230,15 +240,21 @@
// If device is debug build, use hardcoded test key for authentication
Slog.d(TAG, "Creating an unauthenticated secure channel");
final byte[] testKey = "CDM".getBytes(StandardCharsets.UTF_8);
- transport = new SecureTransport(associationId, fd, mContext, testKey, null);
+ transport = new SecureTransport(associationId, fd, mContext, testKey, null, 0);
} else if (preSharedKey != null) {
// If either device is not Android, then use app-specific pre-shared key
Slog.d(TAG, "Creating a PSK-authenticated secure channel");
- transport = new SecureTransport(associationId, fd, mContext, preSharedKey, null);
+ transport = new SecureTransport(associationId, fd, mContext, preSharedKey, null, 0);
+ } else if (DEVICE_PROFILE_WEARABLE_SENSING.equals(association.getDeviceProfile())) {
+ // If device is glasses with WEARABLE_SENSING profile, extend the allowed patch
+ // difference to 2 years instead of 1.
+ Slog.d(TAG, "Creating a secure channel with extended patch difference allowance");
+ transport = new SecureTransport(associationId, fd, mContext,
+ TRANSPORT_FLAG_EXTEND_PATCH_DIFF);
} else {
// If none of the above applies, then use secure channel with attestation verification
Slog.d(TAG, "Creating a secure channel");
- transport = new SecureTransport(associationId, fd, mContext);
+ transport = new SecureTransport(associationId, fd, mContext, flags);
}
addMessageListenersToTransport(transport);
diff --git a/services/companion/java/com/android/server/companion/transport/SecureTransport.java b/services/companion/java/com/android/server/companion/transport/SecureTransport.java
index 1e95e65..77dc809 100644
--- a/services/companion/java/com/android/server/companion/transport/SecureTransport.java
+++ b/services/companion/java/com/android/server/companion/transport/SecureTransport.java
@@ -36,15 +36,22 @@
private final BlockingQueue<byte[]> mRequestQueue = new ArrayBlockingQueue<>(500);
- SecureTransport(int associationId, ParcelFileDescriptor fd, Context context) {
+ SecureTransport(int associationId, ParcelFileDescriptor fd, Context context, int flags) {
super(associationId, fd, context);
- mSecureChannel = new SecureChannel(mRemoteIn, mRemoteOut, this, context);
+ mSecureChannel = new SecureChannel(mRemoteIn, mRemoteOut, this, context, flags);
}
SecureTransport(int associationId, ParcelFileDescriptor fd, Context context,
- byte[] preSharedKey, AttestationVerifier verifier) {
+ byte[] preSharedKey, AttestationVerifier verifier, int flags) {
super(associationId, fd, context);
- mSecureChannel = new SecureChannel(mRemoteIn, mRemoteOut, this, preSharedKey, verifier);
+ mSecureChannel = new SecureChannel(
+ mRemoteIn,
+ mRemoteOut,
+ this,
+ preSharedKey,
+ verifier,
+ flags
+ );
}
@Override
diff --git a/services/companion/java/com/android/server/companion/transport/TransportUtils.java b/services/companion/java/com/android/server/companion/transport/TransportUtils.java
new file mode 100644
index 0000000..7a15c11
--- /dev/null
+++ b/services/companion/java/com/android/server/companion/transport/TransportUtils.java
@@ -0,0 +1,77 @@
+/*
+ * 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.companion.transport;
+
+import static android.companion.AssociationRequest.DEVICE_PROFILE_WEARABLE_SENSING;
+import static android.companion.CompanionDeviceManager.TRANSPORT_FLAG_EXTEND_PATCH_DIFF;
+
+import static java.util.Collections.unmodifiableMap;
+
+import android.companion.AssociationInfo;
+import android.util.ArrayMap;
+
+import java.util.Map;
+
+/**
+ * Utility class for transport manager.
+ * @hide
+ */
+public final class TransportUtils {
+
+ /**
+ * Device profile -> Union of allowlisted transport flags
+ */
+ private static final Map<String, Integer> DEVICE_PROFILE_TRANSPORT_FLAGS_ALLOWLIST;
+ static {
+ final Map<String, Integer> map = new ArrayMap<>();
+ map.put(DEVICE_PROFILE_WEARABLE_SENSING,
+ TRANSPORT_FLAG_EXTEND_PATCH_DIFF);
+ DEVICE_PROFILE_TRANSPORT_FLAGS_ALLOWLIST = unmodifiableMap(map);
+ }
+
+ /**
+ * Enforce that the association that is trying to attach a transport with provided flags has
+ * one of the allowlisted device profiles that may apply the flagged features.
+ *
+ * @param association Association for which transport is being attached
+ * @param flags Flags for features being applied to the transport
+ */
+ public static void enforceAssociationCanUseTransportFlags(
+ AssociationInfo association, int flags) {
+ if (flags == 0) {
+ return;
+ }
+
+ final String deviceProfile = association.getDeviceProfile();
+ if (!DEVICE_PROFILE_TRANSPORT_FLAGS_ALLOWLIST.containsKey(deviceProfile)) {
+ throw new IllegalArgumentException("Association (id=" + association.getId()
+ + ") with device profile " + deviceProfile + " does not support the "
+ + "usage of transport flags.");
+ }
+
+ int allowedFlags = DEVICE_PROFILE_TRANSPORT_FLAGS_ALLOWLIST.get(deviceProfile);
+
+ // Ensure that every non-zero bits in flags are also present in allowed flags
+ if ((allowedFlags & flags) != flags) {
+ throw new IllegalArgumentException("Association (id=" + association.getId()
+ + ") does not have the device profile required to use at least "
+ + "one of the flags in this transport.");
+ }
+ }
+
+ private TransportUtils() {}
+}
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index c633830..f1007e7 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -186,9 +186,28 @@
"core_libraries",
"crumpet",
"dck_framework",
+ "desktop_apps",
+ "desktop_better_together",
+ "desktop_bsp",
+ "desktop_camera",
"desktop_connectivity",
+ "desktop_display",
+ "desktop_commercial",
+ "desktop_firmware",
+ "desktop_graphics",
"desktop_hwsec",
+ "desktop_input",
+ "desktop_kernel",
+ "desktop_ml",
+ "desktop_serviceability",
+ "desktop_oobe",
+ "desktop_peripherals",
+ "desktop_pnp",
+ "desktop_security",
"desktop_stats",
+ "desktop_sysui",
+ "desktop_users_and_accounts",
+ "desktop_video",
"desktop_wifi",
"devoptions_settings",
"game",
diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java
index c0a97db..7672011 100644
--- a/services/core/java/com/android/server/app/GameManagerService.java
+++ b/services/core/java/com/android/server/app/GameManagerService.java
@@ -361,6 +361,14 @@
case POPULATE_GAME_MODE_SETTINGS: {
removeEqualMessages(POPULATE_GAME_MODE_SETTINGS, msg.obj);
final int userId = (int) msg.obj;
+ synchronized (mLock) {
+ if (!mSettings.containsKey(userId)) {
+ GameManagerSettings userSettings = new GameManagerSettings(
+ Environment.getDataSystemDeDirectory(userId));
+ mSettings.put(userId, userSettings);
+ userSettings.readPersistentDataLocked();
+ }
+ }
final String[] packageNames = getInstalledGamePackageNames(userId);
updateConfigsForUser(userId, false /*checkGamePackage*/, packageNames);
break;
@@ -990,8 +998,7 @@
@Override
public void onUserStarting(@NonNull TargetUser user) {
Slog.d(TAG, "Starting user " + user.getUserIdentifier());
- mService.onUserStarting(user,
- Environment.getDataSystemDeDirectory(user.getUserIdentifier()));
+ mService.onUserStarting(user, /*settingDataDirOverride*/ null);
}
@Override
@@ -1596,13 +1603,16 @@
}
}
- void onUserStarting(@NonNull TargetUser user, File settingDataDir) {
+ void onUserStarting(@NonNull TargetUser user, File settingDataDirOverride) {
final int userId = user.getUserIdentifier();
- synchronized (mLock) {
- if (!mSettings.containsKey(userId)) {
- GameManagerSettings userSettings = new GameManagerSettings(settingDataDir);
- mSettings.put(userId, userSettings);
- userSettings.readPersistentDataLocked();
+ if (settingDataDirOverride != null) {
+ synchronized (mLock) {
+ if (!mSettings.containsKey(userId)) {
+ GameManagerSettings userSettings = new GameManagerSettings(
+ settingDataDirOverride);
+ mSettings.put(userId, userSettings);
+ userSettings.readPersistentDataLocked();
+ }
}
}
sendUserMessage(userId, POPULATE_GAME_MODE_SETTINGS, EVENT_ON_USER_STARTING,
diff --git a/services/core/java/com/android/server/app/GameManagerSettings.java b/services/core/java/com/android/server/app/GameManagerSettings.java
index c57a1f7..fd4bf2f 100644
--- a/services/core/java/com/android/server/app/GameManagerSettings.java
+++ b/services/core/java/com/android/server/app/GameManagerSettings.java
@@ -221,9 +221,7 @@
return false;
}
- try {
- final FileInputStream str = mSettingsFile.openRead();
-
+ try (FileInputStream str = mSettingsFile.openRead()) {
final TypedXmlPullParser parser = Xml.resolvePullParser(str);
int type;
while ((type = parser.next()) != XmlPullParser.START_TAG
@@ -251,7 +249,6 @@
+ type);
}
}
- str.close();
} catch (XmlPullParserException | java.io.IOException e) {
Slog.wtf(TAG, "Error reading game manager settings", e);
return false;
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 6f79f70..86871ea 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -12598,7 +12598,7 @@
//==========================================================================================
static final int LOG_NB_EVENTS_LIFECYCLE = 20;
static final int LOG_NB_EVENTS_PHONE_STATE = 20;
- static final int LOG_NB_EVENTS_DEVICE_CONNECTION = 50;
+ static final int LOG_NB_EVENTS_DEVICE_CONNECTION = 200;
static final int LOG_NB_EVENTS_FORCE_USE = 20;
static final int LOG_NB_EVENTS_VOLUME = 100;
static final int LOG_NB_EVENTS_DYN_POLICY = 10;
@@ -15082,11 +15082,13 @@
final String key = "additional_output_device_delay";
final String reply = AudioSystem.getParameters(
key + "=" + device.getInternalType() + "," + device.getAddress());
- long delayMillis;
- try {
- delayMillis = Long.parseLong(reply.substring(key.length() + 1));
- } catch (NullPointerException e) {
- delayMillis = 0;
+ long delayMillis = 0;
+ if (reply.contains(key)) {
+ try {
+ delayMillis = Long.parseLong(reply.substring(key.length() + 1));
+ } catch (NullPointerException e) {
+ delayMillis = 0;
+ }
}
return delayMillis;
}
@@ -15112,11 +15114,13 @@
final String key = "max_additional_output_device_delay";
final String reply = AudioSystem.getParameters(
key + "=" + device.getInternalType() + "," + device.getAddress());
- long delayMillis;
- try {
- delayMillis = Long.parseLong(reply.substring(key.length() + 1));
- } catch (NullPointerException e) {
- delayMillis = 0;
+ long delayMillis = 0;
+ if (reply.contains(key)) {
+ try {
+ delayMillis = Long.parseLong(reply.substring(key.length() + 1));
+ } catch (NullPointerException e) {
+ delayMillis = 0;
+ }
}
return delayMillis;
}
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 a1e8f08..aab2760 100644
--- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
+++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
@@ -122,11 +122,6 @@
Flags.FLAG_ALWAYS_ROTATE_DISPLAY_DEVICE,
Flags::alwaysRotateDisplayDevice);
- private final FlagState mRefreshRateVotingTelemetry = new FlagState(
- Flags.FLAG_REFRESH_RATE_VOTING_TELEMETRY,
- Flags::refreshRateVotingTelemetry
- );
-
private final FlagState mPixelAnisotropyCorrectionEnabled = new FlagState(
Flags.FLAG_ENABLE_PIXEL_ANISOTROPY_CORRECTION,
Flags::enablePixelAnisotropyCorrection
@@ -403,10 +398,6 @@
return mAlwaysRotateDisplayDevice.isEnabled();
}
- public boolean isRefreshRateVotingTelemetryEnabled() {
- return mRefreshRateVotingTelemetry.isEnabled();
- }
-
public boolean isPixelAnisotropyCorrectionInLogicalDisplayEnabled() {
return mPixelAnisotropyCorrectionEnabled.isEnabled();
}
@@ -626,7 +617,6 @@
pw.println(" " + mAutoBrightnessModesFlagState);
pw.println(" " + mFastHdrTransitions);
pw.println(" " + mAlwaysRotateDisplayDevice);
- pw.println(" " + mRefreshRateVotingTelemetry);
pw.println(" " + mPixelAnisotropyCorrectionEnabled);
pw.println(" " + mSensorBasedBrightnessThrottling);
pw.println(" " + mIdleScreenRefreshRateTimeout);
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 cc0bbde..8211feb 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
@@ -191,14 +191,6 @@
}
flag {
- name: "refresh_rate_voting_telemetry"
- namespace: "display_manager"
- description: "Feature flag for enabling telemetry for refresh rate voting in DisplayManager"
- bug: "310029108"
- is_fixed_read_only: true
-}
-
-flag {
name: "enable_pixel_anisotropy_correction"
namespace: "display_manager"
description: "Feature flag for enabling display anisotropy correction through LogicalDisplay upscaling"
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 1dd4a9b..c37733b 100644
--- a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
@@ -229,8 +229,7 @@
mContext = context;
mHandler = new DisplayModeDirectorHandler(handler.getLooper());
mInjector = injector;
- mVotesStatsReporter = injector.getVotesStatsReporter(
- displayManagerFlags.isRefreshRateVotingTelemetryEnabled());
+ mVotesStatsReporter = injector.getVotesStatsReporter();
mSupportedModesByDisplay = new SparseArray<>();
mAppSupportedModesByDisplay = new SparseArray<>();
mDefaultModeByDisplay = new SparseArray<>();
@@ -3141,7 +3140,7 @@
SensorManagerInternal getSensorManagerInternal();
@Nullable
- VotesStatsReporter getVotesStatsReporter(boolean refreshRateVotingTelemetryEnabled);
+ VotesStatsReporter getVotesStatsReporter();
}
@VisibleForTesting
@@ -3281,10 +3280,9 @@
}
@Override
- public VotesStatsReporter getVotesStatsReporter(boolean refreshRateVotingTelemetryEnabled) {
+ public VotesStatsReporter getVotesStatsReporter() {
// if frame rate override supported, renderRates will be ignored in mode selection
- return new VotesStatsReporter(supportsFrameRateOverride(),
- refreshRateVotingTelemetryEnabled);
+ return new VotesStatsReporter(supportsFrameRateOverride());
}
private DisplayManager getDisplayManager() {
diff --git a/services/core/java/com/android/server/display/mode/VotesStatsReporter.java b/services/core/java/com/android/server/display/mode/VotesStatsReporter.java
index 7562a52..7b579c0 100644
--- a/services/core/java/com/android/server/display/mode/VotesStatsReporter.java
+++ b/services/core/java/com/android/server/display/mode/VotesStatsReporter.java
@@ -25,6 +25,7 @@
import android.annotation.Nullable;
import android.os.Trace;
import android.util.SparseArray;
+import android.util.SparseIntArray;
import android.view.Display;
import com.android.internal.util.FrameworkStatsLog;
@@ -36,13 +37,11 @@
private static final String TAG = "VotesStatsReporter";
private static final int REFRESH_RATE_NOT_LIMITED = 1000;
private final boolean mIgnoredRenderRate;
- private final boolean mFrameworkStatsLogReportingEnabled;
- private int mLastMinPriorityReported = Vote.MAX_PRIORITY + 1;
+ private final SparseIntArray mLastMinPriorityByDisplay = new SparseIntArray();
- public VotesStatsReporter(boolean ignoreRenderRate, boolean refreshRateVotingTelemetryEnabled) {
+ VotesStatsReporter(boolean ignoreRenderRate) {
mIgnoredRenderRate = ignoreRenderRate;
- mFrameworkStatsLogReportingEnabled = refreshRateVotingTelemetryEnabled;
}
void reportVoteChanged(int displayId, int priority, @Nullable Vote vote) {
@@ -57,32 +56,27 @@
int maxRefreshRate = getMaxRefreshRate(vote, mIgnoredRenderRate);
Trace.traceCounter(Trace.TRACE_TAG_POWER,
TAG + "." + displayId + ":" + Vote.priorityToString(priority), maxRefreshRate);
- if (mFrameworkStatsLogReportingEnabled) {
- FrameworkStatsLog.write(
- DISPLAY_MODE_DIRECTOR_VOTE_CHANGED, displayId, priority,
- DISPLAY_MODE_DIRECTOR_VOTE_CHANGED__VOTE_STATUS__STATUS_ADDED,
- maxRefreshRate, -1);
- }
+ FrameworkStatsLog.write(
+ DISPLAY_MODE_DIRECTOR_VOTE_CHANGED, displayId, priority,
+ DISPLAY_MODE_DIRECTOR_VOTE_CHANGED__VOTE_STATUS__STATUS_ADDED,
+ maxRefreshRate, -1);
}
private void reportVoteRemoved(int displayId, int priority) {
Trace.traceCounter(Trace.TRACE_TAG_POWER,
TAG + "." + displayId + ":" + Vote.priorityToString(priority), -1);
- if (mFrameworkStatsLogReportingEnabled) {
- FrameworkStatsLog.write(
- DISPLAY_MODE_DIRECTOR_VOTE_CHANGED, displayId, priority,
- DISPLAY_MODE_DIRECTOR_VOTE_CHANGED__VOTE_STATUS__STATUS_REMOVED, -1, -1);
- }
+ FrameworkStatsLog.write(
+ DISPLAY_MODE_DIRECTOR_VOTE_CHANGED, displayId, priority,
+ DISPLAY_MODE_DIRECTOR_VOTE_CHANGED__VOTE_STATUS__STATUS_REMOVED, -1, -1);
}
void reportVotesActivated(int displayId, int minPriority, @Nullable Display.Mode baseMode,
SparseArray<Vote> votes) {
- if (!mFrameworkStatsLogReportingEnabled) {
- return;
- }
+ int lastMinPriorityReported = mLastMinPriorityByDisplay.get(
+ displayId, Vote.MAX_PRIORITY + 1);
int selectedRefreshRate = baseMode != null ? (int) baseMode.getRefreshRate() : -1;
for (int priority = Vote.MIN_PRIORITY; priority <= Vote.MAX_PRIORITY; priority++) {
- if (priority < mLastMinPriorityReported && priority < minPriority) {
+ if (priority < lastMinPriorityReported && priority < minPriority) {
continue;
}
Vote vote = votes.get(priority);
@@ -91,7 +85,7 @@
}
// Was previously reported ACTIVE, changed to ADDED
- if (priority >= mLastMinPriorityReported && priority < minPriority) {
+ if (priority >= lastMinPriorityReported && priority < minPriority) {
int maxRefreshRate = getMaxRefreshRate(vote, mIgnoredRenderRate);
FrameworkStatsLog.write(
DISPLAY_MODE_DIRECTOR_VOTE_CHANGED, displayId, priority,
@@ -99,7 +93,7 @@
maxRefreshRate, selectedRefreshRate);
}
// Was previously reported ADDED, changed to ACTIVE
- if (priority >= minPriority && priority < mLastMinPriorityReported) {
+ if (priority >= minPriority && priority < lastMinPriorityReported) {
int maxRefreshRate = getMaxRefreshRate(vote, mIgnoredRenderRate);
FrameworkStatsLog.write(
DISPLAY_MODE_DIRECTOR_VOTE_CHANGED, displayId, priority,
@@ -107,7 +101,7 @@
maxRefreshRate, selectedRefreshRate);
}
- mLastMinPriorityReported = minPriority;
+ mLastMinPriorityByDisplay.put(displayId, minPriority);
}
}
diff --git a/services/core/java/com/android/server/flags/services.aconfig b/services/core/java/com/android/server/flags/services.aconfig
index 4505d0e..7e5c1bc 100644
--- a/services/core/java/com/android/server/flags/services.aconfig
+++ b/services/core/java/com/android/server/flags/services.aconfig
@@ -86,3 +86,10 @@
description: "Enable the time notifications feature, a toggle to enable/disable time-related notifications in Date & Time Settings"
bug: "283267917"
}
+
+flag {
+ name: "certpininstaller_removal"
+ namespace: "network_security"
+ description: "Remove CertPinInstallReceiver from the platform"
+ bug: "391205997"
+}
diff --git a/services/core/java/com/android/server/input/InputGestureManager.java b/services/core/java/com/android/server/input/InputGestureManager.java
index 108afba..977c029 100644
--- a/services/core/java/com/android/server/input/InputGestureManager.java
+++ b/services/core/java/com/android/server/input/InputGestureManager.java
@@ -256,13 +256,11 @@
));
}
if (keyboardA11yShortcutControl()) {
- if (InputSettings.isAccessibilityBounceKeysFeatureEnabled()) {
- systemShortcuts.add(createKeyGesture(
- KeyEvent.KEYCODE_3,
- KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
- KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_BOUNCE_KEYS
- ));
- }
+ systemShortcuts.add(createKeyGesture(
+ KeyEvent.KEYCODE_3,
+ KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
+ KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_BOUNCE_KEYS
+ ));
if (InputSettings.isAccessibilityMouseKeysFeatureFlagEnabled()) {
systemShortcuts.add(createKeyGesture(
KeyEvent.KEYCODE_4,
@@ -270,20 +268,16 @@
KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MOUSE_KEYS
));
}
- if (InputSettings.isAccessibilityStickyKeysFeatureEnabled()) {
- systemShortcuts.add(createKeyGesture(
- KeyEvent.KEYCODE_5,
- KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
- KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_STICKY_KEYS
- ));
- }
- if (InputSettings.isAccessibilitySlowKeysFeatureFlagEnabled()) {
- systemShortcuts.add(createKeyGesture(
- KeyEvent.KEYCODE_6,
- KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
- KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_SLOW_KEYS
- ));
- }
+ systemShortcuts.add(createKeyGesture(
+ KeyEvent.KEYCODE_5,
+ KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
+ KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_STICKY_KEYS
+ ));
+ systemShortcuts.add(createKeyGesture(
+ KeyEvent.KEYCODE_6,
+ KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
+ KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_SLOW_KEYS
+ ));
}
synchronized (mGestureLock) {
for (InputGestureData systemShortcut : systemShortcuts) {
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 4a5f4a1..8624f42 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -2775,7 +2775,7 @@
}
return true;
case KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_BOUNCE_KEYS:
- if (complete && InputSettings.isAccessibilityBounceKeysFeatureEnabled()) {
+ if (complete) {
final boolean bounceKeysEnabled =
InputSettings.isAccessibilityBounceKeysEnabled(mContext);
InputSettings.setAccessibilityBounceKeysThreshold(mContext,
@@ -2793,7 +2793,7 @@
}
break;
case KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_STICKY_KEYS:
- if (complete && InputSettings.isAccessibilityStickyKeysFeatureEnabled()) {
+ if (complete) {
final boolean stickyKeysEnabled =
InputSettings.isAccessibilityStickyKeysEnabled(mContext);
InputSettings.setAccessibilityStickyKeysEnabled(mContext, !stickyKeysEnabled);
@@ -2801,7 +2801,7 @@
}
break;
case KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_SLOW_KEYS:
- if (complete && InputSettings.isAccessibilitySlowKeysFeatureFlagEnabled()) {
+ if (complete) {
final boolean slowKeysEnabled =
InputSettings.isAccessibilitySlowKeysEnabled(mContext);
InputSettings.setAccessibilitySlowKeysThreshold(mContext,
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java b/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java
index ddace17..2ea6117 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java
@@ -43,7 +43,6 @@
import java.util.Collection;
import java.util.Optional;
-import java.util.concurrent.atomic.AtomicBoolean;
/**
* A class that represents a broker for the endpoint registered by the client app. This class
@@ -89,8 +88,11 @@
/** The remote callback interface for this endpoint. */
private final IContextHubEndpointCallback mContextHubEndpointCallback;
- /** True if this endpoint is registered with the service. */
- private AtomicBoolean mIsRegistered = new AtomicBoolean(true);
+ /** True if this endpoint is registered with the service/HAL. */
+ @GuardedBy("mRegistrationLock")
+ private boolean mIsRegistered = false;
+
+ private final Object mRegistrationLock = new Object();
private final Object mOpenSessionLock = new Object();
@@ -192,7 +194,7 @@
public int openSession(HubEndpointInfo destination, String serviceDescriptor)
throws RemoteException {
super.openSession_enforcePermission();
- if (!mIsRegistered.get()) throw new IllegalStateException("Endpoint is not registered");
+ if (!isRegistered()) throw new IllegalStateException("Endpoint is not registered");
if (!hasEndpointPermissions(destination)) {
throw new SecurityException(
"Insufficient permission to open a session with endpoint: " + destination);
@@ -223,7 +225,7 @@
@android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
public void closeSession(int sessionId, int reason) throws RemoteException {
super.closeSession_enforcePermission();
- if (!mIsRegistered.get()) throw new IllegalStateException("Endpoint is not registered");
+ if (!isRegistered()) throw new IllegalStateException("Endpoint is not registered");
if (!cleanupSessionResources(sessionId)) {
throw new IllegalArgumentException(
"Unknown session ID in closeSession: id=" + sessionId);
@@ -235,19 +237,26 @@
@android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
public void unregister() {
super.unregister_enforcePermission();
- mIsRegistered.set(false);
- try {
- mHubInterface.unregisterEndpoint(mHalEndpointInfo);
- } catch (RemoteException e) {
- Log.e(TAG, "RemoteException while calling HAL unregisterEndpoint", e);
- }
synchronized (mOpenSessionLock) {
// Iterate in reverse since cleanupSessionResources will remove the entry
for (int i = mSessionInfoMap.size() - 1; i >= 0; i--) {
int id = mSessionInfoMap.keyAt(i);
+ halCloseEndpointSessionNoThrow(id, Reason.ENDPOINT_GONE);
cleanupSessionResources(id);
}
}
+ synchronized (mRegistrationLock) {
+ if (!isRegistered()) {
+ Log.w(TAG, "Attempting to unregister when already unregistered");
+ return;
+ }
+ mIsRegistered = false;
+ try {
+ mHubInterface.unregisterEndpoint(mHalEndpointInfo);
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException while calling HAL unregisterEndpoint", e);
+ }
+ }
mEndpointManager.unregisterEndpoint(mEndpointInfo.getIdentifier().getEndpoint());
releaseWakeLockOnExit();
}
@@ -335,7 +344,7 @@
/** Invoked when the underlying binder of this broker has died at the client process. */
@Override
public void binderDied() {
- if (mIsRegistered.get()) {
+ if (isRegistered()) {
unregister();
}
}
@@ -365,6 +374,22 @@
}
}
+ /**
+ * Registers this endpoints with the Context Hub HAL.
+ *
+ * @throws RemoteException if the registrations fails with a RemoteException
+ */
+ /* package */ void register() throws RemoteException {
+ synchronized (mRegistrationLock) {
+ if (isRegistered()) {
+ Log.w(TAG, "Attempting to register when already registered");
+ } else {
+ mHubInterface.registerEndpoint(mHalEndpointInfo);
+ mIsRegistered = true;
+ }
+ }
+ }
+
/* package */ void attachDeathRecipient() throws RemoteException {
if (mContextHubEndpointCallback != null) {
mContextHubEndpointCallback.asBinder().linkToDeath(this, 0 /* flags */);
@@ -425,6 +450,24 @@
}
}
+ /* package */ void onHalRestart() {
+ synchronized (mRegistrationLock) {
+ mIsRegistered = false;
+ try {
+ register();
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException while calling HAL registerEndpoint", e);
+ }
+ }
+ synchronized (mOpenSessionLock) {
+ for (int i = mSessionInfoMap.size() - 1; i >= 0; i--) {
+ int id = mSessionInfoMap.keyAt(i);
+ onCloseEndpointSession(id, Reason.HUB_RESET);
+ }
+ }
+ // TODO(b/390029594): Cancel any ongoing reliable communication transactions
+ }
+
private Optional<Byte> onEndpointSessionOpenRequestInternal(
int sessionId, HubEndpointInfo initiator, String serviceDescriptor) {
if (!hasEndpointPermissions(initiator)) {
@@ -465,24 +508,21 @@
}
remote = mSessionInfoMap.get(sessionId).getRemoteEndpointInfo();
}
- if (!ContextHubServiceUtil.notePermissions(
- mAppOpsManager,
- mUid,
- mPackageName,
- mAttributionTag,
- remote.getRequiredPermissions(),
- RECEIVE_MSG_NOTE
- + "-0x"
- + Long.toHexString(remote.getIdentifier().getHub())
- + "-0x"
- + Long.toHexString(remote.getIdentifier().getEndpoint()))) {
- Log.e(
- TAG,
- "Dropping message from "
- + remote
- + ". "
- + mPackageName
- + " doesn't have permission");
+
+ try {
+ Binder.withCleanCallingIdentity(
+ () -> {
+ if (!notePermissions(remote)) {
+ throw new RuntimeException(
+ "Dropping message from "
+ + remote
+ + ". "
+ + mPackageName
+ + " doesn't have permission");
+ }
+ });
+ } catch (RuntimeException e) {
+ Log.e(TAG, e.getMessage());
return ErrorCode.PERMISSION_DENIED;
}
@@ -553,7 +593,7 @@
private void acquireWakeLock() {
Binder.withCleanCallingIdentity(
() -> {
- if (mIsRegistered.get()) {
+ if (isRegistered()) {
mWakeLock.acquire(WAKELOCK_TIMEOUT_MILLIS);
}
});
@@ -608,4 +648,30 @@
}
return true;
}
+
+ private boolean isRegistered() {
+ synchronized (mRegistrationLock) {
+ return mIsRegistered;
+ }
+ }
+
+ /**
+ * Utility to call notePermissions for e.g. when processing a message from a given endpoint for
+ * this broker.
+ *
+ * @param endpoint The endpoint to check permissions for this broker.
+ */
+ private boolean notePermissions(HubEndpointInfo endpoint) {
+ return ContextHubServiceUtil.notePermissions(
+ mAppOpsManager,
+ mUid,
+ mPackageName,
+ mAttributionTag,
+ endpoint.getRequiredPermissions(),
+ RECEIVE_MSG_NOTE
+ + "-0x"
+ + Long.toHexString(endpoint.getIdentifier().getHub())
+ + "-0x"
+ + Long.toHexString(endpoint.getIdentifier().getEndpoint()));
+ }
}
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubEndpointManager.java b/services/core/java/com/android/server/location/contexthub/ContextHubEndpointManager.java
index ed98bf9..06aeb62 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubEndpointManager.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubEndpointManager.java
@@ -206,12 +206,6 @@
EndpointInfo halEndpointInfo =
ContextHubServiceUtil.createHalEndpointInfo(
pendingEndpointInfo, endpointId, SERVICE_HUB_ID);
- try {
- mHubInterface.registerEndpoint(halEndpointInfo);
- } catch (RemoteException e) {
- Log.e(TAG, "RemoteException while calling HAL registerEndpoint", e);
- throw e;
- }
broker =
new ContextHubEndpointBroker(
mContext,
@@ -222,6 +216,7 @@
packageName,
attributionTag,
mTransactionManager);
+ broker.register();
mEndpointMap.put(endpointId, broker);
try {
@@ -282,6 +277,14 @@
mEndpointMap.remove(endpointId);
}
+ /** Invoked by the service when the Context Hub HAL restarts. */
+ /* package */ void onHalRestart() {
+ for (ContextHubEndpointBroker broker : mEndpointMap.values()) {
+ // The broker will close existing sessions and re-register itself
+ broker.onHalRestart();
+ }
+ }
+
@Override
public void onEndpointSessionOpenRequest(
int sessionId,
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubHalEndpointCallback.java b/services/core/java/com/android/server/location/contexthub/ContextHubHalEndpointCallback.java
index 88764b6..a3d9429 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubHalEndpointCallback.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubHalEndpointCallback.java
@@ -21,9 +21,6 @@
import android.hardware.contexthub.IEndpointCallback;
import android.hardware.contexthub.Message;
import android.hardware.contexthub.MessageDeliveryStatus;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Process;
import android.os.RemoteException;
/** IEndpointCallback implementation. */
@@ -32,11 +29,6 @@
private final IEndpointLifecycleCallback mEndpointLifecycleCallback;
private final IEndpointSessionCallback mEndpointSessionCallback;
- // Use this thread in case where the execution requires to be on an async service thread.
- private final HandlerThread mHandlerThread =
- new HandlerThread("Context Hub endpoint callback", Process.THREAD_PRIORITY_BACKGROUND);
- private Handler mHandler;
-
/** Interface for listening for endpoint start and stop events. */
public interface IEndpointLifecycleCallback {
/** Called when a batch of endpoints started. */
@@ -73,9 +65,6 @@
IEndpointSessionCallback endpointSessionCallback) {
mEndpointLifecycleCallback = endpointLifecycleCallback;
mEndpointSessionCallback = endpointSessionCallback;
-
- mHandlerThread.start();
- mHandler = new Handler(mHandlerThread.getLooper());
}
@Override
@@ -88,7 +77,7 @@
for (int i = 0; i < halEndpointInfos.length; i++) {
endpointInfos[i] = new HubEndpointInfo(halEndpointInfos[i]);
}
- mHandler.post(() -> mEndpointLifecycleCallback.onEndpointStarted(endpointInfos));
+ mEndpointLifecycleCallback.onEndpointStarted(endpointInfos);
}
@Override
@@ -98,7 +87,7 @@
for (int i = 0; i < halEndpointIds.length; i++) {
endpointIds[i] = new HubEndpointInfo.HubEndpointIdentifier(halEndpointIds[i]);
}
- mHandler.post(() -> mEndpointLifecycleCallback.onEndpointStopped(endpointIds, reason));
+ mEndpointLifecycleCallback.onEndpointStopped(endpointIds, reason);
}
@Override
@@ -109,37 +98,33 @@
new HubEndpointInfo.HubEndpointIdentifier(destination.hubId, destination.id);
HubEndpointInfo.HubEndpointIdentifier initiatorId =
new HubEndpointInfo.HubEndpointIdentifier(initiator.hubId, initiator.id);
- mHandler.post(
- () ->
- mEndpointSessionCallback.onEndpointSessionOpenRequest(
- sessionId, destinationId, initiatorId, serviceDescriptor));
+ mEndpointSessionCallback.onEndpointSessionOpenRequest(
+ sessionId, destinationId, initiatorId, serviceDescriptor);
}
@Override
public void onCloseEndpointSession(int sessionId, byte reason) throws RemoteException {
- mHandler.post(() -> mEndpointSessionCallback.onCloseEndpointSession(sessionId, reason));
+ mEndpointSessionCallback.onCloseEndpointSession(sessionId, reason);
}
@Override
public void onEndpointSessionOpenComplete(int sessionId) throws RemoteException {
- mHandler.post(() -> mEndpointSessionCallback.onEndpointSessionOpenComplete(sessionId));
+ mEndpointSessionCallback.onEndpointSessionOpenComplete(sessionId);
}
@Override
public void onMessageReceived(int sessionId, Message message) throws RemoteException {
HubMessage hubMessage = ContextHubServiceUtil.createHubMessage(message);
- mHandler.post(() -> mEndpointSessionCallback.onMessageReceived(sessionId, hubMessage));
+ mEndpointSessionCallback.onMessageReceived(sessionId, hubMessage);
}
@Override
public void onMessageDeliveryStatusReceived(
int sessionId, MessageDeliveryStatus messageDeliveryStatus) throws RemoteException {
- mHandler.post(
- () ->
- mEndpointSessionCallback.onMessageDeliveryStatusReceived(
- sessionId,
- messageDeliveryStatus.messageSequenceNumber,
- messageDeliveryStatus.errorCode));
+ mEndpointSessionCallback.onMessageDeliveryStatusReceived(
+ sessionId,
+ messageDeliveryStatus.messageSequenceNumber,
+ messageDeliveryStatus.errorCode);
}
@Override
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubService.java b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
index 2b0ca14..502a7ae 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubService.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
@@ -259,6 +259,9 @@
if (mHubInfoRegistry != null) {
mHubInfoRegistry.onHalRestart();
}
+ if (mEndpointManager != null) {
+ mEndpointManager.onHalRestart();
+ }
resetSettings();
if (Flags.reconnectHostEndpointsAfterHalRestart()) {
mClientManager.forEachClientOfHub(mContextHubId,
diff --git a/services/core/java/com/android/server/notification/NotificationChannelExtractor.java b/services/core/java/com/android/server/notification/NotificationChannelExtractor.java
index e2889fa..18bccd8 100644
--- a/services/core/java/com/android/server/notification/NotificationChannelExtractor.java
+++ b/services/core/java/com/android/server/notification/NotificationChannelExtractor.java
@@ -91,7 +91,7 @@
updateAttributes = true;
}
if (restrictAudioAttributesAlarm()
- && record.getNotification().category != CATEGORY_ALARM
+ && !CATEGORY_ALARM.equals(record.getNotification().category)
&& attributes.getUsage() == AudioAttributes.USAGE_ALARM) {
updateAttributes = true;
}
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 0aaa0fe..76c5240 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -3733,8 +3733,7 @@
}
break;
case KeyEvent.KEYCODE_3:
- if (InputSettings.isAccessibilityBounceKeysFeatureEnabled()
- && keyboardA11yShortcutControl()) {
+ if (keyboardA11yShortcutControl()) {
if (firstDown && event.isMetaPressed()
&& event.isAltPressed()) {
final boolean bounceKeysEnabled =
@@ -3765,8 +3764,7 @@
}
break;
case KeyEvent.KEYCODE_5:
- if (InputSettings.isAccessibilityStickyKeysFeatureEnabled()
- && keyboardA11yShortcutControl()) {
+ if (keyboardA11yShortcutControl()) {
if (firstDown && event.isMetaPressed() && event.isAltPressed()) {
final boolean stickyKeysEnabled =
InputSettings.isAccessibilityStickyKeysEnabled(
@@ -3780,8 +3778,7 @@
}
break;
case KeyEvent.KEYCODE_6:
- if (InputSettings.isAccessibilitySlowKeysFeatureFlagEnabled()
- && keyboardA11yShortcutControl()) {
+ if (keyboardA11yShortcutControl()) {
if (firstDown && event.isMetaPressed() && event.isAltPressed()) {
final boolean slowKeysEnabled =
InputSettings.isAccessibilitySlowKeysEnabled(mContext);
diff --git a/services/core/java/com/android/server/security/AttestationVerificationPeerDeviceVerifier.java b/services/core/java/com/android/server/security/AttestationVerificationPeerDeviceVerifier.java
index dc1f936..f060e4d 100644
--- a/services/core/java/com/android/server/security/AttestationVerificationPeerDeviceVerifier.java
+++ b/services/core/java/com/android/server/security/AttestationVerificationPeerDeviceVerifier.java
@@ -17,8 +17,8 @@
package com.android.server.security;
import static android.security.attestationverification.AttestationVerificationManager.FLAG_FAILURE_BOOT_STATE;
-import static android.security.attestationverification.AttestationVerificationManager.FLAG_FAILURE_KEYSTORE_REQUIREMENTS;
import static android.security.attestationverification.AttestationVerificationManager.FLAG_FAILURE_CERTS;
+import static android.security.attestationverification.AttestationVerificationManager.FLAG_FAILURE_KEYSTORE_REQUIREMENTS;
import static android.security.attestationverification.AttestationVerificationManager.FLAG_FAILURE_LOCAL_BINDING_REQUIREMENTS;
import static android.security.attestationverification.AttestationVerificationManager.FLAG_FAILURE_PATCH_LEVEL_DIFF;
import static android.security.attestationverification.AttestationVerificationManager.FLAG_FAILURE_UNKNOWN;
@@ -47,12 +47,8 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.security.AttestationVerificationManagerService.DumpLogger;
-import org.json.JSONObject;
-
import java.io.ByteArrayInputStream;
import java.io.IOException;
-import java.io.InputStream;
-import java.net.URL;
import java.security.InvalidAlgorithmParameterException;
import java.security.cert.CertPath;
import java.security.cert.CertPathValidator;
@@ -60,7 +56,6 @@
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
-import java.security.cert.PKIXCertPathChecker;
import java.security.cert.PKIXParameters;
import java.security.cert.TrustAnchor;
import java.security.cert.X509Certificate;
@@ -69,7 +64,6 @@
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
@@ -126,6 +120,7 @@
private final LocalDate mTestLocalPatchDate;
private final CertificateFactory mCertificateFactory;
private final CertPathValidator mCertPathValidator;
+ private final CertificateRevocationStatusManager mCertificateRevocationStatusManager;
private final DumpLogger mDumpLogger;
AttestationVerificationPeerDeviceVerifier(@NonNull Context context,
@@ -135,6 +130,7 @@
mCertificateFactory = CertificateFactory.getInstance("X.509");
mCertPathValidator = CertPathValidator.getInstance("PKIX");
mTrustAnchors = getTrustAnchors();
+ mCertificateRevocationStatusManager = new CertificateRevocationStatusManager(mContext);
mRevocationEnabled = true;
mTestSystemDate = null;
mTestLocalPatchDate = null;
@@ -150,6 +146,7 @@
mCertificateFactory = CertificateFactory.getInstance("X.509");
mCertPathValidator = CertPathValidator.getInstance("PKIX");
mTrustAnchors = trustAnchors;
+ mCertificateRevocationStatusManager = new CertificateRevocationStatusManager(mContext);
mRevocationEnabled = revocationEnabled;
mTestSystemDate = systemDate;
mTestLocalPatchDate = localPatchDate;
@@ -300,15 +297,14 @@
CertPath certificatePath = mCertificateFactory.generateCertPath(certificates);
PKIXParameters validationParams = new PKIXParameters(mTrustAnchors);
- if (mRevocationEnabled) {
- // Checks Revocation Status List based on
- // https://developer.android.com/training/articles/security-key-attestation#certificate_status
- PKIXCertPathChecker checker = new AndroidRevocationStatusListChecker();
- validationParams.addCertPathChecker(checker);
- }
// Do not use built-in revocation status checker.
validationParams.setRevocationEnabled(false);
mCertPathValidator.validate(certificatePath, validationParams);
+ if (mRevocationEnabled) {
+ // Checks Revocation Status List based on
+ // https://developer.android.com/training/articles/security-key-attestation#certificate_status
+ mCertificateRevocationStatusManager.checkRevocationStatus(certificates);
+ }
}
private Set<TrustAnchor> getTrustAnchors() throws CertPathValidatorException {
@@ -574,96 +570,6 @@
<= maxPatchLevelDiffMonths;
}
- /**
- * Checks certificate revocation status.
- *
- * Queries status list from android.googleapis.com/attestation/status and checks for
- * the existence of certificate's serial number. If serial number exists in map, then fail.
- */
- private final class AndroidRevocationStatusListChecker extends PKIXCertPathChecker {
- private static final String TOP_LEVEL_JSON_PROPERTY_KEY = "entries";
- private static final String STATUS_PROPERTY_KEY = "status";
- private static final String REASON_PROPERTY_KEY = "reason";
- private String mStatusUrl;
- private JSONObject mJsonStatusMap;
-
- @Override
- public void init(boolean forward) throws CertPathValidatorException {
- mStatusUrl = getRevocationListUrl();
- if (mStatusUrl == null || mStatusUrl.isEmpty()) {
- throw new CertPathValidatorException(
- "R.string.vendor_required_attestation_revocation_list_url is empty.");
- }
- // TODO(b/221067843): Update to only pull status map on non critical path and if
- // out of date (24hrs).
- mJsonStatusMap = getStatusMap(mStatusUrl);
- }
-
- @Override
- public boolean isForwardCheckingSupported() {
- return false;
- }
-
- @Override
- public Set<String> getSupportedExtensions() {
- return null;
- }
-
- @Override
- public void check(Certificate cert, Collection<String> unresolvedCritExts)
- throws CertPathValidatorException {
- X509Certificate x509Certificate = (X509Certificate) cert;
- // The json key is the certificate's serial number converted to lowercase hex.
- String serialNumber = x509Certificate.getSerialNumber().toString(16);
-
- if (serialNumber == null) {
- throw new CertPathValidatorException("Certificate serial number can not be null.");
- }
-
- if (mJsonStatusMap.has(serialNumber)) {
- JSONObject revocationStatus;
- String status;
- String reason;
- try {
- revocationStatus = mJsonStatusMap.getJSONObject(serialNumber);
- status = revocationStatus.getString(STATUS_PROPERTY_KEY);
- reason = revocationStatus.getString(REASON_PROPERTY_KEY);
- } catch (Throwable t) {
- throw new CertPathValidatorException("Unable get properties for certificate "
- + "with serial number " + serialNumber);
- }
- throw new CertPathValidatorException(
- "Invalid certificate with serial number " + serialNumber
- + " has status " + status
- + " because reason " + reason);
- }
- }
-
- private JSONObject getStatusMap(String stringUrl) throws CertPathValidatorException {
- URL url;
- try {
- url = new URL(stringUrl);
- } catch (Throwable t) {
- throw new CertPathValidatorException(
- "Unable to get revocation status from " + mStatusUrl, t);
- }
-
- try (InputStream inputStream = url.openStream()) {
- JSONObject statusListJson = new JSONObject(
- new String(inputStream.readAllBytes(), UTF_8));
- return statusListJson.getJSONObject(TOP_LEVEL_JSON_PROPERTY_KEY);
- } catch (Throwable t) {
- throw new CertPathValidatorException(
- "Unable to parse revocation status from " + mStatusUrl, t);
- }
- }
-
- private String getRevocationListUrl() {
- return mContext.getResources().getString(
- R.string.vendor_required_attestation_revocation_list_url);
- }
- }
-
/* Mutable data class for tracking dump data from verifications. */
private static class MyDumpData extends AttestationVerificationManagerService.DumpData {
diff --git a/services/core/java/com/android/server/security/CertificateRevocationStatusManager.java b/services/core/java/com/android/server/security/CertificateRevocationStatusManager.java
new file mode 100644
index 0000000..d36d9f5
--- /dev/null
+++ b/services/core/java/com/android/server/security/CertificateRevocationStatusManager.java
@@ -0,0 +1,366 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.security;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import android.app.job.JobInfo;
+import android.app.job.JobScheduler;
+import android.content.ComponentName;
+import android.content.Context;
+import android.net.NetworkCapabilities;
+import android.net.NetworkRequest;
+import android.os.Environment;
+import android.os.PersistableBundle;
+import android.util.Slog;
+
+import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.security.cert.CertPathValidatorException;
+import java.security.cert.X509Certificate;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.time.format.DateTimeParseException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/** Manages the revocation status of certificates used in remote attestation. */
+class CertificateRevocationStatusManager {
+ private static final String TAG = "AVF_CRL";
+ // Must be unique within system server
+ private static final int JOB_ID = 1737671340;
+ private static final String REVOCATION_STATUS_FILE_NAME = "certificate_revocation_status.txt";
+ private static final String REVOCATION_STATUS_FILE_FIELD_DELIMITER = ",";
+
+ /**
+ * The number of days since last update for which a stored revocation status can be accepted.
+ */
+ @VisibleForTesting static final int MAX_DAYS_SINCE_LAST_CHECK = 30;
+
+ /**
+ * The number of days since issue date for an intermediary certificate to be considered fresh
+ * and not require a revocation list check.
+ */
+ private static final int FRESH_INTERMEDIARY_CERT_DAYS = 70;
+
+ /**
+ * The expected number of days between a certificate's issue date and notBefore date. Used to
+ * infer a certificate's issue date from its notBefore date.
+ */
+ private static final int DAYS_BETWEEN_ISSUE_AND_NOT_BEFORE_DATES = 2;
+
+ private static final String TOP_LEVEL_JSON_PROPERTY_KEY = "entries";
+ private static final Object sFileLock = new Object();
+
+ private final Context mContext;
+ private final String mTestRemoteRevocationListUrl;
+ private final File mTestRevocationStatusFile;
+ private final boolean mShouldScheduleJob;
+
+ CertificateRevocationStatusManager(Context context) {
+ this(context, null, null, true);
+ }
+
+ @VisibleForTesting
+ CertificateRevocationStatusManager(
+ Context context,
+ String testRemoteRevocationListUrl,
+ File testRevocationStatusFile,
+ boolean shouldScheduleJob) {
+ mContext = context;
+ mTestRemoteRevocationListUrl = testRemoteRevocationListUrl;
+ mTestRevocationStatusFile = testRevocationStatusFile;
+ mShouldScheduleJob = shouldScheduleJob;
+ }
+
+ /**
+ * Check the revocation status of the provided {@link X509Certificate}s.
+ *
+ * <p>The provided certificates should have been validated and ordered from leaf to a
+ * certificate issued by the trust anchor, per the convention specified in the javadoc of {@link
+ * java.security.cert.CertPath}.
+ *
+ * @param certificates List of certificates to be checked
+ * @throws CertPathValidatorException if the check failed
+ */
+ void checkRevocationStatus(List<X509Certificate> certificates)
+ throws CertPathValidatorException {
+ if (!needToCheckRevocationStatus(certificates)) {
+ return;
+ }
+ List<String> serialNumbers = new ArrayList<>();
+ for (X509Certificate certificate : certificates) {
+ String serialNumber = certificate.getSerialNumber().toString(16);
+ if (serialNumber == null) {
+ throw new CertPathValidatorException("Certificate serial number cannot be null.");
+ }
+ serialNumbers.add(serialNumber);
+ }
+ try {
+ JSONObject revocationList = fetchRemoteRevocationList();
+ Map<String, Boolean> areCertificatesRevoked = new HashMap<>();
+ for (String serialNumber : serialNumbers) {
+ areCertificatesRevoked.put(serialNumber, revocationList.has(serialNumber));
+ }
+ updateLastRevocationCheckData(areCertificatesRevoked);
+ for (Map.Entry<String, Boolean> entry : areCertificatesRevoked.entrySet()) {
+ if (entry.getValue()) {
+ throw new CertPathValidatorException(
+ "Certificate " + entry.getKey() + " has been revoked.");
+ }
+ }
+ } catch (IOException | JSONException ex) {
+ Slog.d(TAG, "Fallback to check stored revocation status", ex);
+ if (ex instanceof IOException && mShouldScheduleJob) {
+ scheduleJobToUpdateStoredDataWithRemoteRevocationList(serialNumbers);
+ }
+ for (X509Certificate certificate : certificates) {
+ // Assume recently issued certificates are not revoked.
+ if (isIssuedWithinDays(certificate, MAX_DAYS_SINCE_LAST_CHECK)) {
+ String serialNumber = certificate.getSerialNumber().toString(16);
+ serialNumbers.remove(serialNumber);
+ }
+ }
+ Map<String, LocalDateTime> lastRevocationCheckData;
+ try {
+ lastRevocationCheckData = getLastRevocationCheckData();
+ } catch (IOException ex2) {
+ throw new CertPathValidatorException(
+ "Unable to load stored revocation status", ex2);
+ }
+ for (String serialNumber : serialNumbers) {
+ if (!lastRevocationCheckData.containsKey(serialNumber)
+ || lastRevocationCheckData
+ .get(serialNumber)
+ .isBefore(
+ LocalDateTime.now().minusDays(MAX_DAYS_SINCE_LAST_CHECK))) {
+ throw new CertPathValidatorException(
+ "Unable to verify the revocation status of certificate "
+ + serialNumber);
+ }
+ }
+ }
+ }
+
+ private static boolean needToCheckRevocationStatus(
+ List<X509Certificate> certificatesOrderedLeafFirst) {
+ if (certificatesOrderedLeafFirst.isEmpty()) {
+ return false;
+ }
+ // A certificate isn't revoked when it is first issued, so we treat it as checked on its
+ // issue date.
+ if (!isIssuedWithinDays(certificatesOrderedLeafFirst.get(0), MAX_DAYS_SINCE_LAST_CHECK)) {
+ return true;
+ }
+ for (int i = 1; i < certificatesOrderedLeafFirst.size(); i++) {
+ if (!isIssuedWithinDays(
+ certificatesOrderedLeafFirst.get(i), FRESH_INTERMEDIARY_CERT_DAYS)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static boolean isIssuedWithinDays(X509Certificate certificate, int days) {
+ LocalDate notBeforeDate =
+ LocalDate.ofInstant(certificate.getNotBefore().toInstant(), ZoneId.systemDefault());
+ LocalDate expectedIssueData =
+ notBeforeDate.plusDays(DAYS_BETWEEN_ISSUE_AND_NOT_BEFORE_DATES);
+ return LocalDate.now().minusDays(days + 1).isBefore(expectedIssueData);
+ }
+
+ void updateLastRevocationCheckDataForAllPreviouslySeenCertificates(
+ JSONObject revocationList, Collection<String> otherCertificatesToCheck) {
+ Set<String> allCertificatesToCheck = new HashSet<>(otherCertificatesToCheck);
+ try {
+ allCertificatesToCheck.addAll(getLastRevocationCheckData().keySet());
+ } catch (IOException ex) {
+ Slog.e(TAG, "Unable to update last check date of stored data.", ex);
+ }
+ Map<String, Boolean> areCertificatesRevoked = new HashMap<>();
+ for (String serialNumber : allCertificatesToCheck) {
+ areCertificatesRevoked.put(serialNumber, revocationList.has(serialNumber));
+ }
+ updateLastRevocationCheckData(areCertificatesRevoked);
+ }
+
+ /**
+ * Update the last revocation check data stored on this device.
+ *
+ * @param areCertificatesRevoked A Map whose keys are certificate serial numbers and values are
+ * whether that certificate has been revoked
+ */
+ void updateLastRevocationCheckData(Map<String, Boolean> areCertificatesRevoked) {
+ LocalDateTime now = LocalDateTime.now();
+ synchronized (sFileLock) {
+ Map<String, LocalDateTime> lastRevocationCheckData;
+ try {
+ lastRevocationCheckData = getLastRevocationCheckData();
+ } catch (IOException ex) {
+ Slog.e(TAG, "Unable to updateLastRevocationCheckData", ex);
+ return;
+ }
+ for (Map.Entry<String, Boolean> entry : areCertificatesRevoked.entrySet()) {
+ if (entry.getValue()) {
+ lastRevocationCheckData.remove(entry.getKey());
+ } else {
+ lastRevocationCheckData.put(entry.getKey(), now);
+ }
+ }
+ storeLastRevocationCheckData(lastRevocationCheckData);
+ }
+ }
+
+ Map<String, LocalDateTime> getLastRevocationCheckData() throws IOException {
+ Map<String, LocalDateTime> data = new HashMap<>();
+ File dataFile = getLastRevocationCheckDataFile();
+ synchronized (sFileLock) {
+ if (!dataFile.exists()) {
+ return data;
+ }
+ String dataString;
+ try (FileInputStream in = new FileInputStream(dataFile)) {
+ dataString = new String(in.readAllBytes(), UTF_8);
+ }
+ for (String line : dataString.split(System.lineSeparator())) {
+ String[] elements = line.split(REVOCATION_STATUS_FILE_FIELD_DELIMITER);
+ if (elements.length != 2) {
+ continue;
+ }
+ try {
+ data.put(elements[0], LocalDateTime.parse(elements[1]));
+ } catch (DateTimeParseException ex) {
+ Slog.e(
+ TAG,
+ "Unable to parse last checked LocalDateTime from file. Deleting the"
+ + " potentially corrupted file.",
+ ex);
+ dataFile.delete();
+ return data;
+ }
+ }
+ }
+ return data;
+ }
+
+ @VisibleForTesting
+ void storeLastRevocationCheckData(Map<String, LocalDateTime> lastRevocationCheckData) {
+ StringBuilder dataStringBuilder = new StringBuilder();
+ for (Map.Entry<String, LocalDateTime> entry : lastRevocationCheckData.entrySet()) {
+ dataStringBuilder
+ .append(entry.getKey())
+ .append(REVOCATION_STATUS_FILE_FIELD_DELIMITER)
+ .append(entry.getValue())
+ .append(System.lineSeparator());
+ }
+ synchronized (sFileLock) {
+ try (FileOutputStream fileOutputStream =
+ new FileOutputStream(getLastRevocationCheckDataFile())) {
+ fileOutputStream.write(dataStringBuilder.toString().getBytes(UTF_8));
+ Slog.d(TAG, "Successfully stored revocation status data.");
+ } catch (IOException ex) {
+ Slog.e(TAG, "Failed to store revocation status data.", ex);
+ }
+ }
+ }
+
+ private File getLastRevocationCheckDataFile() {
+ if (mTestRevocationStatusFile != null) {
+ return mTestRevocationStatusFile;
+ }
+ return new File(Environment.getDataSystemDirectory(), REVOCATION_STATUS_FILE_NAME);
+ }
+
+ private void scheduleJobToUpdateStoredDataWithRemoteRevocationList(List<String> serialNumbers) {
+ JobScheduler jobScheduler = mContext.getSystemService(JobScheduler.class);
+ if (jobScheduler == null) {
+ Slog.e(TAG, "Unable to get job scheduler.");
+ return;
+ }
+ Slog.d(TAG, "Scheduling job to fetch remote CRL.");
+ PersistableBundle extras = new PersistableBundle();
+ extras.putStringArray(
+ UpdateCertificateRevocationStatusJobService.EXTRA_KEY_CERTIFICATES_TO_CHECK,
+ serialNumbers.toArray(new String[0]));
+ jobScheduler.schedule(
+ new JobInfo.Builder(
+ JOB_ID,
+ new ComponentName(
+ mContext,
+ UpdateCertificateRevocationStatusJobService.class))
+ .setExtras(extras)
+ .setRequiredNetwork(
+ new NetworkRequest.Builder()
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+ .build())
+ .build());
+ }
+
+ /**
+ * Fetches the revocation list from the URL specified in
+ * R.string.vendor_required_attestation_revocation_list_url
+ *
+ * @return The remote revocation list entries in a JSONObject
+ * @throws CertPathValidatorException if the URL is not defined or is malformed.
+ * @throws IOException if the URL is valid but the fetch failed.
+ * @throws JSONException if the revocation list content cannot be parsed
+ */
+ JSONObject fetchRemoteRevocationList()
+ throws CertPathValidatorException, IOException, JSONException {
+ String urlString = getRemoteRevocationListUrl();
+ if (urlString == null || urlString.isEmpty()) {
+ throw new CertPathValidatorException(
+ "R.string.vendor_required_attestation_revocation_list_url is empty.");
+ }
+ URL url;
+ try {
+ url = new URL(urlString);
+ } catch (MalformedURLException ex) {
+ throw new CertPathValidatorException("Unable to parse the URL " + urlString, ex);
+ }
+ byte[] revocationListBytes;
+ try (InputStream inputStream = url.openStream()) {
+ revocationListBytes = inputStream.readAllBytes();
+ }
+ JSONObject revocationListJson = new JSONObject(new String(revocationListBytes, UTF_8));
+ return revocationListJson.getJSONObject(TOP_LEVEL_JSON_PROPERTY_KEY);
+ }
+
+ private String getRemoteRevocationListUrl() {
+ if (mTestRemoteRevocationListUrl != null) {
+ return mTestRemoteRevocationListUrl;
+ }
+ return mContext.getResources()
+ .getString(R.string.vendor_required_attestation_revocation_list_url);
+ }
+}
diff --git a/services/core/java/com/android/server/security/OWNERS b/services/core/java/com/android/server/security/OWNERS
index fa4bf22..7a31a00 100644
--- a/services/core/java/com/android/server/security/OWNERS
+++ b/services/core/java/com/android/server/security/OWNERS
@@ -3,5 +3,6 @@
include /core/java/android/security/OWNERS
per-file *AttestationVerification* = file:/core/java/android/security/attestationverification/OWNERS
+per-file *CertificateRevocationStatus* = file:/core/java/android/security/attestationverification/OWNERS
per-file FileIntegrity*.java = victorhsieh@google.com
per-file KeyChainSystemService.java = file:platform/packages/apps/KeyChain:/OWNERS
diff --git a/services/core/java/com/android/server/security/UpdateCertificateRevocationStatusJobService.java b/services/core/java/com/android/server/security/UpdateCertificateRevocationStatusJobService.java
new file mode 100644
index 0000000..768c812
--- /dev/null
+++ b/services/core/java/com/android/server/security/UpdateCertificateRevocationStatusJobService.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.security;
+
+import android.app.job.JobParameters;
+import android.app.job.JobService;
+import android.util.Slog;
+
+import org.json.JSONObject;
+
+import java.util.Arrays;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+/** A {@link JobService} that fetches the certificate revocation list from a remote location. */
+public class UpdateCertificateRevocationStatusJobService extends JobService {
+
+ static final String EXTRA_KEY_CERTIFICATES_TO_CHECK =
+ "com.android.server.security.extra.CERTIFICATES_TO_CHECK";
+ private static final String TAG = "AVF_CRL";
+ private ExecutorService mExecutorService;
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ mExecutorService = Executors.newSingleThreadExecutor();
+ }
+
+ @Override
+ public boolean onStartJob(JobParameters params) {
+ mExecutorService.execute(
+ () -> {
+ try {
+ CertificateRevocationStatusManager certificateRevocationStatusManager =
+ new CertificateRevocationStatusManager(this);
+ Slog.d(TAG, "Starting to fetch remote CRL from job service.");
+ JSONObject revocationList =
+ certificateRevocationStatusManager.fetchRemoteRevocationList();
+ String[] certificatesToCheckFromJobParams =
+ params.getExtras().getStringArray(EXTRA_KEY_CERTIFICATES_TO_CHECK);
+ if (certificatesToCheckFromJobParams == null) {
+ Slog.e(TAG, "Extras not found: " + EXTRA_KEY_CERTIFICATES_TO_CHECK);
+ return;
+ }
+ certificateRevocationStatusManager
+ .updateLastRevocationCheckDataForAllPreviouslySeenCertificates(
+ revocationList,
+ Arrays.asList(certificatesToCheckFromJobParams));
+ } catch (Throwable t) {
+ Slog.e(TAG, "Unable to update the stored revocation status.", t);
+ }
+ jobFinished(params, false);
+ });
+ return true;
+ }
+
+ @Override
+ public boolean onStopJob(JobParameters params) {
+ return false;
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ mExecutorService.shutdown();
+ }
+}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 20917ba..cf9c57a 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -113,7 +113,6 @@
import static android.view.WindowManager.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED;
import static android.view.WindowManager.PROPERTY_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING_STATE_SHARING;
import static android.view.WindowManager.TRANSIT_CLOSE;
-import static android.view.WindowManager.TRANSIT_FLAG_OPEN_BEHIND;
import static android.view.WindowManager.TRANSIT_OLD_UNSET;
import static android.view.WindowManager.TRANSIT_RELAUNCH;
import static android.view.WindowManager.hasWindowExtensionsEnabled;
@@ -247,7 +246,6 @@
import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_NORMAL;
import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_WILL_PLACE_SURFACES;
import static com.android.server.wm.WindowState.LEGACY_POLICY_VISIBILITY;
-import static com.android.window.flags.Flags.enablePresentationForConnectedDisplays;
import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
import static org.xmlpull.v1.XmlPullParser.END_TAG;
@@ -659,8 +657,6 @@
private RemoteAnimationDefinition mRemoteAnimationDefinition;
- AnimatingActivityRegistry mAnimatingActivityRegistry;
-
// Set to the previous Task parent of the ActivityRecord when it is reparented to a new Task
// due to picture-in-picture. This gets cleared whenever this activity or the Task
// it references to gets removed. This should also be cleared when we move out of pip.
@@ -857,12 +853,6 @@
})
@interface SplashScreenBehavior { }
- // Force an app transition to be ran in the case the visibility of the app did not change.
- // We use this for the case of moving a Root Task to the back with multiple activities, and the
- // top activity enters PIP; the bottom activity's visibility stays the same, but we need to
- // run the transition.
- boolean mRequestForceTransition;
-
boolean mEnteringAnimation;
boolean mOverrideTaskTransition;
boolean mDismissKeyguardIfInsecure;
@@ -1587,9 +1577,6 @@
}
}
final Task rootTask = getRootTask();
-
- updateAnimatingActivityRegistry();
-
if (task == mLastParentBeforePip && task != null) {
// Notify the TaskFragmentOrganizer that the activity is reparented back from pip.
mAtmService.mWindowOrganizerController.mTaskFragmentOrganizerController
@@ -1691,20 +1678,6 @@
return !organizedTaskFragment.isAllowedToEmbedActivityInTrustedMode(this);
}
- void updateAnimatingActivityRegistry() {
- final Task rootTask = getRootTask();
- final AnimatingActivityRegistry registry = rootTask != null
- ? rootTask.getAnimatingActivityRegistry()
- : null;
-
- // If we reparent, make sure to remove ourselves from the old animation registry.
- if (mAnimatingActivityRegistry != null && mAnimatingActivityRegistry != registry) {
- mAnimatingActivityRegistry.notifyFinished(this);
- }
-
- mAnimatingActivityRegistry = registry;
- }
-
boolean canAutoEnterPip() {
// beforeStopping=false since the actual pip-ing will take place after startPausing()
final boolean activityCanPip = checkEnterPictureInPictureState(
@@ -1789,7 +1762,6 @@
if (prevDc.mOpeningApps.remove(this)) {
// Transfer opening transition to new display.
mDisplayContent.mOpeningApps.add(this);
- mDisplayContent.transferAppTransitionFrom(prevDc);
mDisplayContent.executeAppTransition();
}
@@ -4632,12 +4604,6 @@
}
}
- // In this case, the starting icon has already been displayed, so start
- // letting windows get shown immediately without any more transitions.
- if (fromActivity.mVisible) {
- mDisplayContent.mSkipAppTransitionAnimation = true;
- }
-
ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Moving existing starting %s"
+ " from %s to %s", tStartingWindow, fromActivity, this);
@@ -5668,76 +5634,17 @@
mTransitionController.mValidateCommitVis.add(this);
return;
}
- // If we are preparing an app transition, then delay changing
- // the visibility of this token until we execute that transition.
- if (deferCommitVisibilityChange(visible)) {
- return;
- }
commitVisibility(visible, true /* performLayout */);
updateReportedVisibilityLocked();
}
- /**
- * Returns {@code true} if this activity is either added to opening-apps or closing-apps.
- * Then its visibility will be committed until the transition is ready.
- */
- private boolean deferCommitVisibilityChange(boolean visible) {
- if (mTransitionController.isShellTransitionsEnabled()) {
- // Shell transition doesn't use opening/closing sets.
- return false;
- }
- if (!mDisplayContent.mAppTransition.isTransitionSet()) {
- return false;
- }
- if (mWaitForEnteringPinnedMode && mVisible == visible) {
- // If the visibility is not changed during enter PIP, we don't want to include it in
- // app transition to affect the animation theme, because the Pip organizer will
- // animate the entering PIP instead.
- return false;
- }
-
- // The animation will be visible soon so do not skip by screen off.
- final boolean ignoreScreenOn = canTurnScreenOn() || mTaskSupervisor.getKeyguardController()
- .isKeyguardGoingAway(mDisplayContent.mDisplayId);
- // Ignore display frozen so the opening / closing transition type can be updated correctly
- // even if the display is frozen. And it's safe since in applyAnimation will still check
- // DC#okToAnimate again if the transition animation is fine to apply.
- if (!okToAnimate(true /* ignoreFrozen */, ignoreScreenOn)) {
- return false;
- }
- if (visible) {
- mDisplayContent.mOpeningApps.add(this);
- mEnteringAnimation = true;
- } else if (mVisible) {
- mDisplayContent.mClosingApps.add(this);
- mEnteringAnimation = false;
- }
- if ((mDisplayContent.mAppTransition.getTransitFlags() & TRANSIT_FLAG_OPEN_BEHIND) != 0) {
- // Add the launching-behind activity to mOpeningApps.
- final WindowState win = mDisplayContent.findFocusedWindow();
- if (win != null) {
- final ActivityRecord focusedActivity = win.mActivityRecord;
- if (focusedActivity != null) {
- ProtoLog.d(WM_DEBUG_APP_TRANSITIONS,
- "TRANSIT_FLAG_OPEN_BEHIND, adding %s to mOpeningApps",
- focusedActivity);
- // Force animation to be loaded.
- mDisplayContent.mOpeningApps.add(focusedActivity);
- }
- }
- }
- return true;
- }
-
@Override
boolean applyAnimation(LayoutParams lp, @TransitionOldType int transit, boolean enter,
boolean isVoiceInteraction, @Nullable ArrayList<WindowContainer> sources) {
if ((mTransitionChangeFlags & FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT) != 0) {
return false;
}
- // If it was set to true, reset the last request to force the transition.
- mRequestForceTransition = false;
return super.applyAnimation(lp, transit, enter, isVoiceInteraction, sources);
}
@@ -5900,27 +5807,6 @@
}
/**
- * Check if visibility of this {@link ActivityRecord} should be updated as part of an app
- * transition.
- *
- * <p class="note><strong>Note:</strong> If the visibility of this {@link ActivityRecord} is
- * already set to {@link #mVisible}, we don't need to update the visibility. So {@code false} is
- * returned.</p>
- *
- * @param visible {@code true} if this {@link ActivityRecord} should become visible,
- * {@code false} if this should become invisible.
- * @return {@code true} if visibility of this {@link ActivityRecord} should be updated, and
- * an app transition animation should be run.
- */
- boolean shouldApplyAnimation(boolean visible) {
- // Allow for state update and animation to be applied if:
- // * activity is transitioning visibility state
- // * or the activity was marked as hidden and is exiting before we had a chance to play the
- // transition animation
- return isVisible() != visible || mRequestForceTransition || (!isVisible() && mIsExiting);
- }
-
- /**
* See {@link Activity#setRecentsScreenshotEnabled}.
*/
void setRecentsScreenshotEnabled(boolean enabled) {
@@ -6208,13 +6094,8 @@
return false;
}
- // Hide all activities on the presenting display so that malicious apps can't do tap
- // jacking (b/391466268).
- // For now, this should only be applied to external displays because presentations can only
- // be shown on them.
- // TODO(b/390481621): Disallow a presentation from covering its controlling activity so that
- // the presentation won't stop its controlling activity.
- if (enablePresentationForConnectedDisplays() && mDisplayContent.mIsPresenting) {
+ // A presentation stopps all activities behind on the same display.
+ if (mWmService.mPresentationController.shouldOccludeActivities(getDisplayId())) {
return false;
}
@@ -7635,13 +7516,6 @@
}
@Override
- public boolean shouldDeferAnimationFinish(Runnable endDeferFinishCallback) {
- return mAnimatingActivityRegistry != null
- && mAnimatingActivityRegistry.notifyAboutToFinish(
- this, endDeferFinishCallback);
- }
-
- @Override
boolean isWaitingForTransitionStart() {
final DisplayContent dc = getDisplayContent();
return dc != null && dc.mAppTransition.isTransitionSet()
@@ -7662,10 +7536,6 @@
@Override
public void onLeashAnimationStarting(Transaction t, SurfaceControl leash) {
- if (mAnimatingActivityRegistry != null) {
- mAnimatingActivityRegistry.notifyStarting(this);
- }
-
if (mNeedsLetterboxedAnimation) {
updateLetterboxSurfaceIfNeeded(findMainWindow(), t);
mNeedsAnimationBoundsLayer = true;
@@ -7676,17 +7546,7 @@
// new layer.
if (mNeedsAnimationBoundsLayer) {
mTmpRect.setEmpty();
- if (getDisplayContent().mAppTransitionController.isTransitWithinTask(
- getTransit(), task)) {
- task.getBounds(mTmpRect);
- } else {
- final Task rootTask = getRootTask();
- if (rootTask == null) {
- return;
- }
- // Set clip rect to root task bounds.
- rootTask.getBounds(mTmpRect);
- }
+ task.getBounds(mTmpRect);
mAnimationBoundsLayer = createAnimationBoundsLayer(t);
// Crop to root task bounds.
@@ -7842,10 +7702,6 @@
mNeedsLetterboxedAnimation = false;
updateLetterboxSurfaceIfNeeded(findMainWindow(), t);
}
-
- if (mAnimatingActivityRegistry != null) {
- mAnimatingActivityRegistry.notifyFinished(this);
- }
}
@Override
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 254127d..819e117 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -4128,22 +4128,7 @@
@Override
public void registerRemoteAnimationsForDisplay(int displayId,
RemoteAnimationDefinition definition) {
- mAmInternal.enforceCallingPermission(CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS,
- "registerRemoteAnimations");
- definition.setCallingPidUid(Binder.getCallingPid(), Binder.getCallingUid());
- synchronized (mGlobalLock) {
- final DisplayContent display = mRootWindowContainer.getDisplayContent(displayId);
- if (display == null) {
- Slog.e(TAG, "Couldn't find display with id: " + displayId);
- return;
- }
- final long origId = Binder.clearCallingIdentity();
- try {
- display.registerRemoteAnimations(definition);
- } finally {
- Binder.restoreCallingIdentity(origId);
- }
- }
+ // TODO(b/365884835): Remove callers.
}
/** @see android.app.ActivityManager#alwaysShowUnsupportedCompileSdkWarning */
diff --git a/services/core/java/com/android/server/wm/AnimatingActivityRegistry.java b/services/core/java/com/android/server/wm/AnimatingActivityRegistry.java
deleted file mode 100644
index 18ec96c..0000000
--- a/services/core/java/com/android/server/wm/AnimatingActivityRegistry.java
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server.wm;
-
-import android.util.ArrayMap;
-import android.util.ArraySet;
-
-import java.io.PrintWriter;
-import java.util.ArrayList;
-
-/**
- * Keeps track of all {@link ActivityRecord} that are animating and makes sure all animations are
- * finished at the same time such that we don't run into issues with z-ordering: An activity A
- * that has a shorter animation that is above another activity B with a longer animation in the same
- * task, the animation layer would put the B on top of A, but from the hierarchy, A needs to be on
- * top of B. Thus, we defer reparenting A to the original hierarchy such that it stays on top of B
- * until B finishes animating.
- */
-class AnimatingActivityRegistry {
-
- private ArraySet<ActivityRecord> mAnimatingActivities = new ArraySet<>();
- private ArrayMap<ActivityRecord, Runnable> mFinishedTokens = new ArrayMap<>();
-
- private ArrayList<Runnable> mTmpRunnableList = new ArrayList<>();
-
- private boolean mEndingDeferredFinish;
-
- /**
- * Notifies that an {@link ActivityRecord} has started animating.
- */
- void notifyStarting(ActivityRecord token) {
- mAnimatingActivities.add(token);
- }
-
- /**
- * Notifies that an {@link ActivityRecord} has finished animating.
- */
- void notifyFinished(ActivityRecord activity) {
- mAnimatingActivities.remove(activity);
- mFinishedTokens.remove(activity);
-
- // If we were the last activity, make sure the end all deferred finishes.
- if (mAnimatingActivities.isEmpty()) {
- endDeferringFinished();
- }
- }
-
- /**
- * Called when an {@link ActivityRecord} is about to finish animating.
- *
- * @param endDeferFinishCallback Callback to run when defer finish should be ended.
- * @return {@code true} if finishing the animation should be deferred, {@code false} otherwise.
- */
- boolean notifyAboutToFinish(ActivityRecord activity, Runnable endDeferFinishCallback) {
- final boolean removed = mAnimatingActivities.remove(activity);
- if (!removed) {
- return false;
- }
-
- if (mAnimatingActivities.isEmpty()) {
-
- // If no animations are animating anymore, finish all others.
- endDeferringFinished();
- return false;
- } else {
-
- // Otherwise let's put it into the pending list of to be finished animations.
- mFinishedTokens.put(activity, endDeferFinishCallback);
- return true;
- }
- }
-
- private void endDeferringFinished() {
-
- // Don't start recursing. Running the finished listener invokes notifyFinished, which may
- // invoked us again.
- if (mEndingDeferredFinish) {
- return;
- }
- try {
- mEndingDeferredFinish = true;
-
- // Copy it into a separate temp list to avoid modifying the collection while iterating
- // as calling the callback may call back into notifyFinished.
- for (int i = mFinishedTokens.size() - 1; i >= 0; i--) {
- mTmpRunnableList.add(mFinishedTokens.valueAt(i));
- }
- mFinishedTokens.clear();
- for (int i = mTmpRunnableList.size() - 1; i >= 0; i--) {
- mTmpRunnableList.get(i).run();
- }
- mTmpRunnableList.clear();
- } finally {
- mEndingDeferredFinish = false;
- }
- }
-
- void dump(PrintWriter pw, String header, String prefix) {
- if (!mAnimatingActivities.isEmpty() || !mFinishedTokens.isEmpty()) {
- pw.print(prefix); pw.println(header);
- prefix = prefix + " ";
- pw.print(prefix); pw.print("mAnimatingActivities="); pw.println(mAnimatingActivities);
- pw.print(prefix); pw.print("mFinishedTokens="); pw.println(mFinishedTokens);
- }
- }
-}
diff --git a/services/core/java/com/android/server/wm/AnimationAdapter.java b/services/core/java/com/android/server/wm/AnimationAdapter.java
index 3dc377d..4458ed7 100644
--- a/services/core/java/com/android/server/wm/AnimationAdapter.java
+++ b/services/core/java/com/android/server/wm/AnimationAdapter.java
@@ -109,15 +109,6 @@
* Gets called when the animation is about to finish and gives the client the opportunity to
* defer finishing the animation, i.e. it keeps the leash around until the client calls
* endDeferFinishCallback.
- * <p>
- * This has the same effect as
- * {@link com.android.server.wm.SurfaceAnimator.Animatable#shouldDeferAnimationFinish(Runnable)}
- * . The later will be evaluated first and has precedence over this method if it returns true,
- * which means that if the {@link com.android.server.wm.SurfaceAnimator.Animatable} requests to
- * defer its finish, this method won't be called so this adapter will never have access to the
- * finish callback. On the other hand, if the
- * {@link com.android.server.wm.SurfaceAnimator.Animatable}, doesn't request to defer, this
- * {@link AnimationAdapter} is responsible for ending the animation.
*
* @param endDeferFinishCallback The callback to call when defer finishing should be ended.
* @return Whether the client would like to defer the animation finish.
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index 932f268..9c4b722 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -1462,7 +1462,7 @@
}
boolean prepareAppTransition(@TransitionType int transit, @TransitionFlags int flags) {
- if (mDisplayContent.mTransitionController.isShellTransitionsEnabled()) {
+ if (WindowManagerService.sEnableShellTransitions) {
return false;
}
mNextAppTransitionRequests.add(transit);
diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java
deleted file mode 100644
index d5fe056..0000000
--- a/services/core/java/com/android/server/wm/AppTransitionController.java
+++ /dev/null
@@ -1,1352 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server.wm;
-
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM;
-import static android.view.WindowManager.TRANSIT_CHANGE;
-import static android.view.WindowManager.TRANSIT_CLOSE;
-import static android.view.WindowManager.TRANSIT_FLAG_APP_CRASHED;
-import static android.view.WindowManager.TRANSIT_FLAG_OPEN_BEHIND;
-import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY;
-import static android.view.WindowManager.TRANSIT_KEYGUARD_OCCLUDE;
-import static android.view.WindowManager.TRANSIT_KEYGUARD_UNOCCLUDE;
-import static android.view.WindowManager.TRANSIT_NONE;
-import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_CLOSE;
-import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_OPEN;
-import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_RELAUNCH;
-import static android.view.WindowManager.TRANSIT_OLD_CRASHING_ACTIVITY_CLOSE;
-import static android.view.WindowManager.TRANSIT_OLD_DREAM_ACTIVITY_CLOSE;
-import static android.view.WindowManager.TRANSIT_OLD_DREAM_ACTIVITY_OPEN;
-import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_GOING_AWAY;
-import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_GOING_AWAY_ON_WALLPAPER;
-import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_OCCLUDE;
-import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_OCCLUDE_BY_DREAM;
-import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_UNOCCLUDE;
-import static android.view.WindowManager.TRANSIT_OLD_NONE;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_CLOSE;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CHANGE;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CLOSE;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_OPEN;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_OPEN;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_OPEN_BEHIND;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_TO_BACK;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_TO_FRONT;
-import static android.view.WindowManager.TRANSIT_OLD_TRANSLUCENT_ACTIVITY_CLOSE;
-import static android.view.WindowManager.TRANSIT_OLD_TRANSLUCENT_ACTIVITY_OPEN;
-import static android.view.WindowManager.TRANSIT_OLD_WALLPAPER_CLOSE;
-import static android.view.WindowManager.TRANSIT_OLD_WALLPAPER_INTRA_CLOSE;
-import static android.view.WindowManager.TRANSIT_OLD_WALLPAPER_INTRA_OPEN;
-import static android.view.WindowManager.TRANSIT_OLD_WALLPAPER_OPEN;
-import static android.view.WindowManager.TRANSIT_OPEN;
-import static android.view.WindowManager.TRANSIT_RELAUNCH;
-import static android.view.WindowManager.TRANSIT_TO_BACK;
-import static android.view.WindowManager.TRANSIT_TO_FRONT;
-
-import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_APP_TRANSITIONS;
-import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_APP_TRANSITIONS_ANIM;
-import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_CONFIG;
-import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT;
-import static com.android.server.wm.ActivityTaskManagerInternal.APP_TRANSITION_SNAPSHOT;
-import static com.android.server.wm.ActivityTaskManagerInternal.APP_TRANSITION_SPLASH_SCREEN;
-import static com.android.server.wm.ActivityTaskManagerInternal.APP_TRANSITION_WINDOWS_DRAWN;
-import static com.android.server.wm.AppTransition.isNormalTransit;
-import static com.android.server.wm.NonAppWindowAnimationAdapter.shouldAttachNavBarToApp;
-import static com.android.server.wm.NonAppWindowAnimationAdapter.shouldStartNonAppWindowAnimationsForKeyguardExit;
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
-import static com.android.server.wm.WallpaperAnimationAdapter.shouldStartWallpaperAnimation;
-import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
-import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
-import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
-
-import android.annotation.IntDef;
-import android.annotation.Nullable;
-import android.graphics.Rect;
-import android.os.Trace;
-import android.util.ArrayMap;
-import android.util.ArraySet;
-import android.util.Pair;
-import android.view.RemoteAnimationAdapter;
-import android.view.RemoteAnimationDefinition;
-import android.view.WindowManager;
-import android.view.WindowManager.LayoutParams;
-import android.view.WindowManager.TransitionFlags;
-import android.view.WindowManager.TransitionOldType;
-import android.view.WindowManager.TransitionType;
-import android.window.ITaskFragmentOrganizer;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.protolog.ProtoLog;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.ArrayDeque;
-import java.util.ArrayList;
-import java.util.function.Consumer;
-import java.util.function.Predicate;
-
-/**
- * Checks for app transition readiness, resolves animation attributes and performs visibility
- * change for apps that animate as part of an app transition.
- */
-public class AppTransitionController {
- private static final String TAG = TAG_WITH_CLASS_NAME ? "AppTransitionController" : TAG_WM;
- private final WindowManagerService mService;
- private final DisplayContent mDisplayContent;
- private final WallpaperController mWallpaperControllerLocked;
- private RemoteAnimationDefinition mRemoteAnimationDefinition = null;
-
- private static final int TYPE_NONE = 0;
- private static final int TYPE_ACTIVITY = 1;
- private static final int TYPE_TASK_FRAGMENT = 2;
- private static final int TYPE_TASK = 3;
-
- @IntDef(prefix = { "TYPE_" }, value = {
- TYPE_NONE,
- TYPE_ACTIVITY,
- TYPE_TASK_FRAGMENT,
- TYPE_TASK
- })
- @Retention(RetentionPolicy.SOURCE)
- @interface TransitContainerType {}
-
- private final ArrayMap<WindowContainer, Integer> mTempTransitionReasons = new ArrayMap<>();
- private final ArrayList<WindowContainer> mTempTransitionWindows = new ArrayList<>();
-
- AppTransitionController(WindowManagerService service, DisplayContent displayContent) {
- mService = service;
- mDisplayContent = displayContent;
- mWallpaperControllerLocked = mDisplayContent.mWallpaperController;
- }
-
- void registerRemoteAnimations(RemoteAnimationDefinition definition) {
- mRemoteAnimationDefinition = definition;
- }
-
- /**
- * Returns the currently visible window that is associated with the wallpaper in case we are
- * transitioning from an activity with a wallpaper to one without.
- */
- @Nullable
- private WindowState getOldWallpaper() {
- final WindowState wallpaperTarget = mWallpaperControllerLocked.getWallpaperTarget();
- final @TransitionType int firstTransit =
- mDisplayContent.mAppTransition.getFirstAppTransition();
-
- final ArraySet<WindowContainer> openingWcs = getAnimationTargets(
- mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps, true /* visible */);
- final boolean showWallpaper = wallpaperTarget != null
- && (wallpaperTarget.hasWallpaper()
- // Update task open transition to wallpaper transition when wallpaper is visible.
- // (i.e.launching app info activity from recent tasks)
- || ((firstTransit == TRANSIT_OPEN || firstTransit == TRANSIT_TO_FRONT)
- && (!openingWcs.isEmpty() && openingWcs.valueAt(0).asTask() != null)
- && mWallpaperControllerLocked.isWallpaperVisible()));
- // If wallpaper is animating or wallpaperTarget doesn't have SHOW_WALLPAPER flag set,
- // don't consider upgrading to wallpaper transition.
- return (mWallpaperControllerLocked.isWallpaperTargetAnimating() || !showWallpaper)
- ? null : wallpaperTarget;
- }
-
- /**
- * Handle application transition for given display.
- */
- void handleAppTransitionReady() {
- mTempTransitionReasons.clear();
- if (!transitionGoodToGo(mDisplayContent.mOpeningApps, mTempTransitionReasons)
- || !transitionGoodToGo(mDisplayContent.mChangingContainers, mTempTransitionReasons)
- || !transitionGoodToGoForTaskFragments()) {
- return;
- }
-
- Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "AppTransitionReady");
-
- ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, "**** GOOD TO GO");
- // TODO(b/205335975): Remove window which stuck in animatingExit status. Find actual cause.
- mDisplayContent.forAllWindows(WindowState::cleanupAnimatingExitWindow,
- true /* traverseTopToBottom */);
- // TODO(new-app-transition): Remove code using appTransition.getAppTransition()
- final AppTransition appTransition = mDisplayContent.mAppTransition;
-
- mDisplayContent.mNoAnimationNotifyOnTransitionFinished.clear();
-
- appTransition.removeAppTransitionTimeoutCallbacks();
-
- mDisplayContent.mWallpaperMayChange = false;
-
- int appCount = mDisplayContent.mOpeningApps.size();
- for (int i = 0; i < appCount; ++i) {
- // Clearing the mAnimatingExit flag before entering animation. It's set to true if app
- // window is removed, or window relayout to invisible. This also affects window
- // visibility. We need to clear it *before* maybeUpdateTransitToWallpaper() as the
- // transition selection depends on wallpaper target visibility.
- mDisplayContent.mOpeningApps.valueAtUnchecked(i).clearAnimatingFlags();
- }
- appCount = mDisplayContent.mChangingContainers.size();
- for (int i = 0; i < appCount; ++i) {
- // Clearing for same reason as above.
- final ActivityRecord activity = getAppFromContainer(
- mDisplayContent.mChangingContainers.valueAtUnchecked(i));
- if (activity != null) {
- activity.clearAnimatingFlags();
- }
- }
-
- // Adjust wallpaper before we pull the lower/upper target, since pending changes
- // (like the clearAnimatingFlags() above) might affect wallpaper target result.
- // Or, the opening app window should be a wallpaper target.
- mWallpaperControllerLocked.adjustWallpaperWindowsForAppTransitionIfNeeded(
- mDisplayContent.mOpeningApps);
-
- ArraySet<ActivityRecord> tmpOpenApps = mDisplayContent.mOpeningApps;
- ArraySet<ActivityRecord> tmpCloseApps = mDisplayContent.mClosingApps;
- if (mDisplayContent.mAtmService.mBackNavigationController.isMonitoringFinishTransition()) {
- tmpOpenApps = new ArraySet<>(mDisplayContent.mOpeningApps);
- tmpCloseApps = new ArraySet<>(mDisplayContent.mClosingApps);
- }
-
- @TransitionOldType final int transit = getTransitCompatType(
- mDisplayContent.mAppTransition, tmpOpenApps,
- tmpCloseApps, mDisplayContent.mChangingContainers,
- mWallpaperControllerLocked.getWallpaperTarget(), getOldWallpaper(),
- mDisplayContent.mSkipAppTransitionAnimation);
- mDisplayContent.mSkipAppTransitionAnimation = false;
-
- ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
- "handleAppTransitionReady: displayId=%d appTransition={%s}"
- + " openingApps=[%s] closingApps=[%s] transit=%s",
- mDisplayContent.mDisplayId, appTransition.toString(), tmpOpenApps,
- tmpCloseApps, AppTransition.appTransitionOldToString(transit));
-
- // Find the layout params of the top-most application window in the tokens, which is
- // what will control the animation theme. If all closing windows are obscured, then there is
- // no need to do an animation. This is the case, for example, when this transition is being
- // done behind a dream window.
- final ArraySet<Integer> activityTypes = collectActivityTypes(tmpOpenApps,
- tmpCloseApps, mDisplayContent.mChangingContainers);
- final ActivityRecord animLpActivity = findAnimLayoutParamsToken(transit, activityTypes,
- tmpOpenApps, tmpCloseApps, mDisplayContent.mChangingContainers);
- final ActivityRecord topOpeningApp =
- getTopApp(tmpOpenApps, false /* ignoreHidden */);
- final ActivityRecord topClosingApp =
- getTopApp(tmpCloseApps, false /* ignoreHidden */);
- final ActivityRecord topChangingApp =
- getTopApp(mDisplayContent.mChangingContainers, false /* ignoreHidden */);
- final WindowManager.LayoutParams animLp = getAnimLp(animLpActivity);
-
- // Check if there is any override
- if (!overrideWithTaskFragmentRemoteAnimation(transit, activityTypes)) {
- // Unfreeze the windows that were previously frozen for TaskFragment animation.
- overrideWithRemoteAnimationIfSet(animLpActivity, transit, activityTypes);
- }
-
- final boolean voiceInteraction = containsVoiceInteraction(mDisplayContent.mClosingApps)
- || containsVoiceInteraction(mDisplayContent.mOpeningApps);
-
- final int layoutRedo;
- mService.mSurfaceAnimationRunner.deferStartingAnimations();
- try {
- applyAnimations(tmpOpenApps, tmpCloseApps, transit, animLp, voiceInteraction);
- handleClosingApps();
- handleOpeningApps();
- handleChangingApps(transit);
- handleClosingChangingContainers();
-
- appTransition.setLastAppTransition(transit, topOpeningApp,
- topClosingApp, topChangingApp);
-
- final int flags = appTransition.getTransitFlags();
- layoutRedo = appTransition.goodToGo(transit, topOpeningApp);
- appTransition.postAnimationCallback();
- } finally {
- appTransition.clear();
- mService.mSurfaceAnimationRunner.continueStartingAnimations();
- }
-
- mService.mSnapshotController.onTransitionStarting(mDisplayContent);
-
- mDisplayContent.mOpeningApps.clear();
- mDisplayContent.mClosingApps.clear();
- mDisplayContent.mChangingContainers.clear();
- mDisplayContent.mUnknownAppVisibilityController.clear();
- mDisplayContent.mClosingChangingContainers.clear();
-
- // This has changed the visibility of windows, so perform
- // a new layout to get them all up-to-date.
- mDisplayContent.setLayoutNeeded();
-
- mDisplayContent.computeImeTarget(true /* updateImeTarget */);
-
- mService.mAtmService.mTaskSupervisor.getActivityMetricsLogger().notifyTransitionStarting(
- mTempTransitionReasons);
-
- Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
-
- mDisplayContent.pendingLayoutChanges |=
- layoutRedo | FINISH_LAYOUT_REDO_LAYOUT | FINISH_LAYOUT_REDO_CONFIG;
- }
-
- /**
- * Get old transit type based on the current transit requests.
- *
- * @param appTransition {@link AppTransition} for managing app transition state.
- * @param openingApps {@link ActivityRecord}s which are becoming visible.
- * @param closingApps {@link ActivityRecord}s which are becoming invisible.
- * @param changingContainers {@link WindowContainer}s which are changed in configuration.
- * @param wallpaperTarget If non-null, this is the currently visible window that is associated
- * with the wallpaper.
- * @param oldWallpaper The currently visible window that is associated with the wallpaper in
- * case we are transitioning from an activity with a wallpaper to one
- * without. Otherwise null.
- */
- @TransitionOldType static int getTransitCompatType(AppTransition appTransition,
- ArraySet<ActivityRecord> openingApps, ArraySet<ActivityRecord> closingApps,
- ArraySet<WindowContainer> changingContainers, @Nullable WindowState wallpaperTarget,
- @Nullable WindowState oldWallpaper, boolean skipAppTransitionAnimation) {
-
- final ActivityRecord topOpeningApp = getTopApp(openingApps, false /* ignoreHidden */);
- final ActivityRecord topClosingApp = getTopApp(closingApps, true /* ignoreHidden */);
-
- // Determine if closing and opening app token sets are wallpaper targets, in which case
- // special animations are needed.
- final boolean openingAppHasWallpaper = canBeWallpaperTarget(openingApps)
- && wallpaperTarget != null;
- final boolean closingAppHasWallpaper = canBeWallpaperTarget(closingApps)
- && wallpaperTarget != null;
-
- // Keyguard transit has high priority.
- switch (appTransition.getKeyguardTransition()) {
- case TRANSIT_KEYGUARD_GOING_AWAY:
- return openingAppHasWallpaper ? TRANSIT_OLD_KEYGUARD_GOING_AWAY_ON_WALLPAPER
- : TRANSIT_OLD_KEYGUARD_GOING_AWAY;
- case TRANSIT_KEYGUARD_OCCLUDE:
- // When there is a closing app, the keyguard has already been occluded by an
- // activity, and another activity has started on top of that activity, so normal
- // app transition animation should be used.
- if (!closingApps.isEmpty()) {
- return TRANSIT_OLD_ACTIVITY_OPEN;
- }
- if (!openingApps.isEmpty() && openingApps.valueAt(0).getActivityType()
- == ACTIVITY_TYPE_DREAM) {
- return TRANSIT_OLD_KEYGUARD_OCCLUDE_BY_DREAM;
- }
- return TRANSIT_OLD_KEYGUARD_OCCLUDE;
- case TRANSIT_KEYGUARD_UNOCCLUDE:
- return TRANSIT_OLD_KEYGUARD_UNOCCLUDE;
- }
-
- // Determine whether the top opening and closing activity is a dream activity. If so, this
- // has higher priority than others except keyguard transit.
- if (topOpeningApp != null && topOpeningApp.getActivityType() == ACTIVITY_TYPE_DREAM) {
- return TRANSIT_OLD_DREAM_ACTIVITY_OPEN;
- } else if (topClosingApp != null
- && topClosingApp.getActivityType() == ACTIVITY_TYPE_DREAM) {
- return TRANSIT_OLD_DREAM_ACTIVITY_CLOSE;
- }
-
- // This is not keyguard transition and one of the app has request to skip app transition.
- if (skipAppTransitionAnimation) {
- return WindowManager.TRANSIT_OLD_UNSET;
- }
- @TransitionFlags final int flags = appTransition.getTransitFlags();
- @TransitionType final int firstTransit = appTransition.getFirstAppTransition();
-
- // Special transitions
- // TODO(new-app-transitions): Revisit if those can be rewritten by using flags.
- if (appTransition.containsTransitRequest(TRANSIT_CHANGE) && !changingContainers.isEmpty()) {
- @TransitContainerType int changingType =
- getTransitContainerType(changingContainers.valueAt(0));
- switch (changingType) {
- case TYPE_TASK:
- return TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE;
- case TYPE_TASK_FRAGMENT:
- return TRANSIT_OLD_TASK_FRAGMENT_CHANGE;
- default:
- throw new IllegalStateException(
- "TRANSIT_CHANGE with unrecognized changing type=" + changingType);
- }
- }
- if ((flags & TRANSIT_FLAG_APP_CRASHED) != 0) {
- return TRANSIT_OLD_CRASHING_ACTIVITY_CLOSE;
- }
- if (firstTransit == TRANSIT_NONE) {
- return TRANSIT_OLD_NONE;
- }
-
- /*
- * There are cases where we open/close a new task/activity, but in reality only a
- * translucent activity on top of existing activities is opening/closing. For that one, we
- * have a different animation because non of the task/activity animations actually work well
- * with translucent apps.
- */
- if (isNormalTransit(firstTransit)) {
- boolean allOpeningVisible = true;
- boolean allTranslucentOpeningApps = !openingApps.isEmpty();
- for (int i = openingApps.size() - 1; i >= 0; i--) {
- final ActivityRecord activity = openingApps.valueAt(i);
- if (!activity.isVisible()) {
- allOpeningVisible = false;
- if (activity.fillsParent()) {
- allTranslucentOpeningApps = false;
- }
- }
- }
- boolean allTranslucentClosingApps = !closingApps.isEmpty();
- for (int i = closingApps.size() - 1; i >= 0; i--) {
- if (closingApps.valueAt(i).fillsParent()) {
- allTranslucentClosingApps = false;
- break;
- }
- }
-
- if (allTranslucentClosingApps && allOpeningVisible) {
- return TRANSIT_OLD_TRANSLUCENT_ACTIVITY_CLOSE;
- }
- if (allTranslucentOpeningApps && closingApps.isEmpty()) {
- return TRANSIT_OLD_TRANSLUCENT_ACTIVITY_OPEN;
- }
- }
-
- if (closingAppHasWallpaper && openingAppHasWallpaper) {
- ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, "Wallpaper animation!");
- switch (firstTransit) {
- case TRANSIT_OPEN:
- case TRANSIT_TO_FRONT:
- return TRANSIT_OLD_WALLPAPER_INTRA_OPEN;
- case TRANSIT_CLOSE:
- case TRANSIT_TO_BACK:
- return TRANSIT_OLD_WALLPAPER_INTRA_CLOSE;
- }
- } else if (oldWallpaper != null && !openingApps.isEmpty()
- && !openingApps.contains(oldWallpaper.mActivityRecord)
- && closingApps.contains(oldWallpaper.mActivityRecord)
- && topClosingApp == oldWallpaper.mActivityRecord) {
- // We are transitioning from an activity with a wallpaper to one without.
- return TRANSIT_OLD_WALLPAPER_CLOSE;
- } else if (wallpaperTarget != null && wallpaperTarget.isVisible()
- && openingApps.contains(wallpaperTarget.mActivityRecord)
- && topOpeningApp == wallpaperTarget.mActivityRecord
- /* && transit != TRANSIT_TRANSLUCENT_ACTIVITY_CLOSE */) {
- // We are transitioning from an activity without
- // a wallpaper to now showing the wallpaper
- return TRANSIT_OLD_WALLPAPER_OPEN;
- }
-
- final ArraySet<WindowContainer> openingWcs = getAnimationTargets(
- openingApps, closingApps, true /* visible */);
- final ArraySet<WindowContainer> closingWcs = getAnimationTargets(
- openingApps, closingApps, false /* visible */);
- final WindowContainer<?> openingContainer = !openingWcs.isEmpty()
- ? openingWcs.valueAt(0) : null;
- final WindowContainer<?> closingContainer = !closingWcs.isEmpty()
- ? closingWcs.valueAt(0) : null;
- @TransitContainerType int openingType = getTransitContainerType(openingContainer);
- @TransitContainerType int closingType = getTransitContainerType(closingContainer);
- if (appTransition.containsTransitRequest(TRANSIT_TO_FRONT) && openingType == TYPE_TASK) {
- if (topOpeningApp != null && topOpeningApp.isActivityTypeHome()) {
- // If we are opening the home task, we want to play an animation as if
- // the task on top is being brought to back.
- return TRANSIT_OLD_TASK_TO_BACK;
- }
- return TRANSIT_OLD_TASK_TO_FRONT;
- }
- if (appTransition.containsTransitRequest(TRANSIT_TO_BACK) && closingType == TYPE_TASK) {
- return TRANSIT_OLD_TASK_TO_BACK;
- }
- if (appTransition.containsTransitRequest(TRANSIT_OPEN)) {
- if (openingType == TYPE_TASK) {
- return (appTransition.getTransitFlags() & TRANSIT_FLAG_OPEN_BEHIND) != 0
- ? TRANSIT_OLD_TASK_OPEN_BEHIND : TRANSIT_OLD_TASK_OPEN;
- }
- if (openingType == TYPE_ACTIVITY) {
- return TRANSIT_OLD_ACTIVITY_OPEN;
- }
- if (openingType == TYPE_TASK_FRAGMENT) {
- return TRANSIT_OLD_TASK_FRAGMENT_OPEN;
- }
- }
- if (appTransition.containsTransitRequest(TRANSIT_CLOSE)) {
- if (closingType == TYPE_TASK) {
- return TRANSIT_OLD_TASK_CLOSE;
- }
- if (closingType == TYPE_TASK_FRAGMENT) {
- return TRANSIT_OLD_TASK_FRAGMENT_CLOSE;
- }
- if (closingType == TYPE_ACTIVITY) {
- for (int i = closingApps.size() - 1; i >= 0; i--) {
- if (closingApps.valueAt(i).visibleIgnoringKeyguard) {
- return TRANSIT_OLD_ACTIVITY_CLOSE;
- }
- }
- // Skip close activity transition since no closing app can be visible
- return WindowManager.TRANSIT_OLD_UNSET;
- }
- }
- if (appTransition.containsTransitRequest(TRANSIT_RELAUNCH)
- && !openingWcs.isEmpty() && !openingApps.isEmpty()) {
- return TRANSIT_OLD_ACTIVITY_RELAUNCH;
- }
- return TRANSIT_OLD_NONE;
- }
-
- @TransitContainerType
- private static int getTransitContainerType(@Nullable WindowContainer<?> container) {
- if (container == null) {
- return TYPE_NONE;
- }
- if (container.asTask() != null) {
- return TYPE_TASK;
- }
- if (container.asTaskFragment() != null) {
- return TYPE_TASK_FRAGMENT;
- }
- if (container.asActivityRecord() != null) {
- return TYPE_ACTIVITY;
- }
- return TYPE_NONE;
- }
-
- @Nullable
- private static WindowManager.LayoutParams getAnimLp(ActivityRecord activity) {
- final WindowState mainWindow = activity != null ? activity.findMainWindow() : null;
- return mainWindow != null ? mainWindow.mAttrs : null;
- }
-
- RemoteAnimationAdapter getRemoteAnimationOverride(@Nullable WindowContainer container,
- @TransitionOldType int transit, ArraySet<Integer> activityTypes) {
- if (container != null) {
- final RemoteAnimationDefinition definition = container.getRemoteAnimationDefinition();
- if (definition != null) {
- final RemoteAnimationAdapter adapter = definition.getAdapter(transit,
- activityTypes);
- if (adapter != null) {
- return adapter;
- }
- }
- }
- return mRemoteAnimationDefinition != null
- ? mRemoteAnimationDefinition.getAdapter(transit, activityTypes)
- : null;
- }
-
- private boolean transitionMayContainNonAppWindows(@TransitionOldType int transit) {
- // We don't want to have the client to animate any non-app windows.
- // Having {@code transit} of those types doesn't mean it will contain non-app windows, but
- // non-app windows will only be included with those transition types. And we don't currently
- // have any use case of those for TaskFragment transition.
- return shouldStartNonAppWindowAnimationsForKeyguardExit(transit)
- || shouldAttachNavBarToApp(mService, mDisplayContent, transit)
- || shouldStartWallpaperAnimation(mDisplayContent);
- }
-
- /**
- * Whether the transition contains any embedded {@link TaskFragment} that does not fill the
- * parent {@link Task} before or after the transition.
- */
- private boolean transitionContainsTaskFragmentWithBoundsOverride() {
- for (int i = mDisplayContent.mChangingContainers.size() - 1; i >= 0; i--) {
- final WindowContainer wc = mDisplayContent.mChangingContainers.valueAt(i);
- if (wc.isEmbedded()) {
- // Contains embedded TaskFragment with bounds changed.
- return true;
- }
- }
- mTempTransitionWindows.clear();
- mTempTransitionWindows.addAll(mDisplayContent.mClosingApps);
- mTempTransitionWindows.addAll(mDisplayContent.mOpeningApps);
- boolean containsTaskFragmentWithBoundsOverride = false;
- for (int i = mTempTransitionWindows.size() - 1; i >= 0; i--) {
- final ActivityRecord r = mTempTransitionWindows.get(i).asActivityRecord();
- final TaskFragment tf = r.getTaskFragment();
- if (tf != null && tf.isEmbeddedWithBoundsOverride()) {
- containsTaskFragmentWithBoundsOverride = true;
- break;
- }
- }
- mTempTransitionWindows.clear();
- return containsTaskFragmentWithBoundsOverride;
- }
-
- /**
- * Finds the common parent {@link Task} that is parent of all embedded app windows in the
- * current transition.
- * @return {@code null} if app windows in the transition are not children of the same Task, or
- * if none of the app windows is embedded.
- */
- @Nullable
- private Task findParentTaskForAllEmbeddedWindows() {
- mTempTransitionWindows.clear();
- mTempTransitionWindows.addAll(mDisplayContent.mClosingApps);
- mTempTransitionWindows.addAll(mDisplayContent.mOpeningApps);
- mTempTransitionWindows.addAll(mDisplayContent.mChangingContainers);
-
- // It should only animated by the organizer if all windows are below the same leaf Task.
- Task leafTask = null;
- for (int i = mTempTransitionWindows.size() - 1; i >= 0; i--) {
- final ActivityRecord r = getAppFromContainer(mTempTransitionWindows.get(i));
- if (r == null) {
- leafTask = null;
- break;
- }
- // There are also cases where the Task contains non-embedded activity, such as launching
- // split TaskFragments from a non-embedded activity.
- // The hierarchy may looks like this:
- // - Task
- // - Activity
- // - TaskFragment
- // - Activity
- // - TaskFragment
- // - Activity
- // We also want to have the organizer handle the transition for such case.
- final Task task = r.getTask();
- // We don't support embedding in PiP, leave the animation to the PipTaskOrganizer.
- if (task == null || task.inPinnedWindowingMode()) {
- leafTask = null;
- break;
- }
- // We don't want the organizer to handle transition of other non-embedded Task.
- if (leafTask != null && leafTask != task) {
- leafTask = null;
- break;
- }
- final ActivityRecord rootActivity = task.getRootActivity();
- // We don't want the organizer to handle transition when the whole app is closing.
- if (rootActivity == null) {
- leafTask = null;
- break;
- }
- // We don't want the organizer to handle transition of non-embedded activity of other
- // app.
- if (r.getUid() != task.effectiveUid && !r.isEmbedded()) {
- leafTask = null;
- break;
- }
- leafTask = task;
- }
- mTempTransitionWindows.clear();
- return leafTask;
- }
-
- /**
- * Finds the common {@link android.window.TaskFragmentOrganizer} that organizes all embedded
- * {@link TaskFragment} belong to the given {@link Task}.
- * @return {@code null} if there is no such organizer, or if there are more than one.
- */
- @Nullable
- private ITaskFragmentOrganizer findTaskFragmentOrganizer(@Nullable Task task) {
- if (task == null) {
- return null;
- }
- // We don't support remote animation for Task with multiple TaskFragmentOrganizers.
- final ITaskFragmentOrganizer[] organizer = new ITaskFragmentOrganizer[1];
- final boolean hasMultipleOrganizers = task.forAllLeafTaskFragments(taskFragment -> {
- final ITaskFragmentOrganizer tfOrganizer = taskFragment.getTaskFragmentOrganizer();
- if (tfOrganizer == null) {
- return false;
- }
- if (organizer[0] != null && !organizer[0].asBinder().equals(tfOrganizer.asBinder())) {
- return true;
- }
- organizer[0] = tfOrganizer;
- return false;
- });
- if (hasMultipleOrganizers) {
- ProtoLog.e(WM_DEBUG_APP_TRANSITIONS, "We don't support remote animation for"
- + " Task with multiple TaskFragmentOrganizers.");
- return null;
- }
- return organizer[0];
- }
-
- /**
- * Overrides the pending transition with the remote animation defined by the
- * {@link ITaskFragmentOrganizer} if all windows in the transition are children of
- * {@link TaskFragment} that are organized by the same organizer.
- *
- * @return {@code true} if the transition is overridden.
- */
- private boolean overrideWithTaskFragmentRemoteAnimation(@TransitionOldType int transit,
- ArraySet<Integer> activityTypes) {
- if (transitionMayContainNonAppWindows(transit)) {
- return false;
- }
- if (!transitionContainsTaskFragmentWithBoundsOverride()) {
- // No need to play TaskFragment remote animation if all embedded TaskFragment in the
- // transition fill the Task.
- return false;
- }
-
- final Task task = findParentTaskForAllEmbeddedWindows();
- final ITaskFragmentOrganizer organizer = findTaskFragmentOrganizer(task);
- final RemoteAnimationDefinition definition = organizer != null
- ? mDisplayContent.mAtmService.mTaskFragmentOrganizerController
- .getRemoteAnimationDefinition(organizer)
- : null;
- final RemoteAnimationAdapter adapter = definition != null
- ? definition.getAdapter(transit, activityTypes)
- : null;
- if (adapter == null) {
- return false;
- }
- mDisplayContent.mAppTransition.overridePendingAppTransitionRemote(
- adapter, false /* sync */, true /*isActivityEmbedding*/);
- ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
- "Override with TaskFragment remote animation for transit=%s",
- AppTransition.appTransitionOldToString(transit));
-
- final int organizerUid = mDisplayContent.mAtmService.mTaskFragmentOrganizerController
- .getTaskFragmentOrganizerUid(organizer);
- final boolean shouldDisableInputForRemoteAnimation = !task.isFullyTrustedEmbedding(
- organizerUid);
- final RemoteAnimationController remoteAnimationController =
- mDisplayContent.mAppTransition.getRemoteAnimationController();
- if (shouldDisableInputForRemoteAnimation && remoteAnimationController != null) {
- // We are going to use client-driven animation, Disable all input on activity windows
- // during the animation (unless it is fully trusted) to ensure it is safe to allow
- // client to animate the surfaces.
- // This is needed for all activity windows in the animation Task.
- remoteAnimationController.setOnRemoteAnimationReady(() -> {
- final Consumer<ActivityRecord> updateActivities =
- activity -> activity.setDropInputForAnimation(true);
- task.forAllActivities(updateActivities);
- });
- ProtoLog.d(WM_DEBUG_APP_TRANSITIONS, "Task=%d contains embedded TaskFragment."
- + " Disabled all input during TaskFragment remote animation.", task.mTaskId);
- }
- return true;
- }
-
- /**
- * Overrides the pending transition with the remote animation defined for the transition in the
- * set of defined remote animations in the app window token.
- */
- private void overrideWithRemoteAnimationIfSet(@Nullable ActivityRecord animLpActivity,
- @TransitionOldType int transit, ArraySet<Integer> activityTypes) {
- RemoteAnimationAdapter adapter = null;
- if (transit == TRANSIT_OLD_CRASHING_ACTIVITY_CLOSE) {
- // The crash transition has higher priority than any involved remote animations.
- } else if (AppTransition.isKeyguardGoingAwayTransitOld(transit)) {
- adapter = mRemoteAnimationDefinition != null
- ? mRemoteAnimationDefinition.getAdapter(transit, activityTypes)
- : null;
- } else if (mDisplayContent.mAppTransition.getRemoteAnimationController() == null) {
- adapter = getRemoteAnimationOverride(animLpActivity, transit, activityTypes);
- }
- if (adapter != null) {
- mDisplayContent.mAppTransition.overridePendingAppTransitionRemote(adapter);
- }
- }
-
- @Nullable
- static Task findRootTaskFromContainer(WindowContainer wc) {
- return wc.asTaskFragment() != null ? wc.asTaskFragment().getRootTask()
- : wc.asActivityRecord().getRootTask();
- }
-
- @Nullable
- static ActivityRecord getAppFromContainer(WindowContainer wc) {
- return wc.asTaskFragment() != null ? wc.asTaskFragment().getTopNonFinishingActivity()
- : wc.asActivityRecord();
- }
-
- /**
- * @return The window token that determines the animation theme.
- */
- @Nullable
- private ActivityRecord findAnimLayoutParamsToken(@TransitionOldType int transit,
- ArraySet<Integer> activityTypes, ArraySet<ActivityRecord> openingApps,
- ArraySet<ActivityRecord> closingApps, ArraySet<WindowContainer> changingApps) {
- ActivityRecord result;
-
- // Remote animations always win, but fullscreen tokens override non-fullscreen tokens.
- result = lookForHighestTokenWithFilter(closingApps, openingApps, changingApps,
- w -> w.getRemoteAnimationDefinition() != null
- && w.getRemoteAnimationDefinition().hasTransition(transit, activityTypes));
- if (result != null) {
- return result;
- }
- result = lookForHighestTokenWithFilter(closingApps, openingApps, changingApps,
- w -> w.fillsParent() && w.findMainWindow() != null);
- if (result != null) {
- return result;
- }
- return lookForHighestTokenWithFilter(closingApps, openingApps, changingApps,
- w -> w.findMainWindow() != null);
- }
-
- /**
- * @return The set of {@link android.app.WindowConfiguration.ActivityType}s contained in the set
- * of apps in {@code array1}, {@code array2}, and {@code array3}.
- */
- private static ArraySet<Integer> collectActivityTypes(ArraySet<ActivityRecord> array1,
- ArraySet<ActivityRecord> array2, ArraySet<WindowContainer> array3) {
- final ArraySet<Integer> result = new ArraySet<>();
- for (int i = array1.size() - 1; i >= 0; i--) {
- result.add(array1.valueAt(i).getActivityType());
- }
- for (int i = array2.size() - 1; i >= 0; i--) {
- result.add(array2.valueAt(i).getActivityType());
- }
- for (int i = array3.size() - 1; i >= 0; i--) {
- result.add(array3.valueAt(i).getActivityType());
- }
- return result;
- }
-
- private static ActivityRecord lookForHighestTokenWithFilter(ArraySet<ActivityRecord> array1,
- ArraySet<ActivityRecord> array2, ArraySet<WindowContainer> array3,
- Predicate<ActivityRecord> filter) {
- final int array2base = array1.size();
- final int array3base = array2.size() + array2base;
- final int count = array3base + array3.size();
- int bestPrefixOrderIndex = Integer.MIN_VALUE;
- ActivityRecord bestToken = null;
- for (int i = 0; i < count; i++) {
- final WindowContainer wtoken = i < array2base
- ? array1.valueAt(i)
- : (i < array3base
- ? array2.valueAt(i - array2base)
- : array3.valueAt(i - array3base));
- final int prefixOrderIndex = wtoken.getPrefixOrderIndex();
- final ActivityRecord r = getAppFromContainer(wtoken);
- if (r != null && filter.test(r) && prefixOrderIndex > bestPrefixOrderIndex) {
- bestPrefixOrderIndex = prefixOrderIndex;
- bestToken = r;
- }
- }
- return bestToken;
- }
-
- private boolean containsVoiceInteraction(ArraySet<ActivityRecord> apps) {
- for (int i = apps.size() - 1; i >= 0; i--) {
- if (apps.valueAt(i).mVoiceInteraction) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * Apply animation to the set of window containers.
- *
- * @param wcs The list of {@link WindowContainer}s to which an app transition animation applies.
- * @param apps The list of {@link ActivityRecord}s being transitioning.
- * @param transit The current transition type.
- * @param visible {@code true} if the apps becomes visible, {@code false} if the apps becomes
- * invisible.
- * @param animLp Layout parameters in which an app transition animation runs.
- * @param voiceInteraction {@code true} if one of the apps in this transition belongs to a voice
- * interaction session driving task.
- */
- private void applyAnimations(ArraySet<WindowContainer> wcs, ArraySet<ActivityRecord> apps,
- @TransitionOldType int transit, boolean visible, LayoutParams animLp,
- boolean voiceInteraction) {
- final int wcsCount = wcs.size();
- for (int i = 0; i < wcsCount; i++) {
- final WindowContainer wc = wcs.valueAt(i);
- // If app transition animation target is promoted to higher level, SurfaceAnimator
- // triggers WC#onAnimationFinished only on the promoted target. So we need to take care
- // of triggering AR#onAnimationFinished on each ActivityRecord which is a part of the
- // app transition.
- final ArrayList<ActivityRecord> transitioningDescendants = new ArrayList<>();
- for (int j = 0; j < apps.size(); ++j) {
- final ActivityRecord app = apps.valueAt(j);
- if (app.isDescendantOf(wc)) {
- transitioningDescendants.add(app);
- }
- }
- wc.applyAnimation(animLp, transit, visible, voiceInteraction, transitioningDescendants);
- }
- }
-
- /**
- * Returns {@code true} if a given {@link WindowContainer} is an embedded Task in
- * {@link TaskView}.
- *
- * Note that this is a short term workaround to support Android Auto until it migrate to
- * ShellTransition. This should only be used by {@link #getAnimationTargets}.
- *
- * TODO(b/213312721): Remove this predicate and its callers once ShellTransition is enabled.
- */
- static boolean isTaskViewTask(WindowContainer wc) {
- // Use Task#mRemoveWithTaskOrganizer to identify an embedded Task, but this is a hack and
- // it is not guaranteed to work this logic in the future version.
- boolean isTaskViewTask = wc instanceof Task && ((Task) wc).mRemoveWithTaskOrganizer;
- if (isTaskViewTask) {
- return true;
- }
-
- WindowContainer parent = wc.getParent();
- boolean isParentATaskViewTask = parent != null
- && parent instanceof Task
- && ((Task) parent).mRemoveWithTaskOrganizer;
- return isParentATaskViewTask;
- }
-
- /**
- * Find WindowContainers to be animated from a set of opening and closing apps. We will promote
- * animation targets to higher level in the window hierarchy if possible.
- *
- * @param visible {@code true} to get animation targets for opening apps, {@code false} to get
- * animation targets for closing apps.
- * @return {@link WindowContainer}s to be animated.
- */
- @VisibleForTesting
- static ArraySet<WindowContainer> getAnimationTargets(
- ArraySet<ActivityRecord> openingApps, ArraySet<ActivityRecord> closingApps,
- boolean visible) {
-
- // The candidates of animation targets, which might be able to promote to higher level.
- final ArrayDeque<WindowContainer> candidates = new ArrayDeque<>();
- final ArraySet<ActivityRecord> apps = visible ? openingApps : closingApps;
- for (int i = 0; i < apps.size(); ++i) {
- final ActivityRecord app = apps.valueAt(i);
- if (app.shouldApplyAnimation(visible)) {
- candidates.add(app);
- ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
- "Changing app %s visible=%b performLayout=%b",
- app, app.isVisible(), false);
- }
- }
-
- final ArraySet<ActivityRecord> otherApps = visible ? closingApps : openingApps;
- // Ancestors of closing apps while finding animation targets for opening apps, or ancestors
- // of opening apps while finding animation targets for closing apps.
- final ArraySet<WindowContainer> otherAncestors = new ArraySet<>();
- for (int i = 0; i < otherApps.size(); ++i) {
- for (WindowContainer wc = otherApps.valueAt(i); wc != null; wc = wc.getParent()) {
- otherAncestors.add(wc);
- }
- }
-
- // The final animation targets which cannot promote to higher level anymore.
- final ArraySet<WindowContainer> targets = new ArraySet<>();
- final ArrayList<WindowContainer> siblings = new ArrayList<>();
- while (!candidates.isEmpty()) {
- final WindowContainer current = candidates.removeFirst();
- final WindowContainer parent = current.getParent();
- siblings.clear();
- siblings.add(current);
- boolean canPromote = true;
-
- if (isTaskViewTask(current)) {
- // Don't animate an embedded Task in app transition. This is a short term workaround
- // to prevent conflict of surface hierarchy changes between legacy app transition
- // and TaskView (b/205189147).
- // TODO(b/213312721): Remove this once ShellTransition is enabled.
- continue;
- } else if (parent == null || !parent.canCreateRemoteAnimationTarget()
- // We cannot promote the animation on Task's parent when the task is in
- // clearing task in case the animating get stuck when performing the opening
- // task that behind it.
- || (current.asTask() != null && current.asTask().mInRemoveTask)
- // We cannot promote the animation to changing window. This may happen when an
- // activity is open in a TaskFragment that is resizing, while the existing
- // activity in the TaskFragment is reparented to another TaskFragment.
- || parent.isChangingAppTransition()) {
- canPromote = false;
- } else {
- // In case a descendant of the parent belongs to the other group, we cannot promote
- // the animation target from "current" to the parent.
- //
- // Example: Imagine we're checking if we can animate a Task instead of a set of
- // ActivityRecords. In case an activity starts a new activity within a same Task,
- // an ActivityRecord of an existing activity belongs to the opening apps, at the
- // same time, the other ActivityRecord of a new activity belongs to the closing
- // apps. In this case, we cannot promote the animation target to Task level, but
- // need to animate each individual activity.
- //
- // [Task] +- [ActivityRecord1] (in opening apps)
- // +- [ActivityRecord2] (in closing apps)
- if (otherAncestors.contains(parent)) {
- canPromote = false;
- }
-
- // If the current window container is a task with adjacent task set, the both
- // adjacent tasks will be opened or closed together. To get their opening or
- // closing animation target independently, skip promoting their animation targets.
- if (current.asTask() != null && current.asTask().hasAdjacentTask()) {
- canPromote = false;
- }
-
- // Find all siblings of the current WindowContainer in "candidates", move them into
- // a separate list "siblings", and checks if an animation target can be promoted
- // to its parent.
- //
- // We can promote an animation target to its parent if and only if all visible
- // siblings will be animating.
- //
- // Example: Imagine that a Task contains two visible activity record, but only one
- // of them is included in the opening apps and the other belongs to neither opening
- // or closing apps. This happens when an activity launches another translucent
- // activity in the same Task. In this case, we cannot animate Task, but have to
- // animate each activity, otherwise an activity behind the translucent activity also
- // animates.
- //
- // [Task] +- [ActivityRecord1] (visible, in opening apps)
- // +- [ActivityRecord2] (visible, not in opening apps)
- for (int j = 0; j < parent.getChildCount(); ++j) {
- final WindowContainer sibling = parent.getChildAt(j);
- if (candidates.remove(sibling)) {
- if (!isTaskViewTask(sibling)) {
- // Don't animate an embedded Task in app transition. This is a short
- // term workaround to prevent conflict of surface hierarchy changes
- // between legacy app transition and TaskView (b/205189147).
- // TODO(b/213312721): Remove this once ShellTransition is enabled.
- siblings.add(sibling);
- }
- } else if (sibling != current && sibling.isVisible()) {
- canPromote = false;
- }
- }
- }
-
- if (canPromote) {
- candidates.add(parent);
- } else {
- targets.addAll(siblings);
- }
- }
- ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM, "getAnimationTarget in=%s, out=%s",
- apps, targets);
- return targets;
- }
-
- /**
- * Apply an app transition animation based on a set of {@link ActivityRecord}
- *
- * @param openingApps The list of opening apps to which an app transition animation applies.
- * @param closingApps The list of closing apps to which an app transition animation applies.
- * @param transit The current transition type.
- * @param animLp Layout parameters in which an app transition animation runs.
- * @param voiceInteraction {@code true} if one of the apps in this transition belongs to a voice
- * interaction session driving task.
- */
- private void applyAnimations(ArraySet<ActivityRecord> openingApps,
- ArraySet<ActivityRecord> closingApps, @TransitionOldType int transit,
- LayoutParams animLp, boolean voiceInteraction) {
- if (transit == WindowManager.TRANSIT_OLD_UNSET
- || (openingApps.isEmpty() && closingApps.isEmpty())) {
- return;
- }
-
- if (AppTransition.isActivityTransitOld(transit)) {
- final ArrayList<Pair<ActivityRecord, Rect>> closingLetterboxes = new ArrayList();
- for (int i = 0; i < closingApps.size(); ++i) {
- ActivityRecord closingApp = closingApps.valueAt(i);
- if (closingApp.areBoundsLetterboxed()) {
- final Rect insets = closingApp.getLetterboxInsets();
- closingLetterboxes.add(new Pair(closingApp, insets));
- }
- }
-
- for (int i = 0; i < openingApps.size(); ++i) {
- ActivityRecord openingApp = openingApps.valueAt(i);
- if (openingApp.areBoundsLetterboxed()) {
- final Rect openingInsets = openingApp.getLetterboxInsets();
- for (Pair<ActivityRecord, Rect> closingLetterbox : closingLetterboxes) {
- final Rect closingInsets = closingLetterbox.second;
- if (openingInsets.equals(closingInsets)) {
- ActivityRecord closingApp = closingLetterbox.first;
- openingApp.setNeedsLetterboxedAnimation(true);
- closingApp.setNeedsLetterboxedAnimation(true);
- }
- }
- }
- }
- }
-
- final ArraySet<WindowContainer> openingWcs = getAnimationTargets(
- openingApps, closingApps, true /* visible */);
- final ArraySet<WindowContainer> closingWcs = getAnimationTargets(
- openingApps, closingApps, false /* visible */);
- applyAnimations(openingWcs, openingApps, transit, true /* visible */, animLp,
- voiceInteraction);
- applyAnimations(closingWcs, closingApps, transit, false /* visible */, animLp,
- voiceInteraction);
-
- for (int i = 0; i < openingApps.size(); ++i) {
- openingApps.valueAtUnchecked(i).mOverrideTaskTransition = false;
- }
- for (int i = 0; i < closingApps.size(); ++i) {
- closingApps.valueAtUnchecked(i).mOverrideTaskTransition = false;
- }
-
- final AccessibilityController accessibilityController =
- mDisplayContent.mWmService.mAccessibilityController;
- if (accessibilityController.hasCallbacks()) {
- accessibilityController.onAppWindowTransition(mDisplayContent.getDisplayId(), transit);
- }
- }
-
- private void handleOpeningApps() {
- final ArraySet<ActivityRecord> openingApps = mDisplayContent.mOpeningApps;
- final int appsCount = openingApps.size();
-
- for (int i = 0; i < appsCount; i++) {
- final ActivityRecord app = openingApps.valueAt(i);
- ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, "Now opening app %s", app);
-
- app.commitVisibility(true /* visible */, false /* performLayout */);
-
- // In case a trampoline activity is used, it can happen that a new ActivityRecord is
- // added and a new app transition starts before the previous app transition animation
- // ends. So we cannot simply use app.isAnimating(PARENTS) to determine if the app must
- // to be added to the list of tokens to be notified of app transition complete.
- final WindowContainer wc = app.getAnimatingContainer(PARENTS,
- ANIMATION_TYPE_APP_TRANSITION);
- if (wc == null || !wc.getAnimationSources().contains(app)) {
- // This token isn't going to be animating. Add it to the list of tokens to
- // be notified of app transition complete since the notification will not be
- // sent be the app window animator.
- mDisplayContent.mNoAnimationNotifyOnTransitionFinished.add(app.token);
- }
- app.updateReportedVisibilityLocked();
- app.showAllWindowsLocked();
-
- if (mDisplayContent.mAppTransition.isNextAppTransitionThumbnailUp()) {
- app.attachThumbnailAnimation();
- } else if (mDisplayContent.mAppTransition.isNextAppTransitionOpenCrossProfileApps()) {
- app.attachCrossProfileAppsThumbnailAnimation();
- }
- }
- }
-
- private void handleClosingApps() {
- final ArraySet<ActivityRecord> closingApps = mDisplayContent.mClosingApps;
- final int appsCount = closingApps.size();
-
- for (int i = 0; i < appsCount; i++) {
- final ActivityRecord app = closingApps.valueAt(i);
- ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, "Now closing app %s", app);
-
- app.commitVisibility(false /* visible */, false /* performLayout */);
- app.updateReportedVisibilityLocked();
- // Force the allDrawn flag, because we want to start
- // this guy's animations regardless of whether it's
- // gotten drawn.
- app.allDrawn = true;
- // Ensure that apps that are mid-starting are also scheduled to have their
- // starting windows removed after the animation is complete
- if (app.mStartingWindow != null && !app.mStartingWindow.mAnimatingExit) {
- app.removeStartingWindow();
- }
-
- if (mDisplayContent.mAppTransition.isNextAppTransitionThumbnailDown()) {
- app.attachThumbnailAnimation();
- }
- }
- }
-
- private void handleClosingChangingContainers() {
- final ArrayMap<WindowContainer, Rect> containers =
- mDisplayContent.mClosingChangingContainers;
- while (!containers.isEmpty()) {
- final WindowContainer container = containers.keyAt(0);
- containers.remove(container);
-
- // For closing changing windows that are part of the transition, they should have been
- // removed from mClosingChangingContainers in WindowContainer#getAnimationAdapter()
- // If the closing changing TaskFragment is not part of the transition, update its
- // surface after removing it from mClosingChangingContainers.
- final TaskFragment taskFragment = container.asTaskFragment();
- if (taskFragment != null) {
- taskFragment.updateOrganizedTaskFragmentSurface();
- }
- }
- }
-
- private void handleChangingApps(@TransitionOldType int transit) {
- final ArraySet<WindowContainer> apps = mDisplayContent.mChangingContainers;
- final int appsCount = apps.size();
- for (int i = 0; i < appsCount; i++) {
- WindowContainer wc = apps.valueAt(i);
- ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, "Now changing app %s", wc);
- wc.applyAnimation(null, transit, true, false, null /* sources */);
- }
- }
-
- private boolean transitionGoodToGo(ArraySet<? extends WindowContainer> apps,
- ArrayMap<WindowContainer, Integer> outReasons) {
- ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
- "Checking %d opening apps (timeout=%b)...", apps.size(),
- mDisplayContent.mAppTransition.isTimeout());
- if (mDisplayContent.mAppTransition.isTimeout()) {
- return true;
- }
-
- for (int i = 0; i < apps.size(); i++) {
- WindowContainer wc = apps.valueAt(i);
- final ActivityRecord activity = getAppFromContainer(wc);
- if (activity == null) {
- continue;
- }
- ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
- "Check opening app=%s: allDrawn=%b startingDisplayed=%b "
- + "startingMoved=%b isRelaunching()=%b startingWindow=%s",
- activity, activity.allDrawn, activity.isStartingWindowDisplayed(),
- activity.startingMoved, activity.isRelaunching(),
- activity.mStartingWindow);
- final boolean allDrawn = activity.allDrawn && !activity.isRelaunching();
- if (!allDrawn && !activity.isStartingWindowDisplayed() && !activity.startingMoved) {
- return false;
- }
- if (allDrawn) {
- outReasons.put(activity, APP_TRANSITION_WINDOWS_DRAWN);
- } else {
- outReasons.put(activity,
- activity.mStartingData instanceof SplashScreenStartingData
- ? APP_TRANSITION_SPLASH_SCREEN
- : APP_TRANSITION_SNAPSHOT);
- }
- }
-
- // We also need to wait for the specs to be fetched, if needed.
- if (mDisplayContent.mAppTransition.isFetchingAppTransitionsSpecs()) {
- ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, "isFetchingAppTransitionSpecs=true");
- return false;
- }
-
- if (!mDisplayContent.mUnknownAppVisibilityController.allResolved()) {
- ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, "unknownApps is not empty: %s",
- mDisplayContent.mUnknownAppVisibilityController.getDebugMessage());
- return false;
- }
-
- // If the wallpaper is visible, we need to check it's ready too.
- return !mWallpaperControllerLocked.isWallpaperVisible()
- || mWallpaperControllerLocked.wallpaperTransitionReady();
- }
-
- private boolean transitionGoodToGoForTaskFragments() {
- if (mDisplayContent.mAppTransition.isTimeout()) {
- return true;
- }
-
- // Check all Tasks in this transition. This is needed because new TaskFragment created for
- // launching activity may not be in the tracking lists, but we still want to wait for the
- // activity launch to start the transition.
- final ArraySet<Task> rootTasks = new ArraySet<>();
- for (int i = mDisplayContent.mOpeningApps.size() - 1; i >= 0; i--) {
- rootTasks.add(mDisplayContent.mOpeningApps.valueAt(i).getRootTask());
- }
- for (int i = mDisplayContent.mClosingApps.size() - 1; i >= 0; i--) {
- rootTasks.add(mDisplayContent.mClosingApps.valueAt(i).getRootTask());
- }
- for (int i = mDisplayContent.mChangingContainers.size() - 1; i >= 0; i--) {
- rootTasks.add(
- findRootTaskFromContainer(mDisplayContent.mChangingContainers.valueAt(i)));
- }
-
- // Organized TaskFragment can be empty for two situations:
- // 1. New created and is waiting for Activity launch. In this case, we want to wait for
- // the Activity launch to trigger the transition.
- // 2. Last Activity is just removed. In this case, we want to wait for organizer to
- // remove the TaskFragment because it may also want to change other TaskFragments in
- // the same transition.
- for (int i = rootTasks.size() - 1; i >= 0; i--) {
- final Task rootTask = rootTasks.valueAt(i);
- if (rootTask == null) {
- // It is possible that one activity may have been removed from the hierarchy. No
- // need to check for this case.
- continue;
- }
- final boolean notReady = rootTask.forAllLeafTaskFragments(taskFragment -> {
- if (!taskFragment.isReadyToTransit()) {
- ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, "Organized TaskFragment is not ready= %s",
- taskFragment);
- return true;
- }
- return false;
- });
- if (notReady) {
- return false;
- }
- }
- return true;
- }
-
- /**
- * Identifies whether the current transition occurs within a single task or not. This is used
- * to determine whether animations should be clipped to the task bounds instead of root task
- * bounds.
- */
- @VisibleForTesting
- boolean isTransitWithinTask(@TransitionOldType int transit, Task task) {
- if (task == null
- || !mDisplayContent.mChangingContainers.isEmpty()) {
- // if there is no task, then we can't constrain to the task.
- // if anything is changing, it can animate outside its task.
- return false;
- }
- if (!(transit == TRANSIT_OLD_ACTIVITY_OPEN
- || transit == TRANSIT_OLD_ACTIVITY_CLOSE
- || transit == TRANSIT_OLD_ACTIVITY_RELAUNCH)) {
- // only activity-level transitions will be within-task.
- return false;
- }
- // check that all components are in the task.
- for (ActivityRecord activity : mDisplayContent.mOpeningApps) {
- Task activityTask = activity.getTask();
- if (activityTask != task) {
- return false;
- }
- }
- for (ActivityRecord activity : mDisplayContent.mClosingApps) {
- if (activity.getTask() != task) {
- return false;
- }
- }
- return true;
- }
-
- private static boolean canBeWallpaperTarget(ArraySet<ActivityRecord> apps) {
- for (int i = apps.size() - 1; i >= 0; i--) {
- if (apps.valueAt(i).windowsCanBeWallpaperTarget()) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * Finds the top app in a list of apps, using its {@link ActivityRecord#getPrefixOrderIndex} to
- * compare z-order.
- *
- * @param apps The list of apps to search.
- * @param ignoreInvisible If set to true, ignores apps that are not
- * {@link ActivityRecord#isVisible}.
- * @return The top {@link ActivityRecord}.
- */
- private static ActivityRecord getTopApp(ArraySet<? extends WindowContainer> apps,
- boolean ignoreInvisible) {
- int topPrefixOrderIndex = Integer.MIN_VALUE;
- ActivityRecord topApp = null;
- for (int i = apps.size() - 1; i >= 0; i--) {
- final ActivityRecord app = getAppFromContainer(apps.valueAt(i));
- if (app == null || ignoreInvisible && !app.isVisible()) {
- continue;
- }
- final int prefixOrderIndex = app.getPrefixOrderIndex();
- if (prefixOrderIndex > topPrefixOrderIndex) {
- topPrefixOrderIndex = prefixOrderIndex;
- topApp = app;
- }
- }
- return topApp;
- }
-}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index bf54c73..682f3d8 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -226,7 +226,6 @@
import android.view.InsetsState;
import android.view.MagnificationSpec;
import android.view.PrivacyIndicatorBounds;
-import android.view.RemoteAnimationDefinition;
import android.view.RoundedCorners;
import android.view.Surface;
import android.view.Surface.Rotation;
@@ -367,8 +366,6 @@
private int mMaxUiWidth = 0;
final AppTransition mAppTransition;
- final AppTransitionController mAppTransitionController;
- boolean mSkipAppTransitionAnimation = false;
final ArraySet<ActivityRecord> mOpeningApps = new ArraySet<>();
final ArraySet<ActivityRecord> mClosingApps = new ArraySet<>();
@@ -547,9 +544,6 @@
// TODO(multi-display): remove some of the usages.
boolean isDefaultDisplay;
- /** Indicates whether any presentation is shown on this display. */
- boolean mIsPresenting;
-
/** Save allocating when calculating rects */
private final Rect mTmpRect = new Rect();
private final Region mTmpRegion = new Region();
@@ -1164,7 +1158,6 @@
mAppTransition = new AppTransition(mWmService.mContext, mWmService, this);
mAppTransition.registerListenerLocked(mWmService.mActivityManagerAppTransitionNotifier);
mAppTransition.registerListenerLocked(mFixedRotationTransitionListener);
- mAppTransitionController = new AppTransitionController(mWmService, this);
mTransitionController.registerLegacyListener(mFixedRotationTransitionListener);
mUnknownAppVisibilityController = new UnknownAppVisibilityController(mWmService, this);
mRemoteDisplayChangeController = new RemoteDisplayChangeController(this);
@@ -1556,10 +1549,6 @@
return mInputMethodSurfaceParentWindow;
}
- void registerRemoteAnimations(RemoteAnimationDefinition definition) {
- mAppTransitionController.registerRemoteAnimations(definition);
- }
-
void reconfigureDisplayLocked() {
if (!isReady()) {
return;
@@ -5607,20 +5596,6 @@
}
/**
- * Transfer app transition from other display to this display.
- *
- * @param from Display from where the app transition is transferred.
- *
- * TODO(new-app-transition): Remove this once the shell handles app transition.
- */
- void transferAppTransitionFrom(DisplayContent from) {
- final boolean prepared = mAppTransition.transferFrom(from.mAppTransition);
- if (prepared && okToAnimate()) {
- mSkipAppTransitionAnimation = false;
- }
- }
-
- /**
* @deprecated new transition should use {@link #requestTransitionAndLegacyPrepare(int, int)}
*/
@Deprecated
@@ -5634,10 +5609,7 @@
@Deprecated
void prepareAppTransition(@WindowManager.TransitionType int transit,
@WindowManager.TransitionFlags int flags) {
- final boolean prepared = mAppTransition.prepareAppTransition(transit, flags);
- if (prepared && okToAnimate() && transit != TRANSIT_NONE) {
- mSkipAppTransitionAnimation = false;
- }
+ mAppTransition.prepareAppTransition(transit, flags);
}
/**
diff --git a/services/core/java/com/android/server/wm/PresentationController.java b/services/core/java/com/android/server/wm/PresentationController.java
new file mode 100644
index 0000000..6946343
--- /dev/null
+++ b/services/core/java/com/android/server/wm/PresentationController.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static com.android.window.flags.Flags.enablePresentationForConnectedDisplays;
+
+import android.annotation.NonNull;
+import android.util.IntArray;
+
+import com.android.internal.protolog.ProtoLog;
+import com.android.internal.protolog.WmProtoLogGroups;
+
+/**
+ * Manages presentation windows.
+ */
+class PresentationController {
+
+ // TODO(b/395475549): Add support for display add/remove, and activity move across displays.
+ private final IntArray mPresentingDisplayIds = new IntArray();
+
+ PresentationController() {}
+
+ private boolean isPresenting(int displayId) {
+ return mPresentingDisplayIds.contains(displayId);
+ }
+
+ boolean shouldOccludeActivities(int displayId) {
+ // All activities on the presenting display must be hidden so that malicious apps can't do
+ // tap jacking (b/391466268).
+ // For now, this should only be applied to external displays because presentations can only
+ // be shown on them.
+ // TODO(b/390481621): Disallow a presentation from covering its controlling activity so that
+ // the presentation won't stop its controlling activity.
+ return enablePresentationForConnectedDisplays() && isPresenting(displayId);
+ }
+
+ void onPresentationAdded(@NonNull WindowState win) {
+ final int displayId = win.getDisplayId();
+ if (isPresenting(displayId)) {
+ return;
+ }
+ ProtoLog.v(WmProtoLogGroups.WM_DEBUG_PRESENTATION, "Presentation added to display %d: %s",
+ win.getDisplayId(), win);
+ mPresentingDisplayIds.add(win.getDisplayId());
+ if (enablePresentationForConnectedDisplays()) {
+ // A presentation hides all activities behind on the same display.
+ win.mDisplayContent.ensureActivitiesVisible(/*starting=*/ null,
+ /*notifyClients=*/ true);
+ }
+ win.mWmService.mDisplayManagerInternal.onPresentation(displayId, /*isShown=*/ true);
+ }
+
+ void onPresentationRemoved(@NonNull WindowState win) {
+ final int displayId = win.getDisplayId();
+ if (!isPresenting(displayId)) {
+ return;
+ }
+ ProtoLog.v(WmProtoLogGroups.WM_DEBUG_PRESENTATION,
+ "Presentation removed from display %d: %s", win.getDisplayId(), win);
+ // TODO(b/393945496): Make sure that there's one presentation at most per display.
+ final int displayIdIndex = mPresentingDisplayIds.indexOf(displayId);
+ if (displayIdIndex != -1) {
+ mPresentingDisplayIds.remove(displayIdIndex);
+ }
+ if (enablePresentationForConnectedDisplays()) {
+ // A presentation hides all activities behind on the same display.
+ win.mDisplayContent.ensureActivitiesVisible(/*starting=*/ null,
+ /*notifyClients=*/ true);
+ }
+ win.mWmService.mDisplayManagerInternal.onPresentation(displayId, /*isShown=*/ false);
+ }
+}
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 95d9b3e..c93efd3 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -35,7 +35,6 @@
import static android.view.WindowManager.TRANSIT_NONE;
import static android.view.WindowManager.TRANSIT_PIP;
import static android.view.WindowManager.TRANSIT_SLEEP;
-import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_WAKE;
import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_FOCUS_LIGHT;
@@ -68,7 +67,6 @@
import static com.android.server.wm.ActivityTaskSupervisor.ON_TOP;
import static com.android.server.wm.ActivityTaskSupervisor.dumpHistoryList;
import static com.android.server.wm.ActivityTaskSupervisor.printThisActivity;
-import static com.android.server.wm.KeyguardController.KEYGUARD_SLEEP_TOKEN_TAG;
import static com.android.server.wm.RootWindowContainerProto.IS_HOME_RECENTS_COMPONENT;
import static com.android.server.wm.RootWindowContainerProto.KEYGUARD_CONTROLLER;
import static com.android.server.wm.RootWindowContainerProto.WINDOW_CONTAINER;
@@ -803,8 +801,6 @@
mWmService.mAtmService.mTaskFragmentOrganizerController.dispatchPendingEvents();
mWmService.mSyncEngine.onSurfacePlacement();
- checkAppTransitionReady(surfacePlacer);
-
mWmService.mAtmService.mBackNavigationController
.checkAnimationReady(defaultDisplay.mWallpaperController);
@@ -898,38 +894,6 @@
if (DEBUG_WINDOW_TRACE) Slog.e(TAG, "performSurfacePlacementInner exit");
}
- private void checkAppTransitionReady(WindowSurfacePlacer surfacePlacer) {
- // Trace all displays app transition by Z-order for pending layout change.
- for (int i = mChildren.size() - 1; i >= 0; --i) {
- final DisplayContent curDisplay = mChildren.get(i);
-
- // If we are ready to perform an app transition, check through all of the app tokens
- // to be shown and see if they are ready to go.
- if (curDisplay.mAppTransition.isReady()) {
- // handleAppTransitionReady may modify curDisplay.pendingLayoutChanges.
- curDisplay.mAppTransitionController.handleAppTransitionReady();
- if (DEBUG_LAYOUT_REPEATS) {
- surfacePlacer.debugLayoutRepeats("after handleAppTransitionReady",
- curDisplay.pendingLayoutChanges);
- }
- }
-
- if (curDisplay.mAppTransition.isRunning() && !curDisplay.isAppTransitioning()) {
- // We have finished the animation of an app transition. To do this, we have
- // delayed a lot of operations like showing and hiding apps, moving apps in
- // Z-order, etc.
- // The app token list reflects the correct Z-order, but the window list may now
- // be out of sync with it. So here we will just rebuild the entire app window
- // list. Fun!
- curDisplay.handleAnimatingStoppedAndTransition();
- if (DEBUG_LAYOUT_REPEATS) {
- surfacePlacer.debugLayoutRepeats("after handleAnimStopAndXitionLock",
- curDisplay.pendingLayoutChanges);
- }
- }
- }
- }
-
private void applySurfaceChangesTransaction() {
// TODO(multi-display): Support these features on secondary screens.
final DisplayContent defaultDc = mDefaultDisplay;
@@ -2266,20 +2230,6 @@
// Ensure the leash of new task is in sync with its current bounds after reparent.
rootTask.maybeApplyLastRecentsAnimationTransaction();
-
- // In the case of this activity entering PIP due to it being moved to the back,
- // the old activity would have a TRANSIT_TASK_TO_BACK transition that needs to be
- // ran. But, since its visibility did not change (note how it was STOPPED/not
- // visible, and with it now at the back stack, it remains not visible), the logic to
- // add the transition is automatically skipped. We then add this activity manually
- // to the list of apps being closed, and request its transition to be ran.
- final ActivityRecord oldTopActivity = task.getTopMostActivity();
- if (oldTopActivity != null && oldTopActivity.isState(STOPPED)
- && task.getDisplayContent().mAppTransition.containsTransitRequest(
- TRANSIT_TO_BACK)) {
- task.getDisplayContent().mClosingApps.add(oldTopActivity);
- oldTopActivity.mRequestForceTransition = true;
- }
}
// TODO(remove-legacy-transit): Move this to the `singleActivity` case when removing
@@ -2958,20 +2908,6 @@
display.mAllSleepTokens.remove(token);
if (display.mAllSleepTokens.isEmpty()) {
mService.updateSleepIfNeededLocked();
- // Assuming no lock screen is set and a user launches an activity, turns off the screen
- // and turn on the screen again, then the launched activity should be displayed on the
- // screen without app transition animation. When the screen turns on, both keyguard
- // sleep token and display off sleep token are removed, but the order is
- // non-deterministic.
- // Note: Display#mSkipAppTransitionAnimation will be ignored when keyguard related
- // transition exists, so this affects only when no lock screen is set. Otherwise
- // keyguard going away animation will be played.
- // See also AppTransitionController#getTransitCompatType for more details.
- if ((!mTaskSupervisor.getKeyguardController().isKeyguardOccluded(display.mDisplayId)
- && token.mTag.equals(KEYGUARD_SLEEP_TOKEN_TAG))
- || token.mTag.equals(DISPLAY_OFF_SLEEP_TOKEN_TAG)) {
- display.mSkipAppTransitionAnimation = true;
- }
}
}
diff --git a/services/core/java/com/android/server/wm/SnapshotController.java b/services/core/java/com/android/server/wm/SnapshotController.java
index dcdffa4..2664dcd 100644
--- a/services/core/java/com/android/server/wm/SnapshotController.java
+++ b/services/core/java/com/android/server/wm/SnapshotController.java
@@ -72,11 +72,6 @@
mActivitySnapshotController.notifyAppVisibilityChanged(appWindowToken, visible);
}
- // For legacy transition, which won't support activity snapshot
- void onTransitionStarting(DisplayContent displayContent) {
- mTaskSnapshotController.handleClosingApps(displayContent.mClosingApps);
- }
-
// For shell transition, record snapshots before transaction start.
void onTransactionReady(@WindowManager.TransitionType int type,
ArrayList<Transition.ChangeInfo> changeInfos) {
diff --git a/services/core/java/com/android/server/wm/SurfaceAnimator.java b/services/core/java/com/android/server/wm/SurfaceAnimator.java
index 3dfff39..c5425fe 100644
--- a/services/core/java/com/android/server/wm/SurfaceAnimator.java
+++ b/services/core/java/com/android/server/wm/SurfaceAnimator.java
@@ -132,10 +132,7 @@
animationFinishCallback.onAnimationFinished(type, anim);
}
};
- // If both the Animatable and AnimationAdapter requests to be deferred, only the
- // first one will be called.
- if (!(mAnimatable.shouldDeferAnimationFinish(resetAndInvokeFinish)
- || anim.shouldDeferAnimationFinish(resetAndInvokeFinish))) {
+ if (!anim.shouldDeferAnimationFinish(resetAndInvokeFinish)) {
resetAndInvokeFinish.run();
}
mAnimationFinished = true;
@@ -639,23 +636,5 @@
* @return The height of the surface to be animated.
*/
int getSurfaceHeight();
-
- /**
- * Gets called when the animation is about to finish and gives the client the opportunity to
- * defer finishing the animation, i.e. it keeps the leash around until the client calls
- * {@link #cancelAnimation}.
- * <p>
- * {@link AnimationAdapter} has a similar method which is called only if this method returns
- * false. This mean that if both this {@link Animatable} and the {@link AnimationAdapter}
- * request to be deferred, this method is the sole responsible to call
- * endDeferFinishCallback. On the other hand, the animation finish might still be deferred
- * if this method return false and the one from the {@link AnimationAdapter} returns true.
- *
- * @param endDeferFinishCallback The callback to call when defer finishing should be ended.
- * @return Whether the client would like to defer the animation finish.
- */
- default boolean shouldDeferAnimationFinish(Runnable endDeferFinishCallback) {
- return false;
- }
}
}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index f75e717..3abab8b 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -508,9 +508,6 @@
*/
boolean mAllowForceResizeOverride = true;
- private final AnimatingActivityRegistry mAnimatingActivityRegistry =
- new AnimatingActivityRegistry();
-
private static final int TRANSLUCENT_TIMEOUT_MSG = FIRST_ACTIVITY_TASK_MSG + 1;
private final Handler mHandler;
@@ -1122,17 +1119,6 @@
// already ran fully within super.onParentChanged
updateTaskOrganizerState();
- // TODO(b/168037178): The check for null display content and setting it to null doesn't
- // really make sense here...
-
- // TODO(b/168037178): This is mostly taking care of the case where the stask is removing
- // from the display, so we should probably consolidate it there instead.
-
- if (getParent() == null && mDisplayContent != null) {
- mDisplayContent = null;
- mWmService.mWindowPlacerLocked.requestTraversal();
- }
-
if (oldParent != null) {
final Task oldParentTask = oldParent.asTask();
if (oldParentTask != null) {
@@ -1185,9 +1171,6 @@
}
mRootWindowContainer.updateUIDsPresentOnDisplay();
-
- // Ensure all animations are finished at same time in split-screen mode.
- forAllActivities(ActivityRecord::updateAnimatingActivityRegistry);
}
@Override
@@ -2770,6 +2753,7 @@
}
super.removeImmediately();
+ mDisplayContent = null;
mRemoving = false;
}
@@ -3345,13 +3329,6 @@
mLastSurfaceShowing = show;
}
- @Override
- void dump(PrintWriter pw, String prefix, boolean dumpAll) {
- super.dump(pw, prefix, dumpAll);
- mAnimatingActivityRegistry.dump(pw, "AnimatingApps:", prefix);
- }
-
-
/**
* Fills in a {@link TaskInfo} with information from this task. Note that the base intent in the
* task info will not include any extras or clip data.
@@ -6313,10 +6290,6 @@
return mDisplayContent.getDisplayInfo();
}
- AnimatingActivityRegistry getAnimatingActivityRegistry() {
- return mAnimatingActivityRegistry;
- }
-
private Rect getRawBounds() {
return super.getBounds();
}
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 324852d..97a1a34 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -394,6 +394,12 @@
*/
private boolean mAllowTransitionWhenEmpty;
+ /**
+ * Specifies which configuration changes should trigger TaskFragment info changed callbacks.
+ * Only system TaskFragment organizers are allowed to set this value.
+ */
+ private @ActivityInfo.Config int mConfigurationChangeMaskForOrganizer;
+
/** When set, will force the task to report as invisible. */
static final int FLAG_FORCE_HIDDEN_FOR_PINNED_TASK = 1;
static final int FLAG_FORCE_HIDDEN_FOR_TASK_ORG = 1 << 1;
@@ -656,6 +662,17 @@
mAllowTransitionWhenEmpty = allowTransitionWhenEmpty;
}
+ void setConfigurationChangeMaskForOrganizer(@ActivityInfo.Config int mask) {
+ // Only system organizers are allowed to set configuration change mask.
+ if (mTaskFragmentOrganizerController.isSystemOrganizer(mTaskFragmentOrganizer.asBinder())) {
+ mConfigurationChangeMaskForOrganizer = mask;
+ }
+ }
+
+ @ActivityInfo.Config int getConfigurationChangeMaskForOrganizer() {
+ return mConfigurationChangeMaskForOrganizer;
+ }
+
/** @see #mIsolatedNav */
boolean isIsolatedNav() {
return isEmbedded() && mIsolatedNav;
diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
index e63107c..ae329d7 100644
--- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
@@ -349,8 +349,10 @@
// Check if the info is different from the last reported info.
final TaskFragmentInfo info = tf.getTaskFragmentInfo();
final TaskFragmentInfo lastInfo = mLastSentTaskFragmentInfos.get(tf);
- if (info.equalsForTaskFragmentOrganizer(lastInfo) && configurationsAreEqualForOrganizer(
- info.getConfiguration(), lastInfo.getConfiguration())) {
+ final int configurationChangeMask = tf.getConfigurationChangeMaskForOrganizer();
+ if (info.equalsForTaskFragmentOrganizer(lastInfo)
+ && configurationsAreEqualForOrganizer(info.getConfiguration(),
+ lastInfo.getConfiguration(), configurationChangeMask)) {
return null;
}
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java
index 432ed1d..8a93772 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotController.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java
@@ -113,27 +113,6 @@
enableLowResSnapshots, lowResScaleFactor, use16BitFormat);
}
- // Still needed for legacy transition.(AppTransitionControllerTest)
- void handleClosingApps(ArraySet<ActivityRecord> closingApps) {
- if (shouldDisableSnapshots()) {
- return;
- }
- // We need to take a snapshot of the task if and only if all activities of the task are
- // either closing or hidden.
- mTmpTasks.clear();
- for (int i = closingApps.size() - 1; i >= 0; i--) {
- final ActivityRecord activity = closingApps.valueAt(i);
- if (activity.isActivityTypeHome()) continue;
- final Task task = activity.getTask();
- if (task == null) continue;
-
- getClosingTasksInner(task, mTmpTasks);
- }
- snapshotTasks(mTmpTasks);
- mTmpTasks.clear();
- mSkipClosingAppSnapshotTasks.clear();
- }
-
/**
* Adds the given {@param tasks} to the list of tasks which should not have their snapshots
* taken upon the next processing of the set of closing apps. The caller is responsible for
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 55c2668..7af542f 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -3359,7 +3359,7 @@
private Animation loadAnimation(WindowManager.LayoutParams lp, int transit, boolean enter,
boolean isVoiceInteraction) {
- if (AppTransitionController.isTaskViewTask(this) || (isOrganized()
+ if ((isOrganized()
// TODO(b/161711458): Clean-up when moved to shell.
&& getWindowingMode() != WINDOWING_MODE_FULLSCREEN
&& getWindowingMode() != WINDOWING_MODE_FREEFORM
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index c54e7c1..d699a68 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -157,7 +157,6 @@
import static com.android.server.wm.WindowManagerServiceDumpProto.ROOT_WINDOW_CONTAINER;
import static com.android.server.wm.WindowManagerServiceDumpProto.WINDOW_FRAMES_VALID;
import static com.android.window.flags.Flags.enableDisplayFocusInShellTransitions;
-import static com.android.window.flags.Flags.enablePresentationForConnectedDisplays;
import static com.android.window.flags.Flags.multiCrop;
import static com.android.window.flags.Flags.setScPropertiesInClient;
@@ -348,7 +347,6 @@
import com.android.server.DisplayThread;
import com.android.server.FgThread;
import com.android.server.LocalServices;
-import com.android.server.SystemConfig;
import com.android.server.UiThread;
import com.android.server.Watchdog;
import com.android.server.input.InputManagerService;
@@ -450,11 +448,6 @@
/**
* Use WMShell for app transition.
*/
- private static final String ENABLE_SHELL_TRANSITIONS = "persist.wm.debug.shell_transit";
-
- /**
- * @see #ENABLE_SHELL_TRANSITIONS
- */
public static final boolean sEnableShellTransitions = getShellTransitEnabled();
/**
@@ -503,6 +496,8 @@
final StartingSurfaceController mStartingSurfaceController;
+ final PresentationController mPresentationController;
+
private final IVrStateCallbacks mVrStateCallbacks = new IVrStateCallbacks.Stub() {
@Override
public void onVrStateChanged(boolean enabled) {
@@ -1433,6 +1428,7 @@
setGlobalShadowSettings();
mAnrController = new AnrController(this);
mStartingSurfaceController = new StartingSurfaceController(this);
+ mPresentationController = new PresentationController();
mBlurController = new BlurController(mContext, mPowerManager);
mTaskFpsCallbackController = new TaskFpsCallbackController(mContext);
@@ -1937,16 +1933,8 @@
}
outSizeCompatScale[0] = win.getCompatScaleForClient();
- if (res >= ADD_OKAY
- && (type == TYPE_PRESENTATION || type == TYPE_PRIVATE_PRESENTATION)) {
- displayContent.mIsPresenting = true;
- if (enablePresentationForConnectedDisplays()) {
- // A presentation hides all activities behind on the same display.
- displayContent.ensureActivitiesVisible(/*starting=*/ null,
- /*notifyClients=*/ true);
- }
- mDisplayManagerInternal.onPresentation(displayContent.getDisplay().getDisplayId(),
- /*isShown=*/ true);
+ if (res >= ADD_OKAY && win.isPresentation()) {
+ mPresentationController.onPresentationAdded(win);
}
}
@@ -10317,11 +10305,6 @@
}
private static boolean getShellTransitEnabled() {
- android.content.pm.FeatureInfo autoFeature = SystemConfig.getInstance()
- .getAvailableFeatures().get(PackageManager.FEATURE_AUTOMOTIVE);
- if (autoFeature != null && autoFeature.version >= 0) {
- return SystemProperties.getBoolean(ENABLE_SHELL_TRANSITIONS, true);
- }
return true;
}
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 924b9de..3b6a4dc 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -2455,10 +2455,28 @@
/** Whether the configuration changes are important to report back to an organizer. */
static boolean configurationsAreEqualForOrganizer(
Configuration newConfig, @Nullable Configuration oldConfig) {
+ return configurationsAreEqualForOrganizer(newConfig, oldConfig, 0 /* additionalMask */);
+ }
+
+ /**
+ * Whether the configuration changes are important to report back to an organizer.
+ *
+ * @param newConfig the new configuration
+ * @param oldConfig the old configuration
+ * @param additionalMask specifies additional configuration changes that the organizer is
+ * interested in. If the configuration change matches any bit in the mask,
+ * {@code false} is returned.
+ */
+ static boolean configurationsAreEqualForOrganizer(
+ Configuration newConfig, @Nullable Configuration oldConfig,
+ @ActivityInfo.Config int additionalMask) {
if (oldConfig == null) {
return false;
}
int cfgChanges = newConfig.diff(oldConfig);
+ if ((cfgChanges & additionalMask) != 0) {
+ return false;
+ }
final int winCfgChanges = (cfgChanges & ActivityInfo.CONFIG_WINDOW_CONFIGURATION) != 0
? (int) newConfig.windowConfiguration.diff(oldConfig.windowConfiguration,
true /* compareUndefined */) : 0;
@@ -2665,6 +2683,8 @@
ownerActivity.getUid(), ownerActivity.info.processName);
if (mTaskFragmentOrganizerController.isSystemOrganizer(organizerToken.asBinder())) {
taskFragment.setOverrideOrientation(creationParams.getOverrideOrientation());
+ taskFragment.setConfigurationChangeMaskForOrganizer(
+ creationParams.getConfigurationChangeMask());
}
final int position;
if (creationParams.getPairedPrimaryFragmentToken() != null) {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 81895c4..5897241 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -182,7 +182,6 @@
import static com.android.server.wm.WindowStateProto.VIEW_VISIBILITY;
import static com.android.server.wm.WindowStateProto.WINDOW_CONTAINER;
import static com.android.server.wm.WindowStateProto.WINDOW_FRAMES;
-import static com.android.window.flags.Flags.enablePresentationForConnectedDisplays;
import static com.android.window.flags.Flags.surfaceTrustedOverlay;
import android.annotation.CallSuper;
@@ -2300,15 +2299,8 @@
final int type = mAttrs.type;
- if (type == TYPE_PRESENTATION || type == TYPE_PRIVATE_PRESENTATION) {
- // TODO(b/393945496): Make sure that there's one presentation at most per display.
- dc.mIsPresenting = false;
- if (enablePresentationForConnectedDisplays()) {
- // A presentation hides all activities behind on the same display.
- dc.ensureActivitiesVisible(/*starting=*/ null, /*notifyClients=*/ true);
- }
- mWmService.mDisplayManagerInternal.onPresentation(dc.getDisplay().getDisplayId(),
- /*isShown=*/ false);
+ if (isPresentation()) {
+ mWmService.mPresentationController.onPresentationRemoved(this);
}
// Check if window provides non decor insets before clearing its provided insets.
final boolean windowProvidesDisplayDecorInsets = providesDisplayDecorInsets();
@@ -3337,6 +3329,10 @@
}
}
+ boolean isPresentation() {
+ return mAttrs.type == TYPE_PRESENTATION || mAttrs.type == TYPE_PRIVATE_PRESENTATION;
+ }
+
private boolean isOnVirtualDisplay() {
return getDisplayContent().mDisplay.getType() == Display.TYPE_VIRTUAL;
}
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 883cab0..f07e672 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -668,8 +668,10 @@
}
// TODO(b/383092013): Add topology validation
- mInputManager->getChoreographer().setDisplayTopology(
- android_hardware_display_DisplayTopologyGraph_toNative(env, topologyGraph));
+ const DisplayTopologyGraph displayTopology =
+ android_hardware_display_DisplayTopologyGraph_toNative(env, topologyGraph);
+ mInputManager->getDispatcher().setDisplayTopology(displayTopology);
+ mInputManager->getChoreographer().setDisplayTopology(displayTopology);
}
base::Result<std::unique_ptr<InputChannel>> NativeInputManager::createInputChannel(
diff --git a/services/credentials/java/com/android/server/credentials/RequestSession.java b/services/credentials/java/com/android/server/credentials/RequestSession.java
index 2aa0c6b..440eae5 100644
--- a/services/credentials/java/com/android/server/credentials/RequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/RequestSession.java
@@ -446,7 +446,7 @@
@Override
public void binderDied() {
Slog.d(TAG, "Client binder died - clearing session");
- finishSession(isUiWaitingForData(), ApiStatus.CLIENT_CANCELED.getMetricCode());
+ finishSession(isUiWaitingForData(), ApiStatus.BINDER_DIED.getMetricCode());
}
}
diff --git a/services/credentials/java/com/android/server/credentials/metrics/ApiStatus.java b/services/credentials/java/com/android/server/credentials/metrics/ApiStatus.java
index ece729f..c21e645 100644
--- a/services/credentials/java/com/android/server/credentials/metrics/ApiStatus.java
+++ b/services/credentials/java/com/android/server/credentials/metrics/ApiStatus.java
@@ -16,6 +16,7 @@
package com.android.server.credentials.metrics;
+import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_FINAL_PHASE_REPORTED__API_STATUS__API_STATUS_BINDER_DIED;
import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_FINAL_PHASE_REPORTED__API_STATUS__API_STATUS_CLIENT_CANCELED;
import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_FINAL_PHASE_REPORTED__API_STATUS__API_STATUS_FAILURE;
import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_FINAL_PHASE_REPORTED__API_STATUS__API_STATUS_SUCCESS;
@@ -27,7 +28,9 @@
CLIENT_CANCELED(
CREDENTIAL_MANAGER_FINAL_PHASE_REPORTED__API_STATUS__API_STATUS_CLIENT_CANCELED),
USER_CANCELED(
- CREDENTIAL_MANAGER_FINAL_PHASE_REPORTED__API_STATUS__API_STATUS_USER_CANCELED);
+ CREDENTIAL_MANAGER_FINAL_PHASE_REPORTED__API_STATUS__API_STATUS_USER_CANCELED),
+ BINDER_DIED(
+ CREDENTIAL_MANAGER_FINAL_PHASE_REPORTED__API_STATUS__API_STATUS_BINDER_DIED);
private final int mInnerMetricCode;
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index e11c31c..191c21e 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -8288,8 +8288,11 @@
Preconditions.checkCallAuthorization(isSystemUid(caller));
// Managed Profile password can only be changed when it has a separate challenge.
if (!isSeparateProfileChallengeEnabled(userId)) {
- Preconditions.checkCallAuthorization(!isManagedProfile(userId), "You can "
- + "not set the active password for a managed profile, userId = %d", userId);
+ if (isManagedProfile(userId)) {
+ Slogf.i(LOG_TAG, "You can not set the active password for a managed profile,"
+ + " userId = %d", userId);
+ return;
+ }
}
DevicePolicyData policy = getUserData(userId);
@@ -15993,8 +15996,6 @@
@Override
public void notifyUnsafeOperationStateChanged(DevicePolicySafetyChecker checker, int reason,
boolean isSafe) {
- // TODO(b/178494483): use EventLog instead
- // TODO(b/178494483): log metrics?
if (VERBOSE_LOG) {
Slogf.v(LOG_TAG, "notifyUnsafeOperationStateChanged(): %s=%b",
DevicePolicyManager.operationSafetyReasonToString(reason), isSafe);
@@ -16006,16 +16007,20 @@
extras.putInt(DeviceAdminReceiver.EXTRA_OPERATION_SAFETY_REASON, reason);
extras.putBoolean(DeviceAdminReceiver.EXTRA_OPERATION_SAFETY_STATE, isSafe);
- if (mOwners.hasDeviceOwner()) {
- if (VERBOSE_LOG) Slogf.v(LOG_TAG, "Notifying DO");
- sendDeviceOwnerCommand(DeviceAdminReceiver.ACTION_OPERATION_SAFETY_STATE_CHANGED,
- extras);
- }
- for (int profileOwnerId : mOwners.getProfileOwnerKeys()) {
- if (VERBOSE_LOG) Slogf.v(LOG_TAG, "Notifying PO for user " + profileOwnerId);
- sendProfileOwnerCommand(DeviceAdminReceiver.ACTION_OPERATION_SAFETY_STATE_CHANGED,
- extras, profileOwnerId);
- }
+ mInjector.binderWithCleanCallingIdentity(() -> {
+ if (mOwners.hasDeviceOwner()) {
+ if (VERBOSE_LOG) Slogf.v(LOG_TAG, "Notifying DO");
+ sendDeviceOwnerCommand(
+ DeviceAdminReceiver.ACTION_OPERATION_SAFETY_STATE_CHANGED,
+ extras);
+ }
+ for (int profileOwnerId : mOwners.getProfileOwnerKeys()) {
+ if (VERBOSE_LOG) Slogf.v(LOG_TAG, "Notifying PO for user " + profileOwnerId);
+ sendProfileOwnerCommand(
+ DeviceAdminReceiver.ACTION_OPERATION_SAFETY_STATE_CHANGED,
+ extras, profileOwnerId);
+ }
+ });
}
private @Mode int findInteractAcrossProfilesResetMode(String packageName) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/EnterpriseSpecificIdCalculator.java b/services/devicepolicy/java/com/android/server/devicepolicy/EnterpriseSpecificIdCalculator.java
index 6e038f9..ba02122 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/EnterpriseSpecificIdCalculator.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/EnterpriseSpecificIdCalculator.java
@@ -54,17 +54,7 @@
TelephonyManager telephonyService = context.getSystemService(TelephonyManager.class);
Preconditions.checkState(telephonyService != null, "Unable to access telephony service");
- String imei;
- try {
- imei = telephonyService.getImei(0);
- } catch (UnsupportedOperationException doesNotSupportGms) {
- // Instead of catching the exception, we could check for FEATURE_TELEPHONY_GSM.
- // However that runs the risk of changing a device's existing ESID if on these devices
- // telephonyService.getImei() actually returns non-null even when the device does not
- // declare FEATURE_TELEPHONY_GSM.
- imei = null;
- }
- mImei = imei;
+ mImei = telephonyService.getImei(0);
String meid;
try {
meid = telephonyService.getMeid(0);
diff --git a/services/incremental/IncrementalService.cpp b/services/incremental/IncrementalService.cpp
index dae481a..36947a2 100644
--- a/services/incremental/IncrementalService.cpp
+++ b/services/incremental/IncrementalService.cpp
@@ -3198,8 +3198,10 @@
dprintf(fd, " }\n");
}
-void IncrementalService::AppOpsListener::opChanged(int32_t, const String16&) {
+binder::Status IncrementalService::AppOpsListener::opChanged(int32_t, int32_t,
+ const String16&, const String16&) {
incrementalService.onAppOpChanged(packageName);
+ return binder::Status::ok();
}
binder::Status IncrementalService::IncrementalServiceConnector::setStorageParams(
diff --git a/services/incremental/IncrementalService.h b/services/incremental/IncrementalService.h
index b81e1b1..4ee1a70 100644
--- a/services/incremental/IncrementalService.h
+++ b/services/incremental/IncrementalService.h
@@ -26,7 +26,7 @@
#include <android/os/incremental/BnStorageLoadingProgressListener.h>
#include <android/os/incremental/PerUidReadTimeouts.h>
#include <android/os/incremental/StorageHealthCheckParams.h>
-#include <binder/IAppOpsCallback.h>
+#include <binder/AppOpsManager.h>
#include <binder/PersistableBundle.h>
#include <utils/String16.h>
#include <utils/StrongPointer.h>
@@ -200,11 +200,12 @@
void getMetrics(int32_t storageId, android::os::PersistableBundle* _aidl_return);
- class AppOpsListener : public android::BnAppOpsCallback {
+ class AppOpsListener : public com::android::internal::app::BnAppOpsCallback {
public:
AppOpsListener(IncrementalService& incrementalService, std::string packageName)
: incrementalService(incrementalService), packageName(std::move(packageName)) {}
- void opChanged(int32_t op, const String16& packageName) final;
+ binder::Status opChanged(int32_t op, int32_t uid, const String16& packageName,
+ const String16& persistentDeviceId) final;
private:
IncrementalService& incrementalService;
diff --git a/services/incremental/ServiceWrappers.h b/services/incremental/ServiceWrappers.h
index 39e2ee3..36a5b7f 100644
--- a/services/incremental/ServiceWrappers.h
+++ b/services/incremental/ServiceWrappers.h
@@ -23,7 +23,7 @@
#include <android/content/pm/IDataLoader.h>
#include <android/content/pm/IDataLoaderStatusListener.h>
#include <android/os/incremental/PerUidReadTimeouts.h>
-#include <binder/IAppOpsCallback.h>
+#include <binder/AppOpsManager.h>
#include <binder/IServiceManager.h>
#include <binder/Status.h>
#include <incfs.h>
@@ -133,6 +133,7 @@
class AppOpsManagerWrapper {
public:
+ using IAppOpsCallback = ::com::android::internal::app::IAppOpsCallback;
virtual ~AppOpsManagerWrapper() = default;
virtual binder::Status checkPermission(const char* permission, const char* operation,
const char* package) const = 0;
diff --git a/services/incremental/test/IncrementalServiceTest.cpp b/services/incremental/test/IncrementalServiceTest.cpp
index d9d3d62..73849a3 100644
--- a/services/incremental/test/IncrementalServiceTest.cpp
+++ b/services/incremental/test/IncrementalServiceTest.cpp
@@ -1678,7 +1678,7 @@
{}, {}));
ASSERT_GE(mDataLoader->setStorageParams(true), 0);
ASSERT_NE(nullptr, mAppOpsManager->mStoredCallback.get());
- mAppOpsManager->mStoredCallback->opChanged(0, {});
+ mAppOpsManager->mStoredCallback->opChanged(0, 0, {}, {});
}
TEST_F(IncrementalServiceTest, testSetIncFsMountOptionsCheckPermissionFails) {
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index c974d9e..2bbd69c 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -894,6 +894,17 @@
SystemServiceRegistry.sEnableServiceNotFoundWtf = true;
+ // Prepare the thread pool for init tasks that can be parallelized
+ SystemServerInitThreadPool tp = SystemServerInitThreadPool.start();
+ mDumper.addDumpable(tp);
+
+ if (android.server.Flags.earlySystemConfigInit()) {
+ // SystemConfig init is expensive, so enqueue the work as early as possible to allow
+ // concurrent execution before it's needed (typically by ActivityManagerService).
+ // As native library loading is also expensive, this is a good place to start.
+ startSystemConfigInit(t);
+ }
+
// Initialize native services.
System.loadLibrary("android_servers");
@@ -926,9 +937,6 @@
mDumper.addDumpable(mSystemServiceManager);
LocalServices.addService(SystemServiceManager.class, mSystemServiceManager);
- // Prepare the thread pool for init tasks that can be parallelized
- SystemServerInitThreadPool tp = SystemServerInitThreadPool.start();
- mDumper.addDumpable(tp);
// Lazily load the pre-installed system font map in SystemServer only if we're not doing
// the optimized font loading in the FontManagerService.
@@ -1093,6 +1101,14 @@
}
}
+ private void startSystemConfigInit(TimingsTraceAndSlog t) {
+ Slog.i(TAG, "Reading configuration...");
+ final String tagSystemConfig = "ReadingSystemConfig";
+ t.traceBegin(tagSystemConfig);
+ SystemServerInitThreadPool.submit(SystemConfig::getInstance, tagSystemConfig);
+ t.traceEnd();
+ }
+
private void createSystemContext() {
ActivityThread activityThread = ActivityThread.systemMain();
mSystemContext = activityThread.getSystemContext();
@@ -1131,11 +1147,11 @@
mDumper.addDumpable(watchdog);
t.traceEnd();
- Slog.i(TAG, "Reading configuration...");
- final String TAG_SYSTEM_CONFIG = "ReadingSystemConfig";
- t.traceBegin(TAG_SYSTEM_CONFIG);
- SystemServerInitThreadPool.submit(SystemConfig::getInstance, TAG_SYSTEM_CONFIG);
- t.traceEnd();
+ // Legacy entry point for starting SystemConfig init, only needed if the early init flag is
+ // disabled and we haven't already triggered init before bootstrap services.
+ if (!android.server.Flags.earlySystemConfigInit()) {
+ startSystemConfigInit(t);
+ }
// Orchestrates some ProtoLogging functionality.
if (android.tracing.Flags.clientSideProtoLogging()) {
diff --git a/services/java/com/android/server/flags.aconfig b/services/java/com/android/server/flags.aconfig
index 4d021ec..86ccd87 100644
--- a/services/java/com/android/server/flags.aconfig
+++ b/services/java/com/android/server/flags.aconfig
@@ -10,6 +10,13 @@
}
flag {
+ namespace: "system_performance"
+ name: "early_system_config_init"
+ description: "Perform earlier initialization of SystemConfig in system server startup."
+ bug: "383869534"
+}
+
+flag {
name: "remove_text_service"
namespace: "wear_frameworks"
description: "Remove TextServiceManagerService on Wear"
diff --git a/services/proguard.flags b/services/proguard.flags
index 0e1f68e..8d8b418 100644
--- a/services/proguard.flags
+++ b/services/proguard.flags
@@ -15,7 +15,10 @@
# APIs referenced by dependent JAR files and modules
# TODO(b/300514883): Pull @SystemApi keep rules from system-api.pro.
--keep interface android.annotation.SystemApi
+# TODO(b/373579455): Evaluate if <init> needs to be kept.
+-keep interface android.annotation.SystemApi {
+ void <init>();
+}
-keep @android.annotation.SystemApi class * {
public protected *;
}
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java
index a103b05..72e9cc5 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java
@@ -18,6 +18,10 @@
import static android.view.WindowInsets.Type.captionBar;
+import static com.android.apps.inputmethod.simpleime.ims.InputMethodServiceWrapper.EVENT_CONFIG;
+import static com.android.apps.inputmethod.simpleime.ims.InputMethodServiceWrapper.EVENT_HIDE;
+import static com.android.apps.inputmethod.simpleime.ims.InputMethodServiceWrapper.EVENT_SHOW;
+import static com.android.apps.inputmethod.simpleime.ims.InputMethodServiceWrapper.eventToString;
import static com.android.compatibility.common.util.SystemUtil.eventually;
import static com.android.cts.input.injectinputinprocess.InjectInputInProcessKt.clickOnViewCenter;
import static com.android.internal.inputmethod.InputMethodNavButtonFlags.IME_DRAWS_IME_NAV_BAR;
@@ -31,7 +35,6 @@
import static org.junit.Assume.assumeTrue;
import android.app.Instrumentation;
-import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Insets;
import android.os.RemoteException;
@@ -42,7 +45,6 @@
import android.server.wm.WindowManagerStateHelper;
import android.util.Log;
import android.view.WindowManagerGlobal;
-import android.view.WindowManagerPolicyConstants;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.Flags;
import android.view.inputmethod.InputMethodManager;
@@ -58,7 +60,9 @@
import androidx.test.uiautomator.Until;
import com.android.apps.inputmethod.simpleime.ims.InputMethodServiceWrapper;
+import com.android.apps.inputmethod.simpleime.ims.InputMethodServiceWrapper.Event;
import com.android.apps.inputmethod.simpleime.testing.TestActivity;
+import com.android.compatibility.common.util.GestureNavSwitchHelper;
import com.android.compatibility.common.util.SystemUtil;
import org.junit.After;
@@ -90,6 +94,8 @@
private final WindowManagerStateHelper mWmState = new WindowManagerStateHelper();
+ private final GestureNavSwitchHelper mGestureNavSwitchHelper = new GestureNavSwitchHelper();
+
private final DeviceFlagsValueProvider mFlagsValueProvider = new DeviceFlagsValueProvider();
@Rule
@@ -100,7 +106,6 @@
private Instrumentation mInstrumentation;
private UiDevice mUiDevice;
- private Context mContext;
private InputMethodManager mImm;
private String mTargetPackageName;
private String mInputMethodId;
@@ -112,8 +117,7 @@
public void setUp() throws Exception {
mInstrumentation = InstrumentationRegistry.getInstrumentation();
mUiDevice = UiDevice.getInstance(mInstrumentation);
- mContext = mInstrumentation.getContext();
- mImm = mContext.getSystemService(InputMethodManager.class);
+ mImm = mInstrumentation.getContext().getSystemService(InputMethodManager.class);
mTargetPackageName = mInstrumentation.getTargetContext().getPackageName();
mInputMethodId = getInputMethodId();
prepareIme();
@@ -169,6 +173,7 @@
Log.i(TAG, "Click on EditText");
verifyInputViewStatus(
() -> clickOnViewCenter(mActivity.getEditText()),
+ EVENT_SHOW,
true /* expected */,
true /* inputViewStarted */);
assertWithMessage("IME is shown").that(mInputMethodService.isInputViewShown()).isTrue();
@@ -185,6 +190,7 @@
verifyInputViewStatus(
() -> assertWithMessage("Home key press was handled")
.that(mUiDevice.pressHome()).isTrue(),
+ EVENT_HIDE,
true /* expected */,
false /* inputViewStarted */);
assertWithMessage("IME is not shown")
@@ -202,6 +208,7 @@
// Triggers to show IME via public API.
verifyInputViewStatusOnMainSync(
() -> assertThat(mActivity.showImeWithInputMethodManager(0 /* flags */)).isTrue(),
+ EVENT_SHOW,
true /* expected */,
true /* inputViewStarted */);
assertWithMessage("IME is shown").that(mInputMethodService.isInputViewShown()).isTrue();
@@ -209,6 +216,7 @@
// Triggers to hide IME via public API.
verifyInputViewStatusOnMainSync(
() -> assertThat(mActivity.hideImeWithInputMethodManager(0 /* flags */)).isTrue(),
+ EVENT_HIDE,
true /* expected */,
false /* inputViewStarted */);
if (mFlagsValueProvider.getBoolean(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)) {
@@ -232,6 +240,7 @@
// Triggers to show IME via public API.
verifyInputViewStatusOnMainSync(
() -> mActivity.showImeWithWindowInsetsController(),
+ EVENT_SHOW,
true /* expected */,
true /* inputViewStarted */);
assertWithMessage("IME is shown").that(mInputMethodService.isInputViewShown()).isTrue();
@@ -239,6 +248,7 @@
// Triggers to hide IME via public API.
verifyInputViewStatusOnMainSync(
() -> mActivity.hideImeWithWindowInsetsController(),
+ EVENT_HIDE,
true /* expected */,
false /* inputViewStarted */);
if (mFlagsValueProvider.getBoolean(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)) {
@@ -267,6 +277,7 @@
Log.i(TAG, "Call IMS#requestShowSelf(0)");
verifyInputViewStatusOnMainSync(
() -> mInputMethodService.requestShowSelf(0 /* flags */),
+ EVENT_SHOW,
true /* expected */,
true /* inputViewStarted */);
assertWithMessage("IME is shown").that(mInputMethodService.isInputViewShown()).isTrue();
@@ -277,6 +288,7 @@
verifyInputViewStatusOnMainSync(
() -> mInputMethodService.requestHideSelf(
InputMethodManager.HIDE_IMPLICIT_ONLY),
+ EVENT_HIDE,
false /* expected */,
true /* inputViewStarted */);
assertWithMessage("IME is still shown after HIDE_IMPLICIT_ONLY")
@@ -287,6 +299,7 @@
Log.i(TAG, "Call IMS#requestHideSelf(0)");
verifyInputViewStatusOnMainSync(
() -> mInputMethodService.requestHideSelf(0 /* flags */),
+ EVENT_HIDE,
true /* expected */,
false /* inputViewStarted */);
if (mFlagsValueProvider.getBoolean(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)) {
@@ -304,6 +317,7 @@
Log.i(TAG, "Call IMS#requestShowSelf(InputMethodManager.SHOW_IMPLICIT)");
verifyInputViewStatusOnMainSync(
() -> mInputMethodService.requestShowSelf(InputMethodManager.SHOW_IMPLICIT),
+ EVENT_SHOW,
true /* expected */,
true /* inputViewStarted */);
assertWithMessage("IME is shown with SHOW_IMPLICIT")
@@ -314,6 +328,7 @@
verifyInputViewStatusOnMainSync(
() -> mInputMethodService.requestHideSelf(
InputMethodManager.HIDE_IMPLICIT_ONLY),
+ EVENT_HIDE,
true /* expected */,
false /* inputViewStarted */);
assertWithMessage("IME is not shown after HIDE_IMPLICIT_ONLY")
@@ -409,6 +424,7 @@
verifyInputViewStatusOnMainSync(() -> assertThat(
mActivity.showImeWithInputMethodManager(
InputMethodManager.SHOW_IMPLICIT)).isTrue(),
+ EVENT_SHOW,
false /* expected */,
false /* inputViewStarted */);
assertWithMessage("IME is not shown after SHOW_IMPLICIT")
@@ -417,6 +433,7 @@
verifyInputViewStatusOnMainSync(
() -> assertThat(mActivity.showImeWithInputMethodManager(0 /* flags */))
.isTrue(),
+ EVENT_SHOW,
false /* expected */,
false /* inputViewStarted */);
assertWithMessage("IME is not shown after SHOW_EXPLICIT")
@@ -438,6 +455,7 @@
// IME should be shown.
verifyInputViewStatusOnMainSync(
() -> assertThat(mActivity.showImeWithInputMethodManager(0 /* flags */)).isTrue(),
+ EVENT_SHOW,
true /* expected */,
true /* inputViewStarted */);
assertWithMessage("IME is shown").that(mInputMethodService.isInputViewShown()).isTrue();
@@ -454,6 +472,7 @@
// the IME should be shown.
verifyInputViewStatusOnMainSync(() -> assertThat(
mActivity.showImeWithInputMethodManager(InputMethodManager.SHOW_IMPLICIT)).isTrue(),
+ EVENT_SHOW,
true /* expected */,
true /* inputViewStarted */);
assertWithMessage("IME is shown").that(mInputMethodService.isInputViewShown()).isTrue();
@@ -483,6 +502,7 @@
verifyInputViewStatusOnMainSync(() -> assertThat(
mActivity.showImeWithInputMethodManager(0 /* flags */)).isTrue(),
+ EVENT_SHOW,
true /* expected */,
true /* inputViewStarted */);
assertWithMessage("IME is shown").that(mInputMethodService.isInputViewShown()).isTrue();
@@ -517,6 +537,7 @@
verifyInputViewStatusOnMainSync(() -> assertThat(
mActivity.showImeWithInputMethodManager(InputMethodManager.SHOW_IMPLICIT)).isTrue(),
+ EVENT_SHOW,
false /* expected */,
false /* inputViewStarted */);
assertWithMessage("IME is not shown")
@@ -540,6 +561,7 @@
verifyInputViewStatusOnMainSync(() -> assertThat(
mActivity.showImeWithInputMethodManager(0 /* flags */)).isTrue(),
+ EVENT_SHOW,
true /* expected */,
true /* inputViewStarted */);
assertWithMessage("IME is shown").that(mInputMethodService.isInputViewShown()).isTrue();
@@ -572,6 +594,7 @@
verifyInputViewStatusOnMainSync(() ->assertThat(
mActivity.showImeWithInputMethodManager(InputMethodManager.SHOW_IMPLICIT))
.isTrue(),
+ EVENT_SHOW,
false /* expected */,
false /* inputViewStarted */);
assertWithMessage("IME is not shown")
@@ -600,6 +623,7 @@
verifyInputViewStatusOnMainSync(
() -> assertThat(mActivity.showImeWithInputMethodManager(0 /* flags */))
.isTrue(),
+ EVENT_SHOW,
true /* expected */,
true /* inputViewStarted */);
assertWithMessage("IME is shown").that(mInputMethodService.isInputViewShown()).isTrue();
@@ -613,6 +637,7 @@
verifyInputViewStatusOnMainSync(
() -> mInputMethodService.onConfigurationChanged(config),
+ EVENT_CONFIG,
true /* expected */,
true /* inputViewStarted */);
assertWithMessage("IME is still shown after a configuration change")
@@ -647,6 +672,7 @@
verifyInputViewStatusOnMainSync(() -> assertThat(
mActivity.showImeWithInputMethodManager(
InputMethodManager.SHOW_IMPLICIT)).isTrue(),
+ EVENT_SHOW,
true /* expected */,
true /* inputViewStarted */);
assertWithMessage("IME is shown").that(mInputMethodService.isInputViewShown()).isTrue();
@@ -666,6 +692,7 @@
// still alive.
verifyInputViewStatusOnMainSync(
() -> mInputMethodService.onConfigurationChanged(config),
+ EVENT_CONFIG,
true /* expected */,
true /* inputViewStarted */);
assertWithMessage("IME is not shown after a configuration change")
@@ -695,6 +722,7 @@
// Explicit show request.
verifyInputViewStatusOnMainSync(() -> assertThat(
mActivity.showImeWithInputMethodManager(0 /* flags */)).isTrue(),
+ EVENT_SHOW,
true /* expected */,
true /* inputViewStarted */);
assertWithMessage("IME is shown").that(mInputMethodService.isInputViewShown()).isTrue();
@@ -703,6 +731,7 @@
verifyInputViewStatusOnMainSync(() -> assertThat(
mActivity.showImeWithInputMethodManager(
InputMethodManager.SHOW_IMPLICIT)).isTrue(),
+ EVENT_SHOW,
false /* expected */,
true /* inputViewStarted */);
assertWithMessage("IME is still shown")
@@ -713,6 +742,7 @@
// explicit show request, and thus not hide the IME.
verifyInputViewStatusOnMainSync(
() -> mInputMethodService.onConfigurationChanged(config),
+ EVENT_CONFIG,
true /* expected */,
true /* inputViewStarted */);
assertWithMessage("IME is still shown after a configuration change")
@@ -739,12 +769,14 @@
verifyInputViewStatusOnMainSync(() -> assertThat(
mActivity.showImeWithInputMethodManager(InputMethodManager.SHOW_FORCED)).isTrue(),
+ EVENT_SHOW,
true /* expected */,
true /* inputViewStarted */);
assertWithMessage("IME is shown").that(mInputMethodService.isInputViewShown()).isTrue();
verifyInputViewStatusOnMainSync(() -> assertThat(
mActivity.showImeWithInputMethodManager(0 /* flags */)).isTrue(),
+ EVENT_SHOW,
false /* expected */,
true /* inputViewStarted */);
assertWithMessage("IME is still shown")
@@ -753,6 +785,7 @@
verifyInputViewStatusOnMainSync(() -> assertThat(
mActivity.hideImeWithInputMethodManager(InputMethodManager.HIDE_NOT_ALWAYS))
.isTrue(),
+ EVENT_HIDE,
false /* expected */,
true /* inputViewStarted */);
assertWithMessage("IME is still shown after HIDE_NOT_ALWAYS")
@@ -812,6 +845,7 @@
setDrawsImeNavBarAndSwitcherButton(true /* enabled */);
mActivity.showImeWithWindowInsetsController();
},
+ EVENT_SHOW,
true /* expected */,
true /* inputViewStarted */);
assertWithMessage("IME is shown").that(mInputMethodService.isInputViewShown()).isTrue();
@@ -849,6 +883,7 @@
setDrawsImeNavBarAndSwitcherButton(false /* enabled */);
mActivity.showImeWithWindowInsetsController();
},
+ EVENT_SHOW,
true /* expected */,
true /* inputViewStarted */);
assertWithMessage("IME is shown").that(mInputMethodService.isInputViewShown()).isTrue();
@@ -872,35 +907,34 @@
* Verifies that clicking on the IME navigation bar back button hides the IME.
*/
@Test
- public void testBackButtonClick() {
+ public void testBackButtonClick() throws Exception {
assumeTrue("Must have a navigation bar", hasNavigationBar());
- assumeTrue("Must be in gesture navigation mode", isGestureNavEnabled());
waitUntilActivityReadyForInputInjection(mActivity);
setShowImeWithHardKeyboard(true /* enabled */);
- verifyInputViewStatusOnMainSync(
- () -> {
- setDrawsImeNavBarAndSwitcherButton(true /* enabled */);
- mActivity.showImeWithWindowInsetsController();
- },
- true /* expected */,
- true /* inputViewStarted */);
- assertWithMessage("IME is shown").that(mInputMethodService.isInputViewShown()).isTrue();
+ try (var ignored = mGestureNavSwitchHelper.withGestureNavigationMode()) {
+ verifyInputViewStatusOnMainSync(
+ () -> mActivity.showImeWithWindowInsetsController(),
+ EVENT_SHOW,
+ true /* expected */,
+ true /* inputViewStarted */);
+ assertWithMessage("IME is shown").that(mInputMethodService.isInputViewShown()).isTrue();
- final var backButtonUiObject = getUiObject(By.res(INPUT_METHOD_NAV_BACK_ID));
- backButtonUiObject.click();
- mInstrumentation.waitForIdleSync();
+ final var backButton = getUiObject(By.res(INPUT_METHOD_NAV_BACK_ID));
+ backButton.click();
+ mInstrumentation.waitForIdleSync();
- if (mFlagsValueProvider.getBoolean(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)) {
- // The IME visibility is only sent at the end of the animation. Therefore, we have to
- // wait until the visibility was sent to the server and the IME window hidden.
- eventually(() -> assertWithMessage("IME is not shown")
- .that(mInputMethodService.isInputViewShown()).isFalse());
- } else {
- assertWithMessage("IME is not shown")
- .that(mInputMethodService.isInputViewShown()).isFalse();
+ if (mFlagsValueProvider.getBoolean(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)) {
+ // The IME visibility is only sent at the end of the animation. Therefore, we have
+ // to wait until the visibility was sent to the server and the IME window hidden.
+ eventually(() -> assertWithMessage("IME is not shown")
+ .that(mInputMethodService.isInputViewShown()).isFalse());
+ } else {
+ assertWithMessage("IME is not shown")
+ .that(mInputMethodService.isInputViewShown()).isFalse();
+ }
}
}
@@ -908,35 +942,34 @@
* Verifies that long clicking on the IME navigation bar back button hides the IME.
*/
@Test
- public void testBackButtonLongClick() {
+ public void testBackButtonLongClick() throws Exception {
assumeTrue("Must have a navigation bar", hasNavigationBar());
- assumeTrue("Must be in gesture navigation mode", isGestureNavEnabled());
waitUntilActivityReadyForInputInjection(mActivity);
setShowImeWithHardKeyboard(true /* enabled */);
- verifyInputViewStatusOnMainSync(
- () -> {
- setDrawsImeNavBarAndSwitcherButton(true /* enabled */);
- mActivity.showImeWithWindowInsetsController();
- },
- true /* expected */,
- true /* inputViewStarted */);
- assertWithMessage("IME is shown").that(mInputMethodService.isInputViewShown()).isTrue();
+ try (var ignored = mGestureNavSwitchHelper.withGestureNavigationMode()) {
+ verifyInputViewStatusOnMainSync(
+ () -> mActivity.showImeWithWindowInsetsController(),
+ EVENT_SHOW,
+ true /* expected */,
+ true /* inputViewStarted */);
+ assertWithMessage("IME is shown").that(mInputMethodService.isInputViewShown()).isTrue();
- final var backButtonUiObject = getUiObject(By.res(INPUT_METHOD_NAV_BACK_ID));
- backButtonUiObject.longClick();
- mInstrumentation.waitForIdleSync();
+ final var backButton = getUiObject(By.res(INPUT_METHOD_NAV_BACK_ID));
+ backButton.longClick();
+ mInstrumentation.waitForIdleSync();
- if (mFlagsValueProvider.getBoolean(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)) {
- // The IME visibility is only sent at the end of the animation. Therefore, we have to
- // wait until the visibility was sent to the server and the IME window hidden.
- eventually(() -> assertWithMessage("IME is not shown")
- .that(mInputMethodService.isInputViewShown()).isFalse());
- } else {
- assertWithMessage("IME is not shown")
- .that(mInputMethodService.isInputViewShown()).isFalse();
+ if (mFlagsValueProvider.getBoolean(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)) {
+ // The IME visibility is only sent at the end of the animation. Therefore, we have
+ // to wait until the visibility was sent to the server and the IME window hidden.
+ eventually(() -> assertWithMessage("IME is not shown")
+ .that(mInputMethodService.isInputViewShown()).isFalse());
+ } else {
+ assertWithMessage("IME is not shown")
+ .that(mInputMethodService.isInputViewShown()).isFalse();
+ }
}
}
@@ -945,103 +978,108 @@
* or switches the input method.
*/
@Test
- public void testImeSwitchButtonClick() {
+ public void testImeSwitchButtonClick() throws Exception {
assumeTrue("Must have a navigation bar", hasNavigationBar());
- assumeTrue("Must be in gesture navigation mode", isGestureNavEnabled());
waitUntilActivityReadyForInputInjection(mActivity);
setShowImeWithHardKeyboard(true /* enabled */);
- verifyInputViewStatusOnMainSync(
- () -> {
- setDrawsImeNavBarAndSwitcherButton(true /* enabled */);
- mActivity.showImeWithWindowInsetsController();
- },
- true /* expected */,
- true /* inputViewStarted */);
- assertWithMessage("IME is shown").that(mInputMethodService.isInputViewShown()).isTrue();
+ try (var ignored = mGestureNavSwitchHelper.withGestureNavigationMode()) {
+ verifyInputViewStatusOnMainSync(
+ () -> {
+ setDrawsImeNavBarAndSwitcherButton(true /* enabled */);
+ mActivity.showImeWithWindowInsetsController();
+ },
+ EVENT_SHOW,
+ true /* expected */,
+ true /* inputViewStarted */);
+ assertWithMessage("IME is shown").that(mInputMethodService.isInputViewShown()).isTrue();
- final var initialInfo = mImm.getCurrentInputMethodInfo();
+ final var initialInfo = mImm.getCurrentInputMethodInfo();
- final var imeSwitchButtonUiObject = getUiObject(By.res(INPUT_METHOD_NAV_IME_SWITCHER_ID));
- imeSwitchButtonUiObject.click();
- mInstrumentation.waitForIdleSync();
+ final var imeSwitcherButton = getUiObject(By.res(INPUT_METHOD_NAV_IME_SWITCHER_ID));
+ imeSwitcherButton.click();
+ mInstrumentation.waitForIdleSync();
- final var newInfo = mImm.getCurrentInputMethodInfo();
+ final var newInfo = mImm.getCurrentInputMethodInfo();
- assertWithMessage("Input Method Switcher Menu is shown or input method was switched")
- .that(isInputMethodPickerShown(mImm) || !Objects.equals(initialInfo, newInfo))
- .isTrue();
+ assertWithMessage("Input Method Switcher Menu is shown or input method was switched")
+ .that(isInputMethodPickerShown(mImm) || !Objects.equals(initialInfo, newInfo))
+ .isTrue();
- assertWithMessage("IME is still shown after IME Switcher button was clicked")
- .that(mInputMethodService.isInputViewShown()).isTrue();
+ assertWithMessage("IME is still shown after IME Switcher button was clicked")
+ .that(mInputMethodService.isInputViewShown()).isTrue();
- // Hide the IME Switcher Menu before finishing.
- mUiDevice.pressBack();
+ // Hide the IME Switcher Menu before finishing.
+ mUiDevice.pressBack();
+ }
}
/**
* Verifies that long clicking on the IME switch button shows the Input Method Switcher Menu.
*/
@Test
- public void testImeSwitchButtonLongClick() {
+ public void testImeSwitchButtonLongClick() throws Exception {
assumeTrue("Must have a navigation bar", hasNavigationBar());
- assumeTrue("Must be in gesture navigation mode", isGestureNavEnabled());
waitUntilActivityReadyForInputInjection(mActivity);
setShowImeWithHardKeyboard(true /* enabled */);
- verifyInputViewStatusOnMainSync(
- () -> {
- setDrawsImeNavBarAndSwitcherButton(true /* enabled */);
- mActivity.showImeWithWindowInsetsController();
- },
- true /* expected */,
- true /* inputViewStarted */);
- assertWithMessage("IME is shown").that(mInputMethodService.isInputViewShown()).isTrue();
+ try (var ignored = mGestureNavSwitchHelper.withGestureNavigationMode()) {
+ verifyInputViewStatusOnMainSync(
+ () -> {
+ setDrawsImeNavBarAndSwitcherButton(true /* enabled */);
+ mActivity.showImeWithWindowInsetsController();
+ },
+ EVENT_SHOW,
+ true /* expected */,
+ true /* inputViewStarted */);
+ assertWithMessage("IME is shown").that(mInputMethodService.isInputViewShown()).isTrue();
- final var imeSwitchButtonUiObject = getUiObject(By.res(INPUT_METHOD_NAV_IME_SWITCHER_ID));
- imeSwitchButtonUiObject.longClick();
- mInstrumentation.waitForIdleSync();
+ final var imeSwitcherButton = getUiObject(By.res(INPUT_METHOD_NAV_IME_SWITCHER_ID));
+ imeSwitcherButton.longClick();
+ mInstrumentation.waitForIdleSync();
- assertWithMessage("Input Method Switcher Menu is shown")
- .that(isInputMethodPickerShown(mImm)).isTrue();
- assertWithMessage("IME is still shown after IME Switcher button was long clicked")
- .that(mInputMethodService.isInputViewShown()).isTrue();
+ assertWithMessage("Input Method Switcher Menu is shown")
+ .that(isInputMethodPickerShown(mImm)).isTrue();
+ assertWithMessage("IME is still shown after IME Switcher button was long clicked")
+ .that(mInputMethodService.isInputViewShown()).isTrue();
- // Hide the IME Switcher Menu before finishing.
- mUiDevice.pressBack();
+ // Hide the IME Switcher Menu before finishing.
+ mUiDevice.pressBack();
+ }
}
- private void verifyInputViewStatus(@NonNull Runnable runnable, boolean expected,
- boolean inputViewStarted) {
- verifyInputViewStatusInternal(runnable, expected, inputViewStarted,
+ private void verifyInputViewStatus(@NonNull Runnable runnable, @Event int event,
+ boolean expected, boolean inputViewStarted) {
+ verifyInputViewStatusInternal(runnable, event, expected, inputViewStarted,
false /* runOnMainSync */);
}
- private void verifyInputViewStatusOnMainSync(@NonNull Runnable runnable, boolean expected,
- boolean inputViewStarted) {
- verifyInputViewStatusInternal(runnable, expected, inputViewStarted,
+ private void verifyInputViewStatusOnMainSync(@NonNull Runnable runnable, @Event int event,
+ boolean expected, boolean inputViewStarted) {
+ verifyInputViewStatusInternal(runnable, event, expected, inputViewStarted,
true /* runOnMainSync */);
}
/**
- * Verifies the status of the Input View after executing the given runnable.
+ * Verifies the status of the Input View after executing the given runnable, and waiting that
+ * the event was either triggered or not, based on the given expectation.
*
- * @param runnable the runnable to execute for showing or hiding the IME.
- * @param expected whether the runnable is expected to trigger the signal.
+ * @param runnable the runnable to trigger the event
+ * @param event the event to await.
+ * @param expected whether the event is expected to be triggered.
* @param inputViewStarted the expected state of the Input View after executing the runnable.
* @param runOnMainSync whether to execute the runnable on the main thread.
*/
- private void verifyInputViewStatusInternal(@NonNull Runnable runnable, boolean expected,
- boolean inputViewStarted, boolean runOnMainSync) {
+ private void verifyInputViewStatusInternal(@NonNull Runnable runnable, @Event int event,
+ boolean expected, boolean inputViewStarted, boolean runOnMainSync) {
final boolean completed;
try {
final var latch = new CountDownLatch(1);
- mInputMethodService.setCountDownLatchForTesting(latch);
- // Trigger onStartInputView() / onFinishInputView() / onConfigurationChanged()
+ mInputMethodService.setCountDownLatchForTesting(latch, event);
if (runOnMainSync) {
mInstrumentation.runOnMainSync(runnable);
} else {
@@ -1053,15 +1091,13 @@
fail("Interrupted while waiting for latch: " + e.getMessage());
return;
} finally {
- mInputMethodService.setCountDownLatchForTesting(null);
+ mInputMethodService.setCountDownLatchForTesting(null /* latch */, event);
}
if (expected && !completed) {
- fail("Timed out waiting for"
- + " onStartInputView() / onFinishInputView() / onConfigurationChanged()");
+ fail("Timed out waiting for " + eventToString(event));
} else if (!expected && completed) {
- fail("Unexpected call"
- + " onStartInputView() / onFinishInputView() / onConfigurationChanged()");
+ fail("Unexpected call " + eventToString(event));
}
// Input is not finished.
assertWithMessage("Input connection is still started")
@@ -1097,7 +1133,7 @@
*/
private void verifyFullscreenMode(@NonNull Runnable runnable, boolean expected,
boolean orientationPortrait) {
- verifyInputViewStatus(runnable, expected, false /* inputViewStarted */);
+ verifyInputViewStatus(runnable, EVENT_CONFIG, expected, false /* inputViewStarted */);
if (expected) {
// Wait for the TestActivity to be recreated.
eventually(() -> assertWithMessage("Activity was re-created after rotation")
@@ -1105,10 +1141,14 @@
// Get the new TestActivity.
mActivity = TestActivity.getLastCreatedInstance();
assertWithMessage("Re-created activity is not null").that(mActivity).isNotNull();
+ // Wait for the new EditText to be served by InputMethodManager.
+ eventually(() -> assertWithMessage("Has an input connection to the re-created Activity")
+ .that(mImm.hasActiveInputConnection(mActivity.getEditText())).isTrue());
}
verifyInputViewStatusOnMainSync(
() -> mActivity.showImeWithWindowInsetsController(),
+ EVENT_SHOW,
true /* expected */,
true /* inputViewStarted */);
assertWithMessage("IME is shown").that(mInputMethodService.isInputViewShown()).isTrue();
@@ -1134,6 +1174,7 @@
// Hide IME before finishing the run.
verifyInputViewStatusOnMainSync(
() -> mActivity.hideImeWithWindowInsetsController(),
+ EVENT_HIDE,
true /* expected */,
false /* inputViewStarted */);
@@ -1214,18 +1255,12 @@
return uiObject;
}
- /** Checks whether gesture navigation move is enabled. */
- private boolean isGestureNavEnabled() {
- return mContext.getResources().getInteger(
- com.android.internal.R.integer.config_navBarInteractionMode)
- == WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL;
- }
-
/** Checks whether the device has a navigation bar on the IME's display. */
private boolean hasNavigationBar() {
try {
return WindowManagerGlobal.getWindowManagerService()
- .hasNavigationBar(mInputMethodService.getDisplayId());
+ .hasNavigationBar(mInputMethodService.getDisplayId())
+ && mGestureNavSwitchHelper.hasNavigationBar();
} catch (RemoteException e) {
fail("Failed to check whether the device has a navigation bar: " + e.getMessage());
return false;
diff --git a/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/Android.bp b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/Android.bp
index 8a12dcd..e2362f7 100644
--- a/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/Android.bp
+++ b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/Android.bp
@@ -53,6 +53,9 @@
srcs: [
"src/com/android/apps/inputmethod/simpleime/ims/*.java",
],
+ static_libs: [
+ "androidx.annotation_annotation",
+ ],
sdk_version: "current",
}
diff --git a/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/AndroidManifest.xml b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/AndroidManifest.xml
index cf7d660..00873de 100644
--- a/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/AndroidManifest.xml
+++ b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/AndroidManifest.xml
@@ -42,6 +42,7 @@
<activity android:name="com.android.apps.inputmethod.simpleime.testing.TestActivity"
android:exported="false"
android:label="TestActivity"
+ android:configChanges="assetsPaths"
android:launchMode="singleInstance"
android:excludeFromRecents="true"
android:noHistory="true"
diff --git a/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/src/com/android/apps/inputmethod/simpleime/ims/InputMethodServiceWrapper.java b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/src/com/android/apps/inputmethod/simpleime/ims/InputMethodServiceWrapper.java
index be59dd2..3a7abbb 100644
--- a/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/src/com/android/apps/inputmethod/simpleime/ims/InputMethodServiceWrapper.java
+++ b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/src/com/android/apps/inputmethod/simpleime/ims/InputMethodServiceWrapper.java
@@ -21,6 +21,12 @@
import android.util.Log;
import android.view.inputmethod.EditorInfo;
+import androidx.annotation.IntDef;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.concurrent.CountDownLatch;
/** Wrapper of {@link InputMethodService} to expose interfaces for testing purpose. */
@@ -29,16 +35,41 @@
private static final String TAG = "InputMethodServiceWrapper";
/** Last created instance of this wrapper. */
+ @Nullable
private static InputMethodServiceWrapper sInstance;
+ /** IME show event ({@link #onStartInputView}). */
+ public static final int EVENT_SHOW = 0;
+
+ /** IME hide event ({@link #onFinishInputView}). */
+ public static final int EVENT_HIDE = 1;
+
+ /** IME configuration change event ({@link #onConfigurationChanged}). */
+ public static final int EVENT_CONFIG = 2;
+
+ /** The type of event that can be waited with a latch. */
+ @IntDef(value = {
+ EVENT_SHOW,
+ EVENT_HIDE,
+ EVENT_CONFIG,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Event {}
+
+ /** The IME event type that the current latch, if any, waits on. */
+ @Event
+ private int mLatchEvent;
+
private boolean mInputViewStarted;
/**
* @see #setCountDownLatchForTesting
*/
- private CountDownLatch mCountDownLatchForTesting;
+ @Nullable
+ private CountDownLatch mCountDownLatch;
/** Gets the last created instance of this wrapper, if available. */
+ @Nullable
public static InputMethodServiceWrapper getInstance() {
return sInstance;
}
@@ -48,14 +79,14 @@
}
/**
- * Sets the latch used to wait for the IME to start showing ({@link #onStartInputView},
- * start hiding ({@link #onFinishInputView}) or receive a configuration change
- * ({@link #onConfigurationChanged}).
+ * Sets the latch used to wait for the IME event.
*
- * @param countDownLatchForTesting the latch to wait on.
+ * @param latch the latch to wait on.
+ * @param latchEvent the event to set the latch on.
*/
- public void setCountDownLatchForTesting(CountDownLatch countDownLatchForTesting) {
- mCountDownLatchForTesting = countDownLatchForTesting;
+ public void setCountDownLatchForTesting(@Nullable CountDownLatch latch, @Event int latchEvent) {
+ mCountDownLatch = latch;
+ mLatchEvent = latchEvent;
}
@Override
@@ -77,8 +108,8 @@
+ ", restarting=" + restarting);
super.onStartInputView(info, restarting);
mInputViewStarted = true;
- if (mCountDownLatchForTesting != null) {
- mCountDownLatchForTesting.countDown();
+ if (mCountDownLatch != null && mLatchEvent == EVENT_SHOW) {
+ mCountDownLatch.countDown();
}
}
@@ -94,8 +125,8 @@
super.onFinishInputView(finishingInput);
mInputViewStarted = false;
- if (mCountDownLatchForTesting != null) {
- mCountDownLatchForTesting.countDown();
+ if (mCountDownLatch != null && mLatchEvent == EVENT_HIDE) {
+ mCountDownLatch.countDown();
}
}
@@ -110,11 +141,27 @@
Log.i(TAG, "onConfigurationChanged() " + newConfig);
super.onConfigurationChanged(newConfig);
- if (mCountDownLatchForTesting != null) {
- mCountDownLatchForTesting.countDown();
+ if (mCountDownLatch != null && mLatchEvent == EVENT_CONFIG) {
+ mCountDownLatch.countDown();
}
}
+ /**
+ * Gets the string representation of the IME event that is being waited on.
+ *
+ * @param event the IME event.
+ */
+ @NonNull
+ public static String eventToString(@Event int event) {
+ return switch (event) {
+ case EVENT_SHOW -> "onStartInputView";
+ case EVENT_HIDE -> "onFinishInputView";
+ case EVENT_CONFIG -> "onConfigurationChanged";
+ default -> "unknownEvent";
+ };
+ }
+
+ @NonNull
private String dumpEditorInfo(EditorInfo info) {
if (info == null) {
return "null";
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 f154dbc..09ce263 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
@@ -3962,7 +3962,7 @@
}
@Override
- public VotesStatsReporter getVotesStatsReporter(boolean refreshRateVotingTelemetryEnabled) {
+ public VotesStatsReporter getVotesStatsReporter() {
return null;
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueImplTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueImplTest.java
index 1e665c2..409706b 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueImplTest.java
@@ -1550,6 +1550,118 @@
verifyPendingRecords(queue, List.of(closeSystemDialogs1, closeSystemDialogs2));
}
+ @SuppressWarnings("GuardedBy")
+ @Test
+ public void testDeliveryGroupPolicy_sameAction_multiplePolicies() {
+ // Create a PACKAGE_CHANGED broadcast corresponding to a change in the whole PACKAGE_GREEN
+ // package.
+ final Intent greenPackageChangedIntent = createPackageChangedIntent(
+ getUidForPackage(PACKAGE_GREEN), List.of(PACKAGE_GREEN));
+ // Create delivery group policy such that when there are multiple broadcasts within the
+ // delivery group identified by "com.example.green/10002", only the most recent one
+ // gets delivered and the rest get discarded.
+ final BroadcastOptions optionsMostRecentPolicyForPackageGreen =
+ BroadcastOptions.makeBasic();
+ optionsMostRecentPolicyForPackageGreen.setDeliveryGroupMatchingKey("package_changed",
+ PACKAGE_GREEN + "/" + getUidForPackage(PACKAGE_GREEN));
+ optionsMostRecentPolicyForPackageGreen.setDeliveryGroupPolicy(
+ BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT);
+
+ // Create a PACKAGE_CHANGED broadcast corresponding to a change in the whole PACKAGE_RED
+ // package.
+ final Intent redPackageChangedIntent = createPackageChangedIntent(
+ getUidForPackage(PACKAGE_RED), List.of(PACKAGE_RED));
+ // Create delivery group policy such that when there are multiple broadcasts within the
+ // delivery group identified by "com.example.red/10001", only the most recent one
+ // gets delivered and the rest get discarded.
+ final BroadcastOptions optionsMostRecentPolicyForPackageRed =
+ BroadcastOptions.makeBasic();
+ optionsMostRecentPolicyForPackageRed.setDeliveryGroupMatchingKey("package_changed",
+ PACKAGE_RED + "/" + getUidForPackage(PACKAGE_RED));
+ optionsMostRecentPolicyForPackageRed.setDeliveryGroupPolicy(
+ BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT);
+
+ // Create a PACKAGE_CHANGED broadcast corresponding to a change in some components of
+ // PACKAGE_GREEN package.
+ final Intent greenPackageComponentsChangedIntent1 = createPackageChangedIntent(
+ getUidForPackage(PACKAGE_GREEN),
+ List.of(PACKAGE_GREEN + ".comp1", PACKAGE_GREEN + ".comp2"));
+ final Intent greenPackageComponentsChangedIntent2 = createPackageChangedIntent(
+ getUidForPackage(PACKAGE_GREEN),
+ List.of(PACKAGE_GREEN + ".comp3"));
+ // Create delivery group policy such that when there are multiple broadcasts within the
+ // delivery group identified by "components-com.example.green/10002", merge the extras
+ // within these broadcasts such that only one broadcast is sent and the rest are
+ // discarded. Couple of things to note here:
+ // 1. We are intentionally using a different policy group
+ // "components-com.example.green/10002" (as opposed to "com.example.green/10002" used
+ // earlier), because this is corresponding to a change in some particular components,
+ // rather than a change to the whole package and we want to keep these two types of
+ // broadcasts independent.
+ // 2. We are using 'extrasMerger' to indicate how we want the extras to be merged. This
+ // assumes that broadcasts belonging to the group 'components-com.example.green/10002'
+ // will have the same values for all the extras, except for the one extra
+ // 'EXTRA_CHANGED_COMPONENT_NAME_LIST'. So, we explicitly specify how to merge this
+ // extra by using 'STRATEGY_ARRAY_APPEND' strategy, which basically indicates that
+ // the extra values which are arrays should be concatenated.
+ final BundleMerger extrasMerger = new BundleMerger();
+ extrasMerger.setMergeStrategy(Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST,
+ BundleMerger.STRATEGY_ARRAY_APPEND);
+ final BroadcastOptions optionsMergedPolicyForPackageGreen = BroadcastOptions.makeBasic();
+ optionsMergedPolicyForPackageGreen.setDeliveryGroupMatchingKey("package_changed",
+ "components-" + PACKAGE_GREEN + "/" + getUidForPackage(PACKAGE_GREEN));
+ optionsMergedPolicyForPackageGreen.setDeliveryGroupPolicy(
+ BroadcastOptions.DELIVERY_GROUP_POLICY_MERGED);
+ optionsMergedPolicyForPackageGreen.setDeliveryGroupExtrasMerger(extrasMerger);
+
+ // Create a PACKAGE_CHANGED broadcast corresponding to a change in some components of
+ // PACKAGE_RED package.
+ final Intent redPackageComponentsChangedIntent = createPackageChangedIntent(
+ getUidForPackage(PACKAGE_RED),
+ List.of(PACKAGE_RED + ".comp1", PACKAGE_RED + ".comp2"));
+ // Create delivery group policy such that when there are multiple broadcasts within the
+ // delivery group identified by "components-com.example.red/10001", merge the extras
+ // within these broadcasts such that only one broadcast is sent and the rest are
+ // discarded.
+ final BroadcastOptions optionsMergedPolicyForPackageRed = BroadcastOptions.makeBasic();
+ optionsMergedPolicyForPackageGreen.setDeliveryGroupMatchingKey("package_changed",
+ "components-" + PACKAGE_RED + "/" + getUidForPackage(PACKAGE_RED));
+ optionsMergedPolicyForPackageRed.setDeliveryGroupPolicy(
+ BroadcastOptions.DELIVERY_GROUP_POLICY_MERGED);
+ optionsMergedPolicyForPackageRed.setDeliveryGroupExtrasMerger(extrasMerger);
+
+ mImpl.enqueueBroadcastLocked(makeBroadcastRecord(greenPackageChangedIntent,
+ optionsMostRecentPolicyForPackageGreen));
+ mImpl.enqueueBroadcastLocked(makeBroadcastRecord(redPackageChangedIntent,
+ optionsMostRecentPolicyForPackageRed));
+ mImpl.enqueueBroadcastLocked(makeBroadcastRecord(greenPackageComponentsChangedIntent1,
+ optionsMergedPolicyForPackageGreen));
+ mImpl.enqueueBroadcastLocked(makeBroadcastRecord(redPackageComponentsChangedIntent,
+ optionsMergedPolicyForPackageRed));
+ // Since this broadcast has DELIVERY_GROUP_MOST_RECENT policy set, the earlier
+ // greenPackageChangedIntent broadcast with the same policy will be discarded.
+ mImpl.enqueueBroadcastLocked(makeBroadcastRecord(greenPackageChangedIntent,
+ optionsMostRecentPolicyForPackageGreen));
+ // Since this broadcast has DELIVERY_GROUP_MERGED policy set, the earlier
+ // greenPackageComponentsChangedIntent1 broadcast with the same policy will be merged
+ // with this one and then will be discarded.
+ mImpl.enqueueBroadcastLocked(makeBroadcastRecord(greenPackageComponentsChangedIntent2,
+ optionsMergedPolicyForPackageGreen));
+
+ final BroadcastProcessQueue queue = mImpl.getProcessQueue(PACKAGE_GREEN,
+ getUidForPackage(PACKAGE_GREEN));
+ // The extra EXTRA_CHANGED_COMPONENT_NAME_LIST values from
+ // greenPackageComponentsChangedIntent1 and
+ // greenPackageComponentsChangedIntent2 broadcasts would be merged, since
+ // STRATEGY_ARRAY_APPEND was used for this extra.
+ final Intent expectedGreenPackageComponentsChangedIntent = createPackageChangedIntent(
+ getUidForPackage(PACKAGE_GREEN), List.of(PACKAGE_GREEN + ".comp3",
+ PACKAGE_GREEN + ".comp1", PACKAGE_GREEN + ".comp2"));
+ verifyPendingRecords(queue, List.of(redPackageChangedIntent,
+ redPackageComponentsChangedIntent, greenPackageChangedIntent,
+ expectedGreenPackageComponentsChangedIntent));
+ }
+
private Pair<Intent, BroadcastOptions> createDropboxBroadcast(String tag, long timestampMs,
int droppedCount) {
final Intent dropboxEntryAdded = new Intent(DropBoxManager.ACTION_DROPBOX_ENTRY_ADDED);
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
index f79cb11..360d6eb 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
@@ -19,6 +19,11 @@
import static android.content.pm.PackageManager.FEATURE_EMBEDDED;
import static android.content.pm.PackageManager.FEATURE_LEANBACK;
import static android.content.pm.PackageManager.FEATURE_WATCH;
+import static android.multiuser.Flags.FLAG_BLOCK_PRIVATE_SPACE_CREATION;
+import static android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES;
+import static android.multiuser.Flags.FLAG_LOGOUT_USER_API;
+import static android.multiuser.Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE;
+import static android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE;
import static android.os.UserManager.DISALLOW_OUTGOING_CALLS;
import static android.os.UserManager.DISALLOW_SMS;
import static android.os.UserManager.DISALLOW_USER_SWITCH;
@@ -54,7 +59,6 @@
import android.content.pm.PackageManagerInternal;
import android.content.pm.UserInfo;
import android.content.res.Resources;
-import android.multiuser.Flags;
import android.os.PowerManager;
import android.os.ServiceSpecificException;
import android.os.SystemProperties;
@@ -401,15 +405,27 @@
}
@Test
- public void testGetBootUser_Headless_ThrowsIfOnlySystemUserExists() throws Exception {
+ public void testGetBootUser_CannotSwitchToHeadlessSystemUser_ThrowsIfOnlySystemUserExists()
+ throws Exception {
setSystemUserHeadless(true);
removeNonSystemUsers();
+ mockCanSwitchToHeadlessSystemUser(false);
assertThrows(UserManager.CheckedUserOperationException.class,
() -> mUmi.getBootUser(/* waitUntilSet= */ false));
}
@Test
+ public void testGetBootUser_CanSwitchToHeadlessSystemUser_NoThrowIfOnlySystemUserExists()
+ throws Exception {
+ setSystemUserHeadless(true);
+ removeNonSystemUsers();
+ mockCanSwitchToHeadlessSystemUser(true);
+
+ assertThat(mUmi.getBootUser(/* waitUntilSet= */ false)).isEqualTo(UserHandle.USER_SYSTEM);
+ }
+
+ @Test
public void testGetPreviousFullUserToEnterForeground() throws Exception {
addUser(USER_ID);
setLastForegroundTime(USER_ID, 1_000_000L);
@@ -601,9 +617,8 @@
}
@Test
+ @EnableFlags({FLAG_ALLOW_PRIVATE_PROFILE, FLAG_ENABLE_PRIVATE_SPACE_FEATURES})
public void testAutoLockPrivateProfile() {
- mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
- android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
int mainUser = mUms.getMainUserId();
assumeTrue(mUms.canAddPrivateProfile(mainUser));
UserManagerService mSpiedUms = spy(mUms);
@@ -622,10 +637,12 @@
}
@Test
+ @EnableFlags({
+ FLAG_ALLOW_PRIVATE_PROFILE,
+ FLAG_ENABLE_PRIVATE_SPACE_FEATURES,
+ FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE
+ })
public void testAutoLockOnDeviceLockForPrivateProfile() {
- mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
- android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
- mSetFlagsRule.enableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE);
int mainUser = mUms.getMainUserId();
assumeTrue(mUms.canAddPrivateProfile(mainUser));
UserManagerService mSpiedUms = spy(mUms);
@@ -645,10 +662,12 @@
}
@Test
+ @EnableFlags({
+ FLAG_ALLOW_PRIVATE_PROFILE,
+ FLAG_ENABLE_PRIVATE_SPACE_FEATURES,
+ FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE
+ })
public void testAutoLockOnDeviceLockForPrivateProfile_keyguardUnlocked() {
- mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
- android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
- mSetFlagsRule.enableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE);
assumeTrue(mUms.canAddPrivateProfile(0));
UserManagerService mSpiedUms = spy(mUms);
UserInfo privateProfileUser =
@@ -665,10 +684,9 @@
}
@Test
+ @EnableFlags({FLAG_ALLOW_PRIVATE_PROFILE, FLAG_ENABLE_PRIVATE_SPACE_FEATURES})
+ @DisableFlags(FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE)
public void testAutoLockOnDeviceLockForPrivateProfile_flagDisabled() {
- mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
- android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
- mSetFlagsRule.disableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE);
int mainUser = mUms.getMainUserId();
assumeTrue(mUms.canAddPrivateProfile(mainUser));
UserManagerService mSpiedUms = spy(mUms);
@@ -687,10 +705,12 @@
}
@Test
+ @EnableFlags({
+ FLAG_ALLOW_PRIVATE_PROFILE,
+ FLAG_ENABLE_PRIVATE_SPACE_FEATURES,
+ FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE
+ })
public void testAutoLockAfterInactityForPrivateProfile() {
- mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
- android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
- mSetFlagsRule.enableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE);
int mainUser = mUms.getMainUserId();
assumeTrue(mUms.canAddPrivateProfile(mainUser));
UserManagerService mSpiedUms = spy(mUms);
@@ -711,11 +731,12 @@
}
@Test
+ @EnableFlags({
+ FLAG_ALLOW_PRIVATE_PROFILE,
+ FLAG_ENABLE_PRIVATE_SPACE_FEATURES,
+ FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE
+ })
public void testSetOrUpdateAutoLockPreference_noPrivateProfile() {
- mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
- android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
- mSetFlagsRule.enableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE);
-
mUms.setOrUpdateAutoLockPreferenceForPrivateProfile(
Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_AFTER_INACTIVITY);
@@ -726,10 +747,12 @@
}
@Test
+ @EnableFlags({
+ FLAG_ALLOW_PRIVATE_PROFILE,
+ FLAG_ENABLE_PRIVATE_SPACE_FEATURES,
+ FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE
+ })
public void testSetOrUpdateAutoLockPreference() {
- mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
- android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
- mSetFlagsRule.enableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE);
int mainUser = mUms.getMainUserId();
assumeTrue(mUms.canAddPrivateProfile(mainUser));
mUms.createProfileForUserEvenWhenDisallowedWithThrow(PRIVATE_PROFILE_NAME,
@@ -780,10 +803,12 @@
}
@Test
+ @EnableFlags({
+ FLAG_ALLOW_PRIVATE_PROFILE,
+ FLAG_ENABLE_PRIVATE_SPACE_FEATURES,
+ android.multiuser.Flags.FLAG_ENABLE_HIDING_PROFILES
+ })
public void testGetProfileIdsExcludingHidden() {
- mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
- android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
- mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_HIDING_PROFILES);
assumeTrue(mUms.canAddPrivateProfile(0));
UserInfo privateProfileUser =
mUms.createProfileForUserEvenWhenDisallowedWithThrow("TestPrivateProfile",
@@ -794,8 +819,11 @@
}
@Test
- @RequiresFlagsEnabled({android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
- Flags.FLAG_BLOCK_PRIVATE_SPACE_CREATION, Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES})
+ @RequiresFlagsEnabled({
+ FLAG_ALLOW_PRIVATE_PROFILE,
+ FLAG_BLOCK_PRIVATE_SPACE_CREATION,
+ FLAG_ENABLE_PRIVATE_SPACE_FEATURES
+ })
public void testCreatePrivateProfileOnHeadlessSystemUser_shouldAllowCreation() {
UserManagerService mSpiedUms = spy(mUms);
assumeTrue(mUms.isHeadlessSystemUserMode());
@@ -807,8 +835,11 @@
}
@Test
- @RequiresFlagsEnabled({android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
- Flags.FLAG_BLOCK_PRIVATE_SPACE_CREATION, Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES})
+ @RequiresFlagsEnabled({
+ FLAG_ALLOW_PRIVATE_PROFILE,
+ FLAG_BLOCK_PRIVATE_SPACE_CREATION,
+ FLAG_ENABLE_PRIVATE_SPACE_FEATURES
+ })
public void testCreatePrivateProfileOnSecondaryUser_shouldNotAllowCreation() {
assumeTrue(mUms.canAddMoreUsersOfType(USER_TYPE_FULL_SECONDARY));
UserInfo user = mUms.createUserWithThrow(generateLongString(), USER_TYPE_FULL_SECONDARY, 0);
@@ -819,8 +850,11 @@
}
@Test
- @RequiresFlagsEnabled({android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
- Flags.FLAG_BLOCK_PRIVATE_SPACE_CREATION, Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES})
+ @RequiresFlagsEnabled({
+ FLAG_ALLOW_PRIVATE_PROFILE,
+ FLAG_BLOCK_PRIVATE_SPACE_CREATION,
+ FLAG_ENABLE_PRIVATE_SPACE_FEATURES
+ })
public void testCreatePrivateProfileOnAutoDevices_shouldNotAllowCreation() {
doReturn(true).when(mMockPms).hasSystemFeature(eq(FEATURE_AUTOMOTIVE), anyInt());
int mainUser = mUms.getMainUserId();
@@ -831,8 +865,11 @@
}
@Test
- @RequiresFlagsEnabled({android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
- Flags.FLAG_BLOCK_PRIVATE_SPACE_CREATION, Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES})
+ @RequiresFlagsEnabled({
+ FLAG_ALLOW_PRIVATE_PROFILE,
+ FLAG_BLOCK_PRIVATE_SPACE_CREATION,
+ FLAG_ENABLE_PRIVATE_SPACE_FEATURES
+ })
public void testCreatePrivateProfileOnTV_shouldNotAllowCreation() {
doReturn(true).when(mMockPms).hasSystemFeature(eq(FEATURE_LEANBACK), anyInt());
int mainUser = mUms.getMainUserId();
@@ -843,8 +880,11 @@
}
@Test
- @RequiresFlagsEnabled({android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
- Flags.FLAG_BLOCK_PRIVATE_SPACE_CREATION, Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES})
+ @RequiresFlagsEnabled({
+ FLAG_ALLOW_PRIVATE_PROFILE,
+ FLAG_BLOCK_PRIVATE_SPACE_CREATION,
+ FLAG_ENABLE_PRIVATE_SPACE_FEATURES
+ })
public void testCreatePrivateProfileOnEmbedded_shouldNotAllowCreation() {
doReturn(true).when(mMockPms).hasSystemFeature(eq(FEATURE_EMBEDDED), anyInt());
int mainUser = mUms.getMainUserId();
@@ -855,8 +895,11 @@
}
@Test
- @RequiresFlagsEnabled({android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
- Flags.FLAG_BLOCK_PRIVATE_SPACE_CREATION, Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES})
+ @RequiresFlagsEnabled({
+ FLAG_ALLOW_PRIVATE_PROFILE,
+ FLAG_BLOCK_PRIVATE_SPACE_CREATION,
+ FLAG_ENABLE_PRIVATE_SPACE_FEATURES
+ })
public void testCreatePrivateProfileOnWatch_shouldNotAllowCreation() {
doReturn(true).when(mMockPms).hasSystemFeature(eq(FEATURE_WATCH), anyInt());
int mainUser = mUms.getMainUserId();
@@ -910,7 +953,7 @@
}
@Test
- @EnableFlags(android.multiuser.Flags.FLAG_LOGOUT_USER_API)
+ @EnableFlags(FLAG_LOGOUT_USER_API)
public void testGetUserLogoutability_HsumAndInteractiveHeadlessSystem_UserCanLogout()
throws Exception {
setSystemUserHeadless(true);
@@ -926,7 +969,7 @@
}
@Test
- @EnableFlags(android.multiuser.Flags.FLAG_LOGOUT_USER_API)
+ @EnableFlags(FLAG_LOGOUT_USER_API)
public void testGetUserLogoutability_HsumAndNonInteractiveHeadlessSystem_UserCannotLogout()
throws Exception {
setSystemUserHeadless(true);
@@ -941,7 +984,7 @@
}
@Test
- @EnableFlags(android.multiuser.Flags.FLAG_LOGOUT_USER_API)
+ @EnableFlags(FLAG_LOGOUT_USER_API)
public void testGetUserLogoutability_Hsum_SystemUserCannotLogout() throws Exception {
setSystemUserHeadless(true);
mockCurrentUser(UserHandle.USER_SYSTEM);
@@ -950,7 +993,7 @@
}
@Test
- @EnableFlags(android.multiuser.Flags.FLAG_LOGOUT_USER_API)
+ @EnableFlags(FLAG_LOGOUT_USER_API)
public void testGetUserLogoutability_NonHsum_SystemUserCannotLogout() throws Exception {
setSystemUserHeadless(false);
mockCurrentUser(UserHandle.USER_SYSTEM);
@@ -960,7 +1003,7 @@
}
@Test
- @EnableFlags(android.multiuser.Flags.FLAG_LOGOUT_USER_API)
+ @EnableFlags(FLAG_LOGOUT_USER_API)
public void testGetUserLogoutability_CannotSwitch_CannotLogout() throws Exception {
setSystemUserHeadless(true);
addUser(USER_ID);
@@ -973,7 +1016,7 @@
}
@Test
- @DisableFlags(android.multiuser.Flags.FLAG_LOGOUT_USER_API)
+ @DisableFlags(FLAG_LOGOUT_USER_API)
public void testGetUserLogoutability_LogoutDisabled() throws Exception {
assertThrows(UnsupportedOperationException.class, () -> mUms.getUserLogoutability(USER_ID));
}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickTypePanelTest.java b/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickTypePanelTest.java
index 7b8824c..00cc726 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickTypePanelTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickTypePanelTest.java
@@ -18,6 +18,11 @@
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+import static com.android.server.accessibility.autoclick.AutoclickTypePanel.AUTOCLICK_TYPE_LEFT_CLICK;
+import static com.android.server.accessibility.autoclick.AutoclickTypePanel.AUTOCLICK_TYPE_SCROLL;
+import static com.android.server.accessibility.autoclick.AutoclickTypePanel.AutoclickType;
+import static com.android.server.accessibility.autoclick.AutoclickTypePanel.ClickPanelControllerInterface;
+
import static com.google.common.truth.Truth.assertThat;
import android.content.Context;
@@ -59,11 +64,25 @@
private LinearLayout mDragButton;
private LinearLayout mScrollButton;
+ private @AutoclickType int mActiveClickType = AUTOCLICK_TYPE_LEFT_CLICK;
+
+ private final ClickPanelControllerInterface clickPanelController =
+ new ClickPanelControllerInterface() {
+ @Override
+ public void handleAutoclickTypeChange(@AutoclickType int clickType) {
+ mActiveClickType = clickType;
+ }
+
+ @Override
+ public void toggleAutoclickPause() {}
+ };
+
@Before
public void setUp() {
mTestableContext.addMockSystemService(Context.WINDOW_SERVICE, mMockWindowManager);
- mAutoclickTypePanel = new AutoclickTypePanel(mTestableContext, mMockWindowManager);
+ mAutoclickTypePanel =
+ new AutoclickTypePanel(mTestableContext, mMockWindowManager, clickPanelController);
View contentView = mAutoclickTypePanel.getContentViewForTesting();
mLeftClickButton = contentView.findViewById(R.id.accessibility_autoclick_left_click_layout);
mRightClickButton =
@@ -136,6 +155,17 @@
verifyButtonHasSelectedStyle(mScrollButton);
}
+ @Test
+ public void togglePanelExpansion_selectButton_correctActiveClickType() {
+ // By first click, the panel is expanded.
+ mLeftClickButton.callOnClick();
+
+ // Clicks any button in the expanded state to select a type button.
+ mScrollButton.callOnClick();
+
+ assertThat(mActiveClickType).isEqualTo(AUTOCLICK_TYPE_SCROLL);
+ }
+
private void verifyButtonHasSelectedStyle(@NonNull LinearLayout button) {
GradientDrawable gradientDrawable = (GradientDrawable) button.getBackground();
assertThat(gradientDrawable.getColor().getDefaultColor())
diff --git a/services/tests/servicestests/src/com/android/server/app/GameManagerServiceSettingsTests.java b/services/tests/servicestests/src/com/android/server/app/GameManagerServiceSettingsTests.java
index 17f5ebb..7349c5f4 100644
--- a/services/tests/servicestests/src/com/android/server/app/GameManagerServiceSettingsTests.java
+++ b/services/tests/servicestests/src/com/android/server/app/GameManagerServiceSettingsTests.java
@@ -116,6 +116,10 @@
deleteFolder(InstrumentationRegistry.getTargetContext().getFilesDir());
}
+ static {
+ System.loadLibrary("servicestestjni");
+ }
+
@Test
public void testReadGameServiceSettings() {
writeOldFiles();
diff --git a/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubEndpointTest.java b/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubEndpointTest.java
index 04d0752..a4e77c0 100644
--- a/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubEndpointTest.java
+++ b/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubEndpointTest.java
@@ -20,10 +20,12 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.Context;
+import android.hardware.contexthub.EndpointInfo;
import android.hardware.contexthub.ErrorCode;
import android.hardware.contexthub.HubEndpointInfo;
import android.hardware.contexthub.HubEndpointInfo.HubEndpointIdentifier;
@@ -32,6 +34,7 @@
import android.hardware.contexthub.IContextHubEndpointCallback;
import android.hardware.contexthub.IEndpointCommunication;
import android.hardware.contexthub.MessageDeliveryStatus;
+import android.hardware.contexthub.Reason;
import android.os.Binder;
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
@@ -61,6 +64,9 @@
private static final int ENDPOINT_ID = 1;
private static final String ENDPOINT_PACKAGE_NAME = "com.android.server.location.contexthub";
+ private static final String TARGET_ENDPOINT_NAME = "Example target endpoint";
+ private static final int TARGET_ENDPOINT_ID = 1;
+
private ContextHubClientManager mClientManager;
private ContextHubEndpointManager mEndpointManager;
private HubInfoRegistry mHubInfoRegistry;
@@ -95,23 +101,8 @@
@Test
public void testRegisterEndpoint() throws RemoteException {
- // Register an endpoint and confirm we can get a valid IContextHubEndoint reference
- HubEndpointInfo info =
- new HubEndpointInfo(
- ENDPOINT_NAME, ENDPOINT_ID, ENDPOINT_PACKAGE_NAME, Collections.emptyList());
- IContextHubEndpoint endpoint =
- mEndpointManager.registerEndpoint(
- info, mMockCallback, ENDPOINT_PACKAGE_NAME, /* attributionTag= */ null);
- assertThat(mEndpointManager.getNumRegisteredClients()).isEqualTo(1);
- assertThat(endpoint).isNotNull();
- HubEndpointInfo assignedInfo = endpoint.getAssignedHubEndpointInfo();
- assertThat(assignedInfo).isNotNull();
- HubEndpointIdentifier assignedIdentifier = assignedInfo.getIdentifier();
- assertThat(assignedIdentifier).isNotNull();
-
- // Unregister the endpoint and confirm proper clean-up
- mEndpointManager.unregisterEndpoint(assignedIdentifier.getEndpoint());
- assertThat(mEndpointManager.getNumRegisteredClients()).isEqualTo(0);
+ IContextHubEndpoint endpoint = registerExampleEndpoint();
+ unregisterExampleEndpoint(endpoint);
}
@Test
@@ -146,4 +137,107 @@
assertThat(statusCaptor.getValue().messageSequenceNumber).isEqualTo(sequenceNumber);
assertThat(statusCaptor.getValue().errorCode).isEqualTo(ErrorCode.DESTINATION_NOT_FOUND);
}
+
+ @Test
+ public void testHalRestart() throws RemoteException {
+ IContextHubEndpoint endpoint = registerExampleEndpoint();
+
+ // Verify that the endpoint is still registered after a HAL restart
+ HubEndpointInfo assignedInfo = endpoint.getAssignedHubEndpointInfo();
+ HubEndpointIdentifier assignedIdentifier = assignedInfo.getIdentifier();
+ mEndpointManager.onHalRestart();
+ ArgumentCaptor<EndpointInfo> statusCaptor = ArgumentCaptor.forClass(EndpointInfo.class);
+ verify(mMockEndpointCommunications, times(2)).registerEndpoint(statusCaptor.capture());
+ assertThat(statusCaptor.getValue().id.id).isEqualTo(assignedIdentifier.getEndpoint());
+ assertThat(statusCaptor.getValue().id.hubId).isEqualTo(assignedIdentifier.getHub());
+
+ unregisterExampleEndpoint(endpoint);
+ }
+
+ @Test
+ public void testHalRestartOnOpenSession() throws RemoteException {
+ assertThat(mEndpointManager.getNumAvailableSessions()).isEqualTo(SESSION_ID_RANGE);
+ IContextHubEndpoint endpoint = registerExampleEndpoint();
+
+ HubEndpointInfo targetInfo =
+ new HubEndpointInfo(
+ TARGET_ENDPOINT_NAME,
+ TARGET_ENDPOINT_ID,
+ ENDPOINT_PACKAGE_NAME,
+ Collections.emptyList());
+ int sessionId = endpoint.openSession(targetInfo, /* serviceDescriptor= */ null);
+ mEndpointManager.onEndpointSessionOpenComplete(sessionId);
+ assertThat(mEndpointManager.getNumAvailableSessions()).isEqualTo(SESSION_ID_RANGE - 1);
+
+ mEndpointManager.onHalRestart();
+
+ HubEndpointInfo assignedInfo = endpoint.getAssignedHubEndpointInfo();
+ HubEndpointIdentifier assignedIdentifier = assignedInfo.getIdentifier();
+ ArgumentCaptor<EndpointInfo> statusCaptor = ArgumentCaptor.forClass(EndpointInfo.class);
+ verify(mMockEndpointCommunications, times(2)).registerEndpoint(statusCaptor.capture());
+ assertThat(statusCaptor.getValue().id.id).isEqualTo(assignedIdentifier.getEndpoint());
+ assertThat(statusCaptor.getValue().id.hubId).isEqualTo(assignedIdentifier.getHub());
+
+ verify(mMockCallback)
+ .onSessionClosed(
+ sessionId, ContextHubServiceUtil.toAppHubEndpointReason(Reason.HUB_RESET));
+
+ unregisterExampleEndpoint(endpoint);
+ assertThat(mEndpointManager.getNumAvailableSessions()).isEqualTo(SESSION_ID_RANGE);
+ }
+
+ @Test
+ public void testOpenSessionOnUnregistration() throws RemoteException {
+ assertThat(mEndpointManager.getNumAvailableSessions()).isEqualTo(SESSION_ID_RANGE);
+ IContextHubEndpoint endpoint = registerExampleEndpoint();
+
+ HubEndpointInfo targetInfo =
+ new HubEndpointInfo(
+ TARGET_ENDPOINT_NAME,
+ TARGET_ENDPOINT_ID,
+ ENDPOINT_PACKAGE_NAME,
+ Collections.emptyList());
+ int sessionId = endpoint.openSession(targetInfo, /* serviceDescriptor= */ null);
+ mEndpointManager.onEndpointSessionOpenComplete(sessionId);
+ assertThat(mEndpointManager.getNumAvailableSessions()).isEqualTo(SESSION_ID_RANGE - 1);
+
+ unregisterExampleEndpoint(endpoint);
+ verify(mMockEndpointCommunications).closeEndpointSession(sessionId, Reason.ENDPOINT_GONE);
+ assertThat(mEndpointManager.getNumAvailableSessions()).isEqualTo(SESSION_ID_RANGE);
+ }
+
+ private IContextHubEndpoint registerExampleEndpoint() throws RemoteException {
+ HubEndpointInfo info =
+ new HubEndpointInfo(
+ ENDPOINT_NAME, ENDPOINT_ID, ENDPOINT_PACKAGE_NAME, Collections.emptyList());
+ IContextHubEndpoint endpoint =
+ mEndpointManager.registerEndpoint(
+ info, mMockCallback, ENDPOINT_PACKAGE_NAME, /* attributionTag= */ null);
+ assertThat(endpoint).isNotNull();
+ HubEndpointInfo assignedInfo = endpoint.getAssignedHubEndpointInfo();
+ assertThat(assignedInfo).isNotNull();
+ HubEndpointIdentifier assignedIdentifier = assignedInfo.getIdentifier();
+ assertThat(assignedIdentifier).isNotNull();
+
+ // Confirm registerEndpoint was called with the right contents
+ ArgumentCaptor<EndpointInfo> statusCaptor = ArgumentCaptor.forClass(EndpointInfo.class);
+ verify(mMockEndpointCommunications).registerEndpoint(statusCaptor.capture());
+ assertThat(statusCaptor.getValue().id.id).isEqualTo(assignedIdentifier.getEndpoint());
+ assertThat(statusCaptor.getValue().id.hubId).isEqualTo(assignedIdentifier.getHub());
+ assertThat(mEndpointManager.getNumRegisteredClients()).isEqualTo(1);
+
+ return endpoint;
+ }
+
+ private void unregisterExampleEndpoint(IContextHubEndpoint endpoint) throws RemoteException {
+ HubEndpointInfo expectedInfo = endpoint.getAssignedHubEndpointInfo();
+ endpoint.unregister();
+ ArgumentCaptor<EndpointInfo> statusCaptor = ArgumentCaptor.forClass(EndpointInfo.class);
+ verify(mMockEndpointCommunications).unregisterEndpoint(statusCaptor.capture());
+ assertThat(statusCaptor.getValue().id.id)
+ .isEqualTo(expectedInfo.getIdentifier().getEndpoint());
+ assertThat(statusCaptor.getValue().id.hubId)
+ .isEqualTo(expectedInfo.getIdentifier().getHub());
+ assertThat(mEndpointManager.getNumRegisteredClients()).isEqualTo(0);
+ }
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelExtractorTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelExtractorTest.java
index 770712a..4101192 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelExtractorTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelExtractorTest.java
@@ -204,7 +204,7 @@
.build());
final Notification n = new Notification.Builder(getContext())
.setContentTitle("foo")
- .setCategory(CATEGORY_ALARM)
+ .setCategory(new String("alarm"))
.setSmallIcon(android.R.drawable.sym_def_app_icon)
.build();
NotificationRecord r = getRecord(channel, n);
diff --git a/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java b/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java
index c6b431c..8e2cea7 100644
--- a/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java
@@ -402,9 +402,6 @@
@Test
@EnableFlags({com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_SHORTCUT_CONTROL,
- com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_BOUNCE_KEYS_FLAG,
- com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_SLOW_KEYS_FLAG,
- com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_STICKY_KEYS_FLAG,
com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_MOUSE_KEYS})
@DisableFlags(com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER)
public void testKeyboardAccessibilityToggleShortcutPress() {
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 440f43e..bb29614 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -2881,7 +2881,6 @@
activity2.addStartingWindow(mPackageName, android.R.style.Theme, activity1, true, true,
false, true, false, false, false);
waitUntilHandlersIdle();
- assertFalse(mDisplayContent.mSkipAppTransitionAnimation);
assertNoStartingWindow(activity1);
assertHasStartingWindow(activity2);
}
@@ -2965,7 +2964,6 @@
false /* newTask */, false /* isTaskSwitch */, null /* options */,
null /* sourceRecord */);
- assertTrue(mDisplayContent.mSkipAppTransitionAnimation);
assertNull(middle.mStartingWindow);
assertHasStartingWindow(top);
assertTrue(top.isVisible());
@@ -3265,26 +3263,6 @@
> activity.getConfiguration().windowConfiguration.getAppBounds().height());
}
- @Test
- public void testSetVisibility_visibleToVisible() {
- final ActivityRecord activity = new ActivityBuilder(mAtm)
- .setCreateTask(true).build();
- // By default, activity is visible.
- assertTrue(activity.isVisible());
- assertTrue(activity.isVisibleRequested());
- assertFalse(activity.mDisplayContent.mClosingApps.contains(activity));
-
- // Request the activity to be visible. Although the activity is already visible, app
- // transition animation should be applied on this activity. This might be unnecessary, but
- // until we verify no logic relies on this behavior, we'll keep this as is.
- mDisplayContent.prepareAppTransition(0);
- activity.setVisibility(true);
- assertTrue(activity.isVisible());
- assertTrue(activity.isVisibleRequested());
- assertTrue(activity.mDisplayContent.mOpeningApps.contains(activity));
- assertFalse(activity.mDisplayContent.mClosingApps.contains(activity));
- }
-
@SetupWindows(addWindows = W_ACTIVITY)
@Test
public void testSetVisibility_visibleToInvisible() {
@@ -3316,50 +3294,30 @@
public void testSetVisibility_invisibleToVisible() {
final ActivityRecord activity = new ActivityBuilder(mAtm)
.setCreateTask(true).setVisible(false).build();
- // Activiby is invisible. However ATMS requests it to become visible, since this is a top
- // activity.
assertFalse(activity.isVisible());
- assertTrue(activity.isVisibleRequested());
- assertFalse(activity.mDisplayContent.mClosingApps.contains(activity));
+ assertFalse(activity.isVisibleRequested());
// Request the activity to be visible. Since the visibility changes, app transition
// animation should be applied on this activity.
- activity.setVisibility(true);
+ requestTransition(activity, WindowManager.TRANSIT_OPEN);
+ mWm.mRoot.resumeFocusedTasksTopActivities();
assertFalse(activity.isVisible());
assertTrue(activity.isVisibleRequested());
- assertTrue(activity.mDisplayContent.mOpeningApps.contains(activity));
- assertFalse(activity.mDisplayContent.mClosingApps.contains(activity));
-
- // There should still be animation (add to opening) if keyguard is going away while the
- // screen is off because it will be visible after screen is turned on by unlocking.
- mDisplayContent.mOpeningApps.remove(activity);
- mDisplayContent.mClosingApps.remove(activity);
- activity.commitVisibility(false /* visible */, false /* performLayout */);
- mDisplayContent.getDisplayPolicy().screenTurnedOff(false /* acquireSleepToken */);
- final KeyguardController controller = mSupervisor.getKeyguardController();
- doReturn(true).when(controller).isKeyguardGoingAway(anyInt());
- activity.setVisibility(true);
- assertTrue(mDisplayContent.mOpeningApps.contains(activity));
+ assertTrue(activity.inTransition());
}
@Test
public void testSetVisibility_invisibleToInvisible() {
final ActivityRecord activity = new ActivityBuilder(mAtm)
.setCreateTask(true).setVisible(false).build();
- // Activiby is invisible. However ATMS requests it to become visible, since this is a top
- // activity.
- assertFalse(activity.isVisible());
- assertTrue(activity.isVisibleRequested());
- assertTrue(activity.mDisplayContent.mOpeningApps.contains(activity));
- assertFalse(activity.mDisplayContent.mClosingApps.contains(activity));
+ requestTransition(activity, WindowManager.TRANSIT_CLOSE);
// Request the activity to be invisible. Since the activity is already invisible, no app
// transition should be applied on this activity.
activity.setVisibility(false);
assertFalse(activity.isVisible());
assertFalse(activity.isVisibleRequested());
- assertFalse(activity.mDisplayContent.mOpeningApps.contains(activity));
- assertFalse(activity.mDisplayContent.mClosingApps.contains(activity));
+ assertFalse(activity.inTransition());
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/AnimatingActivityRegistryTest.java b/services/tests/wmtests/src/com/android/server/wm/AnimatingActivityRegistryTest.java
deleted file mode 100644
index 8871056..0000000
--- a/services/tests/wmtests/src/com/android/server/wm/AnimatingActivityRegistryTest.java
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm;
-
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
-
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.verifyZeroInteractions;
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
-import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-import android.platform.test.annotations.Presubmit;
-
-import androidx.test.filters.SmallTest;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-/**
- * Tests for the {@link ActivityStack} class.
- *
- * Build/Install/Run:
- * atest WmTests:AnimatingActivityRegistryTest
- */
-@SmallTest
-@Presubmit
-@RunWith(WindowTestRunner.class)
-public class AnimatingActivityRegistryTest extends WindowTestsBase {
-
- @Mock
- AnimationAdapter mAdapter;
-
- @Mock
- Runnable mMockEndDeferFinishCallback1;
- @Mock
- Runnable mMockEndDeferFinishCallback2;
-
- @Before
- public void setUp() throws Exception {
- MockitoAnnotations.initMocks(this);
- }
-
- @Test
- public void testDeferring() {
- final ActivityRecord activity1 = createActivityRecord(mDisplayContent);
- final ActivityRecord activity2 = createAppWindow(activity1.getTask(), ACTIVITY_TYPE_STANDARD,
- "activity2").mActivityRecord;
- final AnimatingActivityRegistry registry =
- activity1.getRootTask().getAnimatingActivityRegistry();
-
- activity1.startAnimation(activity1.getPendingTransaction(), mAdapter, false /* hidden */,
- ANIMATION_TYPE_APP_TRANSITION);
- activity2.startAnimation(activity1.getPendingTransaction(), mAdapter, false /* hidden */,
- ANIMATION_TYPE_APP_TRANSITION);
- assertTrue(activity1.isAnimating(TRANSITION));
- assertTrue(activity2.isAnimating(TRANSITION));
-
- // Make sure that first animation finish is deferred, second one is not deferred, and first
- // one gets cancelled.
- assertTrue(registry.notifyAboutToFinish(activity1, mMockEndDeferFinishCallback1));
- assertFalse(registry.notifyAboutToFinish(activity2, mMockEndDeferFinishCallback2));
- verify(mMockEndDeferFinishCallback1).run();
- verifyZeroInteractions(mMockEndDeferFinishCallback2);
- }
-
- @Test
- public void testContainerRemoved() {
- final ActivityRecord window1 = createActivityRecord(mDisplayContent);
- final ActivityRecord window2 = createAppWindow(window1.getTask(), ACTIVITY_TYPE_STANDARD,
- "window2").mActivityRecord;
- final AnimatingActivityRegistry registry =
- window1.getRootTask().getAnimatingActivityRegistry();
-
- window1.startAnimation(window1.getPendingTransaction(), mAdapter, false /* hidden */,
- ANIMATION_TYPE_APP_TRANSITION);
- window2.startAnimation(window1.getPendingTransaction(), mAdapter, false /* hidden */,
- ANIMATION_TYPE_APP_TRANSITION);
- assertTrue(window1.isAnimating(TRANSITION));
- assertTrue(window2.isAnimating(TRANSITION));
-
- // Make sure that first animation finish is deferred, and removing the second window stops
- // finishes all pending deferred finishings.
- registry.notifyAboutToFinish(window1, mMockEndDeferFinishCallback1);
- window2.setParent(null);
- verify(mMockEndDeferFinishCallback1).run();
- }
-}
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
deleted file mode 100644
index c294bc6..0000000
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
+++ /dev/null
@@ -1,1306 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm;
-
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
-import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
-import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
-import static android.view.WindowManager.TRANSIT_CHANGE;
-import static android.view.WindowManager.TRANSIT_CLOSE;
-import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_CLOSE;
-import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_OPEN;
-import static android.view.WindowManager.TRANSIT_OLD_DREAM_ACTIVITY_CLOSE;
-import static android.view.WindowManager.TRANSIT_OLD_DREAM_ACTIVITY_OPEN;
-import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_UNOCCLUDE;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CHANGE;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CLOSE;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_OPEN;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_OPEN;
-import static android.view.WindowManager.TRANSIT_OPEN;
-import static android.view.WindowManager.TRANSIT_TO_FRONT;
-
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
-import static com.android.server.wm.WindowContainer.POSITION_BOTTOM;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-import static org.junit.Assume.assumeFalse;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Mockito.clearInvocations;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-
-import android.annotation.Nullable;
-import android.graphics.Rect;
-import android.gui.DropInputMode;
-import android.os.Binder;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.platform.test.annotations.Presubmit;
-import android.util.ArraySet;
-import android.view.IRemoteAnimationFinishedCallback;
-import android.view.IRemoteAnimationRunner;
-import android.view.RemoteAnimationAdapter;
-import android.view.RemoteAnimationDefinition;
-import android.view.RemoteAnimationTarget;
-import android.view.WindowManager;
-import android.window.ITaskFragmentOrganizer;
-import android.window.TaskFragmentOrganizer;
-
-import androidx.test.filters.SmallTest;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/**
- * Build/Install/Run:
- * atest WmTests:AppTransitionControllerTest
- */
-@SmallTest
-@Presubmit
-@RunWith(WindowTestRunner.class)
-public class AppTransitionControllerTest extends WindowTestsBase {
-
- private AppTransitionController mAppTransitionController;
-
- @Before
- public void setUp() throws Exception {
- assumeFalse(WindowManagerService.sEnableShellTransitions);
- mAppTransitionController = new AppTransitionController(mWm, mDisplayContent);
- mWm.mAnimator.ready();
- }
-
- @Test
- public void testSkipOccludedActivityCloseTransition() {
- final ActivityRecord behind = createActivityRecord(mDisplayContent,
- WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
- final ActivityRecord topOpening = createActivityRecord(behind.getTask());
- topOpening.setOccludesParent(true);
- topOpening.setVisible(true);
-
- mDisplayContent.prepareAppTransition(TRANSIT_OPEN);
- mDisplayContent.prepareAppTransition(TRANSIT_CLOSE);
- mDisplayContent.mClosingApps.add(behind);
-
- assertEquals(WindowManager.TRANSIT_OLD_UNSET,
- AppTransitionController.getTransitCompatType(mDisplayContent.mAppTransition,
- mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps,
- mDisplayContent.mChangingContainers, null, null, false));
- }
-
- @Test
- public void testClearTaskSkipAppExecuteTransition() {
- final ActivityRecord behind = createActivityRecord(mDisplayContent,
- WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
- final Task task = behind.getTask();
- final ActivityRecord top = createActivityRecord(task);
- top.setState(ActivityRecord.State.RESUMED, "test");
- behind.setState(ActivityRecord.State.STARTED, "test");
- behind.setVisibleRequested(true);
-
- task.removeActivities("test", false /* excludingTaskOverlay */);
- assertFalse(mDisplayContent.mAppTransition.isReady());
- }
-
- @Test
- public void testTranslucentOpen() {
- final ActivityRecord behind = createActivityRecord(mDisplayContent,
- WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
- final ActivityRecord translucentOpening = createActivityRecord(mDisplayContent,
- WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
- doReturn(false).when(translucentOpening).fillsParent();
- translucentOpening.setVisible(false);
- mDisplayContent.prepareAppTransition(TRANSIT_OPEN);
- mDisplayContent.mOpeningApps.add(behind);
- mDisplayContent.mOpeningApps.add(translucentOpening);
-
- assertEquals(WindowManager.TRANSIT_OLD_TRANSLUCENT_ACTIVITY_OPEN,
- AppTransitionController.getTransitCompatType(mDisplayContent.mAppTransition,
- mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps,
- mDisplayContent.mChangingContainers, null, null, false));
- }
-
- @Test
- public void testTranslucentClose() {
- final ActivityRecord behind = createActivityRecord(mDisplayContent,
- WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
- final ActivityRecord translucentClosing = createActivityRecord(mDisplayContent,
- WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
- doReturn(false).when(translucentClosing).fillsParent();
- mDisplayContent.prepareAppTransition(TRANSIT_CLOSE);
- mDisplayContent.mClosingApps.add(translucentClosing);
- assertEquals(WindowManager.TRANSIT_OLD_TRANSLUCENT_ACTIVITY_CLOSE,
- AppTransitionController.getTransitCompatType(mDisplayContent.mAppTransition,
- mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps,
- mDisplayContent.mChangingContainers, null, null, false));
- }
-
- @Test
- public void testDreamActivityOpenTransition() {
- final ActivityRecord dreamActivity = createActivityRecord(mDisplayContent,
- WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_DREAM);
- mDisplayContent.prepareAppTransition(TRANSIT_OPEN);
- mDisplayContent.mOpeningApps.add(dreamActivity);
-
- assertEquals(TRANSIT_OLD_DREAM_ACTIVITY_OPEN,
- AppTransitionController.getTransitCompatType(mDisplayContent.mAppTransition,
- mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps,
- mDisplayContent.mChangingContainers, null, null, false));
- }
-
- @Test
- public void testDreamActivityCloseTransition() {
- final ActivityRecord dreamActivity = createActivityRecord(mDisplayContent,
- WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_DREAM);
- mDisplayContent.prepareAppTransition(TRANSIT_CLOSE);
- mDisplayContent.mClosingApps.add(dreamActivity);
-
- assertEquals(TRANSIT_OLD_DREAM_ACTIVITY_CLOSE,
- AppTransitionController.getTransitCompatType(mDisplayContent.mAppTransition,
- mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps,
- mDisplayContent.mChangingContainers, null, null, false));
- }
-
- @Test
- public void testChangeIsNotOverwritten() {
- final ActivityRecord behind = createActivityRecord(mDisplayContent,
- WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
- final ActivityRecord translucentOpening = createActivityRecord(mDisplayContent,
- WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
- translucentOpening.setOccludesParent(false);
- translucentOpening.setVisible(false);
- mDisplayContent.prepareAppTransition(TRANSIT_CHANGE);
- mDisplayContent.mOpeningApps.add(behind);
- mDisplayContent.mOpeningApps.add(translucentOpening);
- mDisplayContent.mChangingContainers.add(translucentOpening.getTask());
- assertEquals(TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE,
- AppTransitionController.getTransitCompatType(mDisplayContent.mAppTransition,
- mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps,
- mDisplayContent.mChangingContainers, null, null, false));
- }
-
- @Test
- public void testTransitWithinTask() {
- final ActivityRecord opening = createActivityRecord(mDisplayContent,
- WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD);
- opening.setOccludesParent(false);
- final ActivityRecord closing = createActivityRecord(mDisplayContent,
- WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD);
- closing.setOccludesParent(false);
- final Task task = opening.getTask();
- mDisplayContent.mOpeningApps.add(opening);
- mDisplayContent.mClosingApps.add(closing);
- assertFalse(mAppTransitionController.isTransitWithinTask(TRANSIT_OLD_ACTIVITY_OPEN, task));
- closing.getTask().removeChild(closing);
- task.addChild(closing, 0);
- assertTrue(mAppTransitionController.isTransitWithinTask(TRANSIT_OLD_ACTIVITY_OPEN, task));
- assertFalse(mAppTransitionController.isTransitWithinTask(TRANSIT_OLD_TASK_OPEN, task));
- }
-
-
- @Test
- public void testIntraWallpaper_open() {
- final ActivityRecord opening = createActivityRecord(mDisplayContent,
- WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
- opening.setVisible(false);
- final WindowManager.LayoutParams attrOpening = new WindowManager.LayoutParams(
- TYPE_BASE_APPLICATION);
- attrOpening.setTitle("WallpaperOpening");
- attrOpening.flags |= FLAG_SHOW_WALLPAPER;
- final TestWindowState appWindowOpening = createWindowState(attrOpening, opening);
- opening.addWindow(appWindowOpening);
-
- final ActivityRecord closing = createActivityRecord(mDisplayContent,
- WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
- final WindowManager.LayoutParams attrClosing = new WindowManager.LayoutParams(
- TYPE_BASE_APPLICATION);
- attrOpening.setTitle("WallpaperClosing");
- attrClosing.flags |= FLAG_SHOW_WALLPAPER;
- final TestWindowState appWindowClosing = createWindowState(attrClosing, closing);
- closing.addWindow(appWindowClosing);
-
- mDisplayContent.prepareAppTransition(TRANSIT_OPEN);
- mDisplayContent.mOpeningApps.add(opening);
- mDisplayContent.mClosingApps.add(closing);
-
- assertEquals(WindowManager.TRANSIT_OLD_WALLPAPER_INTRA_OPEN,
- AppTransitionController.getTransitCompatType(mDisplayContent.mAppTransition,
- mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps,
- mDisplayContent.mChangingContainers, appWindowClosing, null, false));
- }
-
- @Test
- public void testIntraWallpaper_toFront() {
- final ActivityRecord opening = createActivityRecord(mDisplayContent,
- WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
- opening.setVisible(false);
- final WindowManager.LayoutParams attrOpening = new WindowManager.LayoutParams(
- TYPE_BASE_APPLICATION);
- attrOpening.setTitle("WallpaperOpening");
- attrOpening.flags |= FLAG_SHOW_WALLPAPER;
- final TestWindowState appWindowOpening = createWindowState(attrOpening, opening);
- opening.addWindow(appWindowOpening);
-
- final ActivityRecord closing = createActivityRecord(mDisplayContent,
- WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
- final WindowManager.LayoutParams attrClosing = new WindowManager.LayoutParams(
- TYPE_BASE_APPLICATION);
- attrOpening.setTitle("WallpaperClosing");
- attrClosing.flags |= FLAG_SHOW_WALLPAPER;
- final TestWindowState appWindowClosing = createWindowState(attrClosing, closing);
- closing.addWindow(appWindowClosing);
-
- mDisplayContent.prepareAppTransition(TRANSIT_TO_FRONT);
- mDisplayContent.mOpeningApps.add(opening);
- mDisplayContent.mClosingApps.add(closing);
-
- assertEquals(WindowManager.TRANSIT_OLD_WALLPAPER_INTRA_OPEN,
- AppTransitionController.getTransitCompatType(mDisplayContent.mAppTransition,
- mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps,
- mDisplayContent.mChangingContainers, appWindowClosing, null, false));
- }
-
- @Test
- public void testGetAnimationTargets_visibilityAlreadyUpdated() {
- // [DisplayContent] -+- [Task1] - [ActivityRecord1] (opening, visible)
- // +- [Task2] - [ActivityRecord2] (closing, invisible)
- final ActivityRecord activity1 = createActivityRecord(mDisplayContent);
-
- final ActivityRecord activity2 = createActivityRecord(mDisplayContent);
- activity2.setVisible(false);
- activity2.setVisibleRequested(false);
-
- final ArraySet<ActivityRecord> opening = new ArraySet<>();
- opening.add(activity1);
- final ArraySet<ActivityRecord> closing = new ArraySet<>();
- closing.add(activity2);
-
- // No animation, since visibility of the opening and closing apps are already updated
- // outside of AppTransition framework.
- assertEquals(
- new ArraySet<>(),
- AppTransitionController.getAnimationTargets(
- opening, closing, true /* visible */));
- assertEquals(
- new ArraySet<>(),
- AppTransitionController.getAnimationTargets(
- opening, closing, false /* visible */));
- }
-
- @Test
- public void testGetAnimationTargets_visibilityAlreadyUpdated_butForcedTransitionRequested() {
- // [DisplayContent] -+- [Task1] - [ActivityRecord1] (closing, invisible)
- // +- [Task2] - [ActivityRecord2] (opening, visible)
- final ActivityRecord activity1 = createActivityRecord(mDisplayContent);
- activity1.setVisible(true);
- activity1.setVisibleRequested(true);
- activity1.mRequestForceTransition = true;
-
- final ActivityRecord activity2 = createActivityRecord(mDisplayContent);
- activity2.setVisible(false);
- activity2.setVisibleRequested(false);
- activity2.mRequestForceTransition = true;
-
- final ArraySet<ActivityRecord> opening = new ArraySet<>();
- opening.add(activity1);
- final ArraySet<ActivityRecord> closing = new ArraySet<>();
- closing.add(activity2);
-
- // The visibility are already updated, but since forced transition is requested, it will
- // be included.
- assertEquals(
- new ArraySet<>(new WindowContainer[]{activity1.getRootTask()}),
- AppTransitionController.getAnimationTargets(
- opening, closing, true /* visible */));
- assertEquals(
- new ArraySet<>(new WindowContainer[]{activity2.getRootTask()}),
- AppTransitionController.getAnimationTargets(
- opening, closing, false /* visible */));
- }
-
- @Test
- public void testGetAnimationTargets_exitingBeforeTransition() {
- // Create another non-empty task so the animation target won't promote to task display area.
- createActivityRecord(mDisplayContent);
- final ActivityRecord activity = createActivityRecord(mDisplayContent);
- activity.setVisible(false);
- activity.mIsExiting = true;
-
- final ArraySet<ActivityRecord> closing = new ArraySet<>();
- closing.add(activity);
-
- // Animate closing apps even if it's not visible when it is exiting before we had a chance
- // to play the transition animation.
- assertEquals(
- new ArraySet<>(new WindowContainer[]{activity.getRootTask()}),
- AppTransitionController.getAnimationTargets(
- new ArraySet<>(), closing, false /* visible */));
- }
-
- @Test
- public void testExitAnimationDone_beforeAppTransition() {
- final Task task = createTask(mDisplayContent);
- final WindowState win = createAppWindow(task, ACTIVITY_TYPE_STANDARD, "Win");
- spyOn(win);
- win.mAnimatingExit = true;
- mDisplayContent.mAppTransition.setTimeout();
- mDisplayContent.mAppTransitionController.handleAppTransitionReady();
-
- verify(win).onExitAnimationDone();
- }
-
- @Test
- public void testGetAnimationTargets_openingClosingInDifferentTask() {
- // [DisplayContent] -+- [Task1] -+- [ActivityRecord1] (opening, invisible)
- // | +- [ActivityRecord2] (invisible)
- // |
- // +- [Task2] -+- [ActivityRecord3] (closing, visible)
- // +- [ActivityRecord4] (invisible)
- final ActivityRecord activity1 = createActivityRecord(mDisplayContent);
- activity1.setVisible(false);
- activity1.setVisibleRequested(true);
- final ActivityRecord activity2 = createActivityRecord(mDisplayContent,
- activity1.getTask());
- activity2.setVisible(false);
- activity2.setVisibleRequested(false);
-
- final ActivityRecord activity3 = createActivityRecord(mDisplayContent);
- final ActivityRecord activity4 = createActivityRecord(mDisplayContent,
- activity3.getTask());
- activity4.setVisible(false);
- activity4.setVisibleRequested(false);
-
- final ArraySet<ActivityRecord> opening = new ArraySet<>();
- opening.add(activity1);
- final ArraySet<ActivityRecord> closing = new ArraySet<>();
- closing.add(activity3);
-
- // Promote animation targets to root Task level. Invisible ActivityRecords don't affect
- // promotion decision.
- assertEquals(
- new ArraySet<>(new WindowContainer[]{activity1.getRootTask()}),
- AppTransitionController.getAnimationTargets(
- opening, closing, true /* visible */));
- assertEquals(
- new ArraySet<>(new WindowContainer[]{activity3.getRootTask()}),
- AppTransitionController.getAnimationTargets(
- opening, closing, false /* visible */));
- }
-
- @Test
- public void testGetAnimationTargets_openingClosingInSameTask() {
- // [DisplayContent] - [Task] -+- [ActivityRecord1] (opening, invisible)
- // +- [ActivityRecord2] (closing, visible)
- final ActivityRecord activity1 = createActivityRecord(mDisplayContent);
- activity1.setVisible(false);
- activity1.setVisibleRequested(true);
- final ActivityRecord activity2 = createActivityRecord(mDisplayContent,
- activity1.getTask());
-
- final ArraySet<ActivityRecord> opening = new ArraySet<>();
- opening.add(activity1);
- final ArraySet<ActivityRecord> closing = new ArraySet<>();
- closing.add(activity2);
-
- // Don't promote an animation target to Task level, since the same task contains both
- // opening and closing app.
- assertEquals(
- new ArraySet<>(new WindowContainer[]{activity1}),
- AppTransitionController.getAnimationTargets(
- opening, closing, true /* visible */));
- assertEquals(
- new ArraySet<>(new WindowContainer[]{activity2}),
- AppTransitionController.getAnimationTargets(
- opening, closing, false /* visible */));
- }
-
- @Test
- public void testGetAnimationTargets_animateOnlyTranslucentApp() {
- // [DisplayContent] -+- [Task1] -+- [ActivityRecord1] (opening, invisible)
- // | +- [ActivityRecord2] (visible)
- // |
- // +- [Task2] -+- [ActivityRecord3] (closing, visible)
- // +- [ActivityRecord4] (visible)
-
- final ActivityRecord activity1 = createActivityRecord(mDisplayContent);
- activity1.setVisible(false);
- activity1.setVisibleRequested(true);
- activity1.setOccludesParent(false);
-
- final ActivityRecord activity2 = createActivityRecord(mDisplayContent,
- activity1.getTask());
-
- final ActivityRecord activity3 = createActivityRecord(mDisplayContent);
- activity3.setOccludesParent(false);
- final ActivityRecord activity4 = createActivityRecord(mDisplayContent,
- activity3.getTask());
-
- final ArraySet<ActivityRecord> opening = new ArraySet<>();
- opening.add(activity1);
- final ArraySet<ActivityRecord> closing = new ArraySet<>();
- closing.add(activity3);
-
- // Don't promote an animation target to Task level, since opening (closing) app is
- // translucent and is displayed over other non-animating app.
- assertEquals(
- new ArraySet<>(new WindowContainer[]{activity1}),
- AppTransitionController.getAnimationTargets(
- opening, closing, true /* visible */));
- assertEquals(
- new ArraySet<>(new WindowContainer[]{activity3}),
- AppTransitionController.getAnimationTargets(
- opening, closing, false /* visible */));
- }
-
- @Test
- public void testGetAnimationTargets_animateTranslucentAndOpaqueApps() {
- // [DisplayContent] -+- [Task1] -+- [ActivityRecord1] (opening, invisible)
- // | +- [ActivityRecord2] (opening, invisible)
- // |
- // +- [Task2] -+- [ActivityRecord3] (closing, visible)
- // +- [ActivityRecord4] (closing, visible)
-
- final ActivityRecord activity1 = createActivityRecord(mDisplayContent);
- activity1.setVisible(false);
- activity1.setVisibleRequested(true);
- activity1.setOccludesParent(false);
-
- final ActivityRecord activity2 = createActivityRecord(mDisplayContent,
- activity1.getTask());
- activity2.setVisible(false);
- activity2.setVisibleRequested(true);
-
- final ActivityRecord activity3 = createActivityRecord(mDisplayContent);
- activity3.setOccludesParent(false);
- final ActivityRecord activity4 = createActivityRecord(mDisplayContent,
- activity3.getTask());
-
- final ArraySet<ActivityRecord> opening = new ArraySet<>();
- opening.add(activity1);
- opening.add(activity2);
- final ArraySet<ActivityRecord> closing = new ArraySet<>();
- closing.add(activity3);
- closing.add(activity4);
-
- // Promote animation targets to TaskStack level even though opening (closing) app is
- // translucent as long as all visible siblings animate at the same time.
- assertEquals(
- new ArraySet<>(new WindowContainer[]{activity1.getRootTask()}),
- AppTransitionController.getAnimationTargets(
- opening, closing, true /* visible */));
- assertEquals(
- new ArraySet<>(new WindowContainer[]{activity3.getRootTask()}),
- AppTransitionController.getAnimationTargets(
- opening, closing, false /* visible */));
- }
-
- @Test
- public void testGetAnimationTargets_taskContainsMultipleTasks() {
- // [DisplayContent] - [Task] -+- [Task1] - [ActivityRecord1] (opening, invisible)
- // +- [Task2] - [ActivityRecord2] (closing, visible)
- final Task parentTask = createTask(mDisplayContent);
- final ActivityRecord activity1 = createActivityRecordWithParentTask(parentTask);
- activity1.setVisible(false);
- activity1.setVisibleRequested(true);
- final ActivityRecord activity2 = createActivityRecordWithParentTask(parentTask);
-
- final ArraySet<ActivityRecord> opening = new ArraySet<>();
- opening.add(activity1);
- final ArraySet<ActivityRecord> closing = new ArraySet<>();
- closing.add(activity2);
-
- // Promote animation targets up to Task level, not beyond.
- assertEquals(
- new ArraySet<>(new WindowContainer[]{activity1.getTask()}),
- AppTransitionController.getAnimationTargets(
- opening, closing, true /* visible */));
- assertEquals(
- new ArraySet<>(new WindowContainer[]{activity2.getTask()}),
- AppTransitionController.getAnimationTargets(
- opening, closing, false /* visible */));
- }
-
- @Test
- public void testGetAnimationTargets_splitScreenOpening() {
- // [DisplayContent] - [Task] -+- [split task 1] -+- [Task1] - [AR1] (opening, invisible)
- // +- [split task 2] -+- [Task2] - [AR2] (opening, invisible)
- final Task singleTopRoot = createTask(mDisplayContent);
- final TaskBuilder builder = new TaskBuilder(mSupervisor)
- .setWindowingMode(WINDOWING_MODE_MULTI_WINDOW)
- .setParentTask(singleTopRoot)
- .setCreatedByOrganizer(true);
- final Task splitRoot1 = builder.build();
- final Task splitRoot2 = builder.build();
- splitRoot1.setAdjacentTaskFragment(splitRoot2);
- final ActivityRecord activity1 = createActivityRecordWithParentTask(splitRoot1);
- activity1.setVisible(false);
- activity1.setVisibleRequested(true);
- final ActivityRecord activity2 = createActivityRecordWithParentTask(splitRoot2);
- activity2.setVisible(false);
- activity2.setVisibleRequested(true);
-
- final ArraySet<ActivityRecord> opening = new ArraySet<>();
- opening.add(activity1);
- opening.add(activity2);
- final ArraySet<ActivityRecord> closing = new ArraySet<>();
-
- // Promote animation targets up to Task level, not beyond.
- assertEquals(
- new ArraySet<>(new WindowContainer[]{splitRoot1, splitRoot2}),
- AppTransitionController.getAnimationTargets(
- opening, closing, true /* visible */));
- }
-
- @Test
- public void testGetAnimationTargets_openingClosingTaskFragment() {
- // [DefaultTDA] - [Task] -+- [TaskFragment1] - [ActivityRecord1] (opening, invisible)
- // +- [TaskFragment2] - [ActivityRecord2] (closing, visible)
- final Task parentTask = createTask(mDisplayContent);
- final TaskFragment taskFragment1 = createTaskFragmentWithActivity(parentTask);
- final ActivityRecord activity1 = taskFragment1.getTopMostActivity();
- activity1.setVisible(false);
- activity1.setVisibleRequested(true);
-
- final TaskFragment taskFragment2 = createTaskFragmentWithActivity(parentTask);
- final ActivityRecord activity2 = taskFragment2.getTopMostActivity();
- activity2.setVisible(true);
- activity2.setVisibleRequested(false);
-
- final ArraySet<ActivityRecord> opening = new ArraySet<>();
- opening.add(activity1);
- final ArraySet<ActivityRecord> closing = new ArraySet<>();
- closing.add(activity2);
-
- // Promote animation targets up to TaskFragment level, not beyond.
- assertEquals(new ArraySet<>(new WindowContainer[]{taskFragment1}),
- AppTransitionController.getAnimationTargets(
- opening, closing, true /* visible */));
- assertEquals(new ArraySet<>(new WindowContainer[]{taskFragment2}),
- AppTransitionController.getAnimationTargets(
- opening, closing, false /* visible */));
- }
-
- @Test
- public void testGetAnimationTargets_openingTheOnlyTaskFragmentInTask() {
- // [DefaultTDA] -+- [Task1] - [TaskFragment1] - [ActivityRecord1] (opening, invisible)
- // +- [Task2] - [ActivityRecord2] (closing, visible)
- final Task task1 = createTask(mDisplayContent);
- final TaskFragment taskFragment1 = createTaskFragmentWithActivity(task1);
- final ActivityRecord activity1 = taskFragment1.getTopMostActivity();
- activity1.setVisible(false);
- activity1.setVisibleRequested(true);
-
- final ActivityRecord activity2 = createActivityRecord(mDisplayContent);
- activity2.setVisible(true);
- activity2.setVisibleRequested(false);
-
- final ArraySet<ActivityRecord> opening = new ArraySet<>();
- opening.add(activity1);
- final ArraySet<ActivityRecord> closing = new ArraySet<>();
- closing.add(activity2);
-
- // Promote animation targets up to leaf Task level because there's only one TaskFragment in
- // the Task.
- assertEquals(new ArraySet<>(new WindowContainer[]{task1}),
- AppTransitionController.getAnimationTargets(
- opening, closing, true /* visible */));
- assertEquals(new ArraySet<>(new WindowContainer[]{activity2.getTask()}),
- AppTransitionController.getAnimationTargets(
- opening, closing, false /* visible */));
- }
-
- @Test
- public void testGetAnimationTargets_closingTheOnlyTaskFragmentInTask() {
- // [DefaultTDA] -+- [Task1] - [TaskFragment1] - [ActivityRecord1] (closing, visible)
- // +- [Task2] - [ActivityRecord2] (opening, invisible)
- final Task task1 = createTask(mDisplayContent);
- final TaskFragment taskFragment1 = createTaskFragmentWithActivity(task1);
- final ActivityRecord activity1 = taskFragment1.getTopMostActivity();
- activity1.setVisible(true);
- activity1.setVisibleRequested(false);
-
- final ActivityRecord activity2 = createActivityRecord(mDisplayContent);
- activity2.setVisible(false);
- activity2.setVisibleRequested(true);
-
- final ArraySet<ActivityRecord> opening = new ArraySet<>();
- opening.add(activity2);
- final ArraySet<ActivityRecord> closing = new ArraySet<>();
- closing.add(activity1);
-
- // Promote animation targets up to leaf Task level because there's only one TaskFragment in
- // the Task.
- assertEquals(new ArraySet<>(new WindowContainer[]{activity2.getTask()}),
- AppTransitionController.getAnimationTargets(
- opening, closing, true /* visible */));
- assertEquals(new ArraySet<>(new WindowContainer[]{task1}),
- AppTransitionController.getAnimationTargets(
- opening, closing, false /* visible */));
- }
-
- @Test
- public void testGetAnimationTargets_embeddedTask() {
- // [DisplayContent] -+- [Task1] - [ActivityRecord1] (opening, invisible)
- // +- [Task2] (embedded) - [ActivityRecord2] (opening, invisible)
- final ActivityRecord activity1 = createActivityRecord(mDisplayContent);
- activity1.setVisible(false);
- activity1.setVisibleRequested(true);
-
- final Task task2 = createTask(mDisplayContent);
- task2.mRemoveWithTaskOrganizer = true;
- final ActivityRecord activity2 = createActivityRecord(task2);
- activity2.setVisible(false);
- activity2.setVisibleRequested(true);
-
- final ArraySet<ActivityRecord> opening = new ArraySet<>();
- opening.add(activity1);
- opening.add(activity2);
- final ArraySet<ActivityRecord> closing = new ArraySet<>();
-
- // No animation on the embedded task.
- assertEquals(
- new ArraySet<>(new WindowContainer[]{activity1.getTask()}),
- AppTransitionController.getAnimationTargets(
- opening, closing, true /* visible */));
- assertEquals(
- new ArraySet<>(),
- AppTransitionController.getAnimationTargets(
- opening, closing, false /* visible */));
- }
-
-
- @Test
- public void testGetAnimationTargets_activityInEmbeddedTask() {
- // [DisplayContent] - [Task] (embedded)-+- [ActivityRecord1] (opening, invisible)
- // +- [ActivityRecord2] (closing, visible)
- final Task task = createTask(mDisplayContent);
- task.mRemoveWithTaskOrganizer = true;
-
- final ActivityRecord activity1 = createActivityRecord(task);
- activity1.setVisible(false);
- activity1.setVisibleRequested(true);
- final ActivityRecord activity2 = createActivityRecord(task);
-
- final ArraySet<ActivityRecord> opening = new ArraySet<>();
- opening.add(activity1);
- final ArraySet<ActivityRecord> closing = new ArraySet<>();
- closing.add(activity2);
-
- // Even though embedded task itself doesn't animate, activities in an embedded task
- // animate.
- assertEquals(
- new ArraySet<>(new WindowContainer[]{activity1}),
- AppTransitionController.getAnimationTargets(
- opening, closing, true /* visible */));
- assertEquals(
- new ArraySet<>(new WindowContainer[]{activity2}),
- AppTransitionController.getAnimationTargets(
- opening, closing, false /* visible */));
- }
-
- static class TestRemoteAnimationRunner implements IRemoteAnimationRunner {
- private IRemoteAnimationFinishedCallback mFinishedCallback;
-
- @Override
- public void onAnimationStart(int transit, RemoteAnimationTarget[] apps,
- RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps,
- IRemoteAnimationFinishedCallback finishedCallback) throws RemoteException {
- mFinishedCallback = finishedCallback;
- }
-
- @Override
- public void onAnimationCancelled() throws RemoteException {
- mFinishedCallback = null;
- }
-
- @Override
- public IBinder asBinder() {
- return new Binder();
- }
-
- boolean isAnimationStarted() {
- return mFinishedCallback != null;
- }
-
- void finishAnimation() {
- try {
- mFinishedCallback.onAnimationFinished();
- } catch (RemoteException e) {
- fail();
- }
- }
- }
-
- @Test
- public void testGetRemoteAnimationOverrideEmpty() {
- final ActivityRecord activity = createActivityRecord(mDisplayContent);
- assertNull(mAppTransitionController.getRemoteAnimationOverride(activity,
- TRANSIT_OLD_ACTIVITY_OPEN, new ArraySet<Integer>()));
- }
-
- @Test
- public void testGetRemoteAnimationOverrideWindowContainer() {
- final ActivityRecord activity = createActivityRecord(mDisplayContent);
- final RemoteAnimationDefinition definition = new RemoteAnimationDefinition();
- final RemoteAnimationAdapter adapter = new RemoteAnimationAdapter(
- new TestRemoteAnimationRunner(), 10, 1);
- definition.addRemoteAnimation(TRANSIT_OLD_ACTIVITY_OPEN, adapter);
- activity.registerRemoteAnimations(definition);
-
- assertEquals(adapter,
- mAppTransitionController.getRemoteAnimationOverride(
- activity, TRANSIT_OLD_ACTIVITY_OPEN, new ArraySet<Integer>()));
- assertNull(mAppTransitionController.getRemoteAnimationOverride(
- null, TRANSIT_OLD_ACTIVITY_OPEN, new ArraySet<Integer>()));
- }
-
- @Test
- public void testGetRemoteAnimationOverrideTransitionController() {
- final ActivityRecord activity = createActivityRecord(mDisplayContent);
- final RemoteAnimationDefinition definition = new RemoteAnimationDefinition();
- final RemoteAnimationAdapter adapter = new RemoteAnimationAdapter(
- new TestRemoteAnimationRunner(), 10, 1);
- definition.addRemoteAnimation(TRANSIT_OLD_ACTIVITY_OPEN, adapter);
- mAppTransitionController.registerRemoteAnimations(definition);
-
- assertEquals(adapter,
- mAppTransitionController.getRemoteAnimationOverride(
- activity, TRANSIT_OLD_ACTIVITY_OPEN, new ArraySet<Integer>()));
- assertEquals(adapter,
- mAppTransitionController.getRemoteAnimationOverride(
- null, TRANSIT_OLD_ACTIVITY_OPEN, new ArraySet<Integer>()));
- }
-
- @Test
- public void testGetRemoteAnimationOverrideBoth() {
- final ActivityRecord activity = createActivityRecord(mDisplayContent);
- final RemoteAnimationDefinition definition1 = new RemoteAnimationDefinition();
- final RemoteAnimationAdapter adapter1 = new RemoteAnimationAdapter(
- new TestRemoteAnimationRunner(), 10, 1);
- definition1.addRemoteAnimation(TRANSIT_OLD_ACTIVITY_OPEN, adapter1);
- activity.registerRemoteAnimations(definition1);
-
- final RemoteAnimationDefinition definition2 = new RemoteAnimationDefinition();
- final RemoteAnimationAdapter adapter2 = new RemoteAnimationAdapter(
- new TestRemoteAnimationRunner(), 10, 1);
- definition2.addRemoteAnimation(TRANSIT_OLD_KEYGUARD_UNOCCLUDE, adapter2);
- mAppTransitionController.registerRemoteAnimations(definition2);
-
- assertEquals(adapter2,
- mAppTransitionController.getRemoteAnimationOverride(
- activity, TRANSIT_OLD_KEYGUARD_UNOCCLUDE, new ArraySet<Integer>()));
- assertEquals(adapter2,
- mAppTransitionController.getRemoteAnimationOverride(
- null, TRANSIT_OLD_KEYGUARD_UNOCCLUDE, new ArraySet<Integer>()));
- }
-
- @Test
- public void testGetRemoteAnimationOverrideWindowContainerHasPriority() {
- final ActivityRecord activity = createActivityRecord(mDisplayContent);
- final RemoteAnimationDefinition definition1 = new RemoteAnimationDefinition();
- final RemoteAnimationAdapter adapter1 = new RemoteAnimationAdapter(
- new TestRemoteAnimationRunner(), 10, 1);
- definition1.addRemoteAnimation(TRANSIT_OLD_ACTIVITY_OPEN, adapter1);
- activity.registerRemoteAnimations(definition1);
-
- final RemoteAnimationDefinition definition2 = new RemoteAnimationDefinition();
- final RemoteAnimationAdapter adapter2 = new RemoteAnimationAdapter(
- new TestRemoteAnimationRunner(), 10, 1);
- definition2.addRemoteAnimation(TRANSIT_OLD_ACTIVITY_OPEN, adapter2);
- mAppTransitionController.registerRemoteAnimations(definition2);
-
- assertEquals(adapter1,
- mAppTransitionController.getRemoteAnimationOverride(
- activity, TRANSIT_OLD_ACTIVITY_OPEN, new ArraySet<Integer>()));
- }
-
- @Test
- public void testOverrideTaskFragmentAdapter_overrideWithEmbeddedActivity() {
- final Task task = createTask(mDisplayContent);
- final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
- final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
- setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
-
- // Create a TaskFragment with embedded activity.
- final TaskFragment taskFragment = createTaskFragmentWithEmbeddedActivity(task, organizer);
- final ActivityRecord activity = taskFragment.getTopMostActivity();
- prepareActivityForAppTransition(activity);
- spyOn(mDisplayContent.mAppTransition);
-
- // Prepare and start transition.
- prepareAndTriggerAppTransition(activity, null /* closingActivity */, taskFragment);
- waitUntilWindowAnimatorIdle();
-
- // Animation run by the remote handler.
- assertTrue(remoteAnimationRunner.isAnimationStarted());
- }
-
- @Test
- public void testOverrideTaskFragmentAdapter_noOverrideWithOnlyTaskFragmentFillingTask() {
- final Task task = createTask(mDisplayContent);
- final ActivityRecord closingActivity = createActivityRecord(task);
- final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
- final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
- setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
-
- // Create a TaskFragment with embedded activity.
- final TaskFragment taskFragment = createTaskFragmentWithEmbeddedActivity(task, organizer);
-
- // Make sure the TaskFragment is not embedded.
- assertFalse(taskFragment.isEmbeddedWithBoundsOverride());
- final ActivityRecord openingActivity = taskFragment.getTopMostActivity();
- prepareActivityForAppTransition(closingActivity);
- prepareActivityForAppTransition(openingActivity);
- final int uid = 12345;
- closingActivity.info.applicationInfo.uid = uid;
- openingActivity.info.applicationInfo.uid = uid;
- task.effectiveUid = uid;
- spyOn(mDisplayContent.mAppTransition);
-
- // Prepare and start transition.
- prepareAndTriggerAppTransition(openingActivity, closingActivity,
- null /* changingTaskFragment */);
- waitUntilWindowAnimatorIdle();
-
- // Animation is not run by the remote handler because the activity is filling the Task.
- assertFalse(remoteAnimationRunner.isAnimationStarted());
- }
-
- @Test
- public void testOverrideTaskFragmentAdapter_overrideWithTaskFragmentNotFillingTask() {
- final Task task = createTask(mDisplayContent);
- final ActivityRecord closingActivity = createActivityRecord(task);
- final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
- final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
- setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
-
- // Create a TaskFragment with embedded activity.
- final TaskFragment taskFragment = createTaskFragmentWithEmbeddedActivity(task, organizer);
-
- // Make sure the TaskFragment is embedded.
- taskFragment.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
- final Rect embeddedBounds = new Rect(task.getBounds());
- embeddedBounds.right = embeddedBounds.left + embeddedBounds.width() / 2;
- taskFragment.setBounds(embeddedBounds);
- assertTrue(taskFragment.isEmbeddedWithBoundsOverride());
- final ActivityRecord openingActivity = taskFragment.getTopMostActivity();
- prepareActivityForAppTransition(closingActivity);
- prepareActivityForAppTransition(openingActivity);
- final int uid = 12345;
- closingActivity.info.applicationInfo.uid = uid;
- openingActivity.info.applicationInfo.uid = uid;
- task.effectiveUid = uid;
- spyOn(mDisplayContent.mAppTransition);
-
- // Prepare and start transition.
- prepareAndTriggerAppTransition(openingActivity, closingActivity,
- null /* changingTaskFragment */);
- waitUntilWindowAnimatorIdle();
-
- // Animation run by the remote handler.
- assertTrue(remoteAnimationRunner.isAnimationStarted());
- }
-
- @Test
- public void testOverrideTaskFragmentAdapter_overrideWithNonEmbeddedActivity() {
- final Task task = createTask(mDisplayContent);
- final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
- final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
- setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
-
- // Closing non-embedded activity.
- final ActivityRecord closingActivity = createActivityRecord(task);
- prepareActivityForAppTransition(closingActivity);
- // Opening TaskFragment with embedded activity.
- final TaskFragment taskFragment = createTaskFragmentWithEmbeddedActivity(task, organizer);
- final ActivityRecord openingActivity = taskFragment.getTopMostActivity();
- prepareActivityForAppTransition(openingActivity);
- task.effectiveUid = openingActivity.getUid();
- spyOn(mDisplayContent.mAppTransition);
-
- // Prepare and start transition.
- prepareAndTriggerAppTransition(openingActivity, closingActivity, taskFragment);
- waitUntilWindowAnimatorIdle();
-
- // Animation run by the remote handler.
- assertTrue(remoteAnimationRunner.isAnimationStarted());
- }
-
- @Test
- public void testOverrideTaskFragmentAdapter_overrideEmbeddedActivityWithDiffUid() {
- final Task task = createTask(mDisplayContent);
- final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
- final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
- setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
-
- // Closing TaskFragment with embedded activity.
- final TaskFragment taskFragment1 = createTaskFragmentWithEmbeddedActivity(task, organizer);
- final ActivityRecord closingActivity = taskFragment1.getTopMostActivity();
- prepareActivityForAppTransition(closingActivity);
- closingActivity.info.applicationInfo.uid = 12345;
- // Opening TaskFragment with embedded activity with different UID.
- final TaskFragment taskFragment2 = createTaskFragmentWithEmbeddedActivity(task, organizer);
- final ActivityRecord openingActivity = taskFragment2.getTopMostActivity();
- prepareActivityForAppTransition(openingActivity);
- openingActivity.info.applicationInfo.uid = 54321;
- spyOn(mDisplayContent.mAppTransition);
-
- // Prepare and start transition.
- prepareAndTriggerAppTransition(openingActivity, closingActivity, taskFragment1);
- waitUntilWindowAnimatorIdle();
-
- // Animation run by the remote handler.
- assertTrue(remoteAnimationRunner.isAnimationStarted());
- }
-
- @Test
- public void testOverrideTaskFragmentAdapter_noOverrideWithTwoApps() {
- final Task task = createTask(mDisplayContent);
- final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
- final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
- setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
-
- // Closing activity in Task1.
- final ActivityRecord closingActivity = createActivityRecord(mDisplayContent);
- prepareActivityForAppTransition(closingActivity);
- // Opening TaskFragment with embedded activity in Task2.
- final TaskFragment taskFragment = createTaskFragmentWithEmbeddedActivity(task, organizer);
- final ActivityRecord openingActivity = taskFragment.getTopMostActivity();
- prepareActivityForAppTransition(openingActivity);
- spyOn(mDisplayContent.mAppTransition);
-
- // Prepare and start transition.
- prepareAndTriggerAppTransition(openingActivity, closingActivity, taskFragment);
- waitUntilWindowAnimatorIdle();
-
- // Animation not run by the remote handler.
- assertFalse(remoteAnimationRunner.isAnimationStarted());
- }
-
- @Test
- public void testOverrideTaskFragmentAdapter_noOverrideNonEmbeddedActivityWithDiffUid() {
- final Task task = createTask(mDisplayContent);
- final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
- final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
- setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
-
- // Closing TaskFragment with embedded activity.
- final TaskFragment taskFragment = createTaskFragmentWithEmbeddedActivity(task, organizer);
- final ActivityRecord closingActivity = taskFragment.getTopMostActivity();
- prepareActivityForAppTransition(closingActivity);
- closingActivity.info.applicationInfo.uid = 12345;
- task.effectiveUid = closingActivity.getUid();
- // Opening non-embedded activity with different UID.
- final ActivityRecord openingActivity = createActivityRecord(task);
- prepareActivityForAppTransition(openingActivity);
- openingActivity.info.applicationInfo.uid = 54321;
- spyOn(mDisplayContent.mAppTransition);
-
- // Prepare and start transition.
- prepareAndTriggerAppTransition(openingActivity, closingActivity, taskFragment);
- waitUntilWindowAnimatorIdle();
-
- // Animation should not run by the remote handler when there are non-embedded activities of
- // different UID.
- assertFalse(remoteAnimationRunner.isAnimationStarted());
- }
-
- @Test
- public void testOverrideTaskFragmentAdapter_noOverrideWithWallpaper() {
- final Task task = createTask(mDisplayContent);
- final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
- final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
- setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
-
- // Create a TaskFragment with embedded activity.
- final TaskFragment taskFragment = createTaskFragmentWithEmbeddedActivity(task, organizer);
- final ActivityRecord activity = taskFragment.getTopMostActivity();
- prepareActivityForAppTransition(activity);
- // Set wallpaper as visible.
- final WallpaperWindowToken wallpaperWindowToken = new WallpaperWindowToken(mWm,
- mock(IBinder.class), true, mDisplayContent, true /* ownerCanManageAppTokens */);
- spyOn(mDisplayContent.mWallpaperController);
- doReturn(true).when(mDisplayContent.mWallpaperController).isWallpaperVisible();
- spyOn(mDisplayContent.mAppTransition);
-
- // Prepare and start transition.
- prepareAndTriggerAppTransition(activity, null /* closingActivity */, taskFragment);
- waitUntilWindowAnimatorIdle();
-
- // Animation should not run by the remote handler when there is wallpaper in the transition.
- assertFalse(remoteAnimationRunner.isAnimationStarted());
- }
-
- @Test
- public void testOverrideTaskFragmentAdapter_inputProtectedForUntrustedAnimation() {
- final Task task = createTask(mDisplayContent);
- final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
- final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
- setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
-
- // Create a TaskFragment with embedded activities, one is trusted embedded, and the other
- // one is untrusted embedded.
- final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm)
- .setParentTask(task)
- .createActivityCount(2)
- .setOrganizer(organizer)
- .build();
- final ActivityRecord activity0 = taskFragment.getChildAt(0).asActivityRecord();
- final ActivityRecord activity1 = taskFragment.getChildAt(1).asActivityRecord();
- // Also create a non-embedded activity in the Task.
- final ActivityRecord activity2 = new ActivityBuilder(mAtm).build();
- task.addChild(activity2, POSITION_BOTTOM);
- prepareActivityForAppTransition(activity0);
- prepareActivityForAppTransition(activity1);
- prepareActivityForAppTransition(activity2);
- doReturn(false).when(taskFragment).isAllowedToEmbedActivityInTrustedMode(activity0);
- doReturn(true).when(taskFragment).isAllowedToEmbedActivityInTrustedMode(activity1);
- spyOn(mDisplayContent.mAppTransition);
-
- // Prepare and start transition.
- prepareAndTriggerAppTransition(activity1, null /* closingActivity */, taskFragment);
- waitUntilWindowAnimatorIdle();
-
- // The animation will be animated remotely by client and all activities are input disabled
- // for untrusted animation.
- assertTrue(remoteAnimationRunner.isAnimationStarted());
- verify(activity0).setDropInputForAnimation(true);
- verify(activity1).setDropInputForAnimation(true);
- verify(activity2).setDropInputForAnimation(true);
- verify(activity0).setDropInputMode(DropInputMode.ALL);
- verify(activity1).setDropInputMode(DropInputMode.ALL);
- verify(activity2).setDropInputMode(DropInputMode.ALL);
-
- // Reset input after animation is finished.
- clearInvocations(activity0);
- clearInvocations(activity1);
- clearInvocations(activity2);
- remoteAnimationRunner.finishAnimation();
-
- verify(activity0).setDropInputForAnimation(false);
- verify(activity1).setDropInputForAnimation(false);
- verify(activity2).setDropInputForAnimation(false);
- verify(activity0).setDropInputMode(DropInputMode.OBSCURED);
- verify(activity1).setDropInputMode(DropInputMode.NONE);
- verify(activity2).setDropInputMode(DropInputMode.NONE);
- }
-
- /**
- * Since we don't have any use case to rely on handling input during animation, disable it even
- * if it is trusted embedding so that it could cover some edge-cases when a previously trusted
- * host starts doing something bad.
- */
- @Test
- public void testOverrideTaskFragmentAdapter_inputProtectedForTrustedAnimation() {
- final Task task = createTask(mDisplayContent);
- final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
- final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
- setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
-
- // Create a TaskFragment with only trusted embedded activity
- final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm)
- .setParentTask(task)
- .createActivityCount(1)
- .setOrganizer(organizer)
- .build();
- final ActivityRecord activity = taskFragment.getChildAt(0).asActivityRecord();
- prepareActivityForAppTransition(activity);
- doReturn(true).when(taskFragment).isAllowedToEmbedActivityInTrustedMode(activity);
- spyOn(mDisplayContent.mAppTransition);
-
- // Prepare and start transition.
- prepareAndTriggerAppTransition(activity, null /* closingActivity */, taskFragment);
- waitUntilWindowAnimatorIdle();
-
- // The animation will be animated remotely by client and all activities are input disabled
- // for untrusted animation.
- assertTrue(remoteAnimationRunner.isAnimationStarted());
- verify(activity).setDropInputForAnimation(true);
- verify(activity).setDropInputMode(DropInputMode.ALL);
-
- // Reset input after animation is finished.
- clearInvocations(activity);
- remoteAnimationRunner.finishAnimation();
-
- verify(activity).setDropInputForAnimation(false);
- verify(activity).setDropInputMode(DropInputMode.NONE);
- }
-
- /**
- * We don't need to drop input for fully trusted embedding (system app, and embedding in the
- * same app). This will allow users to do fast tapping.
- */
- @Test
- public void testOverrideTaskFragmentAdapter_noInputProtectedForFullyTrustedAnimation() {
- final Task task = createTask(mDisplayContent);
- final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
- final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
- setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
-
- // Create a TaskFragment with only trusted embedded activity
- final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm)
- .setParentTask(task)
- .createActivityCount(1)
- .setOrganizer(organizer)
- .build();
- final ActivityRecord activity = taskFragment.getChildAt(0).asActivityRecord();
- prepareActivityForAppTransition(activity);
- final int uid = mAtm.mTaskFragmentOrganizerController.getTaskFragmentOrganizerUid(
- getITaskFragmentOrganizer(organizer));
- doReturn(true).when(task).isFullyTrustedEmbedding(uid);
- spyOn(mDisplayContent.mAppTransition);
-
- // Prepare and start transition.
- prepareAndTriggerAppTransition(activity, null /* closingActivity */, taskFragment);
- waitUntilWindowAnimatorIdle();
-
- // The animation will be animated remotely by client, but input should not be dropped for
- // fully trusted.
- assertTrue(remoteAnimationRunner.isAnimationStarted());
- verify(activity, never()).setDropInputForAnimation(true);
- verify(activity, never()).setDropInputMode(DropInputMode.ALL);
- }
-
- @Test
- public void testTransitionGoodToGoForTaskFragments() {
- final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
- final Task task = createTask(mDisplayContent);
- final TaskFragment changeTaskFragment =
- createTaskFragmentWithEmbeddedActivity(task, organizer);
- final TaskFragment emptyTaskFragment = new TaskFragmentBuilder(mAtm)
- .setParentTask(task)
- .setOrganizer(organizer)
- .build();
- prepareActivityForAppTransition(changeTaskFragment.getTopMostActivity());
- spyOn(mDisplayContent.mAppTransition);
- spyOn(emptyTaskFragment);
-
- prepareAndTriggerAppTransition(
- null /* openingActivity */, null /* closingActivity*/, changeTaskFragment);
-
- // Transition not ready because there is an empty non-finishing TaskFragment.
- verify(mDisplayContent.mAppTransition, never()).goodToGo(anyInt(), any());
-
- doReturn(true).when(emptyTaskFragment).hasChild();
- emptyTaskFragment.remove(false /* withTransition */, "test");
-
- mDisplayContent.mAppTransitionController.handleAppTransitionReady();
-
- // Transition ready because the empty (no running activity) TaskFragment is requested to be
- // removed.
- verify(mDisplayContent.mAppTransition).goodToGo(anyInt(), any());
- }
-
- @Test
- public void testTransitionGoodToGoForTaskFragments_detachedApp() {
- final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
- final ITaskFragmentOrganizer iOrganizer = getITaskFragmentOrganizer(organizer);
- registerTaskFragmentOrganizer(iOrganizer);
- final Task task = createTask(mDisplayContent);
- final TaskFragment changeTaskFragment =
- createTaskFragmentWithEmbeddedActivity(task, organizer);
- final TaskFragment emptyTaskFragment = new TaskFragmentBuilder(mAtm)
- .setParentTask(task)
- .setOrganizer(organizer)
- .build();
- prepareActivityForAppTransition(changeTaskFragment.getTopMostActivity());
- // To make sure that having a detached activity won't cause any issue.
- final ActivityRecord detachedActivity = createActivityRecord(task);
- detachedActivity.removeImmediately();
- assertNull(detachedActivity.getRootTask());
- spyOn(mDisplayContent.mAppTransition);
- spyOn(emptyTaskFragment);
-
- prepareAndTriggerAppTransition(
- null /* openingActivity */, detachedActivity, changeTaskFragment);
-
- // Transition not ready because there is an empty non-finishing TaskFragment.
- verify(mDisplayContent.mAppTransition, never()).goodToGo(anyInt(), any());
-
- doReturn(true).when(emptyTaskFragment).hasChild();
- emptyTaskFragment.remove(false /* withTransition */, "test");
-
- mDisplayContent.mAppTransitionController.handleAppTransitionReady();
-
- // Transition ready because the empty (no running activity) TaskFragment is requested to be
- // removed.
- verify(mDisplayContent.mAppTransition).goodToGo(anyInt(), any());
- }
-
- /** Registers remote animation for the organizer. */
- private void setupTaskFragmentRemoteAnimation(TaskFragmentOrganizer organizer,
- TestRemoteAnimationRunner remoteAnimationRunner) {
- final RemoteAnimationAdapter adapter = new RemoteAnimationAdapter(
- remoteAnimationRunner, 10, 1);
- final ITaskFragmentOrganizer iOrganizer = getITaskFragmentOrganizer(organizer);
- final RemoteAnimationDefinition definition = new RemoteAnimationDefinition();
- definition.addRemoteAnimation(TRANSIT_OLD_TASK_FRAGMENT_CHANGE, adapter);
- definition.addRemoteAnimation(TRANSIT_OLD_TASK_FRAGMENT_OPEN, adapter);
- definition.addRemoteAnimation(TRANSIT_OLD_TASK_FRAGMENT_CLOSE, adapter);
- definition.addRemoteAnimation(TRANSIT_OLD_ACTIVITY_OPEN, adapter);
- definition.addRemoteAnimation(TRANSIT_OLD_ACTIVITY_CLOSE, adapter);
- registerTaskFragmentOrganizer(iOrganizer);
- mAtm.mTaskFragmentOrganizerController.registerRemoteAnimations(iOrganizer, definition);
- }
-
- private static ITaskFragmentOrganizer getITaskFragmentOrganizer(
- TaskFragmentOrganizer organizer) {
- return ITaskFragmentOrganizer.Stub.asInterface(organizer.getOrganizerToken().asBinder());
- }
-
- private void prepareAndTriggerAppTransition(@Nullable ActivityRecord openingActivity,
- @Nullable ActivityRecord closingActivity, @Nullable TaskFragment changingTaskFragment) {
- if (openingActivity != null) {
- mDisplayContent.mAppTransition.prepareAppTransition(TRANSIT_OPEN, 0);
- mDisplayContent.mOpeningApps.add(openingActivity);
- }
- if (closingActivity != null) {
- mDisplayContent.mAppTransition.prepareAppTransition(TRANSIT_CLOSE, 0);
- mDisplayContent.mClosingApps.add(closingActivity);
- }
- if (changingTaskFragment != null) {
- mDisplayContent.mAppTransition.prepareAppTransition(TRANSIT_CHANGE, 0);
- mDisplayContent.mChangingContainers.add(changingTaskFragment);
- }
- mDisplayContent.mAppTransitionController.handleAppTransitionReady();
- }
-
- private static void prepareActivityForAppTransition(ActivityRecord activity) {
- // Transition will wait until all participated activities to be drawn.
- activity.allDrawn = true;
- // Skip manipulate the SurfaceControl.
- doNothing().when(activity).setDropInputMode(anyInt());
- // Assume the activity contains a window.
- doReturn(true).when(activity).hasChild();
- // Make sure activity can create remote animation target.
- doReturn(mock(RemoteAnimationTarget.class)).when(activity).createRemoteAnimationTarget(
- any());
- }
-}
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
deleted file mode 100644
index 8553fbd..0000000
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
+++ /dev/null
@@ -1,520 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm;
-
-import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
-import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
-import static android.view.WindowManager.TRANSIT_CHANGE;
-import static android.view.WindowManager.TRANSIT_CLOSE;
-import static android.view.WindowManager.TRANSIT_FLAG_APP_CRASHED;
-import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY;
-import static android.view.WindowManager.TRANSIT_KEYGUARD_OCCLUDE;
-import static android.view.WindowManager.TRANSIT_KEYGUARD_UNOCCLUDE;
-import static android.view.WindowManager.TRANSIT_NONE;
-import static android.view.WindowManager.TRANSIT_OLD_CRASHING_ACTIVITY_CLOSE;
-import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_GOING_AWAY;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CHANGE;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CLOSE;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_OPEN;
-import static android.view.WindowManager.TRANSIT_OLD_UNSET;
-import static android.view.WindowManager.TRANSIT_OPEN;
-
-import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
-
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
-import static com.android.server.wm.WindowContainer.POSITION_TOP;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assume.assumeFalse;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.mock;
-
-import android.graphics.Rect;
-import android.os.Binder;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.platform.test.annotations.Presubmit;
-import android.util.ArraySet;
-import android.view.Display;
-import android.view.IRemoteAnimationFinishedCallback;
-import android.view.IRemoteAnimationRunner;
-import android.view.RemoteAnimationAdapter;
-import android.view.RemoteAnimationTarget;
-import android.view.SurfaceControl;
-import android.view.WindowManager;
-import android.view.animation.Animation;
-import android.window.ITaskFragmentOrganizer;
-import android.window.TaskFragmentOrganizer;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.internal.policy.TransitionAnimation;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/**
- * Test class for {@link AppTransition}.
- *
- * Build/Install/Run:
- * atest WmTests:AppTransitionTests
- */
-@SmallTest
-@Presubmit
-@RunWith(WindowTestRunner.class)
-public class AppTransitionTests extends WindowTestsBase {
- private DisplayContent mDc;
-
- @Before
- public void setUp() throws Exception {
- doNothing().when(mWm.mRoot).performSurfacePlacement();
- mDc = mWm.getDefaultDisplayContentLocked();
- }
-
- @Test
- public void testKeyguardOverride() {
- final DisplayContent dc = createNewDisplay(Display.STATE_ON);
- final ActivityRecord activity = createActivityRecord(dc);
-
- mDc.prepareAppTransition(TRANSIT_OPEN);
- mDc.prepareAppTransition(TRANSIT_KEYGUARD_OCCLUDE);
- mDc.prepareAppTransition(TRANSIT_KEYGUARD_GOING_AWAY);
- mDc.mOpeningApps.add(activity);
- assertEquals(TRANSIT_OLD_KEYGUARD_GOING_AWAY,
- AppTransitionController.getTransitCompatType(mDc.mAppTransition,
- mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps,
- mDisplayContent.mChangingContainers, null /* wallpaperTarget */,
- null /* oldWallpaper */, false /*skipAppTransitionAnimation*/));
- }
-
- @Test
- public void testKeyguardUnoccludeOcclude() {
- final DisplayContent dc = createNewDisplay(Display.STATE_ON);
- final ActivityRecord activity = createActivityRecord(dc);
-
- mDc.prepareAppTransition(TRANSIT_KEYGUARD_UNOCCLUDE);
- mDc.prepareAppTransition(TRANSIT_KEYGUARD_OCCLUDE);
- mDc.mOpeningApps.add(activity);
- assertEquals(TRANSIT_NONE,
- AppTransitionController.getTransitCompatType(mDc.mAppTransition,
- mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps,
- mDisplayContent.mChangingContainers, null /* wallpaperTarget */,
- null /* oldWallpaper */, false /*skipAppTransitionAnimation*/));
-
- }
-
- @Test
- public void testKeyguardKeep() {
- final DisplayContent dc = createNewDisplay(Display.STATE_ON);
- final ActivityRecord activity = createActivityRecord(dc);
-
- mDc.prepareAppTransition(TRANSIT_KEYGUARD_GOING_AWAY);
- mDc.prepareAppTransition(TRANSIT_OPEN);
- mDc.mOpeningApps.add(activity);
- assertEquals(TRANSIT_OLD_KEYGUARD_GOING_AWAY,
- AppTransitionController.getTransitCompatType(mDc.mAppTransition,
- mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps,
- mDisplayContent.mChangingContainers, null /* wallpaperTarget */,
- null /* oldWallpaper */, false /*skipAppTransitionAnimation*/));
- }
-
- @Test
- public void testCrashing() {
- final DisplayContent dc = createNewDisplay(Display.STATE_ON);
- final ActivityRecord activity = createActivityRecord(dc);
-
- mDc.prepareAppTransition(TRANSIT_OPEN);
- mDc.prepareAppTransition(TRANSIT_CLOSE, TRANSIT_FLAG_APP_CRASHED);
- mDc.mClosingApps.add(activity);
- assertEquals(TRANSIT_OLD_CRASHING_ACTIVITY_CLOSE,
- AppTransitionController.getTransitCompatType(mDc.mAppTransition,
- mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps,
- mDisplayContent.mChangingContainers, null /* wallpaperTarget */,
- null /* oldWallpaper */, false /*skipAppTransitionAnimation*/));
- }
-
- @Test
- public void testKeepKeyguard_withCrashing() {
- final DisplayContent dc = createNewDisplay(Display.STATE_ON);
- final ActivityRecord activity = createActivityRecord(dc);
-
- mDc.prepareAppTransition(TRANSIT_KEYGUARD_GOING_AWAY);
- mDc.prepareAppTransition(TRANSIT_CLOSE, TRANSIT_FLAG_APP_CRASHED);
- mDc.mClosingApps.add(activity);
- assertEquals(TRANSIT_OLD_KEYGUARD_GOING_AWAY,
- AppTransitionController.getTransitCompatType(mDc.mAppTransition,
- mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps,
- mDisplayContent.mChangingContainers, null /* wallpaperTarget */,
- null /* oldWallpaper */, false /*skipAppTransitionAnimation*/));
- }
-
- @Test
- public void testSkipTransitionAnimation() {
- final DisplayContent dc = createNewDisplay(Display.STATE_ON);
- final ActivityRecord activity = createActivityRecord(dc);
-
- mDc.prepareAppTransition(TRANSIT_OPEN);
- mDc.prepareAppTransition(TRANSIT_CLOSE);
- mDc.mClosingApps.add(activity);
- assertEquals(TRANSIT_OLD_UNSET,
- AppTransitionController.getTransitCompatType(mDc.mAppTransition,
- mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps,
- mDisplayContent.mChangingContainers, null /* wallpaperTarget */,
- null /* oldWallpaper */, true /*skipAppTransitionAnimation*/));
- }
-
- @Test
- public void testTaskChangeWindowingMode() {
- final ActivityRecord activity = createActivityRecord(mDc);
-
- mDc.prepareAppTransition(TRANSIT_OPEN);
- mDc.prepareAppTransition(TRANSIT_CHANGE);
- mDc.mOpeningApps.add(activity); // Make sure TRANSIT_CHANGE has the priority
- mDc.mChangingContainers.add(activity.getTask());
-
- assertEquals(TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE,
- AppTransitionController.getTransitCompatType(mDc.mAppTransition,
- mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps,
- mDisplayContent.mChangingContainers, null /* wallpaperTarget */,
- null /* oldWallpaper */, false /*skipAppTransitionAnimation*/));
- }
-
- @Test
- public void testTaskFragmentChange() {
- final ActivityRecord activity = createActivityRecord(mDc);
- final TaskFragment taskFragment = new TaskFragment(mAtm, new Binder(),
- true /* createdByOrganizer */, true /* isEmbedded */);
- activity.getTask().addChild(taskFragment, POSITION_TOP);
- activity.reparent(taskFragment, POSITION_TOP);
-
- mDc.prepareAppTransition(TRANSIT_OPEN);
- mDc.prepareAppTransition(TRANSIT_CHANGE);
- mDc.mOpeningApps.add(activity); // Make sure TRANSIT_CHANGE has the priority
- mDc.mChangingContainers.add(taskFragment);
-
- assertEquals(TRANSIT_OLD_TASK_FRAGMENT_CHANGE,
- AppTransitionController.getTransitCompatType(mDc.mAppTransition,
- mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps,
- mDisplayContent.mChangingContainers, null /* wallpaperTarget */,
- null /* oldWallpaper */, false /*skipAppTransitionAnimation*/));
- }
-
- @Test
- public void testTaskFragmentOpeningTransition() {
- final ActivityRecord activity = createHierarchyForTaskFragmentTest();
- activity.setVisible(false);
-
- mDisplayContent.prepareAppTransition(TRANSIT_OPEN);
- mDisplayContent.mOpeningApps.add(activity);
- assertEquals(TRANSIT_OLD_TASK_FRAGMENT_OPEN,
- AppTransitionController.getTransitCompatType(mDisplayContent.mAppTransition,
- mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps,
- mDisplayContent.mChangingContainers, null /* wallpaperTarget */,
- null /* oldWallpaper */, false /* skipAppTransitionAnimation */));
- }
-
- @Test
- public void testTaskFragmentClosingTransition() {
- final ActivityRecord activity = createHierarchyForTaskFragmentTest();
- activity.setVisible(true);
-
- mDisplayContent.prepareAppTransition(TRANSIT_CLOSE);
- mDisplayContent.mClosingApps.add(activity);
- assertEquals(TRANSIT_OLD_TASK_FRAGMENT_CLOSE,
- AppTransitionController.getTransitCompatType(mDisplayContent.mAppTransition,
- mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps,
- mDisplayContent.mChangingContainers, null /* wallpaperTarget */,
- null /* oldWallpaper */, false /* skipAppTransitionAnimation */));
- }
-
- /**
- * Creates a {@link Task} with two {@link TaskFragment TaskFragments}.
- * The bottom TaskFragment is to prevent
- * {@link AppTransitionController#getAnimationTargets(ArraySet, ArraySet, boolean) the animation
- * target} to promote to Task or above.
- *
- * @return The Activity to be put in either opening or closing Activity
- */
- private ActivityRecord createHierarchyForTaskFragmentTest() {
- final Task parentTask = createTask(mDisplayContent);
- final TaskFragment bottomTaskFragment = createTaskFragmentWithActivity(parentTask);
- final ActivityRecord bottomActivity = bottomTaskFragment.getTopMostActivity();
- bottomActivity.setOccludesParent(true);
- bottomActivity.setVisible(true);
-
- final TaskFragment verifiedTaskFragment = createTaskFragmentWithActivity(parentTask);
- final ActivityRecord activity = verifiedTaskFragment.getTopMostActivity();
- activity.setOccludesParent(true);
-
- return activity;
- }
-
- @Test
- public void testAppTransitionStateForMultiDisplay() {
- // Create 2 displays & presume both display the state is ON for ready to display & animate.
- final DisplayContent dc1 = createNewDisplay(Display.STATE_ON);
- final DisplayContent dc2 = createNewDisplay(Display.STATE_ON);
-
- // Create 2 app window tokens to represent 2 activity window.
- final ActivityRecord activity1 = createActivityRecord(dc1);
- final ActivityRecord activity2 = createActivityRecord(dc2);
-
- activity1.allDrawn = true;
- activity1.startingMoved = true;
-
- // Simulate activity resume / finish flows to prepare app transition & set visibility,
- // make sure transition is set as expected for each display.
- dc1.prepareAppTransition(TRANSIT_OPEN);
- dc2.prepareAppTransition(TRANSIT_CLOSE);
- // One activity window is visible for resuming & the other activity window is invisible
- // for finishing in different display.
- activity1.setVisibility(true);
- activity2.setVisibility(false);
-
- // Make sure each display is in animating stage.
- assertTrue(dc1.mOpeningApps.size() > 0);
- assertTrue(dc2.mClosingApps.size() > 0);
- assertTrue(dc1.isAppTransitioning());
- assertTrue(dc2.isAppTransitioning());
- }
-
- @Test
- public void testCleanAppTransitionWhenRootTaskReparent() {
- // Create 2 displays & presume both display the state is ON for ready to display & animate.
- final DisplayContent dc1 = createNewDisplay(Display.STATE_ON);
- final DisplayContent dc2 = createNewDisplay(Display.STATE_ON);
-
- final Task rootTask1 = createTask(dc1);
- final Task task1 = createTaskInRootTask(rootTask1, 0 /* userId */);
- final ActivityRecord activity1 = createNonAttachedActivityRecord(dc1);
- task1.addChild(activity1, 0);
-
- // Simulate same app is during opening / closing transition set stage.
- dc1.mClosingApps.add(activity1);
- assertTrue(dc1.mClosingApps.size() > 0);
-
- dc1.prepareAppTransition(TRANSIT_OPEN);
- assertTrue(dc1.mAppTransition.containsTransitRequest(TRANSIT_OPEN));
- assertTrue(dc1.mAppTransition.isTransitionSet());
-
- dc1.mOpeningApps.add(activity1);
- assertTrue(dc1.mOpeningApps.size() > 0);
-
- // Move root task to another display.
- rootTask1.reparent(dc2.getDefaultTaskDisplayArea(), true);
-
- // Verify if token are cleared from both pending transition list in former display.
- assertFalse(dc1.mOpeningApps.contains(activity1));
- assertFalse(dc1.mOpeningApps.contains(activity1));
- }
-
- @Test
- public void testLoadAnimationSafely() {
- DisplayContent dc = createNewDisplay(Display.STATE_ON);
- assertNull(dc.mAppTransition.loadAnimationSafely(
- getInstrumentation().getTargetContext(), -1));
- }
-
- @Test
- public void testCancelRemoteAnimationWhenFreeze() {
- final DisplayContent dc = createNewDisplay(Display.STATE_ON);
- doReturn(false).when(dc).onDescendantOrientationChanged(any());
- final WindowState exitingAppWindow = newWindowBuilder("exiting app",
- TYPE_BASE_APPLICATION).setDisplay(dc).build();
- final ActivityRecord exitingActivity = exitingAppWindow.mActivityRecord;
- // Wait until everything in animation handler get executed to prevent the exiting window
- // from being removed during WindowSurfacePlacer Traversal.
- waitUntilHandlersIdle();
-
- // Set a remote animator.
- final TestRemoteAnimationRunner runner = new TestRemoteAnimationRunner();
- final RemoteAnimationAdapter adapter = new RemoteAnimationAdapter(
- runner, 100, 50, true /* changeNeedsSnapshot */);
- // RemoteAnimationController will tracking RemoteAnimationAdapter's caller with calling pid.
- adapter.setCallingPidUid(123, 456);
-
- // Simulate activity finish flows to prepare app transition & set visibility,
- // make sure transition is set as expected.
- dc.prepareAppTransition(TRANSIT_CLOSE);
- assertTrue(dc.mAppTransition.containsTransitRequest(TRANSIT_CLOSE));
- dc.mAppTransition.overridePendingAppTransitionRemote(adapter);
- exitingActivity.setVisibility(false);
- assertTrue(dc.mClosingApps.size() > 0);
-
- // Make sure window is in animating stage before freeze, and cancel after freeze.
- assertTrue(dc.isAppTransitioning());
- assertFalse(runner.mCancelled);
- dc.mAppTransition.freeze();
- assertFalse(dc.isAppTransitioning());
- assertTrue(runner.mCancelled);
- }
-
- @Test
- public void testGetAnimationStyleResId() {
- // Verify getAnimationStyleResId will return as LayoutParams.windowAnimations when without
- // specifying window type.
- final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams();
- attrs.windowAnimations = 0x12345678;
- assertEquals(attrs.windowAnimations, mDc.mAppTransition.getAnimationStyleResId(attrs));
-
- // Verify getAnimationStyleResId will return system resource Id when the window type is
- // starting window.
- attrs.type = TYPE_APPLICATION_STARTING;
- assertEquals(mDc.mAppTransition.getDefaultWindowAnimationStyleResId(),
- mDc.mAppTransition.getAnimationStyleResId(attrs));
- }
-
- @Test
- public void testActivityRecordReparentedToTaskFragment() {
- final ActivityRecord activity = createActivityRecord(mDc);
- final SurfaceControl activityLeash = mock(SurfaceControl.class);
- doNothing().when(activity).setDropInputMode(anyInt());
- activity.setVisibility(true);
- activity.setSurfaceControl(activityLeash);
- final Task task = activity.getTask();
-
- // Add a TaskFragment of half of the Task size.
- final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
- final ITaskFragmentOrganizer iOrganizer =
- ITaskFragmentOrganizer.Stub.asInterface(organizer.getOrganizerToken().asBinder());
- registerTaskFragmentOrganizer(iOrganizer);
- final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm)
- .setParentTask(task)
- .setOrganizer(organizer)
- .build();
- final Rect taskBounds = new Rect();
- task.getBounds(taskBounds);
- taskFragment.setBounds(0, 0, taskBounds.right / 2, taskBounds.bottom);
- spyOn(taskFragment);
-
- assertTrue(mDc.mChangingContainers.isEmpty());
- assertFalse(mDc.mAppTransition.isTransitionSet());
-
- // Schedule app transition when reparent activity to a TaskFragment of different size.
- final Rect startBounds = new Rect(activity.getBounds());
- activity.reparent(taskFragment, POSITION_TOP);
-
- // It should transit at TaskFragment level with snapshot on the activity surface.
- verify(taskFragment).initializeChangeTransition(activity.getBounds(), activityLeash);
- assertTrue(mDc.mChangingContainers.contains(taskFragment));
- assertTrue(mDc.mAppTransition.containsTransitRequest(TRANSIT_CHANGE));
- }
-
- @Test
- public void testGetNextAppTransitionBackgroundColor() {
- assumeFalse(WindowManagerService.sEnableShellTransitions);
-
- // No override by default.
- assertEquals(0, mDc.mAppTransition.getNextAppTransitionBackgroundColor());
-
- // Override with a custom color.
- mDc.mAppTransition.prepareAppTransition(TRANSIT_OPEN, 0);
- final int testColor = 123;
- mDc.mAppTransition.overridePendingAppTransition("testPackage", 0 /* enterAnim */,
- 0 /* exitAnim */, testColor, null /* startedCallback */, null /* endedCallback */,
- false /* overrideTaskTransaction */);
-
- assertEquals(testColor, mDc.mAppTransition.getNextAppTransitionBackgroundColor());
- assertTrue(mDc.mAppTransition.isNextAppTransitionOverrideRequested());
-
- // Override with ActivityEmbedding remote animation. Background color should be kept.
- mDc.mAppTransition.overridePendingAppTransitionRemote(mock(RemoteAnimationAdapter.class),
- false /* sync */, true /* isActivityEmbedding */);
-
- assertEquals(testColor, mDc.mAppTransition.getNextAppTransitionBackgroundColor());
- assertFalse(mDc.mAppTransition.isNextAppTransitionOverrideRequested());
-
- // Background color should not be cleared anymore after #clear().
- mDc.mAppTransition.clear();
- assertEquals(0, mDc.mAppTransition.getNextAppTransitionBackgroundColor());
- assertFalse(mDc.mAppTransition.isNextAppTransitionOverrideRequested());
- }
-
- @Test
- public void testGetNextAppRequestedAnimation() {
- assumeFalse(WindowManagerService.sEnableShellTransitions);
- final String packageName = "testPackage";
- final int enterAnimResId = 1;
- final int exitAnimResId = 2;
- final int testColor = 123;
- final Animation enterAnim = mock(Animation.class);
- final Animation exitAnim = mock(Animation.class);
- final TransitionAnimation transitionAnimation = mDc.mAppTransition.mTransitionAnimation;
- spyOn(transitionAnimation);
- doReturn(enterAnim).when(transitionAnimation)
- .loadAppTransitionAnimation(packageName, enterAnimResId);
- doReturn(exitAnim).when(transitionAnimation)
- .loadAppTransitionAnimation(packageName, exitAnimResId);
-
- // No override by default.
- assertNull(mDc.mAppTransition.getNextAppRequestedAnimation(true /* enter */));
- assertNull(mDc.mAppTransition.getNextAppRequestedAnimation(false /* enter */));
-
- // Override with a custom animation.
- mDc.mAppTransition.prepareAppTransition(TRANSIT_OPEN, 0);
- mDc.mAppTransition.overridePendingAppTransition(packageName, enterAnimResId, exitAnimResId,
- testColor, null /* startedCallback */, null /* endedCallback */,
- false /* overrideTaskTransaction */);
-
- assertEquals(enterAnim, mDc.mAppTransition.getNextAppRequestedAnimation(true /* enter */));
- assertEquals(exitAnim, mDc.mAppTransition.getNextAppRequestedAnimation(false /* enter */));
- assertTrue(mDc.mAppTransition.isNextAppTransitionOverrideRequested());
-
- // Override with ActivityEmbedding remote animation. Custom animation should be kept.
- mDc.mAppTransition.overridePendingAppTransitionRemote(mock(RemoteAnimationAdapter.class),
- false /* sync */, true /* isActivityEmbedding */);
-
- assertEquals(enterAnim, mDc.mAppTransition.getNextAppRequestedAnimation(true /* enter */));
- assertEquals(exitAnim, mDc.mAppTransition.getNextAppRequestedAnimation(false /* enter */));
- assertFalse(mDc.mAppTransition.isNextAppTransitionOverrideRequested());
-
- // Custom animation should not be cleared anymore after #clear().
- mDc.mAppTransition.clear();
- assertNull(mDc.mAppTransition.getNextAppRequestedAnimation(true /* enter */));
- assertNull(mDc.mAppTransition.getNextAppRequestedAnimation(false /* enter */));
- }
-
- private class TestRemoteAnimationRunner implements IRemoteAnimationRunner {
- boolean mCancelled = false;
- @Override
- public void onAnimationStart(@WindowManager.TransitionOldType int transit,
- RemoteAnimationTarget[] apps,
- RemoteAnimationTarget[] wallpapers,
- RemoteAnimationTarget[] nonApps,
- IRemoteAnimationFinishedCallback finishedCallback) throws RemoteException {
- }
-
- @Override
- public void onAnimationCancelled() {
- mCancelled = true;
- }
-
- @Override
- public IBinder asBinder() {
- return null;
- }
- }
-}
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 0964ebe..82435b2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -1172,11 +1172,12 @@
.setScreenOrientation(getRotatedOrientation(mDisplayContent)).build();
prev.setVisibleRequested(false);
final ActivityRecord top = new ActivityBuilder(mAtm).setCreateTask(true)
+ .setVisible(false)
.setScreenOrientation(SCREEN_ORIENTATION_BEHIND).build();
assertNotEquals(WindowConfiguration.ROTATION_UNDEFINED,
mDisplayContent.rotationForActivityInDifferentOrientation(top));
- mDisplayContent.requestTransitionAndLegacyPrepare(WindowManager.TRANSIT_OPEN, 0);
+ requestTransition(top, WindowManager.TRANSIT_OPEN);
top.setVisibility(true);
mDisplayContent.updateOrientation();
// The top uses "behind", so the orientation is decided by the previous.
@@ -1609,8 +1610,7 @@
final ActivityRecord app = mAppWindow.mActivityRecord;
app.setVisible(false);
app.setVisibleRequested(false);
- registerTestTransitionPlayer();
- mDisplayContent.requestTransitionAndLegacyPrepare(WindowManager.TRANSIT_OPEN, 0);
+ requestTransition(app, WindowManager.TRANSIT_OPEN);
app.setVisibility(true);
final int newOrientation = getRotatedOrientation(mDisplayContent);
app.setRequestedOrientation(newOrientation);
diff --git a/services/tests/wmtests/src/com/android/server/wm/PresentationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/PresentationControllerTests.java
new file mode 100644
index 0000000..db90c28
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/PresentationControllerTests.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static android.view.Display.FLAG_PRESENTATION;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.window.flags.Flags.FLAG_ENABLE_PRESENTATION_FOR_CONNECTED_DISPLAYS;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.eq;
+
+import android.graphics.Rect;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.annotations.Presubmit;
+import android.view.DisplayInfo;
+import android.view.IWindow;
+import android.view.InsetsSourceControl;
+import android.view.InsetsState;
+import android.view.View;
+import android.view.WindowInsets;
+import android.view.WindowManager;
+import android.view.WindowManagerGlobal;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Build/Install/Run:
+ * atest WmTests:PresentationControllerTests
+ */
+@SmallTest
+@Presubmit
+@RunWith(WindowTestRunner.class)
+public class PresentationControllerTests extends WindowTestsBase {
+
+ @EnableFlags(FLAG_ENABLE_PRESENTATION_FOR_CONNECTED_DISPLAYS)
+ @Test
+ public void testPresentationHidesActivitiesBehind() {
+ final DisplayInfo displayInfo = new DisplayInfo();
+ displayInfo.copyFrom(mDisplayInfo);
+ displayInfo.flags = FLAG_PRESENTATION;
+ final DisplayContent dc = createNewDisplay(displayInfo);
+ final int displayId = dc.getDisplayId();
+ doReturn(dc).when(mWm.mRoot).getDisplayContentOrCreate(displayId);
+ final ActivityRecord activity = createActivityRecord(createTask(dc));
+ assertTrue(activity.isVisible());
+
+ doReturn(true).when(() -> UserManager.isVisibleBackgroundUsersEnabled());
+ final int uid = 100000; // uid for non-system user
+ final Session session = createTestSession(mAtm, 1234 /* pid */, uid);
+ final int userId = UserHandle.getUserId(uid);
+ doReturn(false).when(mWm.mUmInternal).isUserVisible(eq(userId), eq(displayId));
+ final WindowManager.LayoutParams params = new WindowManager.LayoutParams(
+ WindowManager.LayoutParams.TYPE_PRESENTATION);
+
+ final IWindow clientWindow = new TestIWindow();
+ final int result = mWm.addWindow(session, clientWindow, params, View.VISIBLE, displayId,
+ userId, WindowInsets.Type.defaultVisible(), null, new InsetsState(),
+ new InsetsSourceControl.Array(), new Rect(), new float[1]);
+ assertTrue(result >= WindowManagerGlobal.ADD_OKAY);
+ assertFalse(activity.isVisible());
+
+ final WindowState window = mWm.windowForClientLocked(session, clientWindow, false);
+ window.removeImmediately();
+ assertTrue(activity.isVisible());
+ }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
index fc4f54a..e4a1bf6 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
@@ -454,31 +454,6 @@
}
@Test
- public void testMovingBottomMostRootTaskActivityToPinnedRootTask() {
- final Task fullscreenTask = mRootWindowContainer.getDefaultTaskDisplayArea().createRootTask(
- WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
- final ActivityRecord firstActivity = new ActivityBuilder(mAtm)
- .setTask(fullscreenTask).build();
- final Task task = firstActivity.getTask();
-
- final ActivityRecord secondActivity = new ActivityBuilder(mAtm)
- .setTask(fullscreenTask).build();
-
- fullscreenTask.moveTaskToBack(task);
-
- // Ensure full screen task has both tasks.
- ensureTaskPlacement(fullscreenTask, firstActivity, secondActivity);
- assertEquals(task.getTopMostActivity(), secondActivity);
- firstActivity.setState(STOPPED, "testMovingBottomMostRootTaskActivityToPinnedRootTask");
-
-
- // Move first activity to pinned root task.
- mRootWindowContainer.moveActivityToPinnedRootTask(secondActivity, "initialMove");
-
- assertTrue(firstActivity.mRequestForceTransition);
- }
-
- @Test
public void testMultipleActivitiesTaskEnterPip() {
// Enable shell transition because the order of setting windowing mode is different.
registerTestTransitionPlayer();
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index dba463a..95bca2b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -1811,9 +1811,9 @@
}
addStatusBar(mActivity.mDisplayContent);
- mActivity.setVisible(false);
- mActivity.mDisplayContent.prepareAppTransition(WindowManager.TRANSIT_OPEN);
- mActivity.mDisplayContent.mOpeningApps.add(mActivity);
+ mActivity.setVisibleRequested(false);
+ requestTransition(mActivity, WindowManager.TRANSIT_OPEN);
+ mActivity.setVisibility(true);
final float maxAspect = 1.8f;
prepareUnresizable(mActivity, maxAspect, SCREEN_ORIENTATION_LANDSCAPE);
diff --git a/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java b/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java
index 7dba142..2544550 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java
@@ -22,6 +22,7 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
+import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMATION;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -69,7 +70,6 @@
private MyAnimatable mAnimatable;
private MyAnimatable mAnimatable2;
- private DeferFinishAnimatable mDeferFinishAnimatable;
@Before
public void setUp() throws Exception {
@@ -77,14 +77,12 @@
mAnimatable = new MyAnimatable(mWm, mTransaction);
mAnimatable2 = new MyAnimatable(mWm, mTransaction);
- mDeferFinishAnimatable = new DeferFinishAnimatable(mWm, mTransaction);
}
@After
public void tearDown() {
mAnimatable = null;
mAnimatable2 = null;
- mDeferFinishAnimatable = null;
}
@Test
@@ -202,41 +200,33 @@
}
@Test
- public void testDeferFinish() {
-
- // Start animation
- final OnAnimationFinishedCallback onFinishedCallback = startDeferFinishAnimatable(mSpec);
-
- // Finish the animation but then make sure we are deferring.
- onFinishedCallback.onAnimationFinished(ANIMATION_TYPE_APP_TRANSITION, mSpec);
- assertAnimating(mDeferFinishAnimatable);
-
- // Now end defer finishing.
- mDeferFinishAnimatable.mEndDeferFinishCallback.run();
- assertNotAnimating(mAnimatable2);
- assertTrue(mDeferFinishAnimatable.mFinishedCallbackCalled);
- assertEquals(ANIMATION_TYPE_APP_TRANSITION, mDeferFinishAnimatable.mFinishedAnimationType);
- verify(mTransaction).remove(eq(mDeferFinishAnimatable.mLeash));
- }
-
- @Test
public void testDeferFinishDoNotFinishNextAnimation() {
+ final DeferredFinishAdapter deferredFinishAdapter = new DeferredFinishAdapter();
+ spyOn(deferredFinishAdapter);
// Start the first animation.
- final OnAnimationFinishedCallback onFinishedCallback = startDeferFinishAnimatable(mSpec);
- onFinishedCallback.onAnimationFinished(ANIMATION_TYPE_APP_TRANSITION, mSpec);
+ mAnimatable.mSurfaceAnimator.startAnimation(mTransaction, deferredFinishAdapter,
+ true /* hidden */, ANIMATION_TYPE_WINDOW_ANIMATION);
+ assertAnimating(mAnimatable);
+ final ArgumentCaptor<OnAnimationFinishedCallback> callbackCaptor = ArgumentCaptor.forClass(
+ OnAnimationFinishedCallback.class);
+ verify(deferredFinishAdapter).startAnimation(any(), any(),
+ eq(ANIMATION_TYPE_WINDOW_ANIMATION), callbackCaptor.capture());
+ final OnAnimationFinishedCallback onFinishedCallback = callbackCaptor.getValue();
+ onFinishedCallback.onAnimationFinished(ANIMATION_TYPE_APP_TRANSITION,
+ deferredFinishAdapter);
// The callback is the resetAndInvokeFinish in {@link SurfaceAnimator#getFinishedCallback}.
- final Runnable firstDeferFinishCallback = mDeferFinishAnimatable.mEndDeferFinishCallback;
+ final Runnable firstDeferFinishCallback = deferredFinishAdapter.mEndDeferFinishCallback;
// Start the second animation.
- mDeferFinishAnimatable.mSurfaceAnimator.cancelAnimation();
- startDeferFinishAnimatable(mSpec2);
- mDeferFinishAnimatable.mFinishedCallbackCalled = false;
+ mAnimatable.mSurfaceAnimator.cancelAnimation();
+ mAnimatable.mSurfaceAnimator.startAnimation(mTransaction, mSpec2,
+ true /* hidden */, ANIMATION_TYPE_WINDOW_ANIMATION);
+ mAnimatable.mFinishedCallbackCalled = false;
- // Simulate the first deferred callback is executed from
- // {@link AnimatingActivityRegistry#endDeferringFinished}.
+ // Simulate the first deferred callback is executed.
firstDeferFinishCallback.run();
// The second animation should not be finished.
- assertFalse(mDeferFinishAnimatable.mFinishedCallbackCalled);
+ assertFalse(mAnimatable.mFinishedCallbackCalled);
}
@Test
@@ -260,17 +250,6 @@
verify(mTransaction).remove(eq(deferredFinishAdapter.mAnimationLeash));
}
- private OnAnimationFinishedCallback startDeferFinishAnimatable(AnimationAdapter anim) {
- mDeferFinishAnimatable.mSurfaceAnimator.startAnimation(mTransaction, anim,
- true /* hidden */, ANIMATION_TYPE_APP_TRANSITION);
- final ArgumentCaptor<OnAnimationFinishedCallback> callbackCaptor = ArgumentCaptor.forClass(
- OnAnimationFinishedCallback.class);
- assertAnimating(mDeferFinishAnimatable);
- verify(anim).startAnimation(any(), any(), eq(ANIMATION_TYPE_APP_TRANSITION),
- callbackCaptor.capture());
- return callbackCaptor.getValue();
- }
-
private void assertAnimating(MyAnimatable animatable) {
assertTrue(animatable.mSurfaceAnimator.isAnimating());
assertNotNull(animatable.mSurfaceAnimator.getAnimation());
@@ -370,21 +349,6 @@
};
}
- private static class DeferFinishAnimatable extends MyAnimatable {
-
- Runnable mEndDeferFinishCallback;
-
- DeferFinishAnimatable(WindowManagerService wm, Transaction transaction) {
- super(wm, transaction);
- }
-
- @Override
- public boolean shouldDeferAnimationFinish(Runnable endDeferFinishCallback) {
- mEndDeferFinishCallback = endDeferFinishCallback;
- return true;
- }
- }
-
private static class DeferredFinishAdapter implements AnimationAdapter {
private Runnable mEndDeferFinishCallback;
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 0014465..edffab8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
@@ -32,8 +32,6 @@
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_OLD_TASK_CLOSE;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_OPEN;
-import static android.view.WindowManager.TRANSIT_OPEN;
import static android.window.DisplayAreaOrganizer.FEATURE_DEFAULT_TASK_CONTAINER;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
@@ -85,12 +83,8 @@
import android.platform.test.annotations.Presubmit;
import android.platform.test.annotations.RequiresFlagsEnabled;
import android.util.ArraySet;
-import android.view.IRemoteAnimationFinishedCallback;
-import android.view.IRemoteAnimationRunner;
import android.view.InsetsFrameProvider;
import android.view.InsetsSource;
-import android.view.RemoteAnimationAdapter;
-import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
import android.view.SurfaceSession;
import android.view.WindowInsets;
@@ -1055,25 +1049,6 @@
}
@Test
- public void testTaskCanApplyAnimation() {
- final Task rootTask = createTask(mDisplayContent);
- final Task task = createTaskInRootTask(rootTask, 0 /* userId */);
- final ActivityRecord activity2 = createActivityRecord(mDisplayContent, task);
- final ActivityRecord activity1 = createActivityRecord(mDisplayContent, task);
- verifyWindowContainerApplyAnimation(task, activity1, activity2);
- }
-
- @Test
- public void testRootTaskCanApplyAnimation() {
- final Task rootTask = createTask(mDisplayContent);
- final ActivityRecord activity2 = createActivityRecord(mDisplayContent,
- createTaskInRootTask(rootTask, 0 /* userId */));
- final ActivityRecord activity1 = createActivityRecord(mDisplayContent,
- createTaskInRootTask(rootTask, 0 /* userId */));
- verifyWindowContainerApplyAnimation(rootTask, activity1, activity2);
- }
-
- @Test
public void testGetDisplayArea() {
// WindowContainer
final WindowContainer windowContainer = new WindowContainer(mWm);
@@ -1103,59 +1078,6 @@
assertEquals(displayArea, displayArea.getDisplayArea());
}
- private void verifyWindowContainerApplyAnimation(WindowContainer wc, ActivityRecord act,
- ActivityRecord act2) {
- // Initial remote animation for app transition.
- final RemoteAnimationAdapter adapter = new RemoteAnimationAdapter(
- new IRemoteAnimationRunner.Stub() {
- @Override
- public void onAnimationStart(@WindowManager.TransitionOldType int transit,
- RemoteAnimationTarget[] apps,
- RemoteAnimationTarget[] wallpapers,
- RemoteAnimationTarget[] nonApps,
- IRemoteAnimationFinishedCallback finishedCallback) {
- try {
- finishedCallback.onAnimationFinished();
- } catch (RemoteException e) {
- e.printStackTrace();
- }
- }
-
- @Override
- public void onAnimationCancelled() {
- }
- }, 0, 0, false);
- adapter.setCallingPidUid(123, 456);
- wc.getDisplayContent().prepareAppTransition(TRANSIT_OPEN);
- wc.getDisplayContent().mAppTransition.overridePendingAppTransitionRemote(adapter);
- spyOn(wc);
- doReturn(true).when(wc).okToAnimate();
-
- // Make sure animating state is as expected after applied animation.
-
- // Animation target is promoted from act to wc. act2 is a descendant of wc, but not a source
- // of the animation.
- ArrayList<WindowContainer<WindowState>> sources = new ArrayList<>();
- sources.add(act);
- assertTrue(wc.applyAnimation(null, TRANSIT_OLD_TASK_OPEN, true, false, sources));
-
- assertEquals(act, wc.getTopMostActivity());
- assertTrue(wc.isAnimating());
- assertTrue(wc.isAnimating(0, ANIMATION_TYPE_APP_TRANSITION));
- assertTrue(wc.getAnimationSources().contains(act));
- assertFalse(wc.getAnimationSources().contains(act2));
- assertTrue(act.isAnimating(PARENTS));
- assertTrue(act.isAnimating(PARENTS, ANIMATION_TYPE_APP_TRANSITION));
- assertEquals(wc, act.getAnimatingContainer(PARENTS, ANIMATION_TYPE_APP_TRANSITION));
-
- // Make sure animation finish callback will be received and reset animating state after
- // animation finish.
- wc.getDisplayContent().mAppTransition.goodToGo(TRANSIT_OLD_TASK_OPEN, act);
- verify(wc).onAnimationFinished(eq(ANIMATION_TYPE_APP_TRANSITION), any());
- assertFalse(wc.isAnimating());
- assertFalse(act.isAnimating(PARENTS));
- }
-
@Test
public void testRegisterWindowContainerListener() {
final WindowContainer container = new WindowContainer(mWm);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
index 1323d8a..71e84c0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -26,7 +26,6 @@
import static android.permission.flags.Flags.FLAG_SENSITIVE_NOTIFICATION_APP_PROTECTION;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.FLAG_OWN_FOCUS;
-import static android.view.Display.FLAG_PRESENTATION;
import static android.view.Display.INVALID_DISPLAY;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
@@ -55,7 +54,6 @@
import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING;
import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_BACKGROUND_SOLID_COLOR;
import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_BACKGROUND_WALLPAPER;
-import static com.android.window.flags.Flags.FLAG_ENABLE_PRESENTATION_FOR_CONNECTED_DISPLAYS;
import static com.google.common.truth.Truth.assertThat;
@@ -102,7 +100,6 @@
import android.util.ArraySet;
import android.util.MergedConfiguration;
import android.view.ContentRecordingSession;
-import android.view.DisplayInfo;
import android.view.IWindow;
import android.view.InputChannel;
import android.view.InputDevice;
@@ -1409,38 +1406,6 @@
assertEquals(activityWindowInfo2, activityWindowInfo3);
}
- @EnableFlags(FLAG_ENABLE_PRESENTATION_FOR_CONNECTED_DISPLAYS)
- @Test
- public void testPresentationHidesActivitiesBehind() {
- DisplayInfo displayInfo = new DisplayInfo();
- displayInfo.copyFrom(mDisplayInfo);
- displayInfo.flags = FLAG_PRESENTATION;
- DisplayContent dc = createNewDisplay(displayInfo);
- int displayId = dc.getDisplayId();
- doReturn(dc).when(mWm.mRoot).getDisplayContentOrCreate(displayId);
- ActivityRecord activity = createActivityRecord(createTask(dc));
- assertTrue(activity.isVisible());
-
- doReturn(true).when(() -> UserManager.isVisibleBackgroundUsersEnabled());
- int uid = 100000; // uid for non-system user
- Session session = createTestSession(mAtm, 1234 /* pid */, uid);
- int userId = UserHandle.getUserId(uid);
- doReturn(false).when(mWm.mUmInternal).isUserVisible(eq(userId), eq(displayId));
- WindowManager.LayoutParams params = new WindowManager.LayoutParams(
- LayoutParams.TYPE_PRESENTATION);
-
- final IWindow clientWindow = new TestIWindow();
- int result = mWm.addWindow(session, clientWindow, params, View.VISIBLE, displayId,
- userId, WindowInsets.Type.defaultVisible(), null, new InsetsState(),
- new InsetsSourceControl.Array(), new Rect(), new float[1]);
- assertTrue(result >= WindowManagerGlobal.ADD_OKAY);
- assertFalse(activity.isVisible());
-
- final WindowState window = mWm.windowForClientLocked(session, clientWindow, false);
- window.removeImmediately();
- assertTrue(activity.isVisible());
- }
-
@Test
public void testAddOverlayWindowToUnassignedDisplay_notAllowed_ForVisibleBackgroundUsers() {
doReturn(true).when(() -> UserManager.isVisibleBackgroundUsersEnabled());
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
index 1281be51..7030d986 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -26,6 +26,7 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.content.pm.ActivityInfo.CONFIG_UI_MODE;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
@@ -33,6 +34,8 @@
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.content.res.Configuration.SCREEN_HEIGHT_DP_UNDEFINED;
import static android.content.res.Configuration.SCREEN_WIDTH_DP_UNDEFINED;
+import static android.content.res.Configuration.UI_MODE_NIGHT_NO;
+import static android.content.res.Configuration.UI_MODE_NIGHT_YES;
import static android.view.InsetsSource.FLAG_FORCE_CONSUMING;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
import static android.window.DisplayAreaOrganizer.FEATURE_VENDOR_FIRST;
@@ -1983,6 +1986,30 @@
testSetAlwaysOnTop(displayArea);
}
+ @Test
+ public void testConfigurationsAreEqualForOrganizer() {
+ Configuration config1 = new Configuration();
+ config1.smallestScreenWidthDp = 300;
+ config1.uiMode = UI_MODE_NIGHT_YES;
+
+ Configuration config2 = new Configuration(config1);
+ config2.uiMode = UI_MODE_NIGHT_NO;
+
+ Configuration config3 = new Configuration(config1);
+ config3.smallestScreenWidthDp = 500;
+
+ // Should be equal for non-controllable configuration changes.
+ assertTrue(WindowOrganizerController.configurationsAreEqualForOrganizer(config1, config2));
+
+ // Should be unequal for non-controllable configuration changes if the organizer is
+ // interested in that change.
+ assertFalse(WindowOrganizerController.configurationsAreEqualForOrganizer(
+ config1, config2, CONFIG_UI_MODE));
+
+ // Should be unequal for controllable configuration changes.
+ assertFalse(WindowOrganizerController.configurationsAreEqualForOrganizer(config1, config3));
+ }
+
private void testSetAlwaysOnTop(WindowContainer wc) {
final WindowContainerTransaction t = new WindowContainerTransaction();
t.setAlwaysOnTop(wc.mRemoteToken.toWindowContainerToken(), true);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index b16f528..7f9e591 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -961,6 +961,15 @@
return testPlayer;
}
+ void requestTransition(WindowContainer<?> wc, int transit) {
+ final TransitionController controller = mRootWindowContainer.mTransitionController;
+ if (controller.getTransitionPlayer() == null) {
+ registerTestTransitionPlayer();
+ }
+ controller.requestTransitionIfNeeded(transit, 0 /* flags */, null /* trigger */,
+ wc.mDisplayContent);
+ }
+
/** Overrides the behavior of config_reverseDefaultRotation for the given display. */
void setReverseDefaultRotation(DisplayContent dc, boolean reverse) {
final DisplayRotation displayRotation = dc.getDisplayRotation();
@@ -1417,7 +1426,9 @@
activity.setProcess(wpc);
// Resume top activities to make sure all other signals in the system are connected.
- mService.mRootWindowContainer.resumeFocusedTasksTopActivities();
+ if (mVisible) {
+ mService.mRootWindowContainer.resumeFocusedTasksTopActivities();
+ }
return activity;
}
}
diff --git a/services/usb/java/com/android/server/usb/UsbPortManager.java b/services/usb/java/com/android/server/usb/UsbPortManager.java
index 55a8923..86468b0 100644
--- a/services/usb/java/com/android/server/usb/UsbPortManager.java
+++ b/services/usb/java/com/android/server/usb/UsbPortManager.java
@@ -200,7 +200,11 @@
mHandler.sendEmptyMessage(MSG_SYSTEM_READY);
}
- private void updateContaminantNotification() {
+ private void updateContaminantNotificationLocked() {
+ if (mNotificationManager == null) {
+ return;
+ }
+
PortInfo currentPortInfo = null;
Resources r = mContext.getResources();
int contaminantStatus = UsbPortStatus.CONTAMINANT_DETECTION_NOT_DETECTED;
@@ -1171,7 +1175,7 @@
private void handlePortLocked(PortInfo portInfo, IndentingPrintWriter pw) {
sendPortChangedBroadcastLocked(portInfo);
logToStatsd(portInfo, pw);
- updateContaminantNotification();
+ updateContaminantNotificationLocked();
}
private void handlePortAddedLocked(PortInfo portInfo, IndentingPrintWriter pw) {
@@ -1433,6 +1437,9 @@
case MSG_SYSTEM_READY: {
mNotificationManager = (NotificationManager)
mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+ synchronized (mLock) {
+ updateContaminantNotificationLocked();
+ }
break;
}
}
diff --git a/tests/AttestationVerificationTest/AndroidManifest.xml b/tests/AttestationVerificationTest/AndroidManifest.xml
index 37321ad8..758852b 100644
--- a/tests/AttestationVerificationTest/AndroidManifest.xml
+++ b/tests/AttestationVerificationTest/AndroidManifest.xml
@@ -18,7 +18,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="android.security.attestationverification">
- <uses-sdk android:minSdkVersion="30" android:targetSdkVersion="30" />
+ <uses-sdk android:minSdkVersion="30" android:targetSdkVersion="34" />
<uses-permission android:name="android.permission.USE_ATTESTATION_VERIFICATION_SERVICE" />
<application>
diff --git a/tests/AttestationVerificationTest/assets/test_revocation_list_no_test_certs.json b/tests/AttestationVerificationTest/assets/test_revocation_list_no_test_certs.json
new file mode 100644
index 0000000..2a3ba5e
--- /dev/null
+++ b/tests/AttestationVerificationTest/assets/test_revocation_list_no_test_certs.json
@@ -0,0 +1,12 @@
+{
+ "entries": {
+ "6681152659205225093" : {
+ "status": "REVOKED",
+ "reason": "KEY_COMPROMISE"
+ },
+ "8350192447815228107" : {
+ "status": "REVOKED",
+ "reason": "KEY_COMPROMISE"
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/AttestationVerificationTest/assets/test_revocation_list_with_test_certs.json b/tests/AttestationVerificationTest/assets/test_revocation_list_with_test_certs.json
new file mode 100644
index 0000000..e22a834
--- /dev/null
+++ b/tests/AttestationVerificationTest/assets/test_revocation_list_with_test_certs.json
@@ -0,0 +1,16 @@
+{
+ "entries": {
+ "6681152659205225093" : {
+ "status": "REVOKED",
+ "reason": "KEY_COMPROMISE"
+ },
+ "353017e73dc205a73a9c3de142230370" : {
+ "status": "REVOKED",
+ "reason": "KEY_COMPROMISE"
+ },
+ "8350192447815228107" : {
+ "status": "REVOKED",
+ "reason": "KEY_COMPROMISE"
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/AttestationVerificationTest/src/com/android/server/security/CertificateRevocationStatusManagerTest.java b/tests/AttestationVerificationTest/src/com/android/server/security/CertificateRevocationStatusManagerTest.java
new file mode 100644
index 0000000..c38517a
--- /dev/null
+++ b/tests/AttestationVerificationTest/src/com/android/server/security/CertificateRevocationStatusManagerTest.java
@@ -0,0 +1,303 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.security;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+
+import android.content.Context;
+import android.os.SystemClock;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.json.JSONObject;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.security.cert.CertPathValidatorException;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.time.LocalDateTime;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@RunWith(AndroidJUnit4.class)
+public class CertificateRevocationStatusManagerTest {
+
+ private static final String TEST_CERTIFICATE_FILE_1 = "test_attestation_with_root_certs.pem";
+ private static final String TEST_CERTIFICATE_FILE_2 = "test_attestation_wrong_root_certs.pem";
+ private static final String TEST_REVOCATION_LIST_FILE_NAME = "test_revocation_list.json";
+ private static final String REVOCATION_LIST_WITHOUT_CERTIFICATES_USED_IN_THIS_TEST =
+ "test_revocation_list_no_test_certs.json";
+ private static final String REVOCATION_LIST_WITH_CERTIFICATES_USED_IN_THIS_TEST =
+ "test_revocation_list_with_test_certs.json";
+ private static final String TEST_REVOCATION_STATUS_FILE_NAME = "test_revocation_status.txt";
+ private static final String FILE_URL_PREFIX = "file://";
+ private final Context mContext = InstrumentationRegistry.getInstrumentation().getContext();
+
+ private CertificateFactory mFactory;
+ private List<X509Certificate> mCertificates1;
+ private List<X509Certificate> mCertificates2;
+ private File mRevocationListFile;
+ private String mRevocationListUrl;
+ private String mNonExistentRevocationListUrl;
+ private File mRevocationStatusFile;
+ private CertificateRevocationStatusManager mCertificateRevocationStatusManager;
+
+ @Before
+ public void setUp() throws Exception {
+ mFactory = CertificateFactory.getInstance("X.509");
+ mCertificates1 = getCertificateChain(TEST_CERTIFICATE_FILE_1);
+ mCertificates2 = getCertificateChain(TEST_CERTIFICATE_FILE_2);
+ mRevocationListFile = new File(mContext.getFilesDir(), TEST_REVOCATION_LIST_FILE_NAME);
+ mRevocationListUrl = FILE_URL_PREFIX + mRevocationListFile.getAbsolutePath();
+ File noSuchFile = new File(mContext.getFilesDir(), "file_does_not_exist");
+ mNonExistentRevocationListUrl = FILE_URL_PREFIX + noSuchFile.getAbsolutePath();
+ mRevocationStatusFile = new File(mContext.getFilesDir(), TEST_REVOCATION_STATUS_FILE_NAME);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ mRevocationListFile.delete();
+ mRevocationStatusFile.delete();
+ }
+
+ @Test
+ public void checkRevocationStatus_doesNotExistOnRemoteRevocationList_noException()
+ throws Exception {
+ copyFromAssetToFile(
+ REVOCATION_LIST_WITHOUT_CERTIFICATES_USED_IN_THIS_TEST, mRevocationListFile);
+ mCertificateRevocationStatusManager =
+ new CertificateRevocationStatusManager(
+ mContext, mRevocationListUrl, mRevocationStatusFile, false);
+
+ mCertificateRevocationStatusManager.checkRevocationStatus(mCertificates1);
+ }
+
+ @Test
+ public void checkRevocationStatus_existsOnRemoteRevocationList_throwsException()
+ throws Exception {
+ copyFromAssetToFile(
+ REVOCATION_LIST_WITH_CERTIFICATES_USED_IN_THIS_TEST, mRevocationListFile);
+ mCertificateRevocationStatusManager =
+ new CertificateRevocationStatusManager(
+ mContext, mRevocationListUrl, mRevocationStatusFile, false);
+
+ assertThrows(
+ CertPathValidatorException.class,
+ () -> mCertificateRevocationStatusManager.checkRevocationStatus(mCertificates1));
+ }
+
+ @Test
+ public void
+ checkRevocationStatus_cannotReachRemoteRevocationList_noStoredStatus_throwsException()
+ throws Exception {
+ mCertificateRevocationStatusManager =
+ new CertificateRevocationStatusManager(
+ mContext, mNonExistentRevocationListUrl, mRevocationStatusFile, false);
+
+ assertThrows(
+ CertPathValidatorException.class,
+ () -> mCertificateRevocationStatusManager.checkRevocationStatus(mCertificates1));
+ }
+
+ @Test
+ public void checkRevocationStatus_savesRevocationStatus() throws Exception {
+ copyFromAssetToFile(
+ REVOCATION_LIST_WITHOUT_CERTIFICATES_USED_IN_THIS_TEST, mRevocationListFile);
+ mCertificateRevocationStatusManager =
+ new CertificateRevocationStatusManager(
+ mContext, mRevocationListUrl, mRevocationStatusFile, false);
+
+ mCertificateRevocationStatusManager.checkRevocationStatus(mCertificates1);
+
+ assertThat(mRevocationStatusFile.length()).isGreaterThan(0);
+ }
+
+ @Test
+ public void checkRevocationStatus_cannotReachRemoteList_certsSaved_noException()
+ throws Exception {
+ // call checkRevocationStatus once to save the revocation status
+ copyFromAssetToFile(
+ REVOCATION_LIST_WITHOUT_CERTIFICATES_USED_IN_THIS_TEST, mRevocationListFile);
+ mCertificateRevocationStatusManager =
+ new CertificateRevocationStatusManager(
+ mContext, mRevocationListUrl, mRevocationStatusFile, false);
+ mCertificateRevocationStatusManager.checkRevocationStatus(mCertificates1);
+ // call checkRevocationStatus again with mNonExistentRevocationListUrl
+ mCertificateRevocationStatusManager =
+ new CertificateRevocationStatusManager(
+ mContext, mNonExistentRevocationListUrl, mRevocationStatusFile, false);
+
+ mCertificateRevocationStatusManager.checkRevocationStatus(mCertificates1);
+ }
+
+ @Test
+ public void checkRevocationStatus_cannotReachRemoteList_someCertsNotSaved_exception()
+ throws Exception {
+ // call checkRevocationStatus once to save the revocation status for mCertificates2
+ copyFromAssetToFile(
+ REVOCATION_LIST_WITHOUT_CERTIFICATES_USED_IN_THIS_TEST, mRevocationListFile);
+ mCertificateRevocationStatusManager =
+ new CertificateRevocationStatusManager(
+ mContext, mRevocationListUrl, mRevocationStatusFile, false);
+ mCertificateRevocationStatusManager.checkRevocationStatus(mCertificates2);
+ // call checkRevocationStatus again with mNonExistentRevocationListUrl, this time for
+ // mCertificates1
+ mCertificateRevocationStatusManager =
+ new CertificateRevocationStatusManager(
+ mContext, mNonExistentRevocationListUrl, mRevocationStatusFile, false);
+
+ assertThrows(
+ CertPathValidatorException.class,
+ () -> mCertificateRevocationStatusManager.checkRevocationStatus(mCertificates1));
+ }
+
+ @Test
+ public void checkRevocationStatus_cannotReachRemoteList_someCertsStatusTooOld_exception()
+ throws Exception {
+ mCertificateRevocationStatusManager =
+ new CertificateRevocationStatusManager(
+ mContext, mNonExistentRevocationListUrl, mRevocationStatusFile, false);
+ LocalDateTime now = LocalDateTime.now();
+ LocalDateTime expiredStatusDate =
+ now.minusDays(CertificateRevocationStatusManager.MAX_DAYS_SINCE_LAST_CHECK + 1);
+ Map<String, LocalDateTime> lastRevocationCheckData = new HashMap<>();
+ lastRevocationCheckData.put(getSerialNumber(mCertificates1.get(0)), expiredStatusDate);
+ for (int i = 1; i < mCertificates1.size(); i++) {
+ lastRevocationCheckData.put(getSerialNumber(mCertificates1.get(i)), now);
+ }
+ mCertificateRevocationStatusManager.storeLastRevocationCheckData(lastRevocationCheckData);
+
+ assertThrows(
+ CertPathValidatorException.class,
+ () -> mCertificateRevocationStatusManager.checkRevocationStatus(mCertificates1));
+ }
+
+ @Test
+ public void checkRevocationStatus_cannotReachRemoteList_allCertResultsFresh_noException()
+ throws Exception {
+ mCertificateRevocationStatusManager =
+ new CertificateRevocationStatusManager(
+ mContext, mNonExistentRevocationListUrl, mRevocationStatusFile, false);
+ LocalDateTime bearlyNotExpiredStatusDate =
+ LocalDateTime.now()
+ .minusDays(
+ CertificateRevocationStatusManager.MAX_DAYS_SINCE_LAST_CHECK - 1);
+ Map<String, LocalDateTime> lastRevocationCheckData = new HashMap<>();
+ for (X509Certificate certificate : mCertificates1) {
+ lastRevocationCheckData.put(getSerialNumber(certificate), bearlyNotExpiredStatusDate);
+ }
+ mCertificateRevocationStatusManager.storeLastRevocationCheckData(lastRevocationCheckData);
+
+ mCertificateRevocationStatusManager.checkRevocationStatus(mCertificates1);
+ }
+
+ @Test
+ public void updateLastRevocationCheckData_correctlySavesStatus() throws Exception {
+ mCertificateRevocationStatusManager =
+ new CertificateRevocationStatusManager(
+ mContext, mNonExistentRevocationListUrl, mRevocationStatusFile, false);
+ Map<String, Boolean> areCertificatesRevoked = new HashMap<>();
+ for (X509Certificate certificate : mCertificates1) {
+ areCertificatesRevoked.put(getSerialNumber(certificate), false);
+ }
+
+ mCertificateRevocationStatusManager.updateLastRevocationCheckData(areCertificatesRevoked);
+
+ // no exception
+ mCertificateRevocationStatusManager.checkRevocationStatus(mCertificates1);
+ // revoke one certificate and try again
+ areCertificatesRevoked.put(getSerialNumber(mCertificates1.getLast()), true);
+ mCertificateRevocationStatusManager.updateLastRevocationCheckData(areCertificatesRevoked);
+ assertThrows(
+ CertPathValidatorException.class,
+ () -> mCertificateRevocationStatusManager.checkRevocationStatus(mCertificates1));
+ }
+
+ @Test
+ public void updateLastRevocationCheckDataForAllPreviouslySeenCertificates_updatesCorrectly()
+ throws Exception {
+ copyFromAssetToFile(
+ REVOCATION_LIST_WITHOUT_CERTIFICATES_USED_IN_THIS_TEST, mRevocationListFile);
+ mCertificateRevocationStatusManager =
+ new CertificateRevocationStatusManager(
+ mContext, mRevocationListUrl, mRevocationStatusFile, false);
+ // populate the revocation status file
+ mCertificateRevocationStatusManager.checkRevocationStatus(mCertificates1);
+ // Sleep for 2 second so that the current time changes
+ SystemClock.sleep(2000);
+ LocalDateTime timestampBeforeUpdate = LocalDateTime.now();
+ JSONObject revocationList = mCertificateRevocationStatusManager.fetchRemoteRevocationList();
+ List<String> otherCertificatesToCheck = new ArrayList<>();
+ String serialNumber1 = "1234567"; // not revoked
+ String serialNumber2 = "8350192447815228107"; // revoked
+ String serialNumber3 = "987654"; // not revoked
+ otherCertificatesToCheck.add(serialNumber1);
+ otherCertificatesToCheck.add(serialNumber2);
+ otherCertificatesToCheck.add(serialNumber3);
+
+ mCertificateRevocationStatusManager
+ .updateLastRevocationCheckDataForAllPreviouslySeenCertificates(
+ revocationList, otherCertificatesToCheck);
+
+ Map<String, LocalDateTime> lastRevocationCheckData =
+ mCertificateRevocationStatusManager.getLastRevocationCheckData();
+ assertThat(lastRevocationCheckData.get(serialNumber1)).isAtLeast(timestampBeforeUpdate);
+ assertThat(lastRevocationCheckData).doesNotContainKey(serialNumber2); // revoked
+ assertThat(lastRevocationCheckData.get(serialNumber3)).isAtLeast(timestampBeforeUpdate);
+ // validate that the existing certificates on the file got updated too
+ for (X509Certificate certificate : mCertificates1) {
+ assertThat(lastRevocationCheckData.get(getSerialNumber(certificate)))
+ .isAtLeast(timestampBeforeUpdate);
+ }
+ }
+
+ private List<X509Certificate> getCertificateChain(String fileName) throws Exception {
+ Collection<? extends Certificate> certificates =
+ mFactory.generateCertificates(mContext.getResources().getAssets().open(fileName));
+ ArrayList<X509Certificate> x509Certs = new ArrayList<>();
+ for (Certificate cert : certificates) {
+ x509Certs.add((X509Certificate) cert);
+ }
+ return x509Certs;
+ }
+
+ private void copyFromAssetToFile(String assetFileName, File targetFile) throws Exception {
+ byte[] data;
+ try (InputStream in = mContext.getResources().getAssets().open(assetFileName)) {
+ data = in.readAllBytes();
+ }
+ try (FileOutputStream fileOutputStream = new FileOutputStream(targetFile)) {
+ fileOutputStream.write(data);
+ }
+ }
+
+ private String getSerialNumber(X509Certificate certificate) {
+ return certificate.getSerialNumber().toString(16);
+ }
+}
diff --git a/tests/FlickerTests/IME/AndroidTestTemplate.xml b/tests/FlickerTests/IME/AndroidTestTemplate.xml
index 12670cd..ac704e5 100644
--- a/tests/FlickerTests/IME/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/IME/AndroidTestTemplate.xml
@@ -52,10 +52,12 @@
<option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/>
<option name="run-command" value="settings put system show_touches 1"/>
<option name="run-command" value="settings put system pointer_location 1"/>
+ <option name="run-command" value="settings put secure glanceable_hub_enabled 0"/>
<option name="teardown-command"
value="settings delete secure show_ime_with_hard_keyboard"/>
<option name="teardown-command" value="settings delete system show_touches"/>
<option name="teardown-command" value="settings delete system pointer_location"/>
+ <option name="teardown-command" value="settings delete secure glanceable_hub_enabled"/>
<option name="teardown-command"
value="cmd overlay enable com.android.internal.systemui.navbar.gestural"/>
</target_preparer>
diff --git a/tests/FlickerTests/Rotation/AndroidTestTemplate.xml b/tests/FlickerTests/Rotation/AndroidTestTemplate.xml
index 481a8bb..1b2007d 100644
--- a/tests/FlickerTests/Rotation/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/Rotation/AndroidTestTemplate.xml
@@ -50,10 +50,12 @@
<option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/>
<option name="run-command" value="settings put system show_touches 1"/>
<option name="run-command" value="settings put system pointer_location 1"/>
+ <option name="run-command" value="settings put secure glanceable_hub_enabled 0"/>
<option name="teardown-command"
value="settings delete secure show_ime_with_hard_keyboard"/>
<option name="teardown-command" value="settings delete system show_touches"/>
<option name="teardown-command" value="settings delete system pointer_location"/>
+ <option name="teardown-command" value="settings delete secure glanceable_hub_enabled"/>
<option name="teardown-command"
value="cmd overlay enable com.android.internal.systemui.navbar.gestural"/>
</target_preparer>
diff --git a/tests/Input/src/android/hardware/input/KeyboardLayoutPreviewTests.kt b/tests/Input/src/android/hardware/input/KeyboardLayoutPreviewTests.kt
index ae32bda..bcff2fc 100644
--- a/tests/Input/src/android/hardware/input/KeyboardLayoutPreviewTests.kt
+++ b/tests/Input/src/android/hardware/input/KeyboardLayoutPreviewTests.kt
@@ -16,17 +16,11 @@
package android.hardware.input
-import android.platform.test.annotations.DisableFlags
-import android.platform.test.annotations.EnableFlags
import android.content.ContextWrapper
import android.graphics.drawable.Drawable
import android.platform.test.annotations.Presubmit
-import android.platform.test.flag.junit.SetFlagsRule
import androidx.test.platform.app.InstrumentationRegistry
-import com.android.hardware.input.Flags
import org.junit.Assert.assertEquals
-import org.junit.Assert.assertNull
-import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.junit.MockitoJUnitRunner
@@ -46,9 +40,6 @@
const val HEIGHT = 100
}
- @get:Rule
- val setFlagsRule = SetFlagsRule()
-
private fun createDrawable(): Drawable? {
val context = ContextWrapper(InstrumentationRegistry.getInstrumentation().getContext())
val inputManager = context.getSystemService(InputManager::class.java)!!
@@ -56,16 +47,9 @@
}
@Test
- @EnableFlags(Flags.FLAG_KEYBOARD_LAYOUT_PREVIEW_FLAG)
fun testKeyboardLayoutDrawable_hasCorrectDimensions() {
val drawable = createDrawable()!!
assertEquals(WIDTH, drawable.intrinsicWidth)
assertEquals(HEIGHT, drawable.intrinsicHeight)
}
-
- @Test
- @DisableFlags(Flags.FLAG_KEYBOARD_LAYOUT_PREVIEW_FLAG)
- fun testKeyboardLayoutDrawable_isNull_ifFlagOff() {
- assertNull(createDrawable())
- }
}
\ No newline at end of file
diff --git a/tests/Input/src/android/hardware/input/StickyModifierStateListenerTest.kt b/tests/Input/src/android/hardware/input/StickyModifierStateListenerTest.kt
index c2f9adf..cc58bbc 100644
--- a/tests/Input/src/android/hardware/input/StickyModifierStateListenerTest.kt
+++ b/tests/Input/src/android/hardware/input/StickyModifierStateListenerTest.kt
@@ -21,9 +21,7 @@
import android.os.Handler
import android.os.HandlerExecutor
import android.os.test.TestLooper
-import android.platform.test.annotations.EnableFlags
import android.platform.test.annotations.Presubmit
-import android.platform.test.flag.junit.SetFlagsRule
import android.view.KeyEvent
import androidx.test.core.app.ApplicationProvider
import com.android.server.testutils.any
@@ -50,12 +48,9 @@
*/
@Presubmit
@RunWith(MockitoJUnitRunner::class)
-@EnableFlags(com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_STICKY_KEYS_FLAG)
class StickyModifierStateListenerTest {
@get:Rule
- val rule = SetFlagsRule()
- @get:Rule
val inputManagerRule = MockInputManagerRule()
private val testLooper = TestLooper()
diff --git a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
index 5259455..b22e42d 100644
--- a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
+++ b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
@@ -559,9 +559,6 @@
@Test
fun handleKeyGestures_a11yBounceKeysShortcut() {
- ExtendedMockito.doReturn(true).`when` {
- InputSettings.isAccessibilityBounceKeysFeatureEnabled()
- }
val toggleBounceKeysEvent =
KeyGestureEvent.Builder()
.setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_BOUNCE_KEYS)
@@ -594,9 +591,6 @@
@Test
fun handleKeyGestures_a11yStickyKeysShortcut() {
- ExtendedMockito.doReturn(true).`when` {
- InputSettings.isAccessibilityStickyKeysFeatureEnabled()
- }
val toggleStickyKeysEvent =
KeyGestureEvent.Builder()
.setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_STICKY_KEYS)
@@ -610,9 +604,6 @@
@Test
fun handleKeyGestures_a11ySlowKeysShortcut() {
- ExtendedMockito.doReturn(true).`when` {
- InputSettings.isAccessibilitySlowKeysFeatureFlagEnabled()
- }
val toggleSlowKeysEvent =
KeyGestureEvent.Builder()
.setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_SLOW_KEYS)
diff --git a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
index de47f01..88e8496 100644
--- a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
+++ b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
@@ -754,9 +754,6 @@
@EnableFlags(
com.android.server.flags.Flags.FLAG_NEW_BUGREPORT_KEYBOARD_SHORTCUT,
com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_SHORTCUT_CONTROL,
- com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_BOUNCE_KEYS_FLAG,
- com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_SLOW_KEYS_FLAG,
- com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_STICKY_KEYS_FLAG,
com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_MOUSE_KEYS,
com.android.hardware.input.Flags.FLAG_ENABLE_TALKBACK_AND_MAGNIFIER_KEY_GESTURES,
com.android.hardware.input.Flags.FLAG_ENABLE_VOICE_ACCESS_KEY_GESTURES,
@@ -773,9 +770,6 @@
@EnableFlags(
com.android.server.flags.Flags.FLAG_NEW_BUGREPORT_KEYBOARD_SHORTCUT,
com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_SHORTCUT_CONTROL,
- com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_BOUNCE_KEYS_FLAG,
- com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_SLOW_KEYS_FLAG,
- com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_STICKY_KEYS_FLAG,
com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_MOUSE_KEYS,
com.android.hardware.input.Flags.FLAG_ENABLE_TALKBACK_AND_MAGNIFIER_KEY_GESTURES,
com.android.hardware.input.Flags.FLAG_ENABLE_VOICE_ACCESS_KEY_GESTURES,
diff --git a/tests/InputScreenshotTest/src/android/input/screenshot/KeyboardLayoutPreviewAnsiScreenshotTest.kt b/tests/InputScreenshotTest/src/android/input/screenshot/KeyboardLayoutPreviewAnsiScreenshotTest.kt
index e855786..e1294b1 100644
--- a/tests/InputScreenshotTest/src/android/input/screenshot/KeyboardLayoutPreviewAnsiScreenshotTest.kt
+++ b/tests/InputScreenshotTest/src/android/input/screenshot/KeyboardLayoutPreviewAnsiScreenshotTest.kt
@@ -19,12 +19,9 @@
import android.content.Context
import android.hardware.input.KeyboardLayout
import android.os.LocaleList
-import android.platform.test.flag.junit.SetFlagsRule
-import com.android.hardware.input.Flags
import java.util.Locale
import org.junit.Rule
import org.junit.Test
-import org.junit.rules.RuleChain
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
import platform.test.screenshot.DeviceEmulationSpec
@@ -38,18 +35,14 @@
fun getTestSpecs() = DeviceEmulationSpec.PhoneMinimal
}
- val setFlagsRule = SetFlagsRule()
+ @get:Rule
val screenshotRule = InputScreenshotTestRule(
emulationSpec,
"frameworks/base/tests/InputScreenshotTest/assets"
)
- @get:Rule
- val ruleChain = RuleChain.outerRule(screenshotRule).around(setFlagsRule)
-
@Test
fun test() {
- setFlagsRule.enableFlags(Flags.FLAG_KEYBOARD_LAYOUT_PREVIEW_FLAG)
screenshotRule.screenshotTest("layout-preview-ansi") {
context: Context -> LayoutPreview.createLayoutPreview(
context,
@@ -66,5 +59,4 @@
)
}
}
-
}
\ No newline at end of file
diff --git a/tests/InputScreenshotTest/src/android/input/screenshot/KeyboardLayoutPreviewIsoScreenshotTest.kt b/tests/InputScreenshotTest/src/android/input/screenshot/KeyboardLayoutPreviewIsoScreenshotTest.kt
index ab7bb4e..ddad6de 100644
--- a/tests/InputScreenshotTest/src/android/input/screenshot/KeyboardLayoutPreviewIsoScreenshotTest.kt
+++ b/tests/InputScreenshotTest/src/android/input/screenshot/KeyboardLayoutPreviewIsoScreenshotTest.kt
@@ -17,14 +17,8 @@
package com.android.input.screenshot
import android.content.Context
-import android.hardware.input.KeyboardLayout
-import android.os.LocaleList
-import android.platform.test.flag.junit.SetFlagsRule
-import com.android.hardware.input.Flags
-import java.util.Locale
import org.junit.Rule
import org.junit.Test
-import org.junit.rules.RuleChain
import org.junit.runner.RunWith
import platform.test.runner.parameterized.ParameterizedAndroidJunit4
import platform.test.runner.parameterized.Parameters
@@ -39,21 +33,16 @@
fun getTestSpecs() = DeviceEmulationSpec.PhoneAndTabletMinimal
}
- val setFlagsRule = SetFlagsRule()
+ @get:Rule
val screenshotRule = InputScreenshotTestRule(
emulationSpec,
"frameworks/base/tests/InputScreenshotTest/assets"
)
- @get:Rule
- val ruleChain = RuleChain.outerRule(screenshotRule).around(setFlagsRule)
-
@Test
fun test() {
- setFlagsRule.enableFlags(Flags.FLAG_KEYBOARD_LAYOUT_PREVIEW_FLAG)
screenshotRule.screenshotTest("layout-preview") {
context: Context -> LayoutPreview.createLayoutPreview(context, null)
}
}
-
}
diff --git a/tests/InputScreenshotTest/src/android/input/screenshot/KeyboardLayoutPreviewJisScreenshotTest.kt b/tests/InputScreenshotTest/src/android/input/screenshot/KeyboardLayoutPreviewJisScreenshotTest.kt
index 5231c14..8a8e4f0 100644
--- a/tests/InputScreenshotTest/src/android/input/screenshot/KeyboardLayoutPreviewJisScreenshotTest.kt
+++ b/tests/InputScreenshotTest/src/android/input/screenshot/KeyboardLayoutPreviewJisScreenshotTest.kt
@@ -19,12 +19,9 @@
import android.content.Context
import android.hardware.input.KeyboardLayout
import android.os.LocaleList
-import android.platform.test.flag.junit.SetFlagsRule
-import com.android.hardware.input.Flags
import java.util.Locale
import org.junit.Rule
import org.junit.Test
-import org.junit.rules.RuleChain
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
import platform.test.screenshot.DeviceEmulationSpec
@@ -38,18 +35,14 @@
fun getTestSpecs() = DeviceEmulationSpec.PhoneMinimal
}
- val setFlagsRule = SetFlagsRule()
+ @get:Rule
val screenshotRule = InputScreenshotTestRule(
emulationSpec,
"frameworks/base/tests/InputScreenshotTest/assets"
)
- @get:Rule
- val ruleChain = RuleChain.outerRule(screenshotRule).around(setFlagsRule)
-
@Test
fun test() {
- setFlagsRule.enableFlags(Flags.FLAG_KEYBOARD_LAYOUT_PREVIEW_FLAG)
screenshotRule.screenshotTest("layout-preview-jis") {
context: Context -> LayoutPreview.createLayoutPreview(
context,
@@ -66,5 +59,4 @@
)
}
}
-
}
\ No newline at end of file
diff --git a/tests/testables/src/android/testing/TestableLooper.java b/tests/testables/src/android/testing/TestableLooper.java
index 3ee6dc4..1273826 100644
--- a/tests/testables/src/android/testing/TestableLooper.java
+++ b/tests/testables/src/android/testing/TestableLooper.java
@@ -16,6 +16,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
@@ -24,7 +25,7 @@
import android.os.TestLooperManager;
import android.util.ArrayMap;
-import androidx.test.InstrumentationRegistry;
+import androidx.test.platform.app.InstrumentationRegistry;
import org.junit.runners.model.FrameworkMethod;
@@ -33,8 +34,11 @@
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.ArrayDeque;
import java.util.Map;
import java.util.Objects;
+import java.util.Queue;
import java.util.concurrent.atomic.AtomicBoolean;
/**
@@ -67,16 +71,38 @@
private Handler mHandler;
private TestLooperManager mQueueWrapper;
+ /**
+ * Baklava introduces new {@link TestLooperManager} APIs that we can use instead of reflection.
+ */
+ private static boolean isAtLeastBaklava() {
+ Method[] methods = TestLooperManager.class.getMethods();
+ for (Method method : methods) {
+ if (method.getName().equals("peekWhen")) {
+ return true;
+ }
+ }
+ return false;
+ // TODO(shayba): delete the above, uncomment the below.
+ // SDK_INT has not yet ramped to Baklava in all 25Q2 builds.
+ // return Build.VERSION.SDK_INT >= Build.VERSION_CODES.BAKLAVA;
+ }
+
static {
- try {
- MESSAGE_QUEUE_MESSAGES_FIELD = MessageQueue.class.getDeclaredField("mMessages");
- MESSAGE_QUEUE_MESSAGES_FIELD.setAccessible(true);
- MESSAGE_NEXT_FIELD = Message.class.getDeclaredField("next");
- MESSAGE_NEXT_FIELD.setAccessible(true);
- MESSAGE_WHEN_FIELD = Message.class.getDeclaredField("when");
- MESSAGE_WHEN_FIELD.setAccessible(true);
- } catch (NoSuchFieldException e) {
- throw new RuntimeException("Failed to initialize TestableLooper", e);
+ if (isAtLeastBaklava()) {
+ MESSAGE_QUEUE_MESSAGES_FIELD = null;
+ MESSAGE_NEXT_FIELD = null;
+ MESSAGE_WHEN_FIELD = null;
+ } else {
+ try {
+ MESSAGE_QUEUE_MESSAGES_FIELD = MessageQueue.class.getDeclaredField("mMessages");
+ MESSAGE_QUEUE_MESSAGES_FIELD.setAccessible(true);
+ MESSAGE_NEXT_FIELD = Message.class.getDeclaredField("next");
+ MESSAGE_NEXT_FIELD.setAccessible(true);
+ MESSAGE_WHEN_FIELD = Message.class.getDeclaredField("when");
+ MESSAGE_WHEN_FIELD.setAccessible(true);
+ } catch (NoSuchFieldException e) {
+ throw new RuntimeException("Failed to initialize TestableLooper", e);
+ }
}
}
@@ -222,8 +248,61 @@
}
public void moveTimeForward(long milliSeconds) {
+ if (isAtLeastBaklava()) {
+ moveTimeForwardBaklava(milliSeconds);
+ } else {
+ moveTimeForwardLegacy(milliSeconds);
+ }
+ }
+
+ private void moveTimeForwardBaklava(long milliSeconds) {
+ // Drain all Messages from the queue.
+ Queue<Message> messages = new ArrayDeque<>();
+ while (true) {
+ Message message = mQueueWrapper.poll();
+ if (message == null) {
+ break;
+ }
+
+ // Adjust the Message's delivery time.
+ long newWhen = message.when - milliSeconds;
+ if (newWhen < 0) {
+ newWhen = 0;
+ }
+ message.when = newWhen;
+ messages.add(message);
+ }
+
+ // Repost all Messages back to the queuewith a new time.
+ while (true) {
+ Message message = messages.poll();
+ if (message == null) {
+ break;
+ }
+
+ Runnable callback = message.getCallback();
+ Handler handler = message.getTarget();
+ long when = message.getWhen();
+
+ // The Message cannot be re-enqueued because it is marked in use.
+ // Make a copy of the Message and recycle the original.
+ // This resets {@link Message#isInUse()} but retains all other content.
+ {
+ Message newMessage = Message.obtain();
+ newMessage.copyFrom(message);
+ newMessage.setCallback(callback);
+ mQueueWrapper.recycle(message);
+ message = newMessage;
+ }
+
+ // Send the Message back to its Handler to be re-enqueued.
+ handler.sendMessageAtTime(message, when);
+ }
+ }
+
+ private void moveTimeForwardLegacy(long milliSeconds) {
try {
- Message msg = getMessageLinkedList();
+ Message msg = (Message) MESSAGE_QUEUE_MESSAGES_FIELD.get(mLooper.getQueue());
while (msg != null) {
long updatedWhen = msg.getWhen() - milliSeconds;
if (updatedWhen < 0) {
@@ -237,17 +316,6 @@
}
}
- private Message getMessageLinkedList() {
- try {
- MessageQueue queue = mLooper.getQueue();
- return (Message) MESSAGE_QUEUE_MESSAGES_FIELD.get(queue);
- } catch (IllegalAccessException e) {
- throw new RuntimeException(
- "Access failed in TestableLooper: get - MessageQueue.mMessages",
- e);
- }
- }
-
private int processQueuedMessages() {
int count = 0;
Runnable barrierRunnable = () -> { };